Policy as Code for Terraform: OPA, Sentinel, Checkov, and Human Review
Terraform review fails when every pull request asks humans to rediscover the same constraints: no public storage buckets, no unencrypted databases, no privileged security groups, no unsupported regions, no untagged cost centers.
Situation
Infrastructure teams adopted Terraform because code review, version control, and plan output made infrastructure changes more predictable. That was a real improvement over manual console work, but it also moved a large class of operational risk into the pull request.
A Terraform plan can tell reviewers what will change. It does not decide whether the change is acceptable. A plan can show that an S3 bucket ACL will be public, that an RDS instance will be created without encryption, or that an IAM policy grants broad access. It does not know whether those choices violate the organization’s security, cost, reliability, or compliance rules.
As platform teams scale, the review load becomes uneven. Senior engineers become the enforcement layer for rules that should have been encoded once. Security teams become late-stage approvers instead of policy authors. Application teams wait for comments on issues that could have been caught in seconds.
Policy as code exists to move repeatable judgment closer to the change.
The Problem
The naive answer is to add a scanner to CI and block anything red. That usually works for the first dozen rules, then collapses under exceptions, ambiguous ownership, and noisy findings.
Terraform policy has several different enforcement points:
- Static configuration before
terraform plan - Plan JSON after Terraform has resolved modules, variables, and provider behavior
- Apply-time enforcement inside Terraform Cloud or Terraform Enterprise
- Human review for context that is not visible in code
Each point sees a different version of reality. Checkov can inspect source code quickly, including common Terraform misconfigurations. OPA can evaluate structured input such as Terraform plan JSON using Rego. Sentinel is embedded in HashiCorp’s commercial Terraform workflow and can enforce policy against configuration, state, and plan data in Terraform Cloud and Terraform Enterprise, according to HashiCorp’s Sentinel documentation. Human reviewers can understand migration risk, incident context, and business exceptions that no policy engine should guess.
The core question is not “Which policy tool should we standardize on?”
The better question is: which decisions should be automated, which should be escalated, and which should remain human?
The Answer: A Layered Policy Control Plane
The durable architecture is a layered control plane: fast static checks early, plan-aware checks before merge or apply, hard enforcement for non-negotiable invariants, and human review for exceptions and intent.
flowchart TD
A[developer opens pull request] --> B[static checks — Checkov]
B --> C[terraform plan — normalized change set]
C --> D[plan policy — OPA or Sentinel]
D --> E{policy outcome}
E -->|pass| F[merge or apply]
E -->|warn| G[human review — risk decision]
E -->|deny| H[blocked change — policy feedback]
G -->|approved exception| F
G -->|rejected exception| H
I[policy repository — tests and ownership] --> B
I --> D
J[exception log — expiry and rationale] --> G
Checkov belongs at the first gate. It is fast, easy to run locally, and suited to broad configuration hygiene: encryption flags, public exposure, logging settings, secret patterns, and known bad combinations. Its Terraform scanning documentation describes scanning Terraform configuration directly, which makes it useful before teams spend time producing and reviewing plans.
OPA belongs where teams want a general policy engine across Terraform and other systems. The Open Policy Agent Terraform documentation describes evaluating Terraform plan data as JSON, which is the key distinction: the policy can reason about intended changes after Terraform has resolved more of the configuration. OPA also makes sense when the platform team wants one policy language across CI, Kubernetes admission, service authorization, and infrastructure review.
Sentinel belongs where Terraform Cloud or Terraform Enterprise is already the execution control plane. HashiCorp positions Sentinel as policy enforcement embedded in its enterprise products, including HCP Terraform and Terraform Enterprise. That integration matters because policy is evaluated in the same system that runs Terraform, reducing the gap between CI checks and actual apply behavior.
Human review belongs at the exception boundary. If a policy says “no public bucket,” the normal path should be automatic denial. If a policy says “public bucket allowed only for static website hosting with approved controls,” the tool can detect the risky shape, but the exception decision should be explicit, documented, time-bound, and reviewed by the owner of that risk.
In Practice
Context: The documented Terraform pattern is to generate a plan and inspect the proposed delta before apply. Terraform’s plan JSON gives external tools a structured representation of resource changes. OPA’s Terraform integration documentation builds on that pattern by evaluating policy against the plan representation rather than relying only on raw source files.
Action: Use source scanning for broad hygiene and plan scanning for intent. A Checkov rule can reject obvious problems in a module before the plan exists. An OPA policy can decide whether a proposed resource change violates a rule after module expansion and variable resolution. A Sentinel policy can enforce equivalent constraints in Terraform Cloud or Terraform Enterprise when those platforms own the run.
Result: The documented pattern is a split between early feedback and authoritative enforcement. Developers get fast CI failures on simple issues. Platform teams reserve stronger enforcement for rules that should block apply. Security reviewers see fewer repetitive comments and more explicit exception requests.
Learning: Policy as code is not only a security mechanism. It is a review allocation mechanism. It decides which changes are safe enough to proceed automatically, which changes are categorically forbidden, and which changes require accountable human judgment.
A practical rule set usually separates policies into three classes.
First are invariants. These are deny rules: production databases must be encrypted, public ingress must not use 0.0.0.0 on administrative ports, required tags must exist, and unsupported regions must be blocked. These rules should be boring, heavily tested, and hard to override.
Second are risk signals. These are warnings or soft failures: unusually large instance sizes, deletion of stateful resources, broad IAM actions, disabled backups, or changes to network routing. They should create review focus rather than pretending every risk is equally severe.
Third are workflow rules. These ensure that the change went through the right path: plan generated by CI, approved module source, ticket reference present, exception record attached, or policy waiver not expired.
The control plane should also treat policies like production code. Policies need owners, tests, fixtures, changelogs, and staged rollout. A bad policy can block every team. A vague policy can train every team to bypass the platform. A policy without test cases is an outage waiting for a pull request.
Where It Breaks
| Failure mode | Why it happens | Mitigation |
|---|---|---|
| Scanner noise | Generic rules do not understand local architecture | Disable irrelevant checks, add local policy, track false positives |
| Plan blind spots | Some values are unknown until apply | Prefer deny rules only when input data is reliable |
| Exception sprawl | Waivers become permanent architecture | Require owner, rationale, expiry, and periodic review |
| Tool fragmentation | OPA, Sentinel, and scanners encode duplicate rules | Define policy classes and choose one enforcement owner per class |
| Human rubber stamping | Reviewers see too many low-value warnings | Promote repeat findings to automated deny or suppress them |
| CI-only enforcement gap | Apply can happen through another path | Enforce again in the Terraform execution platform |
| Policy without tests | Rule changes break valid workflows | Version policies and test with representative plan fixtures |
What to Do Next
- Problem: Terraform review is overloaded because humans are repeatedly enforcing rules that machines can evaluate.
- Solution: Build a layered policy control plane: Checkov for fast source checks, OPA for portable plan-aware policy, Sentinel for embedded Terraform Cloud or Terraform Enterprise enforcement, and human review for explicit exceptions.
- Proof: The documented pattern across Terraform plan JSON, OPA policy evaluation, Checkov Terraform scanning, and Sentinel enforcement is that each tool operates best at a different point in the workflow.
- Action: Start with ten deny rules, five warning rules, policy tests, and an exception register with expiry dates. Expand only after the first rules are trusted by the teams they affect.