Home Posts IaC Security with OPA and Rego [Policy-as-Code 2026]
Security Deep-Dive

IaC Security with OPA and Rego [Policy-as-Code 2026]

IaC Security with OPA and Rego [Policy-as-Code 2026]
Dillip Chowdary
Dillip Chowdary
Tech Entrepreneur & Innovator · April 27, 2026 · 11 min read

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:

  1. Generate a Terraform plan and convert it to JSON.
  2. Load shared policy bundles plus team-specific policy modules.
  3. Evaluate a small set of entrypoint decisions such as deny, warn, and exceptions_required.
  4. 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/lib for reusable helpers such as tag normalization, CIDR checks, and owner validation.
  • Keep policy/terraform for rules tied to Terraform plan structure, resource changes, and provider-specific edge cases.
  • Keep policy/tests for 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.
Watch out: Terraform plan JSON can contain unknown values at plan time. If a rule treats unknown data as a clean pass, you may approve a change whose risky attribute is only resolved during apply.

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 deny is non-empty.
  • Surface warn results 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

MetricWhy it mattersHealthy signal
Decision latencyShows whether policy blocks developer flowStable over time, no spikes after bundle updates
Rule evaluation countReveals expensive or repetitive expressionsHot rules are obvious and few
False-positive ratePredicts whether teams will trust the gateLow enough that exceptions stay rare
Unknown-value rateExposes plan-time blind spotsLimited to known resource classes
Policy coverageMeasures how much of the IaC surface is actually governedCritical 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.
Pro tip: Keep the public contract tiny: one entrypoint for blocking findings, one for warnings, and one for exception metadata. Small contracts are easier to benchmark, document, and migrate.

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? +
Generate a binary plan with 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? +
OPA is the general-purpose policy engine and Rego is the language. Conftest is a convenience tool built on OPA for testing structured configuration data; it is useful, but the core policy model and evaluation semantics still come from OPA.
How should Rego handle unknown values in Terraform plan JSON? +
Treat unknown values as a distinct branch in policy logic rather than assuming they are safe. For sensitive attributes such as public exposure, encryption, or network paths, a prudent pattern is to warn or require manual review when the plan cannot resolve a critical field yet.
Should I enforce OPA only in CI, or also at runtime? +
For IaC security, CI is the first mandatory checkpoint because it is where changes are still cheap to fix. Runtime enforcement can complement that for admission control, drift handling, or app-level authorization, but it should not replace pre-apply policy on infrastructure changes.
Can OPA policies compile to Wasm? +
Yes. OPA documents a 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.

Found this useful? Share it.