Policy as code — OPA, Rego, and automatic rule enforcement
Policy as Code transforms security rules — which usually exist only in documents — into executable, testable, versioned code. The Open Policy Agent (OPA) with the Rego language is the most widely adopted standard for this.
What is OPA
OPA is a general-purpose policy engine, decoupled from the application. It receives a JSON input (what is happening), consults a Rego policy (what is permitted), and returns a decision (allow/deny).
OPA architecture:
Application / pipeline
│ input (JSON)
▼
┌─────┐
│ OPA │ ←── policy (.rego)
└─────┘
│ result: { "allow": true } or { "allow": false, "reasons": [...] }
▼
Application acts on the decision
Rego language — basic concepts
# policy/deny_root.rego
package kubernetes.admission
import future.keywords.if
import future.keywords.contains
# Deny pods running as root
deny contains msg if {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
container.securityContext.runAsUser == 0
msg := sprintf("Container '%v' must not run as root (UID 0)", [container.name])
}
# policy/require_labels.rego
package kubernetes.admission
# Require mandatory labels on all Deployments
deny contains msg if {
input.request.kind.kind == "Deployment"
required_labels := {"owner", "env", "cost-center"}
existing := {l | input.request.object.metadata.labels[l]}
missing := required_labels - existing
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}
OPA in Kubernetes — Gatekeeper
Gatekeeper integrates OPA as a Kubernetes admission controller: any resource created or modified passes through policies before being accepted.
# ConstraintTemplate — defines the constraint type
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
required := input.parameters.labels[_]
not input.review.object.metadata.labels[required]
msg := sprintf("Missing required label: %v", [required])
}
---
# Constraint — applies the rule to Namespaces
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-owner
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["owner", "env"]
OPA in the CI/CD pipeline
Policies can be evaluated before deployment, during the pipeline itself:
# Evaluate a Kubernetes manifest against an OPA policy
opa eval \
--input k8s/deployment.yaml \
--data policy/ \
--format pretty \
'data.kubernetes.admission.deny'
# If deny returns non-empty results → pipeline fails
# GitHub Actions — OPA conftest
- name: Policy check with conftest
run: |
conftest test k8s/ --policy policy/ --all-namespaces
# conftest is a CLI wrapper for OPA, supports multiple formats:
# Terraform, Kubernetes YAML, Dockerfile, JSON, TOML
Practical use cases
Kubernetes / Gatekeeper:
- Block images without a fixed digest (sha256 only)
- Require resource limits on all containers
- Forbid hostNetwork: true and hostPID: true
- Deny pods without a readinessProbe
CI/CD pipeline:
- Block deploy to production without security approval
- Require images to come only from authorized registries
- Deny Terraform with security group 0.0.0.0/0
API Gateway / Envoy:
- Request authorization based on JWT claims
- Per-tenant rate limiting
Testing policies
Policies as code have automated tests:
# policy/deny_root_test.rego
package kubernetes.admission_test
test_deny_root_container if {
result := deny with input as {
"request": {
"kind": {"kind": "Pod"},
"object": {"spec": {"containers": [
{"name": "app", "securityContext": {"runAsUser": 0}}
]}}
}
}
count(result) > 0
}
opa test policy/ -v
# PASS: test_deny_root_container
Summary
OPA and Rego allow security policies to be versioned, reviewed in PRs, automatically tested, and consistently enforced across Kubernetes, pipelines, and APIs. They replace compliance documents with executable code — the policy passes or fails, without ambiguity.