Policy as Code
Policy as code enforces your organization's rules on Terraform changes automatically, by evaluating the plan against policies before apply. "No public S3 buckets." "Every resource must carry a cost-center tag." "Only approved instance types in production." Each is a rule a human used to enforce by reading every plan; policy as code turns it into a check that runs on every change and blocks the ones that violate it.
This is governance that scales past the point where a human gatekeeper can read every diff. The two main paths are Sentinel, HashiCorp's policy language built into the paid HCP Terraform and Enterprise platform, and OPA with Conftest, the open-source alternative that evaluates Rego policies against the plan in any pipeline. Both work on the plan's JSON representation, so they judge what Terraform is actually about to do — not the source text, the real proposed change.
What Policy as Code Enforces
The rules fall into a few buckets: tagging (every resource has the required keys), security (no public access, encryption required), cost (no instance type above an approved size, no more than N of an expensive resource), and approved-resource lists (only the regions, types, or services the org sanctions). The check evaluates the plan, so it sees the concrete values about to be created — a bucket with public-access blocks disabled, a security group with an open ingress — and decides pass or fail before any of it reaches AWS.
Sentinel
Sentinel is HashiCorp's own policy framework, integrated directly into HCP Terraform and Terraform Enterprise. You write policies in the Sentinel language, attach them to a workspace, and every run evaluates them between plan and apply. Its advantage is the tight integration: no wiring, the enforcement levels are built in, and policy results show up in the run UI alongside the plan. Its constraint is that it is tied to the paid HashiCorp platform — you do not run Sentinel in a bare open-source pipeline.
OPA and Conftest
The open-source path is Open Policy Agent and its Conftest wrapper, using the Rego policy language. You export the plan to JSON with terraform show -json, then evaluate Rego policies against it in any CI system. Nothing is tied to a vendor platform — it runs in GitHub Actions, GitLab CI, or a shell script. The cost is that you wire it up yourself: export the plan, run the evaluation, decide how a failure blocks the pipeline.
package terraform.s3 # deny any S3 bucket whose ACL makes it public deny[msg] { resource := input.resource_changes[_] resource.type == "aws_s3_bucket_acl" resource.change.after.acl == "public-read" msg := sprintf("S3 bucket %s must not be public-read", [resource.address]) }
The policy reads resource_changes from the plan JSON, matches any bucket ACL set to public-read, and returns a deny message naming the exact resource. Run with conftest test plan.json in CI, it fails the build on a match and prints the message — the engineer sees precisely which bucket and why.
Enforcement Levels
Not every policy should block. The enforcement levels — advisory, soft-mandatory, and hard-mandatory — let you choose the consequence per policy. Advisory warns and lets the run continue, the right setting for a new rule you are rolling out. Soft-mandatory blocks but allows an authorized override, for rules with legitimate exceptions. Hard-mandatory blocks with no override, for the guardrails that must never bend — no public data store, no unencrypted database. Setting every policy to hard-mandatory is a mistake: it blocks legitimate exceptions with no escape hatch and pushes people to disable the system entirely.
Policy vs Static Analysis
Policy as code and the security scanners from the previous topic overlap but are not the same. Scanners ship generic, industry-standard rules — they know a public bucket is generally bad. Policy as code encodes your org's specific rules: your tagging schema, your approved instance types, your cost ceilings, your sanctioned regions. Use scanners for the generic security baseline and policy as code for the rules that are particular to your organization. Duplicating a scanner's generic rules in policy code is wasted effort; the point of policy code is the rules no off-the-shelf scanner could know.
Sentinel — HashiCorp's policy language, tightly integrated into HCP Terraform and Enterprise with built-in advisory, soft-mandatory, and hard-mandatory enforcement levels and results in the run UI. Choose it when you are already inside the paid HashiCorp platform and want zero wiring; it is tied to that platform.
OPA / Conftest — open-source, using the Rego language to evaluate the plan JSON in any CI system with no vendor lock-in. Choose it when you want tool-agnostic, open-source policy, accepting that you wire up the plan export and the enforcement yourself.
- Relying on humans to remember and enforce org rules — tagging, no public access, approved types — that policy as code could check automatically on every plan.
- Setting every policy to hard-mandatory, blocking legitimate exceptions with no override path and pushing teams to disable the system out of frustration.
- Duplicating a security scanner's generic rules in policy code instead of encoding the org-specific rules no off-the-shelf scanner could know.
- Writing a policy with no clear failure message, so a blocked engineer sees only "denied" and cannot tell what to fix or why.
- Evaluating the source HCL instead of the plan JSON, missing the concrete computed values the policy actually needs to judge.
- Encode your org-specific rules — tagging schema, approved instance types, cost ceilings, sanctioned regions — as policy evaluated on every plan.
- Choose enforcement levels deliberately: advisory for a new rule you are rolling out, hard-mandatory only for the critical guardrails that must never bend.
- Use policy as code for org-specific rules and security scanners for the generic baseline, together rather than as substitutes.
- Write violation messages that name the resource and state exactly what is wrong and how to fix it.
- Evaluate the plan JSON (
terraform show -json) so policies judge the concrete change Terraform is about to make.
Knowledge Check
What does policy as code enforce that a generic security scanner does not?
- Your organization's specific rules — its tagging schema, approved instance types, and cost ceilings — that no generic tool knows
- Industry-standard misconfigurations like public S3 buckets and open security-group ports, which only policy code could ever catch
- Syntax and type errors in the raw HCL before any plan command runs
- Formatting consistency and canonical style across the team's Terraform files
When should a policy be set to advisory rather than hard-mandatory?
- When it is a new rule being rolled out and you want to warn without blocking while teams adapt
- When it protects a critical guardrail like "no public database instance" that must never be bypassed
- When it needs to block but allow an authorized human to override per run
- Advisory is only for policies that have already been removed from the pipeline entirely
What is the main trade-off between Sentinel and OPA/Conftest?
- Sentinel is integrated into the paid HashiCorp platform with built-in enforcement; OPA/Conftest is open-source, runs in any CI, but you wire it up
- Sentinel is the fully open-source and free option to run in absolutely any CI, while OPA/Conftest requires a paid HCP Terraform subscription to use at all
- Sentinel evaluates the source HCL directly while OPA can only evaluate the live AWS account
- OPA enforces only resource tagging rules while Sentinel handles security and cost rules
Why should a policy produce a clear, specific violation message?
- So a blocked engineer can tell exactly which resource failed and how to fix it, rather than seeing an opaque "denied"
- Because the message is what Terraform writes into the remote state file on a failed apply run
- Because without a clear violation message the policy silently defaults back to advisory and stops blocking runs entirely
- Because the message text is what is required for the policy engine to evaluate the plan JSON at all
You got correct