Stage 4 · Provision
Validation, Policy & Secrets
OPA & Sentinel Guardrails
Rego policies, Sentinel policies, Conftest, cost checks, and mandatory tag enforcement — policy guardrails for infrastructure.
Guardrails Overview
Policy guardrails enforce organizational standards on infrastructure changes. They run during plan or apply to catch violations before production. Guardrails are different from code review — they are automated, consistent, and cannot be overridden.
Rego Policies
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]
)
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.actions[_] != "delete"
not resource.change.after.versioning_configuration
msg := sprintf(
"%s must have versioning enabled",
[resource.address]
)
}This policy checks that S3 buckets have encryption and versioning enabled. The deny rule produces a message when the condition is true. Multiple deny rules can exist in the same policy.
Sentinel Policies
import "tfplan/v2" as tfplan
required_tags = ["Environment", "Owner", "CostCenter"]
main = rule {
all tfplan.resource_changes as _, rc {
rc.mode is "managed" implies
rc.type in ["aws_instance", "aws_s3_bucket", "aws_db_instance"] implies
has_all_tags(rc)
}
}
has_all_tags(resource) = true {
tags := resource.change.after.tags
all required_tags as tag {
tags[tag] != null
}
}This Sentinel policy requires specific tags on resources. The has_all_tags function checks that all required tags exist. The main rule applies this check to all relevant resource types.
Conftest
$ terraform plan -out=tfplan
$ terraform show -json tfplan > tfplan.json
# Run all policies
$ conftest test tfplan.json -p policies/
# Run with specific output
$ conftest test tfplan.json -p policies/ -o json
$ conftest test tfplan.json -p policies/ -o tap
# Test policies with fixture data
$ conftest test test-data/valid.json -p policies/
$ conftest test test-data/invalid.json -p policies/Conftest evaluates OPA policies against Terraform plan JSON. The test command runs all policies in a directory. Test with both valid and invalid data to verify policy behavior.
Cost Checks
package terraform.cost
monthly_cost_limit = 10000
deny[msg] {
resource := input.resource_changes[_]
resource.change.actions[_] != "delete"
cost := resource.change.after.estimated_monthly_cost
cost > monthly_cost_limit
msg := sprintf(
"%s exceeds monthly cost limit of $%d (estimated: $%d)",
[resource.address, monthly_cost_limit, cost]
)
}This policy blocks resources that exceed a monthly cost limit. The estimated cost must be calculated before the policy runs. Use Infracost to provide cost estimates in the plan JSON.
Tag Enforcement
package terraform.tags
required_tags = {
"Environment": ["dev", "staging", "prod"],
"Owner": [],
"CostCenter": [],
}
deny[msg] {
resource := input.resource_changes[_]
resource.change.actions[_] != "delete"
resource.type != "aws_iam_role"
tags := object.get(resource.change.after, "tags", {})
# Check required tags exist
missing := {tag | required_tags[tag]; not tags[tag]}
count(missing) > 0
msg := sprintf(
"%s missing required tags: %v",
[resource.address, missing]
)
}
deny[msg] {
resource := input.resource_changes[_]
resource.change.actions[_] != "delete"
tags := object.get(resource.change.after, "tags", {})
# Check tag values are valid
tag := "Environment"
tags[tag]
not tags[tag] in required_tags[tag]
msg := sprintf(
"%s has invalid value for %s: %s (must be one of %v)",
[resource.address, tag, tags[tag], required_tags[tag]]
)
}This policy enforces required tags and validates tag values. The first deny rule checks for missing tags. The second deny rule validates that tag values are from an approved list.
name: Policy Enforcement
on: [pull_request]
jobs:
policy:
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 Check
run: infracost breakdown --path tfplan.json --threshold 100The policy step runs OPA checks and cost estimation. If any policy fails, the step fails. The infracost threshold blocks changes exceeding the cost limit.
Run policies in advisory mode for 2-4 weeks. Report violations without blocking. This gives teams time to fix existing resources. Switch to enforce after the team has adapted.
A policy with incorrect logic can deny all plans. 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.