Portal | Level: L2: Operations | Topics: Open Policy Agent | Domain: Security
Open Policy Agent — Primer¶
Why OPA Matters¶
Policy enforcement is usually baked into application code — a tangle of if/else checks spread across services that each team re-implements in their own way. When the policy changes, every service changes. When you want to audit what policy is in effect, there is no single place to look.
Open Policy Agent (OPA) decouples policy from code entirely. You write policy in a purpose-built language (Rego), feed it structured input (a JSON document), and get a decision back. Your application just makes an HTTP call — it doesn't own the policy logic. OPA is a general-purpose engine: the same tool enforces Kubernetes admission control, API authorization, Terraform guardrails, and SSH access decisions.
Architecture¶
OPA can be deployed three ways depending on your latency and availability needs:
Sidecar (per-service, low latency)
[Your Service] --HTTP--> [OPA sidecar] --> decision
Library (in-process, no network hop)
[Your Go/Java app] --SDK call--> [OPA embedded] --> decision
Centralized service (shared, easier to manage)
[Service A] \
[Service B] --HTTP--> [OPA cluster] --> decision
[Service C] /
Sidecar is the Kubernetes-native pattern. OPA runs as a container in the same pod, policy is kept in sync via bundles, and latency is microseconds over localhost.
Centralized works well for organizations that want one policy service for everything, at the cost of making that service a high-availability dependency.
Library (via the OPA SDK for Go, or WASM) eliminates the network entirely and is used in edge or high-throughput scenarios.
Rego Language¶
Rego is a declarative query language designed specifically for policy. It is not imperative — there are no if/else chains, no loops that mutate state. You define what is true, and Rego derives all consequences.
Basic Rule¶
package authz
default allow = false
allow {
input.user.role == "admin"
}
allow {
input.method == "GET"
input.path[0] == "public"
}
Multiple allow blocks are implicitly OR'd. All conditions inside a single block are AND'd. This mirrors logical conjunction/disjunction naturally.
Partial Rules (Set/Object Comprehensions)¶
Partial rules build collections incrementally. Each rule body that evaluates to true contributes an element.
# Collect all violations (partial set rule)
violation[msg] {
input.pod.spec.containers[_].securityContext.privileged == true
msg := "privileged container not allowed"
}
violation[msg] {
not input.pod.spec.securityContext.runAsNonRoot
msg := "must run as non-root"
}
Comprehensions¶
Build sets, arrays, or objects inline:
# Set of all image registries used
registries := {r | r := input.pod.spec.containers[_].image; parts := split(r, "/"); count(parts) > 1}
# Object: label -> value for all "env" labels
env_labels := {k: v | v := input.resource.metadata.labels[k]; startswith(k, "env")}
Unification and Negation¶
Variables in Rego are unified, not assigned. x := 1 succeeds only if x is unbound or already equals 1. Negation uses not:
not is negation-as-failure: the condition is considered false if it cannot be proven true.
Input and Data Model¶
OPA evaluates policy against two sources of structured JSON:
input: the query document sent by your application (the request being evaluated)data: background data loaded into OPA (user directories, allow-lists, config)
// input example (Kubernetes admission webhook)
{
"apiVersion": "admission.k8s.io/v1",
"request": {
"object": {
"metadata": { "name": "nginx", "namespace": "default" },
"spec": {
"containers": [{ "image": "nginx:latest" }]
}
}
}
}
External data (e.g., a list of approved registries) lives in data and is loaded separately from policy, either via the bundle API or the PUT /v1/data REST endpoint.
OPA Gatekeeper for Kubernetes¶
Gatekeeper is the official Kubernetes integration. It implements the Kubernetes admission webhook using OPA as the policy engine, but wraps policy in Kubernetes CRDs for a more native experience.
ConstraintTemplate¶
Defines a new CRD (a constraint kind) and its Rego logic:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items: { type: string }
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}
Constraint¶
An instance of a ConstraintTemplate — specifies parameters and scope:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["team", "env"]
Audit Mode¶
Gatekeeper continuously audits existing resources against all active constraints (not just new admissions). Violations accumulate in the .status.violations field of each Constraint object. This lets you enforce gradually: deploy a constraint in audit mode first, observe violations, fix them, then switch to enforcement.
Decision Logging and Monitoring¶
OPA emits decision logs for every policy query: input, output, policy version, and latency. Ship these to your SIEM or log aggregator for audit and debugging.
# opa config.yaml — send decision logs to a remote endpoint
decision_logs:
service: my-log-sink
reporting:
min_delay_seconds: 5
max_delay_seconds: 10
Key metrics exposed at /metrics (Prometheus format): opa_rego_query_eval_ns_*, opa_runtime_*, bundle download success/failure.
Bundle API¶
Bundles are tarballs containing policy (.rego files) and data (.json files). OPA polls a bundle server (S3, GCS, nginx, OCI registry) and hot-reloads policy without a restart.
# config.yaml bundle section
bundles:
authz:
service: bundle-server
resource: /bundles/authz.tar.gz
polling:
min_delay_seconds: 30
max_delay_seconds: 60
The bundle manifest (/.manifest) pins a revision string so OPA can detect staleness and roll back on corruption.
Testing Policies¶
OPA has a built-in test framework. Test files are Rego files whose rule names start with test_:
package authz_test
import data.authz
test_allow_admin {
authz.allow with input as {"user": {"role": "admin"}, "method": "GET"}
}
test_deny_guest_write {
not authz.allow with input as {"user": {"role": "guest"}, "method": "POST"}
}
Run tests:
opa test ./policies/ # run all tests recursively
opa test -v ./policies/ # verbose: show test names and pass/fail
opa test --coverage ./policies/ # coverage report
CI/CD Integration¶
# Syntax check + type checking
opa check --strict ./policies/
# Auto-format (modifies in place)
opa fmt --write ./policies/
# Benchmark a specific query
opa bench --data ./policies/ 'data.authz.allow'
# Gatekeeper-specific test runner (validates ConstraintTemplates)
gator test -f ./constraints/
Add these to your CI pipeline before merging policy changes. A failing opa test should block the merge.
Common Use Cases¶
| Use case | How OPA is deployed |
|---|---|
| Kubernetes admission control | Gatekeeper webhook (ConstraintTemplate + Constraint) |
| API authorization (microservices) | Sidecar or centralized service; app calls /v1/data/authz/allow |
| Terraform plan validation | Conftest reads terraform plan -out=plan.json and evaluates Rego |
| SSH / sudo access | PAM module calls OPA; policy checks user groups and target host |
| Data filtering (row-level security) | OPA returns a filter expression; app applies it to the query |
Conftest¶
Conftest is a CLI wrapper around OPA for testing configuration files (YAML, JSON, Dockerfile, HCL). Write Rego policies in policy/ and run:
conftest test deployment.yaml # single file
conftest test --policy ./policy k8s/ # directory of manifests
conftest test plan.json # Terraform plan output
Conftest automatically loads policies from the policy/ directory and evaluates deny, warn, and violation rules.
Key Takeaways¶
- OPA decouples policy from application code — policy changes deploy independently
- Rego is declarative: define what is true, not what to do
- Gatekeeper brings OPA to Kubernetes via CRDs, preserving Kubernetes-native workflow
- Always write
test_rules for your policies and runopa testin CI - Bundles enable hot-reload of policy and data across a fleet of OPA instances
- Decision logs are your audit trail — ship them to a log sink from day one
- Conftest makes OPA accessible for config-file testing without HTTP infrastructure
Wiki Navigation¶
Related Content¶
- Open Policy Agent Flashcards (CLI) (flashcard_deck, L1) — Open Policy Agent