Stage 4 · Provision
Terraform at Scale
Policy Guardrails
OPA, Sentinel, Conftest, and enforcing tags, regions, and encryption before apply — automated governance for Terraform.
Guardrails Overview
Policy guardrails enforce organizational standards on infrastructure changes. They run automatically during plan or apply to catch violations before they reach production. Guardrails are different from code review — they are automated, consistent, and cannot be overridden.
The three primary tools are OPA (Open Policy Agent) with Rego, Sentinel (HashiCorp's policy engine), and Conftest (OPA wrapper for testing). Each has different integration points and tradeoffs.
OPA with Rego
OPA evaluates Terraform plan JSON against Rego policies. It is tool-agnostic — the same Rego engine can validate Terraform, Kubernetes, and other configurations. Policies are written in Rego, a declarative language designed for policy.
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(
"%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 encryption is not configured, the policy denies the plan with a descriptive message.
$ terraform plan -out=tfplan
$ terraform show -json tfplan > tfplan.json
# Run all policies
$ conftest test tfplan.json -p policies/
# Run specific policy
$ conftest test tfplan.json -p policies/s3-encryption.regoThe terraform show command converts the plan to JSON. Conftest evaluates the JSON against all Rego files in the policies directory. A non-zero exit code means a policy failed.
Sentinel Policies
Sentinel is HashiCorp's policy engine 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_instance_types = [
"t3.micro", "t3.small", "t3.medium",
"m5.large", "m5.xlarge",
]
main = rule {
all tfplan.resource_changes as _, rc {
rc.mode is "managed" implies
rc.type not in ["aws_instance"] or
rc.change.after.instance_type in approved_instance_types
}
}This Sentinel policy restricts EC2 instance types to an approved list. The tfplan/v2 import provides access to the plan. The main rule evaluates to true only if all instances use approved types.
Conftest Integration
Conftest is a CLI tool for testing structured data using OPA. It wraps OPA with Terraform-specific features — loading plan JSON, running policies, and producing reports. It is the most practical tool for most teams.
$ conftest test tfplan.json -p policies/
$ conftest test tfplan.json -p policies/ -o json
$ conftest test tfplan.json -p policies/ -o tap
# Verify with specific policy
$ conftest verify --policy policies/ tfplan.jsonconftest test evaluates policies against the plan. The -o flag controls output format: json for programmatic use, tap for CI integration. conftest verify checks policies against test data.
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 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.regions
allowed_regions = ["us-east-1", "us-west-2", "eu-west-1"]
deny[msg] {
resource := input.resource_changes[_]
resource.change.actions[_] != "delete"
region := resource.change.after.region
not region in allowed_regions
msg := sprintf(
"%s uses disallowed region %s",
[resource.address, region]
)
}This policy restricts resources to specific regions. It checks the region field in the plan. Any resource outside the allowed regions is denied.
Enforcement Strategies
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
run: conftest test tfplan.json -p policies/
- name: Cost Estimation
run: infracost breakdown --path tfplan.jsonThe CI pipeline runs terraform plan, then policy checks, then cost estimation. If any policy fails, the pipeline stops and the PR cannot be merged. This ensures all changes comply with organizational standards.
- Run policies in advisory mode first — report violations without blocking.
- Test policies with valid and invalid inputs before deploying.
- Use exception policies for system resources that need to bypass rules.
- Monitor policy violations — track trends and fix root causes.
- Document policies and provide guidance for developers.
Run policies in advisory mode for 2-4 weeks. Report violations as warnings. This gives teams time to fix existing resources without blocking all changes. Switch to enforce after the team has adapted.
A policy with incorrect logic can deny all plans, even valid ones. Always test policies before deploying. Use conftest with test data to verify policy behavior.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.