Verified11 commandsAI-assisted

OPA

.md

Verified against OPA 1.20.1, flags verified via `opa --help` / `opa eval --help` / `opa test --help` run · official docs

What it is and where it fits 🎯#

Open Policy Agent is the general-purpose policy engine underneath Kubernetes admission control (Gatekeeper), API authorization layers, and config-validation pipelines across the industry — policies are written in a purpose-built language called Rego. The opa binary itself is the low-level tool: author/test/format Rego, run OPA as a standalone server for real-time authorization decisions, or evaluate a query by hand. For the common "test my config files against Rego policies" job most teams reach for day to day, Conftest (its own cheat sheet in this series) is the far more ergonomic front end — most engineers touch opa directly only when writing or debugging the underlying Rego, not for routine CI checks.

Where OPA sits in a policy-as-code pipeline#

Diagram

The same Rego policy logic can, in principle, back all four of these consumption paths — which is exactly why OPA became the de facto standard rather than each ecosystem inventing its own policy language.

Installation#

curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
chmod 755 opa
sudo mv opa /usr/local/bin/                    # or anywhere on your own PATH without sudo

opa version

Single static binary, no runtime dependencies. The download URL always points at the latest release — pin a specific version by using a versioned URL instead (.../v1.x.y/opa_linux_amd64_static) if you need reproducible CI.

Core concepts: Rego in one page#

package authz

default allow := false

allow if {
    input.method == "GET"
    input.path == ["users"]
}

allow if {
    input.method == "GET"
    input.path == ["users", input.user_id]
    input.requester_id == input.user_id
}
  • package — every Rego file declares a namespace; rules are addressed as data.<package>.<rule>.
  • default allow := false — the deny-by-default posture almost every real authorization policy wants; without it, an undefined result (no matching rule) is undefined, not false, which most consumers treat differently than an explicit denial.
  • Rules can be defined multiple times (like the two allow blocks above) — Rego evaluates them as an OR: the rule is true if any of its definitions match.

Evaluating a query#

opa eval 'x := 1; y := 2; x < y'                        # ad-hoc expression, no files needed
opa eval --data policy.rego --input input.json 'data'    # evaluate a policy against an input document
opa eval --data policy.rego 'data.authz.allow'            # evaluate a specific rule
opa eval --bundle ./bundle 'data'                          # load data + Rego from a bundle directory or tarball

Output formats#

opa eval --format pretty 'data.authz.allow'      # human-readable (default for `run`, not `eval`)
opa eval --format json 'data.authz.allow'         # default for eval — raw JSON result
opa eval --format values 'data.authz.allow'

Sample --format json output for the allow rule above with a matching input:

{
  "result": [
    {
      "expressions": [
        { "value": true, "text": "data.authz.allow", "location": {"row": 1, "col": 1} }
      ]
    }
  ]
}

Testing policies 🧪#

opa test ./policy/                             # runs every rule prefixed test_ as a test case
opa test ./policy/ -v                           # verbose, shows each test case
opa test ./policy/ --coverage                   # report which lines of Rego were actually exercised
opa test ./policy/ -r test_allow_admin          # run only test cases matching this regex
package authz_test

import data.authz.allow

test_get_own_profile_allowed if {
    allow with input as {"method": "GET", "path": ["users", "bob"], "requester_id": "bob", "user_id": "bob"}
}

test_get_other_profile_denied if {
    not allow with input as {"method": "GET", "path": ["users", "bob"], "requester_id": "alice", "user_id": "bob"}
}

Tip

Write the "denied" test case, not just the "allowed" one. A policy engine that never explicitly tests its deny path is the single most common way an authorization bug ships — a rule that's too permissive passes every "should allow" test trivially, and only a "should deny" test catches it.

Formatting and linting Rego source#

opa fmt -w ./policy/                            # rewrite files in canonical Rego style, in place
opa check ./policy/                             # parse + type-check without evaluating

Running OPA as a server#

opa run --server --addr localhost:8181 ./policy/    # for sidecar/API-authorization use cases
curl -X POST localhost:8181/v1/data/authz/allow -d '{"input": {"method": "GET", "path": ["users", "bob"]}}'

Building a distributable bundle#

opa build -b ./policy/ -o bundle.tar.gz         # package policies + data into a versioned bundle for distribution
opa sign --signing-key key.pem -b ./policy/       # sign a bundle for supply-chain integrity

Real-world scenario: deny-by-default RBAC for an internal platform API#

A platform team building an internal service-to-service authorization layer wants a hard "nothing is allowed unless explicitly permitted" posture that's independently testable outside the application it protects:

package authz

import future.keywords.in

default allow := false

allow if {
    input.role in {"admin", "platform-owner"}
}

allow if {
    input.role == "developer"
    input.action == "read"
}

Running this as a sidecar (opa run --server) means the authorization logic is a separate, independently versioned, independently testable artifact from the application code — a security review of "what can a developer-role token actually do" is a Rego-file review, not an application-code audit.

Common pitfalls#

  • Missing default allow := false — without it, a query with no matching rule returns undefined, which some integrations treat as truthy/permissive rather than as an explicit deny. Always set an explicit default.
  • Testing only the happy path — see the TIP above.
  • Forgetting opa fmt -w before committing — Rego, like Go, has one canonical formatting; skipping it creates unnecessary diff noise across a team.

When to reach for something else#

For "test my config files against policies" (the far more common day-to-day job than hand-authoring opa eval calls), reach for Conftest — see its own cheat sheet, which wraps this same Rego engine in a much more ergonomic config-testing CLI.