Policy Automation with Open Policy Agent
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-as-code replaces manual policy enforcement — approval chains, checklists, review meetings — with automated, testable, version-controlled rules. Open Policy Agent (OPA) is the industry standard for this: a general-purpose policy engine that can evaluate any JSON/YAML input against any policy you define.
How OPA Works
Input (JSON) → OPA Engine → Decision (allow/deny + reasons)
↑
Policy (Rego)
OPA takes structured data as input, evaluates it against Rego policies, and returns a decision. The input can be a Kubernetes admission request, a Terraform plan, an HTTP request, or any structured data.
Rego Language Basics
package kubernetes.admission
# Deny pods without resource limits
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.resources.limits
msg := sprintf("Container '%s' must have resource limits", [container.name])
}
# Deny images from untrusted registries
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not startswith(container.image, "registry.internal/")
not startswith(container.image, "gcr.io/company-prod/")
msg := sprintf("Container '%s' uses untrusted image: %s",
[container.name, container.image])
}
Kubernetes Admission Control
OPA Gatekeeper acts as a Kubernetes admission webhook:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels:
- key: "team"
- key: "cost-center"
Every namespace creation is validated: no team or cost-center label → rejected before anything is created.
Terraform Policy Validation
Validate infrastructure changes before terraform apply:
package terraform
# Deny public S3 buckets
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.after.acl == "public-read"
msg := sprintf("S3 bucket '%s' cannot be public", [resource.address])
}
# Require encryption on RDS instances
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_db_instance"
not resource.change.after.storage_encrypted
msg := sprintf("RDS instance '%s' must have encryption enabled", [resource.address])
}
# Enforce instance type limits
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
not resource.change.after.instance_type in allowed_types
msg := sprintf("Instance type '%s' not allowed. Use: %v",
[resource.change.after.instance_type, allowed_types])
}
allowed_types := {"t3.micro", "t3.small", "t3.medium", "m5.large", "m5.xlarge"}
CI Pipeline Integration
# In CI/CD pipeline
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test plan.json --policy policies/
Policy Testing
Policies are code. Test them like code:
package terraform_test
test_deny_public_s3 {
deny with input as {
"resource_changes": [{
"type": "aws_s3_bucket",
"address": "aws_s3_bucket.bad",
"change": {"after": {"acl": "public-read"}}
}]
}
}
test_allow_private_s3 {
count(deny) == 0 with input as {
"resource_changes": [{
"type": "aws_s3_bucket",
"address": "aws_s3_bucket.good",
"change": {"after": {"acl": "private"}}
}]
}
}
opa test policies/ -v
Building a Policy Library
Organize policies by domain:
policies/
├── kubernetes/
│ ├── resource-limits.rego
│ ├── image-registry.rego
│ ├── network-policy.rego
│ └── rbac.rego
├── terraform/
│ ├── encryption.rego
│ ├── tagging.rego
│ ├── networking.rego
│ └── cost-limits.rego
├── ci-cd/
│ ├── branch-protection.rego
│ └── test-coverage.rego
└── tests/
├── kubernetes_test.rego
└── terraform_test.rego
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| Policies without tests | False positives/negatives in production | Test every policy rule |
| Deny-only (no explanations) | Developers do not know how to fix violations | Include fix guidance in deny messages |
| All policies enforced immediately | Teams blocked, shadow IT increases | Audit mode first, then enforce gradually |
| No exception process | Legitimate needs blocked | Exception mechanism with expiry |
| Policies in a monorepo not versioned | Breaking changes affect everyone | Semantic versioning for policy packages |
Policy automation scales governance from “one security engineer reviews everything” to “every change is automatically validated against the entire policy library in seconds.”