Open Policy Agent reached the Cloud Native Computing Foundation's top maturity tier — "graduated" — on January 29, 2021, joining a short list that includes Kubernetes and Prometheus. It got there in under five years: Styra, founded by Torin Sandall, Teemu Koponen, and Tim Hinrichs, started the project in 2016, and CNCF accepted it as a sandbox project on March 29, 2018 before promoting it to incubating status on April 2, 2019. Behind that trajectory sits a policy language most security engineers have heard of but few have actually written: Rego. It's declarative, based on Datalog, and extended with the ability to walk arbitrary JSON and YAML documents — which is exactly what you need when the thing you're evaluating is a Kubernetes manifest, a Terraform plan, or an API authorization request rather than a database table. If your team already gates CI on Conftest, Gatekeeper, or an envoy authorization sidecar, you're running Rego whether you've written a line of it yourself or not. This post is the on-ramp: what Rego actually looks like, the handful of concepts that unlock most real policies, and one working rule you can paste into Conftest today.
What problem was Rego actually built to solve?
Rego was built to separate policy decisions from the systems that enforce them, so a security team could change an authorization rule without redeploying the application that depends on it. Tim Hinrichs had worked on Datalog-derived policy languages academically before co-founding Styra, and Rego inherited Datalog's core idea — rules are logical statements that either hold or don't, evaluated against facts — while adding what Datalog lacks: native traversal of nested JSON/YAML structures, aggregation over collections, and control flow suited to real infrastructure documents. The pitch to security engineers specifically is that the same language and the same OPA engine can authorize an API call in an Envoy sidecar, block a non-compliant Terraform plan in CI, and reject a privileged Kubernetes pod at admission time — three enforcement points, one policy language, one mental model to maintain instead of three bespoke rule engines.
What does a minimal Rego policy actually look like?
Every Rego file starts with a package declaration, which namespaces the rules inside it the way a Python module namespaces functions — `package main` or `package kubernetes.admission` are typical. Rules are logical implications over two data sources: `input`, the document being evaluated (a Kubernetes manifest, a Terraform plan JSON, an HTTP request), and `data`, any reference or policy data you've loaded alongside it, like an allowlist of approved container registries. A rule with no matching condition can still return a value via `default`, which is how you set a policy to fail closed: `default allow := false` means nothing is permitted unless a later rule explicitly proves otherwise. Partial rules generate sets rather than single values — you write the condition once and OPA collects every input that satisfies it, which is exactly the shape you want for "list every violation," not just "yes or no."
What is the deny-set pattern and why does almost every real policy use it?
The idiomatic pattern in Conftest and Gatekeeper policies is `deny[msg]` (or, in Rego v1 syntax, `deny contains msg if`): a partial rule that adds a human-readable violation message to a set every time an input fails a check, rather than a single boolean pass/fail. The reason this pattern dominates is diagnostic value — a CI gate that just says "policy failed" sends an engineer hunting through a 200-line Terraform plan, while a `deny` set can return three distinct messages naming the exact resource and rule each one broke. A typical rule reads: `deny[msg] { input.kind == "Deployment"; not input.spec.template.spec.securityContext.runAsNonRoot; msg := "containers must not run as root" }`. Conftest, originally built by Instrumenta and now maintained under the OPA GitHub organization, was purpose-built to run exactly this pattern against YAML, JSON, HCL, and Dockerfiles at CI time, printing every collected `deny` message as a separate failing test rather than aborting on the first violation.
Where does Rego actually get deployed in a real security pipeline?
Rego shows up wherever a structured document needs a yes/no decision before something is allowed to proceed, and the four most common deployments are Kubernetes admission control, Terraform plan validation, service-mesh authorization, and CI policy gates. Gatekeeper wraps OPA as a Kubernetes admission webhook, intercepting every `kubectl apply` and rejecting manifests that violate a `ConstraintTemplate` written in Rego — the standard way clusters enforce "no privileged containers" or "images must come from an approved registry" before a pod is ever scheduled. Conftest applies the same deny-set pattern to a `terraform plan` JSON export in a CI job, catching a public S3 bucket or an overly permissive security group before `terraform apply` runs. Envoy and Istio use OPA as an external authorization service, evaluating Rego against every inbound request's headers and path to make per-request allow/deny calls at the mesh layer — the same engine, three different enforcement points.
What changed with Rego v1 in OPA 1.0, and why does it matter if you're copying old snippets?
OPA 1.0, released in 2024, made Rego v1 syntax the default rather than an opt-in future-imports flag, and the change that trips up beginners most is that `if` and `contains` keywords are now required in places older tutorials omit them — a rule that used to read `deny[msg] { ... }` in pre-1.0 Rego is written `deny contains msg if { ... }` under v1 syntax. This matters concretely because a large fraction of Rego snippets on blog posts, Stack Overflow answers, and even older Gatekeeper constraint templates predate this change, and pasting them into a current OPA installation without adjustment produces parse errors rather than silent misbehavior — which is the safer failure mode, but still a stumbling block worth knowing about before you copy your first real-world example. Checking your `opa version` and the target policy's declared `rego.v1` import before adapting any snippet you find online will save you the debugging cycle.
How should a security engineer actually get started?
Start with Conftest rather than a full OPA/Gatekeeper deployment — it has no cluster to stand up, runs as a single binary against files already sitting in your repo, and gives immediate feedback with `conftest test` against a `policy/` directory of `.rego` files. Write one `deny` rule against a config file you already care about — a Dockerfile that shouldn't run as root, a Terraform resource that shouldn't be publicly exposed — and iterate from there; the deny-set pattern generalizes to nearly every policy you'll write next. It's also worth being precise about what Rego is not: it's a general-purpose policy evaluator for structured documents and requests, not a static-analysis or SAST engine, and not every policy-as-code tool in a DevSecOps pipeline is Rego-based — plenty of guardrail and compliance tooling, including policy engines built around YAML rule definitions, solve adjacent problems with a different, simpler configuration model rather than a full logic-programming language. Knowing which tool fits which enforcement point matters more than defaulting to the most powerful one.