Policy as Code with Open Policy Agent (OPA)

Khimananda Oli 9 min read Virtualization
Policy as Code with Open Policy Agent (OPA)

By Khimananda Oli | Last reviewed: August 2026

Manual compliance reviews and ad-hoc security checks cannot scale in modern cloud-native environments. Policy as Code with Open Policy Agent (OPA) solves this by decoupling policy logic from application code, allowing you to enforce standards programmatically across your entire stack. Instead of relying on human vigilance during pull requests or post-deployment audits, OPA evaluates structured data against declarative Rego policies to automatically allow, deny, or mutate requests in real time.

How does Policy as Code with Open Policy Agent (OPA) actually work?

At its core, OPA is a general-purpose policy engine that answers the question: "Is this input allowed?" It does not care about the source of the data. Whether the input is a Kubernetes AdmissionReview object, a Terraform plan JSON export, or an HTTP API request, OPA treats it as structured data. You write policies in Rego, which compile into an internal representation that the OPA runtime evaluates efficiently.

The architecture follows a strict separation of concerns. Your software (the PEP, or Policy Enforcement Point) pauses execution to ask OPA (the PDP, or Policy Decision Point) for a decision. OPA evaluates the input against loaded policies and returns a simple result—typically true, false, or a structured object containing violations. This decoupling means you can update security rules without redeploying the applications they protect. For teams managing infrastructure at scale, this aligns perfectly with Infrastructure as Code principles, extending governance from resource provisioning to runtime behavior.

Enforcement Point(K8s / App / CI)Sends Input JSONRequestOPA EngineEvaluates RegoReturns DecisionAllow / DenyPolicy StoreRego + DataVersion ControlledLoaded at Runtime
OPA architecture separates the enforcement point from the policy decision engine, enabling centralized governance.

In practice, most teams interact with OPA through specialized integrations rather than raw HTTP calls. Gatekeeper or Kyverno wrap OPA specifically for Kubernetes, providing CRDs like ConstraintTemplate that make policy management feel native to the cluster. For Terraform, tools like conftest or the official OPA provider evaluate plan files before apply. Understanding this abstraction layer is critical: you are rarely writing raw OPA API calls; you are writing Rego policies that fit into these higher-level frameworks.

How do you write effective Rego policies for Kubernetes admission control?

Rego is often the biggest hurdle for engineers adopting Policy as Code with Open Policy Agent (OPA). Unlike imperative languages, Rego is declarative and logic-based. You define what is true, not how to compute it. A common mistake is trying to write Rego like Python or JavaScript. Instead, think in terms of set comprehensions and logical assertions.

Structuring a basic deny rule

A practical Kubernetes policy prevents pods from running as root. In Gatekeeper, this starts with a ConstraintTemplate. The Rego logic must iterate over containers and check the securityContext.

package k8spspnonroot

violation[{"msg": msg}] {
    container := input.review.object.spec.containers[_]
    not container.securityContext.runAsNonRoot
    msg := sprintf("Container %v must set runAsNonRoot=true", [container.name])
}

violation[{"msg": msg}] {
    container := input.review.object.spec.containers[_]
    container.securityContext.runAsUser == 0
    msg := sprintf("Container %v runs as UID 0 (root)", [container.name])
}

This example highlights two key Rego patterns. First, the underscore (_) iterates over all containers. Second, multiple violation rules act as logical ORs—if any rule body succeeds, a violation is generated. The output is always a set of objects, which Gatekeeper translates into admission webhook responses.

Testing policies before deployment

Never deploy untested Rego. Use the opa test command with mock input data. Create a test_nonroot.rego file alongside your policy:

test_fail_root_user {
    result := violation with input as {"review": {"object": {"spec": {"containers": [{"name": "app", "securityContext": {"runAsUser": 0}}]}}}}
    count(result) > 0
}

test_pass_non_root {
    result := violation with input as {"review": {"object": {"spec": {"containers": [{"name": "app", "securityContext": {"runAsNonRoot": true, "runAsUser": 1000}}]}}}}
    count(result) == 0
}

This testing discipline mirrors unit testing in application development. For teams practicing DevSecOps shift-left strategies, these tests should run in every PR pipeline before policies reach the cluster.

Write Rego+ Unit TestsCI Pipelineopa test + lintBundle BuildSigned ArtifactGatekeeper SyncCluster EnforcementFail Fast on Test Errors
Secure policy lifecycle: Rego code moves from local authoring through CI validation to signed bundles deployed via Gatekeeper.

How do you enforce Terraform compliance using OPA and Conftest?

While Kubernetes admission control catches runtime drift, preventing misconfiguration at the infrastructure provisioning stage is far cheaper. Policy as Code with Open Policy Agent (OPA) integrates directly into Terraform workflows via conftest. This tool parses Terraform plan JSON and evaluates it against Rego policies before any resources are created.

Evaluating Terraform plans

First, generate a plan file and convert it to JSON:

terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json -p policies/terraform

A typical policy ensures all S3 buckets have versioning enabled:

package terraform.s3

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_s3_bucket"
    not resource.change.after.versioning_configuration
    msg := sprintf("S3 bucket %s missing versioning configuration", [resource.address])
}

warn[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_s3_bucket"
    resource.change.after.tags.environment != "production"
    resource.change.after.versioning_configuration.status != "Enabled"
    msg := sprintf("Non-prod bucket %s should still consider versioning", [resource.address])
}

Note the use of deny versus warn. Conftest treats deny as a hard failure (exit code 1), blocking the pipeline, while warn logs issues without stopping execution. This distinction is vital for gradual adoption. Start with warnings to baseline existing infrastructure, then promote critical rules to denies as teams adapt. This approach complements reusable Terraform modules by ensuring module consumers adhere to organizational standards even when modules themselves lack built-in validation.

What are the performance and operational trade-offs of OPA versus native tools?

Adopting OPA is not free. Every admission request adds latency. Every policy evaluation consumes CPU. Understanding these trade-offs prevents production incidents. Native tools like Kyverno or AWS SCPs sometimes offer better integration for specific niches, but OPA's generality remains its strength for multi-stack environments.

CriteriaOPA / GatekeeperKyvernoCloud-Native SCPs / IAM
LanguageRego (declarative, steep curve)YAML (K8s-native, lower barrier)JSON/IAM Policy (vendor-specific)
ScopeAny JSON/YAML inputKubernetes onlySingle cloud account/org
Mutation SupportLimited (experimental in Gatekeeper)Native first-class featureLimited to specific services
Performance OverheadLow (~1-5ms per eval)Medium (heavier webhook processing)Negligible (control plane integrated)
Ecosystem MaturityHighest (CNCF graduated, vast library)Growing rapidly in K8s spaceVendor-dependent roadmap

In high-throughput clusters, OPA's performance depends heavily on policy complexity. Avoid nested iterations over large arrays. Use partial evaluation where possible to pre-compute static decisions. Monitor webhook latency via Prometheus metrics exposed by Gatekeeper. If p99 latency exceeds 100ms, refactor your Rego or split policies into smaller constraints. Remember that admission controllers are on the critical path of every pod creation; inefficient policies cause cascading scheduling delays.

AWS SCP< 1ms (Control Plane)OPA Optimized1-5ms (Typical)Kyverno5-20ms (Mutation Heavy)OPA Unoptimized50-200ms+ (Nested Loops)0ms200ms+
Latency comparison across policy engines. Unoptimized Rego with nested iterations can exceed safe admission control thresholds.

How do you manage OPA policy lifecycle and distribution securely?

Treating policies as ephemeral scripts undermines their value. Policies are security artifacts. They require versioning, signing, and controlled distribution just like container images. The OPA Bundle API enables this by packaging Rego files and data documents into tarballs served over HTTP or OCI registries.

  1. Version Control: Store all Rego in Git. Use semantic versioning for policy releases. Tag commits that correspond to production deployments.
  2. CI Validation: Run opa fmt, opa check, and opa test on every commit. Integrate regal for linting best practices. Block merges on failures.
  3. Bundle Signing: Sign bundles using Cosign or Notary. Configure Gatekeeper or OPA to verify signatures before loading. This prevents supply chain attacks where malicious actors inject permissive policies.
  4. Distribution: Push signed bundles to an OCI registry (ECR, GAR, Harbor). Configure OPA/Gatekeeper to pull periodically or via webhook trigger. Avoid mounting policies as ConfigMaps in production; this bypasses signature verification and complicates rollback.
  5. Audit Trail: Enable OPA decision logging. Forward logs to your observability stack. Every allow/deny decision should be traceable to a specific policy version and input hash. This is non-negotiable for SOC 2 compliance evidence collection.

This lifecycle transforms policy from a fragile afterthought into a robust engineering artifact. When an incident occurs, you can pinpoint exactly which policy version allowed or blocked the action, and reproduce the decision locally using archived inputs.

Implementing Policy as Code with Open Policy Agent (OPA) in Production

Start small. Pick one high-value, low-risk policy—like requiring resource labels or blocking privileged containers. Deploy in warn-only mode for two weeks. Gather feedback from developers. Fix false positives. Only then switch to enforce. Expand gradually to cover network policies, image registries, and IAM configurations. Invest early in developer experience: provide VS Code extensions for Rego, pre-commit hooks, and clear documentation linking violations to remediation steps.

Policy as Code with Open Policy Agent (OPA) succeeds when it becomes invisible infrastructure rather than a bureaucratic gate. The goal is not to block deployments but to make secure deployments the default path. If your team spends more time debugging Rego than shipping features, your policies are too complex or your tooling is immature. Simplify, test rigorously, and treat policy quality with the same standards as application code.

Ready to implement guardrails that actually stick? Contact me to audit your current policy posture or design an OPA strategy tailored to your compliance requirements and team velocity.

Frequently Asked Questions

It is a practice using OPA to enforce rules via Rego code instead of manual configs. Teams version control policies, test them in CI, and apply consistent guardrails across Kubernetes, Terraform, and APIs automatically without human intervention or drift.

Native controllers require custom Go code and redeployment for changes. OPA uses declarative Rego policies that update dynamically without restarting pods. This decouples policy logic from application code, enabling security teams to manage rules independently from platform engineering workflows.

Yes, OPA is open source under Apache 2.0 license.

Rego is declarative but unfamiliar to imperative programmers. Expect two to four weeks for proficiency. Start with simple allow/deny rules, use the official playground for testing, and adopt community libraries like Konstraint to accelerate adoption and reduce boilerplate code significantly.

Yes, using terraform-plan output as JSON input. OPA evaluates resource configurations before apply, blocking non-compliant infrastructure. The conftest tool simplifies this workflow by providing structured testing commands and clear violation messages directly within your CI pipeline stages.

Use opa test command with .rego files containing test_ prefixed rules. Write unit tests asserting expected decisions against mock inputs. Integrate these tests into pre-commit hooks and CI pipelines to catch logic errors before policies reach production clusters or environments.

Yes, opa build creates signed bundles containing policies and data. Configure OPA to load bundles from local filesystem or private registries. This enables secure policy distribution without external network access, satisfying strict compliance requirements in isolated infrastructure deployments.

Large datasets and complex comprehensions cause latency. Enable partial evaluation to precompute static rules, limit input document size, and use indexing on frequently queried fields. Profile policies with opa eval --profile to identify slow expressions and optimize Rego logic accordingly.

OPA supports bundle polling and API-based triggers for live updates. New policies activate immediately without pod restarts. Use decision logs to verify correct policy versions are serving requests during transitions and maintain audit trails for compliance verification purposes.

Yes, OPA offers similar HCL validation capabilities without vendor lock-in. Conftest provides comparable developer experience. However, Sentinel has tighter Terraform Cloud integration. Evaluate based on existing HashiCorp ecosystem dependencies versus desire for unified policy engine across multiple platforms.

Use opa eval with --explain flag to trace rule evaluation paths. The VS Code extension provides real-time diagnostics and test coverage visualization. Decision logs capture full evaluation context in production, enabling post-incident analysis of why specific requests were allowed or denied.

Organize by domain (authz, compliance, cost) with separate namespaces. Use library packages for shared utilities and data packages for external references. Implement code review workflows mirroring application development practices to maintain policy quality and prevent conflicting rules across teams.

Yes, via Envoy external authorization filter. OPA evaluates requests at mesh ingress points using HTTP headers and body content. This enables fine-grained access control independent of application code while maintaining centralized policy management across microservices architectures.

Track request latency percentiles, bundle update success rates, and policy evaluation errors. Expose Prometheus metrics endpoint and alert on p99 latency exceeding SLAs. Monitor decision log volume to detect unexpected traffic patterns indicating misconfigured policies or potential security incidents requiring investigation.

Inventory current permissions, map to Rego equivalents incrementally, and run OPA in log-only mode first. Compare decisions against legacy system outputs to validate parity. Gradually enforce after confirming accuracy to prevent access disruptions during transition periods.