Tuesday, 26 August 2025

EC2 mock and OPA

EC2 Terraform OPA Policy Test

EC2 Terraform OPA Policy Test


1. Mock Terraform Plan JSON (ec2-plan.json)

{
  "format_version": "0.1",
  "terraform_version": "1.13.1",
  "resource_changes": {
    "aws_instance.example": {
      "type": "aws_instance",
      "name": "example",
      "change": {
        "actions": ["create"],
        "before": null,
        "after": {
          "ami": "ami-12345678",
          "instance_type": "t2.micro",
          "ebs_optimized": false,
          "associate_public_ip_address": true,
          "iam_instance_profile": "my-ec2-role",
          "vpc_security_group_ids": ["sg-12345678", "sg-87654321"],
          "tags": {"Environment": "Dev"},
          "ebs_block_device": [
            {"device_name": "/dev/sda1", "volume_size": 30, "encrypted": false},
            {"device_name": "/dev/sdb", "volume_size": 50, "encrypted": true}
          ]
        }
      }
    }
  }
}

Explanation of Fields

Field Purpose / Relevance
instance_typeMust follow allowed instance types
ebs_optimizedMust be true for performance
associate_public_ip_addressMust be false for private instances
iam_instance_profileMust be attached for proper permissions
vpc_security_group_idsMust include required security groups
ebs_block_deviceVolumes must be encrypted
tagsOptional; can enforce Owner or Environment

2. OPA Policy (ec2.rego)

package terraform.ec2

default deny = []

# 1. Disallow t2.micro instances
deny[msg] {
  some resource
  input.resource_changes[resource].type == "aws_instance"
  input.resource_changes[resource].change.after.instance_type == "t2.micro"
  msg := sprintf("Instance %v uses disallowed type t2.micro", [input.resource_changes[resource].name])
}

# 2. Require EBS optimization
deny[msg] {
  some resource
  input.resource_changes[resource].type == "aws_instance"
  not input.resource_changes[resource].change.after.ebs_optimized
  msg := sprintf("Instance %v is not EBS optimized", [input.resource_changes[resource].name])
}

# 3. Disallow public IP
deny[msg] {
  some resource
  input.resource_changes[resource].type == "aws_instance"
  input.resource_changes[resource].change.after.associate_public_ip_address
  msg := sprintf("Instance %v has a public IP assigned", [input.resource_changes[resource].name])
}

# 4. Require IAM role
deny[msg] {
  some resource
  input.resource_changes[resource].type == "aws_instance"
  not input.resource_changes[resource].change.after.iam_instance_profile
  msg := sprintf("Instance %v does not have an IAM role attached", [input.resource_changes[resource].name])
}

# 5. Require Security Groups
deny[msg] {
  some resource
  input.resource_changes[resource].type == "aws_instance"
  sg_ids := input.resource_changes[resource].change.after.vpc_security_group_ids
  count(sg_ids) == 0
  msg := sprintf("Instance %v does not have any Security Groups attached", [input.resource_changes[resource].name])
}

# 6. EBS volumes must be encrypted
deny[msg] {
  some resource
  input.resource_changes[resource].type == "aws_instance"
  volume := input.resource_changes[resource].change.after.ebs_block_device[_]
  not volume.encrypted
  msg := sprintf("Instance %v has unencrypted volume %v", [input.resource_changes[resource].name, volume.device_name])
}

# 7. Optional: Require Owner tag
deny[msg] {
  some resource
  input.resource_changes[resource].type == "aws_instance"
  tags := input.resource_changes[resource].change.after.tags
  not ("Owner" in tags)
  msg := sprintf("Instance %v does not have an 'Owner' tag", [input.resource_changes[resource].name])
}

3. Python Script to Call OPA

import subprocess
import json

plan_file = "ec2-plan.json"
policy_file = "policy/ec2.rego"

cmd = [
    "opa", "eval",
    "-i", plan_file,
    "-d", policy_file,
    "data.terraform.ec2.deny",
    "--format", "json"
]

result = subprocess.run(cmd, capture_output=True, text=True)
opa_output = json.loads(result.stdout)

violations = opa_output["result"][0]["expressions"][0]["value"]

if violations:
    print("❌ Policy violations found:")
    for v in violations:
        print("-", v)
else:
    print("✅ All policies passed.")

4. PowerShell Script to Call OPA

# -----------------------------
# PowerShell OPA Test Script for EC2 with EBS, IAM, SG
# -----------------------------

$PlanFile = "C:\OPA_EC2\ec2-plan.json"
$PolicyFile = "C:\OPA_EC2\policy\ec2.rego"

$OpaCommand = @(
    "opa", "eval",
    "-i", $PlanFile,
    "-d", $PolicyFile,
    "data.terraform.ec2.deny",
    "--format", "json"
)

try {
    $OpaOutputRaw = & $OpaCommand
} catch {
    Write-Error "Failed to run OPA. Make sure opa.exe is in your PATH."
    exit 1
}

$OpaOutput = $OpaOutputRaw | ConvertFrom-Json
$Violations = $OpaOutput.result[0].expressions[0].value

if ($Violations.Count -gt 0) {
    Write-Host "❌ Policy violations found:" -ForegroundColor Red
    foreach ($v in $Violations) {
        Write-Host "- $v" -ForegroundColor Yellow
    }
    Write-Host "Aborting Terraform apply due to policy violations..." -ForegroundColor Red
    exit 1
} else {
    Write-Host "✅ All policies passed." -ForegroundColor Green
}

5. Expected Output for This Mock JSON

❌ Policy violations found:
- Instance aws_instance.example uses disallowed type t2.micro
- Instance aws_instance.example is not EBS optimized
- Instance aws_instance.example has a public IP assigned
- Instance aws_instance.example has unencrypted volume /dev/sda1
- Instance aws_instance.example does not have an 'Owner' tag

No comments:

Post a Comment