13 / 75 · 08 Infrastructure as Code Testing · Policy as Code← prev⊞ allnext →☰ Read as one page
3.3OPA/Rego: The Universal Policy Engine
Open Policy Agent (OPA) evaluates structured data against Rego policies. This is the most flexible approach because it works with any infrastructure format -- Terraform plans, Kubernetes manifests, Docker Compose files, or any JSON/YAML configuration.
Writing Rego Policies
# policy/terraform/s3.rego
package terraform.s3
# Deny S3 buckets without encryption
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not has_encryption(resource)
msg := sprintf("S3 bucket '%s' must have server-side encryption enabled", [resource.address])
}
has_encryption(resource) {
resource.change.after.server_side_encryption_configuration[_].rule[_].apply_server_side_encryption_by_default[_].sse_algorithm
}
# Deny public ACLs
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
acl := resource.change.after.acl
acl != "private"
msg := sprintf("S3 bucket '%s' has ACL '%s' -- must be 'private'", [resource.address, acl])
}
# Deny buckets without versioning
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not has_versioning(resource)
msg := sprintf("S3 bucket '%s' must have versioning enabled", [resource.address])
}
has_versioning(resource) {
resource.change.after.versioning[_].enabled == true
}
Running OPA with Conftest
Conftest is the CLI tool that runs OPA policies against configuration files:
# Run policy against a Terraform plan
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
conftest test tfplan.json --policy policy/terraform/
# Run against Kubernetes manifests
conftest test k8s/deployment.yaml --policy policy/kubernetes/
# Run against Dockerfiles
conftest test Dockerfile --policy policy/docker/
# Run with multiple policy directories
conftest test tfplan.json \
--policy policy/security/ \
--policy policy/compliance/ \
--policy policy/cost/
Organizing Rego Policies by Concern
A well-organized policy repository separates concerns:
policy/
terraform/
s3.rego # S3-specific rules
rds.rego # RDS-specific rules
iam.rego # IAM policy rules
networking.rego # VPC, security group rules
tagging.rego # Resource tagging requirements
kubernetes/
security.rego # Pod security, RBAC
resources.rego # CPU/memory limits
networking.rego # Network policies
docker/
best_practices.rego # Dockerfile linting
common/
helpers.rego # Shared helper functions
Advanced Rego Patterns
# policy/terraform/iam.rego
package terraform.iam
# Deny IAM policies with wildcard actions
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_policy"
policy_doc := json.unmarshal(resource.change.after.policy)
statement := policy_doc.Statement[_]
statement.Effect == "Allow"
statement.Action[_] == "*"
msg := sprintf("IAM policy '%s' grants wildcard actions (*). Use specific actions.",
[resource.address])
}
# Deny IAM policies with wildcard resources on mutating actions
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_policy"
policy_doc := json.unmarshal(resource.change.after.policy)
statement := policy_doc.Statement[_]
statement.Effect == "Allow"
statement.Resource[_] == "*"
action := statement.Action[_]
not is_readonly_action(action)
msg := sprintf("IAM policy '%s' uses wildcard resource with mutating action '%s'",
[resource.address, action])
}
is_readonly_action(action) {
endswith(action, ":Get*")
}
is_readonly_action(action) {
endswith(action, ":List*")
}
is_readonly_action(action) {
endswith(action, ":Describe*")
}