Stage 4 · Provision
Advanced Terraform Patterns
Policy as Code with OPA
Rego policies, Sentinel, and enforcing standards at plan time — automated governance for infrastructure.
Why Policy as Code?
Policy as code codifies organizational standards into automated checks. Instead of relying on documentation or code review to enforce rules, you write policies that run automatically on every plan or apply. Policies catch violations before infrastructure is provisioned.
Common policies include: required tags, approved regions, instance type restrictions, encryption requirements, and naming conventions. These policies are enforced consistently across every team and environment.
OPA and Rego
Open Policy Agent (OPA) is a general-purpose policy engine. Rego is its policy language. In Terraform, OPA evaluates the terraform plan JSON against Rego policies. If any policy fails, the plan is blocked.
package terraform.s3
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.actions[_] != "delete"
not resource.change.after.server_side_encryption_configuration
msg := sprintf(
"S3 bucket %s must have server-side encryption enabled",
[resource.address]
)
}The deny rule produces a message when the condition is true. The policy checks every S3 bucket in the plan. If any bucket lacks encryption configuration, the policy denies the plan with a descriptive message.
$ terraform plan -out=tfplan
$ terraform show -json tfplan > tfplan.json
$ conftest test tfplan.json -p policies/
1 test, 1 passed, 0 warnings, 0 failures, 0 errorsThe terraform show -json command converts the plan to JSON. Conftest evaluates the JSON against all Rego policies in the policies/ directory. A non-zero exit code means a policy failed.
Sentinel Policies
Sentinel is HashiCorp's policy-as-code framework, built into Terraform Cloud and Enterprise. It uses a Python-like language and enforces policies during plan and apply. Sentinel policies are more tightly integrated than OPA but require Terraform Cloud or Enterprise.
import "tfplan/v2" as tfplan
approved_regions = ["us-east-1", "us-west-2", "eu-west-1"]
main = rule {
all tfplan.resource_changes as _, rc {
rc.mode is "managed" implies
rc.type not in [
"aws_instance",
"aws_eks_cluster",
"aws_rds_cluster",
] or
rc.change.after.region in approved_regions
}
}This Sentinel policy restricts which AWS regions can be used for specific resource types. If a resource is outside the approved list, the policy fails. Sentinel evaluates this during both plan and apply.
Conftest
Conftest is a CLI tool for testing structured data using OPA. It works with Terraform plan JSON, Kubernetes manifests, Docker files, and more. It is the most flexible option for policy enforcement across multiple tools.
$ conftest test tfplan.json -p policies/ --all-namespaces
$ conftest test tfplan.json -p policies/ -o json # JSON output
$ conftest test tfplan.json -p policies/ -o tap # TAP outputConftest supports multiple output formats. TAP (Test Anything Protocol) is useful for CI integration. JSON output is useful for programmatic processing. The --all-namespaces flag evaluates all Rego packages.
Policy Examples
package terraform.tags
required_tags = {"Environment", "Owner", "CostCenter"}
deny[msg] {
resource := input.resource_changes[_]
resource.change.actions[_] != "delete"
tags := object.get(resource.change.after, "tags", {})
missing := required_tags - {key | tags[key]}
count(missing) > 0
msg := sprintf(
"%s is missing required tags: %v",
[resource.address, missing]
)
}This policy checks that every resource has the required tags. It uses set difference to find missing tags. The object.get function handles resources without a tags attribute safely.
package terraform.instance_type
allowed_types = [
"t3.micro", "t3.small", "t3.medium",
"m5.large", "m5.xlarge",
]
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
resource.change.actions[_] != "delete"
not resource.change.after.instance_type in allowed_types
msg := sprintf(
"%s uses disallowed instance type %s",
[resource.address, resource.change.after.instance_type]
)
}This policy restricts EC2 instance types to an approved list. It prevents developers from using expensive or non-compliant instance types. The deny rule fires for any instance type not in the allowed list.
Enforcement in CI
name: Terraform Policy Check
on: [pull_request]
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Plan
run: |
terraform init -backend=false
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
- name: OPA Policy Check
uses: open-policy-agent/conftest-action@main
with:
files: tfplan.json
policy: policies/The plan step generates a plan file and converts it to JSON. The policy check step evaluates the JSON against Rego policies. If any policy fails, the step fails and the PR cannot be merged.
Before enforcing policies as hard failures, run them in advisory mode. Report violations as warnings for 2-4 weeks. This gives teams time to fix existing resources without blocking all changes.
A broken policy can block all changes. Always test policies with valid and invalid inputs. Use conftest test with fixture data to verify your policies before deploying them.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.