Policy-as-Code for Infrastructure: OPA and Conftest in Practice
Stop catching bad infrastructure config in code review. Here's how to enforce IaC guardrails automatically with OPA and Conftest — and let AI write the Rego.
- #iac
- #policy-as-code
- #opa
- #conftest
- #rego
- #security
Every team eventually hits the same wall: someone merges infrastructure that opens a security group to 0.0.0.0/0, or ships a bucket without encryption, or a container running as root. Code review is supposed to catch these. It doesn’t, reliably, because humans are tired and the diff is 600 lines.
Policy-as-code moves those rules out of human attention and into a gate that runs every time. The Open Policy Agent (OPA) and its config-friendly wrapper Conftest are how I do it across Terraform, Kubernetes, Dockerfiles, and plain YAML.
Why a dedicated policy engine
You could write a bash script that greps for 0.0.0.0/0. People do. It works until you have forty rules across six config formats, and then it’s an unmaintainable pile of regex.
OPA gives you a real language — Rego — purpose-built for “given this structured data, is it allowed?” Conftest wraps OPA so it reads your YAML, JSON, HCL, or Dockerfiles, evaluates them against your policies, and exits non-zero when something violates. That non-zero exit is the whole value: it fits in CI like any other test.
A first policy
Conftest evaluates input against deny rules. Any deny that produces a message fails the run. Here’s a policy that blocks privileged Kubernetes containers:
# policy/security.rego
package main
deny[msg] {
input.kind == "Deployment"
c := input.spec.template.spec.containers[_]
c.securityContext.privileged == true
msg := sprintf("container '%s' must not run privileged", [c.name])
}
Run it:
conftest test deployment.yaml
If any container is privileged, you get a clear message and a failing exit code. Wire that into CI and the rule is now enforced on every change, forever, without anyone remembering to check.
Rego has a learning curve — AI flattens it
Let’s be honest: Rego is the reason a lot of teams bounce off OPA. It’s a declarative logic language and it does not read like the Python or Go you’re used to. The mental model — rules that succeed by finding a matching document — takes a while.
This is exactly where AI pays off. Describe the rule in English and ask for the Rego:
“Write a Conftest deny rule: fail if any Kubernetes container does not set
resources.limits.memory. Include the container name in the message.”
You’ll get a working rule in seconds. The catch — and it’s a real one — is that LLMs hallucinate Rego helpers and occasionally write rules that always pass (a deny that never matches is silently useless). So you must test every generated policy against both a known-good and a known-bad fixture. Trust, but verify with fixtures.
I keep a set of IaC policy prompts tuned for exactly this, so the model already knows the Conftest conventions.
Test your policies like code
A policy with no tests is a liability — it can rot into a no-op and you’d never know. Conftest supports unit tests for policies:
# policy/security_test.rego
package main
test_denies_privileged {
deny[_] with input as {
"kind": "Deployment",
"spec": {"template": {"spec": {"containers": [
{"name": "app", "securityContext": {"privileged": true}}
]}}}
}
}
test_allows_unprivileged {
count(deny) == 0 with input as {
"kind": "Deployment",
"spec": {"template": {"spec": {"containers": [{"name": "app"}]}}}
}
}
conftest verify
Now your guardrails have guardrails. This pairs naturally with broader IaC testing strategies — policy tests are just one layer.
Organize policies by concern
As policies grow, split them by domain using Rego packages and Conftest namespaces:
policy/
├── security/ # encryption, network exposure, privilege
├── cost/ # instance sizes, untagged resources
├── compliance/ # required tags, naming conventions
└── reliability/ # replica counts, probes, limits
Run a namespace with conftest test --namespace security .... This lets you make security policies hard-fail while cost policies emit warnings, using Rego’s warn rules instead of deny.
Wire it into the pipeline
The pattern that works:
- Pre-commit / local — developers run
conftest testbefore pushing. Fast feedback, fewer surprises. - CI gate — the pipeline runs the full policy set on every PR. A
denyfails the build. This is the enforcement point. - Pre-apply — for Terraform, run Conftest against the
planJSON (terraform show -json plan.out) so policies evaluate the actual proposed change, not just the source.
# CI step
- name: Policy check
run: conftest test --all-namespaces manifests/
What to enforce first
Don’t try to boil the ocean. The high-value starter rules, by impact:
- No public network exposure unless explicitly annotated.
- Encryption at rest required on storage.
- Required tags (owner, environment, cost-center) on every resource.
- No
:latestimage tags — pin everything. - Resource limits set on every container.
Each of these has caused a real outage or a real bill somewhere. Encoding them as policy means it can’t happen to you twice.
The payoff
Policy-as-code changes the conversation. Instead of a senior engineer being the human bottleneck who remembers the rules, the rules live in a repo, run automatically, and are testable. New hires can’t violate a policy they’ve never heard of, because the gate explains itself in the failure message.
Let AI write the first draft of the Rego from your plain-English rules, keep your prompts in a prompt library, and test every policy against fixtures. The engine is deterministic even when the authoring assistant isn’t — which is exactly the safety property you want.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.