Skip to content
ExponaBLOG
TERRAFORM, IAC & CLOUD AUTOMATION · DAY 047 / 100

Policy as code for Terraform: what developers should know

Enforce rules before resources are created.

Expona Admin5 min read

A problem that slips past code review

Your team ships infrastructure code, it passes linting, tests run fine, and then production gets a public S3 bucket with customer data. Two hours later, you're writing the incident report. This is where policy as code for Terraform: what developers should know becomes your safety net. Rather than hoping humans catch security misconfigurations during review, we shift left and enforce rules automatically—before resources ever exist.

Why policy as code stops problems before they start

Manual review doesn't scale. As teams grow and deployment frequency increases, relying on human vigilance to catch every misconfiguration is expensive and fragile. A developer writes one line of Terraform that opens a security group to 0.0.0.0/0, another adds a public ACL to an S3 bucket, and a third enables unencrypted RDS snapshots. Without automated enforcement, at least one slips through.

Policy as code runs checks programmatically against your infrastructure definitions. Instead of hoping reviewers spot issues, the system rejects non-compliant code before it reaches production. This means:

  • No public S3 buckets created without explicit exception
  • Security groups can't open database ports to the internet
  • All RDS instances must have encryption enabled
  • IAM policies must follow least-privilege patterns
  • All resources must have required tags for cost allocation

The real win is that enforcement happens in the pipeline, during the PR stage. Developers get instant feedback and can fix issues before merging, not after deployment fails or security scrambles to remediate.

Tools available: Conftest, Checkov, tfsec, and Sentinel

Several mature tools exist for Terraform policy as code. Each has different strengths, and many teams run more than one because they catch different categories of issues.

OPA and Conftest: flexible, write your own rules

OPA (Open Policy Agent) is a general-purpose policy engine using the Rego language. Conftest wraps OPA for easier integration into CI/CD pipelines. You write your own policies as code, which gives maximum flexibility but requires learning Rego.

Example Conftest rule to deny public S3 buckets:

hcl
package main

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_s3_bucket_public_access_block"
    resource.change.after.block_public_acls == false
    msg := sprintf("S3 bucket %s allows public ACLs", [resource.address])
}

This approach is powerful if your organization has unique compliance needs or wants to encode business logic into infrastructure rules.

Checkov: broad coverage, minimal setup

Checkov comes with hundreds of pre-built checks for AWS, Azure, GCP, and Kubernetes. You don't write rules—you enable the checks you care about. It scans HCL directly and is very simple to integrate:

hcl
checkov --framework terraform --directory . --check CKV_AWS_20

This check ensures S3 buckets have versioning enabled. Checkov includes checks for encryption, public access, logging, tagging, and compliance frameworks like CIS and PCI-DSS. For most teams, Checkov out of the box covers 80% of what you need.

tfsec and Trivy: AWS-focused, fast

tfsec is a static analyzer specifically for Terraform security. Trivy (by Aqua Security) started as a container scanner but now includes Terraform checks too. Both are very fast and integrate easily into CI/CD. tfsec is lightweight and opinionated about AWS best practices.

Terraform Sentinel: policy as code for Enterprise

Sentinel is HashiCorp's policy engine for Terraform Cloud/Enterprise. If your organization uses TFE, Sentinel integrates natively into the run workflow. You write policies in Sentinel's own language and enforce them before applies happen. The downside is it only works with Terraform Cloud/Enterprise, not open-source Terraform.

Scanning the plan, not just the code

A critical distinction: static code analysis (checking HCL syntax and obvious patterns) catches many issues, but scanning the plan is more accurate. The plan shows what Terraform will actually create, after all variables, locals, and interpolations are resolved.

For example, this HCL:

hcl
resource "aws_security_group" "api" {
  name = "api-sg"
  
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.allowed_cidrs]
  }
}

A static scanner can't know if var.allowed_cidrs contains ["0.0.0.0/0"] until the plan is generated with actual values. When you scan the plan JSON, you see the resolved values and can enforce the real configuration.

Here's how most teams do it:

  1. Run terraform plan -out=plan.tfplan
  2. Convert the plan to JSON: terraform show -json plan.tfplan > plan.json
  3. Run your policy tool against the JSON: conftest test plan.json
  4. If policies fail, exit non-zero and block the merge

Checkov can work either way—against HCL files or the JSON plan—but scanning the plan is always more reliable for runtime behavior.

Failing the pipeline to block risky merges

Policy enforcement only works if it blocks the merge. A warning that no one reads has zero impact. Most teams integrate policy checks into their CI/CD pipeline so that a policy violation causes the build to fail.

A typical GitHub Actions workflow:

hcl
name: Terraform Plan & Policy

on:
  pull_request:
    paths:
      - 'terraform/**'

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.6
      
      - run: terraform init
      - run: terraform plan -out=plan.tfplan
      - run: terraform show -json plan.tfplan > plan.json
      
      - uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'config'
          scan-ref: 'terraform/'
          exit-code: '1'

When Trivy finds violations, the action fails and the PR is blocked from merging. Developers see the error immediately and either fix the configuration or work with the security team to add an exception (which should be rare and logged).

The key is that enforcement is automatic, not optional. Every PR that touches infrastructure goes through the same gates. This scales much better than relying on code review.

Common mistakes teams make

  • Too strict, too fast: Rolling out aggressive policies without allowing time for exceptions or feedback causes frustration. Start with critical security checks and gradually expand. Allow exceptions for specific resources with documented justification.
  • Not scanning the plan: Checking only HCL files misses dynamic values from variables. Always scan the resolved plan for accuracy.
  • Ignoring warnings: If your tool has warning-level violations but the pipeline doesn't fail on them, developers treat warnings as noise. Either fail the build or don't warn.
  • No feedback loop: If developers don't understand why a policy failed, they'll resent it. Include clear error messages and links to remediation docs.
  • Tool overlap without coordination: Running Checkov, tfsec, and Conftest on the same code creates noise and confusion. Choose 1–2 tools that complement each other and maintain them as a team.

Key takeaways

  • Policy as code enforces security and compliance rules before infrastructure is deployed, shifting security left and reducing manual review burden.
  • Scan the Terraform plan (JSON), not just HCL files, to catch configuration issues with resolved variable values.
  • Checkov and tfsec are good starting points for teams new to policy as code; OPA/Conftest offers more flexibility for custom rules; Sentinel integrates natively with Terraform Cloud.
  • Pipeline integration is essential—policy violations must fail the build and block merges so enforcement is automatic and consistent.
  • Start with high-impact checks (public buckets, open security groups, missing encryption) and expand gradually; allow documented exceptions for edge cases.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles