ops0ops0

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

At least one IaC project in ops0
Organization admin or Policy admin role

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

1Click Policies in the sidebar
2Click + New Policy
3Enter a name: s3-encryption-required

Option 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:

Advisory Mode

Shows warnings but allows deployment to proceed.

Use for: New policies during rollout, educational warnings

Mandatory Mode

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:

ScopeEffect
All ProjectsPolicy runs on every deployment
Specific ProjectsOnly selected projects
By TagProjects with matching tags (e.g., environment:production)
1In the policy editor, click Assignments tab
2Select All Projects or choose specific ones
3Click Save

Step 4: Test Your Policy

Before relying on the policy in production, test it:

1Open any IaC project
2Create an S3 bucket without encryption
3Run Plan
4Verify the policy violation appears

Expected Result

Policy Check Failed

✗ 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:

1Click Policies > Compliance
2View pass/fail rates by policy
3Drill down to see violations by project

Compliance Overview

PolicyPass RateLast Violation
s3-encryption-required98%2 days ago
no-public-ssh100%Never
required-tags85%4 hours ago
rds-backup-required100%Never

Step 6: Export Compliance Reports

For auditors and compliance reviews:

1Go to Policies > Compliance
2Click Export Report
3Choose format: PDF, CSV, or JSON
4Select date range and policies to include

Best Practices

Start with Advisory Mode
Deploy new policies in advisory mode first. Review violations, then switch to mandatory.
Write Clear Error Messages
Include the resource name and what needs to be fixed. Good: "S3 bucket xyz needs encryption". Bad: "Policy failed".
Version Control Policies
Store Rego files in Git alongside your Terraform. Sync with ops0 for consistency.
Test Thoroughly
Test policies against both compliant and non-compliant resources before enabling mandatory mode.

Next Steps