OPA & Gatekeeper Reference: Rego Policies, ConstraintTemplate, Audit & conftest CI
Open Policy Agent (OPA) is a general-purpose policy engine. Gatekeeper is its Kubernetes admission controller integration — it enforces policies as code using Rego, blocking non-compliant resources before they’re created.
1. OPA vs Gatekeeper vs Kyverno
Policy enforcement options for Kubernetes
| Tool | Scope | Policy Language | Best for |
|---|---|---|---|
| OPA standalone | Any system (HTTP API) | Rego | Microservice authz, API gateways, CI/CD gates |
| OPA Gatekeeper | Kubernetes admission | Rego + K8s CRDs | Enterprise K8s with complex policies |
| Kyverno | Kubernetes admission | YAML (no Rego) | Teams who prefer YAML over a new language |
# Rule of thumb: # Gatekeeper: you already use OPA elsewhere, or need Rego's expressiveness # Kyverno: YAML-native, simpler policies, no Rego learning curve # Both enforce on: CREATE/UPDATE/DELETE via K8s admission webhooks # How Gatekeeper works: # 1. You define ConstraintTemplate (the Rego policy logic) # 2. You define Constraint (instances of the template with parameters) # 3. When a resource is created/updated, Gatekeeper's webhook evaluates the policy # 4. DENY = resource is rejected with your custom error message
2. OPA Rego Basics
Policy language fundamentals — rules, references, and data
# Rego is a declarative query language
# A policy DENIES when a 'deny' rule is true
package kubernetes.admission
# Import request data (the K8s resource being admitted):
# input.request.object = the resource being created/updated
# input.request.operation = CREATE / UPDATE / DELETE
# Simple rule: deny pods without resource limits
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_] # _ = any element
not container.resources.limits # no limits defined
msg := sprintf("Container '%s' has no resource limits", [container.name])
}
# Check a specific field:
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
container.image == "latest" # exact match
msg := sprintf("Container '%s' uses 'latest' tag — pin to a specific version", [container.name])
}
# Check with regex:
import future.keywords.if
deny[msg] if {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("Pin image tag for container '%s'", [container.name])
}
# Test Rego in the playground:
# https://play.openpolicyagent.org/
# Test locally with OPA CLI:
opa eval --data policy.rego --input input.json "data.kubernetes.admission.deny"
opa test ./policy_test.rego -v # run Rego unit tests
3. Gatekeeper ConstraintTemplate
Define reusable policy templates with parameters
# ConstraintTemplate: defines the policy with Rego + the CRD schema
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: requiredlabels # lowercase, no underscores
spec:
crd:
spec:
names:
kind: RequiredLabels # CRD kind — used in Constraint below
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items: {type: string} # parameter: list of required label keys
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package requiredlabels
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])
}
# Note: Gatekeeper uses 'violation' not 'deny', and 'input.review' not 'input.request'
4. Gatekeeper Constraints & Audit
Deploy policies, check violations, audit existing resources
# Install Gatekeeper:
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.15/deploy/gatekeeper.yaml
# Constraint: instance of a ConstraintTemplate with specific parameters
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequiredLabels # must match ConstraintTemplate spec.crd.spec.names.kind
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: ["*"]
kinds: ["Namespace"]
excludedNamespaces:
- kube-system
- gatekeeper-system
- istio-system
parameters:
labels: ["team", "environment"] # must have both labels
# Check for violations:
kubectl get constraint -A # list all constraints
kubectl describe requiredlabels require-team-label
# Look at: Status.Violations — lists existing resources that violate the policy
# Audit mode (gatekeeper audits ALL existing resources on a schedule):
kubectl get constraint require-team-label -o jsonpath='{.status.violations}' | jq .
# Shows existing violations — useful before enforcing a new policy
# Dry-run mode (monitor without blocking):
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequiredLabels
spec:
enforcementAction: dryrun # warn in status.violations but don't block
# enforcementAction: deny # block the request (default)
# enforcementAction: warn # allow but add warning to API response
# Common built-in policies (Gatekeeper Policy Library):
# https://github.com/open-policy-agent/gatekeeper-library
# includes: no-privileged-containers, required-labels, allowed-repos, block-nodeport, etc.
5. OPA Standalone (HTTP API)
Policy decisions for microservices and API gateways
# OPA as an authorization sidecar:
# Your service calls OPA's HTTP API, OPA returns allow/deny
# Start OPA server:
opa run --server --addr=:8181 --bundle ./policies/
# Policy (policies/authz.rego):
package authz
default allow = false
allow {
input.method == "GET"
input.path == ["api", "public"]
}
allow {
input.method == "GET"
input.path[0] == "api"
input.user.role == "admin"
}
# Query OPA from your service:
curl -X POST http://localhost:8181/v1/data/authz/allow -H "Content-Type: application/json" -d '{"input": {"method": "GET", "path": ["api", "users"], "user": {"role": "admin"}}}'
# Returns: {"result": true}
# Bundle (OPA loads from remote URL, polling for updates):
opa run --server --bundle https://s3.amazonaws.com/my-policies/bundle.tar.gz
# OPA polls the bundle URL and automatically loads new policies
# OPA in Envoy (external authorization filter):
# Configure Envoy ExtAuthz to call OPA for every request
# OPA returns allow/deny at the proxy level — no app code changes needed
6. Testing & CI Integration
Rego unit tests and conftest for CI gates
# Rego unit tests (policy_test.rego):
package requiredlabels_test
import data.requiredlabels.violation
test_missing_labels {
violation[{"msg": _}] with input as {
"review": {"object": {
"metadata": {"labels": {"team": "backend"}}
}},
"parameters": {"labels": ["team", "environment"]}
}
}
test_all_labels_present {
count(violation) == 0 with input as {
"review": {"object": {
"metadata": {"labels": {"team": "backend", "environment": "prod"}}
}},
"parameters": {"labels": ["team", "environment"]}
}
}
# Run tests:
opa test ./policies/ -v
# conftest — OPA policy testing for CI (validates Kubernetes YAML, Terraform, Dockerfiles):
# Install: brew install conftest
# policies/deny_latest.rego:
package main
deny[msg] {
input.spec.template.spec.containers[_].image == "latest"
msg := "Do not use the 'latest' image tag"
}
# Run against your manifests in CI:
conftest test deployment.yaml # runs all policies in ./policy/
conftest test deployment.yaml --policy custom/ # specific policy dir
# exit code 1 if any deny rule matches — blocks CI pipeline
Track OPA, Gatekeeper, and Kubernetes policy tool releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.
Related: Kubernetes RBAC Reference | Kubernetes YAML Reference | Vault Reference
🔍 Free tool: K8s YAML Security Linter — check the K8s manifests OPA Gatekeeper constrains for 12 security misconfigurations before they reach the cluster.
Founded
2023 in London, UK
Contact
hello@releaserun.com