Policy as Code with OPA: Enforcing Rules Across Your Stack

Philip Rehberger Sep 27, 2026 7 min read

Use Open Policy Agent for K8s admission, IaC, and API authorization. Includes Rego patterns.

Policy as code lets you express organizational rules — what is allowed, what is not, when, by whom — in version-controlled, testable, and machine-evaluable form. Open Policy Agent (OPA) is the dominant tool. It has been adopted across Kubernetes, infrastructure-as-code, API authorization, and CI/CD pipelines.

This post is what OPA does, where it fits, and the practical patterns for adopting it without it becoming a "yes another DSL" investment that fails to pay back.

What OPA Actually Does

OPA evaluates queries against a policy expressed in Rego, OPA's policy language. A query is structured input; the policy is a set of rules that produce a decision.

A simple example: a Kubernetes admission controller asks OPA "should this pod be admitted?" The pod manifest is the input; the policy decides yes or no with a reason.

package kubernetes.admission

deny[msg] {
  input.kind.kind == "Pod"
  not input.metadata.labels.app
  msg := "Pods must have an 'app' label"
}

deny[msg] {
  input.kind.kind == "Pod"
  container := input.spec.containers[_]
  endswith(container.image, ":latest")
  msg := sprintf("Container '%s' uses :latest tag", [container.name])
}

The policy denies pods missing required labels or using :latest image tags. The decision is data; the policy is reusable.

Where OPA Fits

OPA is most commonly used in five places:

Kubernetes admission control. Gatekeeper (OPA's Kubernetes wrapper) evaluates every API request against policies. Reject pods with privileged containers, deployments without resource limits, services without network policies.

Infrastructure-as-code policy. Conftest runs OPA against Terraform plans, Kubernetes manifests, or any structured config. Catch policy violations at PR time, before they reach the cluster.

API authorization. OPA can decide "is this user allowed to do X?" for an application. Decoupling authorization from application code lets you change policy without redeploying.

CI/CD pipeline policy. Tekton, Argo Workflows, and similar tools can call OPA to decide whether a build can deploy. Enforces things like "production deploys require an approval from the security team."

Service mesh policy. Istio and Envoy can call OPA for fine-grained authorization decisions per request.

Where OPA Does Not Fit

  • Real-time data plane decisions that need microsecond latency
  • Decisions that require database state OPA cannot see
  • Trivial rules (a one-line check is easier in application code)
  • Policies with too many exceptions (Rego is poor at expressing "everything except these 47 specific cases")

The pattern: OPA fits when there are many policies, evaluated at policy enforcement points (admission, deployment, authorization), and the policies benefit from being separated from the code they govern.

A Concrete Kubernetes Admission Example

Using Gatekeeper:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          type: object
          properties:
            labels:
              type: array
              items: { type: string }
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels

        violation[{"msg": msg}] {
          required := input.parameters.labels
          provided := input.review.object.metadata.labels
          missing := required[_]
          not provided[missing]
          msg := sprintf("Missing required label: %v", [missing])
        }

Then the actual constraint:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: pod-required-labels
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    labels: ["app", "team", "environment"]

Pods without app, team, or environment labels are rejected at admission.

Adoption Patterns

Most organizations adopting OPA make similar mistakes. Patterns that work:

Start in audit mode. Gatekeeper and other OPA tools support "warn" mode that logs violations without blocking. Run for weeks. Fix what shows up. Switch to enforcing only after the audit log is clean.

One owner per policy. Each policy needs an owner who maintains it. Otherwise, policies accumulate, conflict, and become liability without clear authority.

Test every policy. OPA has a built-in test framework. A policy that has not been tested has bugs.

test_pod_missing_label {
  count(violations) > 0
  with input as {
    "review": {
      "object": {
        "metadata": {"labels": {}},
        "kind": "Pod"
      }
    },
    "parameters": {"labels": ["app"]}
  }
}

Run tests in CI alongside other tests.

Version policies. Policies are code. They are deployed alongside everything else, with the same review and rollback workflow.

The Rego Learning Curve

Rego is the part most teams underestimate. It is a declarative, datalog-flavored language that looks weird if you have only written Python or Go.

The mental model: a policy is a set of rules that produce values when their conditions are met. Rules are not sequential. Statements within a rule are conjunctions (AND). Multiple rules with the same name are disjunctions (OR).

# Each `deny[msg]` rule that matches contributes a message
deny[msg] {
  some condition1
  msg := "..."
}

deny[msg] {
  some condition2
  msg := "..."
}

The lack of ordering and the implicit conjunction-of-statements trip up newcomers. Plan for a few weeks of "this should work but does not" before the team is fluent.

Alternatives to Rego

If Rego is the blocker, alternatives exist:

  • Cedar (AWS's policy language) — more declarative, simpler syntax. Strong for authorization.
  • Policy languages in your stack's idiom — write policies in Python with a framework like py-perm. Smaller ecosystem.

OPA is the default because of ecosystem breadth: Kubernetes admission, Conftest, OPA Authz, all built around Rego. Adopting OPA buys into the ecosystem. Cedar is gaining traction for IAM-style authorization.

Performance

OPA evaluates fast — typical decisions in microseconds — but at scale, policy count and complexity matter.

  • Admission control for a busy cluster sees hundreds of requests per second. Slow policies become rate-limited admissions.
  • API authorization on every request multiplies even faster.

The tools to manage performance:

  • Policy partial evaluation (compile policies for specific contexts)
  • Decision logging to identify slow policies
  • Bundle distribution so policies are cached locally

For most teams, OPA is fast enough out of the box. Performance tuning becomes a concern at very high scale.

Multi-Tenant and Federated Setups

For organizations with multiple teams and policies that vary by team:

  • One OPA service per tenant is overkill.
  • One OPA with policy partitioning by namespace or scope works well.
  • Federated OPAs for cross-region or cross-cluster deployments.

Bundle distribution (policies served from a central source, distributed to OPA instances) keeps multiple OPA installations in sync.

Auditing Decisions

OPA can log every decision it makes. For compliance and debugging, this is invaluable.

# OPA config — decision logs to S3
decision_logs:
  service: s3
  reporting:
    min_delay_seconds: 5

Every "allow" or "deny" is logged with the input, the policy version, and the result. When someone asks "why was this request denied," the answer is in the log.

When OPA Is Wrong

  • Simple checks that are easier in application code
  • Policies that change every five minutes (the deployment cycle becomes the bottleneck)
  • Decisions requiring external data lookups (OPA has limited data plane integration; use it for policy, not lookups)

The honest framing: OPA is a tool for codifying organizational policy. If your team has no codified policies — or the policies fit in a paragraph of code — OPA is premature.

A Practical Starting Point

Three good first OPA use cases for most platforms:

  1. Kubernetes admission control for "required labels, no :latest, no privileged containers" — high value, well-supported, immediate.
  2. Terraform validation via Conftest — catch bad infrastructure config at PR time.
  3. Deployment gates in CI — "production deploys require approval from a specific group."

These cover most of the OPA value proposition for an average platform team. Roll out one at a time, measure the value, and expand from there.


Looking at adopting policy as code for your platform and not sure where the value will come from first? We help teams scope OPA adoption around the policies they actually need to enforce — not the entire catalog of possibilities. scopeforged.com

Share this article

Related Articles

Need help with your project?

Let's discuss how we can help you build reliable software.