IaC Security with OPA and Rego [Policy-as-Code 2026]
Bottom Line
Treat policy as a compile-and-testable part of the delivery path, not a spreadsheet of controls. OPA and Rego work best when they evaluate Terraform plan JSON before apply, fail predictably on high-risk changes, and emit auditable decisions the platform team can tune over time.
Key Takeaways
- ›OPA v1.15.2 is the current release and keeps the core eval, test, and bench workflow intact.
- ›Run Rego against Terraform plan JSON before apply so developers see violations while the change is still cheap.
- ›Use decision logs with decision_id and bundle metadata to make policy outcomes auditable.
- ›Profile hot rules with opa bench and eval profiling before policy bundles grow into a latency problem.
- ›Handle Terraform unknown values explicitly or your policy may silently pass risky changes.
Infrastructure as Code security usually breaks down in one of two ways: teams bolt on static scanners too late, or they centralize control so hard that developers route around it. Open Policy Agent and Rego offer a cleaner middle path. You evaluate the exact infrastructure change set, express security intent as versioned code, and push decisions into the delivery path early enough to matter without turning every pull request into a governance meeting.
- OPA v1.15.2 is current and supports the same core author-test-benchmark workflow security teams already know.
- For IaC, the highest-signal enforcement point is the Terraform plan JSON before apply, where intent and likely impact are visible together.
- Decision logs add audit context, including a decision_id, queried policy details, and bundle metadata.
- opa bench and eval profiling expose hot rules before policy bundles become a delivery bottleneck.
- Unknown plan-time values must be modeled explicitly or a policy can look correct while still missing real risk.
Architecture & Implementation
The cleanest policy-as-code setup separates authorship, evaluation, and enforcement. Developers change Terraform. Platform and security teams publish shared Rego modules. CI generates plan JSON, evaluates policy, and blocks only when a rule returns a clear deny, warn, or exception-needed result. That structure keeps policy logic reusable and keeps enforcement attached to the software delivery path instead of to a separate approval queue.
Bottom Line
Use OPA to evaluate planned infrastructure changes before they reach cloud APIs. The win is not just stronger security; it is faster, more explainable decisions that can be tested, benchmarked, and audited like application code.
Decision Topology
A production-grade flow usually looks like this:
- Generate a Terraform plan and convert it to JSON.
- Load shared policy bundles plus team-specific policy modules.
- Evaluate a small set of entrypoint decisions such as
deny,warn, andexceptions_required. - Publish the result to CI logs, pull request status checks, and optional downstream audit systems.
This model matches how OPA is designed to work: your pipeline supplies structured input, and the engine returns a decision. The application, admission controller, or pipeline remains the enforcement point; OPA remains the decision point.
Repository Shape That Ages Well
- Keep
policy/libfor reusable helpers such as tag normalization, CIDR checks, and owner validation. - Keep
policy/terraformfor rules tied to Terraform plan structure, resource changes, and provider-specific edge cases. - Keep
policy/testsfor policy unit tests with small, purpose-built input fixtures. - Keep exception data separate from policy logic so waivers are reviewable and expire on schedule.
The implementation detail many teams miss is input normalization. Terraform plans are structurally noisy. If every rule walks raw provider output, policy authors end up rewriting the same lookups and null checks. A small normalization layer reduces both duplication and false negatives.
Policy Authoring
Good Rego for IaC security is narrow, explicit, and biased toward human-readable deny messages. Start with rules that explain what is unsafe, not abstract rules that only a policy expert can decode later.
Write Deny Rules First
A typical pattern is a collection rule that appends a message for each violation. In modern Rego syntax, that looks like this:
package terraform.security
deny contains msg if {
some rc in input.resource_changes
rc.type == "aws_security_group_rule"
rc.change.after.cidr_blocks[_] == "0.0.0.0/0"
rc.change.after.from_port <= 22
rc.change.after.to_port >= 22
msg := sprintf("public SSH exposure in %s", [rc.address])
}This style scales because it produces actionable failures. A reviewer does not need to reverse-engineer a boolean result; they see the resource address and the violated condition immediately.
Normalize Provider Noise
- Convert absent fields into consistent defaults before policy logic consumes them.
- Flatten nested provider objects into smaller helper views when multiple rules need the same data.
- Separate pure helper rules from opinionated security rules so refactors do not change policy intent accidentally.
- Prefer data-driven allowlists for approved regions, instance families, or encryption exemptions.
That unknown-value issue is where many “working” policies fail in real programs. Defensive authorship means modeling uncertainty as a first-class state: deny, warn, or require explicit review when a critical field is unresolved.
If your team shares plan JSON or decision logs across tickets, docs, or incident reviews, scrub account IDs, hostnames, or sensitive tags first. A lightweight step with the Data Masking Tool keeps collaboration safe without weakening the policy review itself.
CI/CD Enforcement
The implementation loop should be boring: deterministic inputs, deterministic policy bundles, deterministic outcomes. That is what turns security from a weekly meeting into a build artifact.
Local and Pull Request Gates
The two most useful commands in the authoring loop are opa eval and opa test. Official docs list common opa eval flags including -d or --data, -i or --input, -b or --bundle, -f or --format, --fail, and --fail-defined. That is enough to build a robust local and CI gate.
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
opa eval -d policy -i tfplan.json 'data.terraform.security.deny'
opa test policy/
In practice, the CI contract should stay simple:
- Fail the build when
denyis non-empty. - Surface
warnresults in pull request comments without blocking the merge. - Require an exception record when unresolved or high-blast-radius findings appear.
- Version policy bundles independently from application repositories so teams can pin and roll forward safely.
Promotion and Audit
Once a policy bundle is stable, promote it like any other shared platform dependency. Bundle metadata matters because it lets you explain which policy version made a decision on a given run.
- Record the bundle version used in every CI decision.
- Enable decision logs where auditability matters; OPA documents that decisions can include a decision_id.
- Keep policy release notes short and operational: new denies, changed exceptions, removed legacy paths.
- Store decision samples for replay when debugging regressions or unexpected denials.
That replay loop is one of the strongest reasons to adopt policy-as-code at all. A failed policy decision is no longer a vague governance complaint; it is an executable artifact tied to policy source, input data, and bundle history.
Benchmarks & Metrics
The right question is not “Is OPA fast?” but “Is our policy set fast enough at the enforcement point we chose?” The answer varies by context. A pull request gate can tolerate much more latency than an in-request authorization check. OPA’s own performance guidance notes that some low-latency use cases operate with a budget around 1 millisecond, which is a useful mental model even if IaC checks usually run off the request path.
What to Measure
| Metric | Why it matters | Healthy signal |
|---|---|---|
| Decision latency | Shows whether policy blocks developer flow | Stable over time, no spikes after bundle updates |
| Rule evaluation count | Reveals expensive or repetitive expressions | Hot rules are obvious and few |
| False-positive rate | Predicts whether teams will trust the gate | Low enough that exceptions stay rare |
| Unknown-value rate | Exposes plan-time blind spots | Limited to known resource classes |
| Policy coverage | Measures how much of the IaC surface is actually governed | Critical resource types covered first |
Tune the Hot Path
OPA ships the tools you need for first-pass tuning. opa bench benchmarks query execution, and the eval profiler shows where time is being spent.
opa bench -b ./policy-bundle -i tfplan.json 'data.terraform.security.deny'
opa eval --data policy --input tfplan.json --profile-limit 5 --profile-sort num_eval --format=pretty 'data.terraform.security.deny'
- Use the profiler to find rules that re-scan large collections or duplicate normalization logic.
- Favor linear, indexed checks over deeply nested joins when the same decision runs on every pull request.
- Cache prepared queries when embedding OPA as a Go library; official docs note prepared queries can be cached in memory and shared across goroutines.
- Benchmark representative plans, not toy fixtures, or the first real monorepo run will surprise you.
For most IaC programs, the real benchmark target is not raw microseconds. It is whether policy stays fast enough that teams keep it enabled on every change. A three-second gate that blocks risky plans consistently is more valuable than a sub-millisecond architecture nobody wires into the default path.
Strategic Impact
Done well, policy-as-code changes who gets to move fast. It is not only a security control; it is a scaling mechanism for platform teams that would otherwise review infrastructure by hand.
What Changes Organizationally
- Security teams stop writing one-off review comments and start publishing reusable controls.
- Platform teams expose a paved road with clear failure modes instead of case-by-case judgment calls.
- Application teams get earlier feedback, smaller fixes, and fewer late-stage deployment surprises.
- Audit and compliance teams gain a replayable record instead of a collection of screenshots and approvals.
This is the strategic win: policy moves from tribal knowledge into a versioned system with tests, owners, and measurable quality. Once that happens, control depth can increase without forcing a matching increase in review headcount.
The deeper point is methodological. Checklists decay because cloud architectures change faster than documentation. Rego rules survive longer because they execute against real change data. When a control becomes outdated, the pipeline tells you immediately through false positives, missed cases, or rising exception volume.
Road Ahead
The next step for mature teams is not “more rules.” It is better portability, stronger test harnesses, and narrower, more explainable policy contracts. OPA’s WebAssembly path also matters here: official docs show policies can be compiled with opa build -t wasm and exposed through declared entrypoints, which opens useful options for edge and embedded enforcement scenarios.
- Compile stable policy entrypoints to Wasm when you need enforcement outside a central OPA process.
- Expand unit tests before expanding rule count; policy debt compounds quietly.
- Track exception age and recurrence so temporary waivers do not become permanent architecture.
- Fold policy outcomes into architecture reviews to spot systemic drift, not just single violations.
The long-term pattern is clear. IaC security works best when policy is executable, portable, and owned like code. OPA and Rego are not valuable because they make controls stricter; they are valuable because they make controls operational.
Frequently Asked Questions
How do I run OPA against a Terraform plan? +
terraform plan -out=tfplan.binary, convert it to JSON with terraform show -json, then evaluate Rego against that JSON using opa eval. This approach lets policy inspect the planned change set before apply, which is the highest-value point for enforcement.What is the difference between OPA and Conftest for IaC security? +
How should Rego handle unknown values in Terraform plan JSON? +
Should I enforce OPA only in CI, or also at runtime? +
Can OPA policies compile to Wasm? +
opa build -t wasm flow that compiles policy entrypoints into a Wasm bundle. That is useful when you need portable evaluation in edge or embedded environments, although some built-ins such as http.send are not natively supported in Wasm.Get Engineering Deep-Dives in Your Inbox
Weekly breakdowns of architecture, security, and developer tooling — no fluff.