The Pipeline Gatekeeper
Policy as Code isn't something you run manually on your laptop occasionally. To be effective, it must be integrated directly into your automated CI/CD pipeline. A policy that can be bypassed by skipping a step is not a guardrail — it's a suggestion.
The typical workflow looks like this:
- Developer commits code (e.g., Terraform HCL) and opens a pull request.
- CI system runs
terraform plan. This generates a JSON representation of exactly what will change in the real world — which resources will be created, modified, or destroyed, and with what attributes. - The Policy Engine runs. It reads that plan JSON and compares it against the rules you've defined. It never touches real infrastructure.
- Pass/Fail Decision.
- If it passes, the pipeline continues to
terraform apply, and the infrastructure is created. - If it fails, the pipeline stops immediately with a clear error message explaining which rule was violated and why. The insecure or non-compliant infrastructure is never created.
Why the Plan JSON Matters
The terraform plan -out=plan.tfplan && terraform show -json plan.tfplan > plan.json step is critical. It converts Terraform's binary plan into a structured JSON document that policy engines can parse. This JSON contains every resource that will be created, every attribute value, and every dependency. Policy engines like OPA operate on this JSON as their input — they never need to understand HCL syntax.
A senior insight here: the plan JSON represents intended state, not current state. This means your policies catch problems before they exist, but they also mean you must account for the difference between resource.change.before (current state) and resource.change.after (desired state) when writing rules. A policy that only checks after will miss cases where a resource is being modified to become non-compliant from a previously compliant state.