The OPA policy language is Rego, a declarative query language that lets you write authorization decisions as code and evaluate them outside your application. Open Policy Agent (OPA) is a general-purpose policy engine, and Rego is how you tell it what "allowed" means — for a Kubernetes admission request, an API call, a Terraform plan, or anything else you can express as structured data. The core idea is decoupling: your services ask OPA "is this allowed?" and OPA answers based on policies you maintain separately from application logic.
That separation is the whole value proposition. Instead of scattering if user.role == "admin" checks across a dozen microservices, you centralize the rules, version them, test them, and change them without redeploying every service. This is policy-as-code, and Rego is the language that makes it concrete.
How Rego thinks
Rego is declarative, not imperative. You do not write step-by-step instructions; you write rules that define what is true. OPA evaluates a query against your policy plus some input data and returns the result. If you have written SQL or Datalog, the mindset transfers — you describe the conditions for a fact to hold, and the engine figures out whether they are satisfied.
A minimal authorization policy:
package authz
import rego.v1
default allow := false
allow if {
input.method == "GET"
input.path == ["public", "status"]
}
allow if {
input.user.role == "admin"
}
Two things to notice. First, default allow := false establishes deny-by-default: unless a rule explicitly grants access, the answer is false. This is the single most important security pattern in Rego, and we return to it below. Second, the two allow rules are independent — if either one holds, access is granted. Rego rules with the same name combine with OR semantics.
The input document is the data your service sends: the request, the user, the resource. OPA can also load external data — role mappings, allowlists — that policies reference. A decision is a function of input plus data plus the policy.
Where OPA gets used
The OPA policy language shows up in several places, and the pattern is the same each time.
Kubernetes admission control is the most common. OPA (via Gatekeeper or as a webhook) evaluates every resource being created against your policies — rejecting pods that run as root, containers without resource limits, or images from untrusted registries. This turns cluster security rules into enforced, testable code.
Microservice authorization: services call OPA to decide whether a request is permitted, keeping the authorization logic out of the application.
Infrastructure-as-code checks: evaluate a Terraform plan against policy before it applies, catching a public S3 bucket or an over-permissive security group before it exists.
CI/CD gates: block a pipeline when a policy — say, "no critical vulnerabilities in the image" — is violated.
The deny-by-default principle
The most dangerous Rego mistake is getting the default wrong. If you omit default allow := false, an undefined result — which happens when no rule matches — is not automatically a denial in every integration, and a policy that "fails open" grants access precisely when something unexpected happens. That is the worst possible failure mode for an authorization system.
Always start from deny. Write default allow := false, then add rules that grant access under specific, well-understood conditions. Never structure a policy as "allow everything except these cases," because the cases you forgot become open doors. The safe shape is a closed door with a few explicit, tested keys.
Test your policies like code
Because Rego is code that makes security decisions, it needs tests. OPA ships a built-in test framework, and policies without tests are a liability — a subtle logic error in a rule can silently grant access to something it should block.
package authz_test
import rego.v1
import data.authz
test_admin_allowed if {
authz.allow with input as {"user": {"role": "admin"}}
}
test_anonymous_denied if {
not authz.allow with input as {"user": {"role": "guest"}, "method": "POST"}
}
Run opa test . in CI. Assert both the positive cases (the right people get in) and, more importantly, the negative cases (the wrong people are kept out). Negative tests are where authorization bugs hide, because a policy that accidentally allows too much still passes every "can the admin do X?" test.
Securing the policy supply chain
Policies are code, and code is a supply-chain surface. A few practices keep OPA deployments honest:
- Version and review policies in Git with the same rigor as application code. A pull request that loosens an authorization rule deserves careful review.
- Sign and verify bundles. OPA can load policies as signed bundles from a remote server. Verify signatures so a compromised bundle server cannot push a policy that grants blanket access.
- Pin the OPA version and keep it updated. OPA is distributed as a binary and as container images; treat those like any dependency, pinning digests and watching for security releases.
- Scan the images you run OPA and Gatekeeper from. An SCA tool such as Safeguard can flag known-vulnerable components in those images before they reach the cluster. See our software composition analysis overview.
The failure mode people underestimate is that the policy engine itself becomes a single point of trust. If an attacker can modify your policies or the bundle they load from, they control every authorization decision in your system. Protect the policy pipeline as carefully as the policies.
Getting started sensibly
Begin with one narrow use case — admission control for a single high-value rule, or authorization for one service — and expand once you trust your testing and deployment flow. Resist the urge to move every decision into OPA on day one. The value compounds as you centralize, but so does the blast radius if a policy is wrong, so grow the surface deliberately. Our academy covers the broader policy-as-code discipline if you want structured grounding.
FAQ
What language does OPA use?
Rego. It is a declarative, purpose-built policy language — not a general-purpose programming language. You describe the conditions under which something is allowed, and OPA evaluates them against input data.
Is Rego hard to learn?
The syntax is small, but the declarative mindset takes adjustment if you come from imperative languages. The two concepts that unlock it are deny-by-default with default allow := false and the OR-combination of same-named rules. Start with small, tested policies.
Why use OPA instead of hardcoding authorization checks?
Centralization. OPA keeps authorization logic out of application code so you can version, test, and change rules without redeploying every service, and enforce consistent policy across many systems from one place.
What's the biggest security mistake with the OPA policy language?
Failing to default to deny. A policy without default allow := false can fail open, granting access when no rule matches — exactly when something has gone wrong. Always start closed and grant access explicitly.