Create and enforce OPA policies to automatically block non-compliant infrastructure deployments.
Your security team requires:
Instead of relying on manual code reviews, you want to automatically block deployments that violate these rules.
Policies run during the Plan phase, before any changes are applied:
Code Change
│
▼
Terraform Plan
│
▼
┌──────────────────┐
│ Policy Engine │◄── Your OPA Policies
│ (OPA/Rego) │
└──────────────────┘
│
├── Pass → Continue to Apply
│
└── Fail → Block Deployment
s3-encryption-requiredIn the policy editor, describe your requirement:
"All S3 buckets must have server-side encryption enabled with AES-256 or KMS"
ops0 produces the Rego policy:
package terraform
import future.keywords.in
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.after != null
# Check if encryption configuration exists
bucket_id := resource.change.after.id
not has_encryption(bucket_id)
msg := sprintf(
"S3 bucket '%s' must have server-side encryption enabled",
[resource.address]
)
}
has_encryption(bucket_id) {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket_server_side_encryption_configuration"
resource.change.after.bucket == bucket_id
}
If you prefer to write Rego directly:
package terraform
# Deny S3 buckets without encryption
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.actions[_] == "create"
# Look for associated encryption config
not bucket_has_encryption(resource.address)
msg := sprintf(
"SECURITY: S3 bucket %s requires encryption. Add aws_s3_bucket_server_side_encryption_configuration.",
[resource.address]
)
}
bucket_has_encryption(bucket_address) {
enc := input.resource_changes[_]
enc.type == "aws_s3_bucket_server_side_encryption_configuration"
contains(enc.address, bucket_address)
}
Choose how violations are handled:
Shows warnings but allows deployment to proceed.
Use for: New policies during rollout, educational warnings
Blocks deployment if policy fails.
Use for: Critical security requirements, compliance mandates
Set the mode in the policy settings, then click Save Policy.
Policies can be applied to:
| Scope | Effect |
|---|---|
| All Projects | Policy runs on every deployment |
| Specific Projects | Only selected projects |
| By Tag | Projects with matching tags (e.g., environment:production) |
Before relying on the policy in production, test it:
✗ s3-encryption-required (Mandatory)
SECURITY: S3 bucket aws_s3_bucket.data requires encryption.
Add aws_s3_bucket_server_side_encryption_configuration.
Here are ready-to-use policies for common requirements:
package terraform
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group_rule"
resource.change.after.type == "ingress"
resource.change.after.from_port <= 22
resource.change.after.to_port >= 22
cidr := resource.change.after.cidr_blocks[_]
cidr == "0.0.0.0/0"
msg := sprintf(
"CRITICAL: Security group rule %s allows SSH from 0.0.0.0/0. Restrict to specific IPs.",
[resource.address]
)
}
package terraform
required_tags := ["Environment", "Owner", "CostCenter"]
deny[msg] {
resource := input.resource_changes[_]
resource.change.actions[_] == "create"
resource.change.after.tags != null
tag := required_tags[_]
not resource.change.after.tags[tag]
msg := sprintf(
"Resource %s is missing required tag: %s",
[resource.address, tag]
)
}
package terraform
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_db_instance"
resource.change.actions[_] == "create"
backup_retention := resource.change.after.backup_retention_period
backup_retention == 0
msg := sprintf(
"RDS instance %s must have automated backups enabled (backup_retention_period > 0)",
[resource.address]
)
}
package terraform
secret_patterns := [
"password",
"secret",
"api_key",
"access_key",
"private_key"
]
deny[msg] {
resource := input.resource_changes[_]
resource.change.after != null
[path, value] := walk(resource.change.after)
is_string(value)
pattern := secret_patterns[_]
contains(lower(path[_]), pattern)
not contains(value, "var.")
not contains(value, "data.")
msg := sprintf(
"Possible hardcoded secret in %s at path %v. Use variables or secrets manager.",
[resource.address, path]
)
}
Track policy compliance across your organization:
| Policy | Pass Rate | Last Violation |
|---|---|---|
| s3-encryption-required | 98% | 2 days ago |
| no-public-ssh | 100% | Never |
| required-tags | 85% | 4 hours ago |
| rds-backup-required | 100% | Never |
For auditors and compliance reviews: