# OPA Cheat Sheet

> **Tool:** Open Policy Agent (opa)
> **Category:** Security & Compliance
> **Verified against:** OPA 1.20.1, flags verified via `opa --help` / `opa eval --help` / `opa test --help` run
> locally, 2026-08-29
> **Official docs:** https://www.openpolicyagent.org/docs/latest/

## 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

```mermaid
flowchart TD
    A["Rego policy source"] --> B{"How is it consumed?"}
    B -->|"opa test"| C["Unit tests during policy development"]
    B -->|"opa eval / opa run --server"| D["Ad-hoc query or live authorization API"]
    B -->|"conftest test"| E["Config file validation (Kubernetes, Terraform, Dockerfiles)"]
    B -->|"Gatekeeper ConstraintTemplate"| F["Kubernetes admission control"]

    classDef accent fill:#f0e9fb,stroke:#6d43c0,color:#10161c
    classDef info fill:#e5f0fa,stroke:#1d6fb8,color:#10161c
    class A accent
    class C,D,E,F info
```

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

```bash
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

```rego
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

```bash
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

```bash
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:

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

## Testing policies 🧪

```bash
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
```

```rego
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

```bash
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

```bash
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

```bash
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:

```rego
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.
