# Enforce Security Policies

Source: https://docs.ops0.com/docs/how-to/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

1. Click **Policies** in the sidebar

2. Click **+ New Policy**

3. Enter 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:

```rego
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:

```rego
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:

| Scope | Effect |
|-------|--------|
| **All Projects** | Policy runs on every deployment |
| **Specific Projects** | Only selected projects |
| **By Tag** | Projects with matching tags (e.g., `environment:production`) |

1. In the policy editor, click **Assignments** tab

2. Select **All Projects** or choose specific ones

3. Click **Save**

---

## Step 4: Test Your Policy

Before relying on the policy in production, test it:

1. Open any IaC project

2. Create an S3 bucket **without** encryption

3. Run **Plan**

4. Verify 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

```rego
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

```rego
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

```rego
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

```rego
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:

1. Click **Policies** > **Compliance**

2. View pass/fail rates by policy

3. Drill down to see violations by project

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

1. Go to **Policies** > **Compliance**

2. Click **Export Report**

3. Choose format: PDF, CSV, or JSON

4. Select 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

[Policy Reference Complete guide to writing Rego policies](https://docs.ops0.com/docs/policies/creating-policies)
[Compliance Frameworks Pre-built policies for SOC 2, HIPAA, CIS](https://docs.ops0.com/docs/policies/compliance)
