Enforce security, compliance, and operational policies as code using Open Policy Agent. Covers Rego language, Kubernetes admission control, Terraform policy validation, CI/CD gate enforcement, and building a policy library that scales across teams.
Policy Automation with Open Policy Agent enables the declarative enforcement of operational, security, and compliance rules across cloud infrastructure, Kubernetes clusters, and service configurations. It matters when teams need to consistently apply and audit policies across heterogeneous environments without writing custom code for every check, especially in dynamic, multi-team, multi-tenant systems. Policies are written once and enforced automatically at build, deploy, and runtime, reducing drift, improving auditability, and enabling self-service infrastructure.
Rego is the policy language used by Open Policy Agent (OPA), and it is the core of policy automation. A policy is a named set of rules that evaluate input data and return decisions (e.g., allow, deny, reason). Policies are stored as .rego files and can be versioned in Git.
A minimal policy file policies/require-namespace.rego:
package policies.require_namespace
import input as request
default allow = false
allow {
request.kind == "Pod"
request.namespace == "prod"
}
reasons {
request.kind == "Pod"
not request.namespace
reason = "Pod must be in the prod namespace"
}
reasons {
request.kind == "Pod"
request.namespace != "prod"
reason = "Pod is in " ++ request.namespace ++ ", but production requires 'prod'"
}
This policy checks that all Pod resources in the input data have a namespace field set to "prod". If not, it returns allow = false and a list of reasons.
OPA runs as a standalone service or embedded in an application. It exposes a REST API at /v1/policies and /v1/decision endpoints. Policies are loaded via POST /v1/policies/{name} with a JSON body containing the Rego code.
To load the policy above:
curl -X POST http://opa:8181/v1/policies/require-namespace \
-H "Content-Type: application/json" \
-d @policies/require-namespace.rego
A policy can be updated by re-posting the same name, overwriting the previous version.
To evaluate a policy, send a request to /v1/decision with an input JSON object:
{
"kind": "Pod",
"namespace": "staging",
"name": "frontend-7f8c9d"
}
curl -X POST http://opa:8181/v1/decision \
-H "Content-Type: application/json" \
-d '{
"input": {
"kind": "Pod",
"namespace": "staging",
"name": "frontend-7f8c9d"
},
"decision": "policies.require_namespace.allow"
}'
Response:
{
"result": false,
"reasons": [
"Pod is in staging, but production requires 'prod'"
]
}
The decision field selects the rule to evaluate, and the result is the value of that rule in the policy. A decision may be a simple boolean, a list, a map, or a complex nested structure.
OPA can be integrated directly into a Kubernetes cluster via OPA Gatekeeper, a controller that watches Kubernetes API objects and enforces policies. Gatekeeper runs as a Kubernetes operator and uses OPA as its policy engine.
A ConstraintTemplate defines the schema of a policy. For example, to enforce that all Pod resources must have resources.limits.memory set:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredresources
spec:
crd:
spec:
names:
kind: K8sRequiredResources
validation:
openAPIV3Schema:
properties:
requiredResources:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresources
violation[msg] {
input.review.object.kind == "Pod"
input.review.object.spec.containers[_].resources.limits.memory == null
msg = "Missing memory limit for container " ++ input.review.object.spec.containers[_].name
}
violation[msg] {
input.review.object.kind == "Pod"
input.review.object.spec.containers[_].resources.limits.memory == null
msg = "Memory limit must be specified for all containers in Pod " ++ input.review.object.metadata.name
}
This template is applied once to the cluster:
kubectl apply -f constraint-template.yaml
Then, a K8sRequiredResources instance enforces the rule:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
name: require-pod-memory
spec:
requiredResources: ["memory"]
Gatekeeper evaluates this constraint for every Pod created or updated. If a Pod lacks resources.limits.memory, it returns a K8sValidation object in the admission review:
{
"apiVersion": "admission.k8s.gatekeeper.sh/v1beta1",
"kind": "K8sValidation",
"metadata": {
"name": "require-pod-memory"
},
"spec": {
"requiredResources": ["memory"]
},
"status": {
"conditions": [
{
"type": "Valid",
"status": "False",
"reason": "MissingMemoryLimit",
"message": "Missing memory limit for container frontend"
}
]
}
}
The admission controller rejects the Pod creation with a detailed error, and the violation is logged in the K8sValidation object.
OPA supports batch evaluation of multiple decisions in a single request. This is useful for real-time policy evaluation across many resources.
{
"input": {
"pods": [
{
"kind": "Pod",
"namespace": "staging",
"name": "frontend-1"
},
{
"kind": "Pod",
"namespace": "prod",
"name": "backend-2"
}
]
},
"decisions": [
{
"name": "require-namespace",
"decision": "policies.require_namespace.allow",
"input": "pods[0]"
},
{
"name": "require-resources",
"decision": "k8srequiredresources.violation",
"input": "pods[1]"
}
]
}
Request:
curl -X POST http://opa:8181/v1/decisions \
-H "Content-Type: application/json" \
-d @batch-request.json
Response:
{
"results": [
{
"name": "require-namespace",
"result": false,
"reasons": [
"Pod is in staging, but production requires 'prod'"
]
},
{
"name": "require-resources",
"result": [
"Missing memory limit for container app"
]
}
]
}
This enables high-throughput policy evaluation in CI/CD pipelines or service mesh proxies.
OPA can load data from external sources such as PostgreSQL, JSON files, or HTTP APIs. Data is loaded at startup or on-demand via data declarations.
To load data from a JSON file at /data/teams.json:
{
"teams": {
"prod": {
"owner": "team-prod",
"slack": "#prod"
},
"staging": {
"owner": "team-staging",
"slack": "#staging"
}
}
}
In Rego:
package policies.team-ownership
import data.teams as teams
default allow = false
allow {
request.kind == "Pod"
request.namespace == "prod"
teams[request.namespace].owner == "team-prod"
}
reason {
request.kind == "Pod"
request.namespace == "staging"
teams[request.namespace].owner != "team-staging"
reason = "Team ownership mismatch: expected team-staging, got " ++ teams[request.namespace].owner
}
Data can also be loaded dynamically via HTTP. A data source is defined in opa.yaml:
data:
- name: teams
source:
type: http
url: http://config-service:8080/teams
polling:
interval: 30s
timeout: 5s
OPA periodically polls the endpoint, and the response is merged into the global data object.
If the HTTP endpoint returns:
{
"teams": {
"prod": { "owner": "prod-team", "slack": "#prod" },
"staging": { "owner": "staging-team", "slack": "#staging" }
}
}
Then the policy can use teams[request.namespace].owner immediately.
OPA silently fails when a policy rule references a non-existent field. For example, if request.spec.containers[0].resources.limits.memory is evaluated but spec.containers is not an array, the entire evaluation returns false with no explicit error.
A common failure is missing input keys. If the request body is missing kind or namespace, the policy returns false for allow even when all other fields are present.
To improve visibility, use the debug endpoint:
curl http://opa:8181/v1/debug \
-X POST \
-H "Content-Type: application/json" \
-d '{
"input": {
"kind": "Pod",
"namespace": "staging"
},
"decision": "policies.require_namespace.allow"
}'
Response includes the full evaluation trace, including which rules fired, what values were computed, and any errors:
{
"evaluation": {
"input": {
"kind": "Pod",
"namespace": "staging"
},
"decision": "policies.require_namespace.allow",
"result": false,
"steps": [
{
"rule": "policies.require_namespace.allow",
"result": false,
"reasons": [
"Pod is in staging, but production requires 'prod'"
],
"conditions": [
{
"name": "request.kind == 'Pod'",
"value": true
},
{
"name": "request.namespace == 'prod'",
"value": false
}
]
}
],
"errors": [
{
"message": "unexpected type: expected string, got null",
"path": "request.namespace",
"value": null
}
]
}
}
This trace helps diagnose why a policy failed and is ideal for integrating into CI/CD pipelines and observability dashboards.
Policies are stored in version control, and changes are automatically deployed via CI.
A GitHub Actions workflow:
name: Deploy OPA Policies
on:
push:
paths: ["policies/**/*.rego"]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to OPA
run: |
for file in policies/*.rego; do
curl -X POST http://opa:8181/v1/policies/$(basename $file .rego) \
-H "Content-Type: application/json" \
-d "@$file"
done
- name: Test Policies
run: |
curl -X POST http://opa:8181/v1/decision \
-H "Content-Type: application/json" \
-d '{
"input": {
"kind": "Pod",
"namespace": "staging"
},
"decision": "policies.require_namespace.allow"
}' \
-o result.json
if ! grep -q '"result": true' result.json; then
echo "Policy evaluation failed"
exit 1
fi
This ensures that every policy change is tested and deployed consistently, with failure feedback visible in the pull request.
OPA can be used not only for admission control but also for runtime policy enforcement. For example, a service can query OPA before starting, to verify that all required configurations are in place.
A service starts with:
curl http://opa:8181/v1/decision \
-H "Content-Type: application/json" \
-d '{
"input": {
"service": "auth",
"config": {
"database": "postgres",
"ssl": true,
"timeout": 30
}
},
"decision": "policies.validate-service-config"
}'
If the service config is invalid, the service fails to start with a clear error.
OPA exposes metrics via /metrics endpoint. These include:
opa_decision_count_total: total number of decisions evaluatedopa_policy_load_duration_seconds: time to load a policyopa_decision_evaluation_time_seconds: per-decision evaluation timeopa_data_source_polling_errors_total: number of failed data source pollsThese metrics are scraped by Prometheus and used to trigger alerts, such as "OPA decision evaluation time > 100ms" or "policy load failure in last 5 minutes."
Policy automation with Open Policy Agent is a robust, declarative way to enforce system-wide rules without code. Policies are written in Rego, evaluated at build, deploy, and runtime, and integrated into Kubernetes via Gatekeeper. Data is loaded from external sources, decisions are batched, and evaluation is observable via metrics and debugging. The system fails gracefully, with clear error messages and traceable evaluations. It is the foundation for automated governance, compliance, and self-service infrastructure.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy