TerraformPilot

Terraform

Terraform Sentinel Policies - Policy as Code

Enforce governance with Terraform Sentinel policies. Cost controls, security guardrails, compliance rules, and custom policy sets for Terraform Cloud and...

LLuca Berton1 min read

Quick Answer

#
# restrict-instance-types.sentinel
import "tfplan/v2" as tfplan
 
main = rule {
  all tfplan.resource_changes as _, rc {
    rc.type is not "aws_instance" or
    rc.change.after.instance_type in ["t3.micro", "t3.small", "t3.medium"]
  }
}

What is Sentinel?

#

Sentinel is HashiCorp's policy-as-code framework for Terraform Cloud and Enterprise. Policies run between plan and apply, blocking non-compliant changes before they reach infrastructure.

terraform plan → Sentinel check → terraform apply

              ❌ Block if non-compliant

Policy Structure

#
policies/
├── sentinel.hcl          # Policy set configuration
├── restrict-instance-types.sentinel
├── require-tags.sentinel
├── restrict-regions.sentinel
└── test/
    ├── restrict-instance-types/
    │   ├── pass.hcl
    │   └── fail.hcl
    └── require-tags/
        ├── pass.hcl
        └── fail.hcl

sentinel.hcl

#
policy "restrict-instance-types" {
  source            = "./restrict-instance-types.sentinel"
  enforcement_level = "hard-mandatory"
}
 
policy "require-tags" {
  source            = "./require-tags.sentinel"
  enforcement_level = "soft-mandatory"  # Can be overridden by admins
}
 
policy "restrict-regions" {
  source            = "./restrict-regions.sentinel"
  enforcement_level = "advisory"  # Warning only
}

Common Policies

#

Require Tags on All Resources

#
import "tfplan/v2" as tfplan
 
required_tags = ["Environment", "Owner", "Project"]
 
main = rule {
  all tfplan.resource_changes as _, rc {
    rc.change.after.tags is not null and
    all required_tags as tag {
      tag in keys(rc.change.after.tags)
    }
  } else true
}

Restrict AWS Regions

#
import "tfplan/v2" as tfplan
 
allowed_regions = ["us-east-1", "us-west-2", "eu-west-1"]
 
main = rule {
  all tfplan.resource_changes as _, rc {
    rc.provider_name is not "registry.terraform.io/hashicorp/aws" or
    rc.change.after.region is undefined or
    rc.change.after.region in allowed_regions
  }
}

Block Public S3 Buckets

#
import "tfplan/v2" as tfplan
 
main = rule {
  all tfplan.resource_changes as _, rc {
    rc.type is not "aws_s3_bucket" or
    (rc.change.after.acl is "private" or rc.change.after.acl is undefined)
  }
}

Limit Instance Cost

#
import "tfplan/v2" as tfplan
import "decimal"
 
expensive_types = {
  "m5.4xlarge":  "0.768",
  "c5.4xlarge":  "0.680",
  "r5.4xlarge":  "1.008",
}
 
max_hourly_cost = decimal.new(5)
 
total = rule {
  sum = decimal.new(0)
  for tfplan.resource_changes as _, rc {
    if rc.type is "aws_instance" {
      type = rc.change.after.instance_type
      if type in expensive_types {
        sum = sum + decimal.new(expensive_types[type])
      }
    }
  }
  sum <= max_hourly_cost
}
 
main = rule { total }

Enforce Encryption

#
import "tfplan/v2" as tfplan
 
main = rule {
  all tfplan.resource_changes as _, rc {
    rc.type is not "aws_db_instance" or
    rc.change.after.storage_encrypted is true
  }
}

Enforcement Levels

#
LevelBehavior
advisoryWarning only, apply proceeds
soft-mandatoryBlocks apply, but admins can override
hard-mandatoryBlocks apply, no override possible

Testing Policies

#
# test/restrict-instance-types/pass.hcl
mock "tfplan/v2" {
  module { source = "mock-pass.sentinel" }
}
test { rules = { main = true } }
sentinel test
# PASS - restrict-instance-types
# PASS - require-tags

Deploy Policy Sets

#
resource "tfe_policy_set" "security" {
  name         = "security-policies"
  organization = tfe_organization.main.name
 
  vcs_repo {
    identifier     = "myorg/sentinel-policies"
    branch         = "main"
    oauth_token_id = tfe_oauth_client.github.oauth_token_id
  }
 
  workspace_ids = [
    tfe_workspace.production.id,
    tfe_workspace.staging.id,
  ]
}
#

Conclusion

#

Start with advisory policies to understand impact, then escalate to soft-mandatory and hard-mandatory. Essential policies: required tags, allowed regions, encryption enforcement, and instance type restrictions. Test policies before deploying and version them in a separate VCS repository.

#Terraform#Sentinel#HashiCorp#Security#Infrastructure as Code

Share this article