Compliance & Policy as Code

Policy for IaC

18 min Lesson 6 of 27

Policy for IaC

Infrastructure as Code gave teams repeatability and velocity — but it also means a single misconfigured Terraform module can stamp the same security hole across every environment in minutes. Gating policy at the IaC layer, before a terraform apply ever runs, is the highest-leverage control point in a cloud-native compliance program. This lesson covers the three dominant tools that enforce that gate: Checkov, tfsec, and HashiCorp Sentinel.

Why Gate at the Plan Stage?

The cost of a misconfiguration grows with how late you catch it. Catching an open S3 bucket in a static scan of a .tf file costs nothing. Catching it after terraform apply in production means incident response, potential data exposure, and audit findings. The goal is to fail fast in the PR pipeline so engineers fix issues while context is fresh and before any cloud API is called.

There are three natural enforcement points:

  1. Pre-commit hooks — catch obvious mistakes on the engineer's laptop before the code ever reaches version control.
  2. CI PR gate — run a full scan on every pull request; block merge if high-severity policies fail.
  3. Terraform plan check — parse the actual plan JSON (not just source files) for runtime-evaluated expressions that static analysis cannot see.

Production-grade pipelines use all three layers; the CI gate is the non-negotiable one because it is the last automated check before code reaches the main branch.

Checkov — Open-Source Static Analysis

Checkov, maintained by Bridgecrew (now Prisma Cloud), scans Terraform HCL, CloudFormation, Kubernetes manifests, ARM templates, and Dockerfiles. It ships with over 1,000 built-in checks mapped to CIS benchmarks, SOC 2, PCI DSS, HIPAA, and NIST 800-53. It is the most common open-source choice because it is free, regularly updated, and integrates cleanly into GitHub Actions and GitLab CI.

# Install (prefer the Docker image in CI to pin a version) pip install checkov==3.2.0 # Scan a Terraform directory checkov -d ./infra/terraform/modules/s3 \ --framework terraform \ --compact \ --output cli \ --output junitxml \ --output-file-path results/ # Scan a specific plan file (more accurate — catches computed values) terraform plan -out=tfplan.binary terraform show -json tfplan.binary > tfplan.json checkov -f tfplan.json --framework terraform_plan

The most useful flags in a CI context: --soft-fail exits 0 even on failures (useful for reporting phases); --check CKV_AWS_18,CKV_AWS_20 runs only specific check IDs; --skip-check CKV_AWS_144 skips a check with a justification comment in your .checkov.yaml. Always suppress via config file, never via --skip-check inline in CI commands — config-file suppressions are reviewable and auditable.

Run Checkov on the plan file, not just the source. Terraform expressions like var.enable_logging ? true : false are not always resolvable from HCL alone. The plan JSON contains the evaluated values, so plan-file scanning catches conditional misconfigurations that source-file scanning misses.

tfsec — Fast, Purpose-Built Terraform Scanner

tfsec (now merged into Trivy as trivy config) is purpose-built for Terraform. It is written in Go, extremely fast, and understands module references across directories — a significant advantage when your repo has a module hierarchy. Its output links directly to remediation guidance and the relevant CIS benchmark control.

# Run via Trivy (recommended for new setups — tfsec is the backend) trivy config ./infra/terraform \ --severity HIGH,CRITICAL \ --exit-code 1 \ --format table # Output as SARIF (uploads to GitHub Security tab) trivy config ./infra/terraform \ --format sarif \ --output trivy-results.sarif

Using SARIF output is a best practice in enterprise pipelines because GitHub and GitLab can render inline code annotations directly in the PR, so engineers see exactly which line triggered which finding without leaving the review interface.

IaC Policy Gate in CI Pipeline PR / Commit (.tf files) Pre-commit tflint / checkov Plan Scan checkov (plan.json) Sentinel Gate Hard / Soft enforce tf apply Infra provisioned BLOCKED Violation found plan.json policy check fail → block PR
IaC policy gates at every CI stage: pre-commit, plan-file scan, and Sentinel before apply.

HashiCorp Sentinel — Policy as Code for the Terraform Ecosystem

Sentinel is HashiCorp's commercial policy framework, available in Terraform Cloud and Terraform Enterprise. Unlike Checkov and tfsec (which run externally), Sentinel runs inside the Terraform run workflow, with direct access to the plan, the run metadata, and the workspace configuration. This makes it the authoritative enforcement point when your organization uses Terraform Cloud as the apply engine.

Sentinel policies are written in the Sentinel language (similar to Go) and can be set to three enforcement levels: advisory (warn only), soft-mandatory (can be overridden with justification), and hard-mandatory (absolutely blocks apply, no override). Use hard-mandatory for regulatory controls (encryption, public access blocks) and soft-mandatory for organizational standards where legitimate exceptions exist.

# sentinel-s3-encryption.sentinel import "tfplan/v2" as tfplan # Collect all S3 bucket resources in the plan s3_buckets = filter tfplan.resource_changes as _, rc { rc.type is "aws_s3_bucket" and (rc.change.actions contains "create" or rc.change.actions contains "update") } # Rule: every bucket must have server-side encryption configured bucket_encryption_enabled = rule { all s3_buckets as _, bucket { bucket.change.after.server_side_encryption_configuration is not null } } main = rule { bucket_encryption_enabled }
Sentinel does not replace external scanners. Sentinel only runs when a Terraform run is triggered through Terraform Cloud. Engineers running terraform plan locally or in a non-TFC CI bypass it entirely. Always pair Sentinel with Checkov/Trivy in the PR pipeline — Sentinel is the final hard stop; Checkov is the early feedback loop.

GitHub Actions Integration Pattern

A production-grade CI job runs the scans in parallel, uploads SARIF results to the Security tab, and fails the pipeline if any CRITICAL or HIGH findings exist in policy-controlled categories. Informational and low-severity findings are reported but non-blocking.

# .github/workflows/iac-policy.yml name: IaC Policy Gate on: pull_request: paths: - 'infra/**' jobs: checkov: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Terraform init & plan run: | cd infra/terraform terraform init -backend=false terraform plan -out=tfplan.binary terraform show -json tfplan.binary > tfplan.json env: AWS_ACCESS_KEY_ID: ${{ secrets.TF_PLAN_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.TF_PLAN_SECRET }} - name: Checkov plan scan uses: bridgecrewio/checkov-action@v12 with: file: infra/terraform/tfplan.json framework: terraform_plan output_format: cli,sarif output_file_path: console,results.sarif soft_fail: false check: CKV_AWS_18,CKV_AWS_20,CKV_AWS_19,CKV_AWS_57 - name: Upload SARIF to Security tab uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: results.sarif trivy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Trivy config scan uses: aquasecurity/trivy-action@master with: scan-type: config scan-ref: infra/terraform severity: HIGH,CRITICAL exit-code: '1' format: sarif output: trivy.sarif - uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: trivy.sarif

Managing Suppressions Without Creating Compliance Debt

Every scan will surface findings that are genuinely acceptable for your environment — a public S3 bucket that hosts a static website, for instance. Suppress these via a .checkov.yaml config file committed to the repo, not inline CLI flags. Each suppression must include the check ID, the resource it applies to, and a comment explaining the business justification. Suppressions without justification comments fail the code review process and, more importantly, fail auditor review.

Track suppression age. Add a date field to every suppression entry and run a quarterly job that alerts on suppressions older than 90 days. Suppressions are technical debt — they should be time-bounded and reviewed, not accumulated indefinitely. Many organizations have failed audits not because they had suppressions, but because they had suppressions nobody remembered authorizing.

Combining Checkov source scans, plan-file scans, Trivy for module-aware analysis, and Sentinel as the hard gate in Terraform Cloud creates a defense-in-depth policy enforcement architecture. Each layer catches different classes of misconfiguration, and together they give you the documented, automated evidence trail that SOC 2 and PCI DSS auditors require.