Enforce Security Policies
Create and enforce OPA policies to automatically block non-compliant infrastructure deployments.
Scenario
Your security team requires:
- All S3 buckets must have encryption enabled
- No public security group rules allowing 0.0.0.0/0 on SSH
- All resources must have specific tags
- RDS databases must have automated backups enabled
Instead of relying on manual code reviews, you want to automatically block deployments that violate these rules.
Prerequisites
Understanding Policy Enforcement
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
Step 1: Create Your First Policy
s3-encryption-requiredOption A: Use AI to Create Policy
In 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
}
Option B: Write Policy Manually
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)
}
Step 2: Configure Enforcement Mode
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.
Step 3: Assign Policy to Projects
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) |
Step 4: Test Your Policy
Before relying on the policy in production, test it:
Expected Result
✗ s3-encryption-required (Mandatory)
SECURITY: S3 bucket aws_s3_bucket.data requires encryption.
Add aws_s3_bucket_server_side_encryption_configuration.
Common Security Policies
Here are ready-to-use policies for common requirements:
No Public SSH
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]
)
}
Required Tags
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]
)
}
RDS Backup Required
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]
)
}
No Hardcoded Secrets
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]
)
}
Step 5: View Compliance Dashboard
Track policy compliance across your organization:
Compliance Overview
| 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 |
Step 6: Export Compliance Reports
For auditors and compliance reviews: