On 4 February 2021 the Cloud Native Computing Foundation (CNCF) promoted Open Policy Agent (OPA) to a graduated project, the highest maturity level the foundation grants. The date says less than the technical fact behind it: a general-purpose decision engine, written in Go and released under Apache 2.0, has gathered enough production adoption that its semantics are now a stable base to build on.

Context

OPA started at Styra, the work of Tim Hinrichs and Torin Sandall, entered the CNCF sandbox in April 2018, moved to incubating about a year later, and reached graduation with two external security audits behind it, the CII Best Practices badge, and a defined governance model. The announcement cites adopters such as Goldman Sachs, Netflix, Pinterest, T-Mobile and Atlassian, and a survey of more than 150 organisations in which 91% reported using OPA at some stage, from QA to production.

The problem the project addresses keeps coming back the same way. Authorisation and compliance decisions end up scattered through application code: an if user.role == "admin" here, a SQL query against ACLs there, an LDAP group check elsewhere, plus the logic specific to each tool — Kubernetes RBAC, CI pipeline rules, Terraform plan validation. Changing one rule means touching N places in M languages, and nobody has the whole picture of what is actually permitted.

Architecture

OPA detaches the decision from the application. The service that needs to decide — an API, an admission controller, a gateway — sends OPA a JSON document as input and gets back the result of evaluating its policies. The logic lives outside the service, written in Rego, a declarative language that borrows from Datalog.

package http.authz

default allow = false

allow {
    input.method == "GET"
    input.path = ["public", _]
}

allow {
    input.user.role == "admin"
}

Almost always OPA runs next to the service that queries it — as a sidecar in the same pod, or as a host-level daemon — so the evaluation call stays local and low-latency. The query goes through the REST API: POST /v1/data/http/authz/allow with the input document in the body returns the decision. Policies and context data can be loaded statically, or distributed through the bundle mechanism: at regular intervals OPA downloads an archive of Rego and data from an HTTP endpoint and applies it without a restart.

The third piece is the decision log. Every decision can be recorded and shipped in batches to a collection service, together with the input, the result, and the policy that produced it. For audit and troubleshooting it changes a great deal: the question “why was that user, at that time, able to do that operation” gets a precise record as an answer, instead of a reconstruction pieced together afterwards from application logs.

The ecosystem around the engine is where most of the real integration happens. Gatekeeper brings OPA into Kubernetes as an admission controller and exposes policy through the ConstraintTemplate custom resource, instead of raw Rego in manifests. conftest runs Rego policy against static files — Kubernetes manifests, Dockerfiles, terraform plan output — and is built for gating in pipelines. The OPA-Envoy plugin intercepts requests as an external authorisation filter (ext_authz).

Critical point

Rego is where OPA moves away from the more common rule systems. It is a query language over documents, not an imperative language in disguise. Rules do not run in sequence: a rule defined more than once under the same name is a disjunction, true if at least one block is satisfied, and iteration is written with free variables that the engine unifies against the data, not with for loops.

The model handles authorisation over structured data well, and turns awkward for anyone expecting general-purpose semantics. Two syntactically valid lines can say quite different things, and a rule that fails — undefined — is not an error but a value, which propagates in ways the author has to keep in mind. That default allow = false is not cosmetic: without it, an undefined allow does not equal false, it stays undefined, and downstream someone has to decide how to treat it.

In practice policies have to be tested as code. OPA ships a test runner that evaluates Rego against sample input and checks assertions; opa test over a folder of _test.rego files weighs as much as the pipeline that distributes them. An untested policy that returns undefined where false was expected can open access without anything raising an error.

Implications

Detaching policy from code has an organisational effect before a technical one. When the authorisation rule is a versioned, distributed, logged bundle, its lifecycle comes apart from the service’s. You change who may do what without redeploying the application, and you show an auditor the rule in force on a given date together with the log of decisions it produced. In environments with compliance obligations — finance, healthcare, public administration — this is where the concrete value sits: the control becomes an inspectable artefact.

The price is one more component on the request path and a new configuration surface to look after. Bundle distribution has to be made reliable — what happens if OPA cannot fetch the latest bundle? — evaluation latency has to be measured under load, and the team has to learn enough Rego to trust its own rules.

Limits

OPA decides, it does not enforce. The decision that allow equals false only takes effect if the calling service honours it: the enforcement point stays in the application code or the proxy, and an integration that forgets to check the response leaves the policy with no grip.

The bundle model opens a consistency window: between publishing a new policy and the moment every OPA instance has downloaded it, different decisions may rest on different versions of the rules. For many cases this is fine; for others it has to be sized carefully.

Finally, “policy as code” moves complexity, it does not erase it. Logic fragmented across the code becomes logic concentrated in Rego: more inspectable, but also a single place where one mistake touches everything that queries that engine. It is worth adopting where the fragmentation was already the problem, less so where a well-isolated local check was enough.


https://www.cncf.io/announcements/2021/02/04/cloud-native-computing-foundation-announces-open-policy-agent-graduation/ https://www.cncf.io/blog/2020/08/13/introducing-policy-as-code-the-open-policy-agent-opa/ https://www.openpolicyagent.org/docs/integration https://www.openpolicyagent.org/docs/rest-api https://www.noze.it/en/insights/opa-open-policy-agent/

Cover image: Cloud Native Computing Foundation logo: a blue and cyan geometric mark of interlocking arrows in a square, next to the black wordmark… — logo by The Linux Foundation, CC0 — https://commons.wikimedia.org/wiki/File:Cloud_Native_Computing_Foundation_logo.png