Securing Your Terraform Code with Sentinel
Terraform

Securing Your Terraform Code with Sentinel

Learn how to implement security policies and governance for your infrastructure using HashiCorp Sentinel.

January 21, 2024
DevHub Team
5 min read

Securing Your Terraform Code with Sentinel

Learn how to implement security policies and governance for your infrastructure using HashiCorp Sentinel and Policy as Code principles.

Understanding Sentinel

Sentinel is HashiCorp's policy as code framework that enables fine-grained, logic-based policy decisions that can be enforced across all HashiCorp products.

Key Benefits

  1. Automated policy enforcement
  2. Version-controlled policies
  3. Test-driven policy development
  4. Integration with Terraform Enterprise/Cloud
  5. Customizable rules and logic

Basic Sentinel Policy Structure

# policy.sentinel import "tfplan" # Main rule main = rule { all tfplan.resources.aws_instance as _, instances { all instances as _, r { r.applied.instance_type in ["t2.micro", "t3.micro"] } } }

Common Policy Examples

1. Enforce Instance Types

# restrict-instance-type.sentinel import "tfplan" # Allowed instance types allowed_types = [ "t2.micro", "t2.small", "t3.micro", "t3.small", ] # Check instance types instance_type_allowed = rule { all tfplan.resources.aws_instance as _, instances { all instances as _, r { r.applied.instance_type in allowed_types } } } main = rule { instance_type_allowed }

2. Enforce Resource Tagging

# enforce-tags.sentinel import "tfplan" required_tags = [ "Environment", "Owner", "CostCenter", ] validate_tags = func(tags) { for required_tags as rt { if length(filter tags as _, t { t == rt }) == 0 { return false } } return true } tag_policy = rule { all tfplan.resources.aws_instance as _, instances { all instances as _, r { validate_tags(keys(r.applied.tags)) } } } main = rule { tag_policy }

3. Enforce Security Group Rules

# secure-ports.sentinel import "tfplan" # Restricted ports restricted_ports = [22, 3389] # Check security group rules validate_sg_rules = rule { all tfplan.resources.aws_security_group as _, sgs { all sgs as _, sg { all sg.applied.ingress as ingress { !(ingress.from_port in restricted_ports and ingress.cidr_blocks contains "0.0.0.0/0") } } } } main = rule { validate_sg_rules }

Advanced Policy Patterns

1. Cost Control Policies

# cost-control.sentinel import "tfrun" import "decimal" # Maximum allowed monthly cost max_monthly_cost = decimal.new(1000) # Validate cost estimate cost_estimate_valid = rule { decimal.new(tfrun.cost_estimate.monthly_cost) <= max_monthly_cost } main = rule { cost_estimate_valid }

2. Compliance Policies

# compliance.sentinel import "tfplan" import "strings" # Check encryption requirements validate_encryption = rule { all tfplan.resources.aws_ebs_volume as _, volumes { all volumes as _, volume { volume.applied.encrypted is true } } } # Check backup requirements validate_backups = rule { all tfplan.resources.aws_db_instance as _, dbs { all dbs as _, db { db.applied.backup_retention_period >= 7 } } } main = rule { validate_encryption and validate_backups }

3. Network Security Policies

# network-security.sentinel import "tfplan" import "ip" # Allowed VPC CIDR ranges allowed_vpc_cidrs = [ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", ] # Validate VPC CIDR blocks validate_vpc_cidrs = rule { all tfplan.resources.aws_vpc as _, vpcs { all vpcs as _, vpc { any allowed_vpc_cidrs as allowed_cidr { ip.in_cidr(vpc.applied.cidr_block, allowed_cidr) } } } } main = rule { validate_vpc_cidrs }

Testing Sentinel Policies

1. Mock Data

# mock-tfplan.sentinel mock "tfplan" { data = { resources = { aws_instance = { "app_server" = { applied = { instance_type = "t2.micro" tags = { "Environment" = "production" "Owner" = "DevOps" } } } } } } }

2. Test Cases

# test/restrict-instance-type/pass.hcl test { rules = { main = true } } mock "tfplan" { module { source = "mock-tfplan.sentinel" } }

3. Policy Sets

# policy-set.sentinel policy "restrict-instance-type" { enforcement_level = "hard-mandatory" } policy "enforce-tags" { enforcement_level = "soft-mandatory" } policy "secure-ports" { enforcement_level = "advisory" }

Integration with Terraform Cloud/Enterprise

1. Workspace Configuration

# workspace-policy.sentinel import "tfrun" import "tfplan" workspace_allowed = rule { tfrun.workspace.name matches "^(prod|staging|dev)-*" } main = rule { workspace_allowed }

2. Organization Policies

# organization-policy.sentinel import "tfrun" import "tfconfig" # Validate provider configurations validate_providers = rule { all tfconfig.providers as _, provider { provider.version_constraint is not null } } # Validate module sources validate_modules = rule { all tfconfig.modules as _, module { module.source matches "^git@github.com:company/" } } main = rule { validate_providers and validate_modules }

Best Practices

1. Policy Organization

policies/ ├── common/ │ ├── required-tags.sentinel │ └── allowed-providers.sentinel ├── security/ │ ├── encryption.sentinel │ └── network-access.sentinel └── compliance/ ├── backup-retention.sentinel └── audit-logging.sentinel

2. Policy Testing

# Run policy checks sentinel test # Test specific policy sentinel test restrict-instance-type.sentinel # Verbose output sentinel test -verbose

3. CI/CD Integration

# .github/workflows/sentinel.yml name: Sentinel Policy Checks on: [pull_request] jobs: sentinel: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install Sentinel run: | wget https://releases.hashicorp.com/sentinel/0.18.4/sentinel_0.18.4_linux_amd64.zip unzip sentinel_0.18.4_linux_amd64.zip sudo mv sentinel /usr/local/bin/ - name: Run Sentinel Tests run: | cd policies sentinel test

Conclusion

Implementing Sentinel policies helps:

  • Enforce security standards
  • Maintain compliance
  • Control costs
  • Standardize infrastructure
  • Prevent misconfigurations

Remember to:

  1. Start with basic policies
  2. Test thoroughly
  3. Implement gradually
  4. Monitor and adjust
  5. Keep policies version controlled
IaC
Security
DevOps
Compliance