Policy as Code Implementation

Policy as Code Implementation

Policy as Code transforms security and compliance requirements from documents into executable code. Rather than manual reviews and checklists, policies become automated gates that enforce standards consistently. This approach scales security governance across large organizations while providing immediate feedback to developers.

Open Policy Agent (OPA) has emerged as a standard for policy enforcement across different platforms. OPA's Rego language enables complex policy logic while remaining readable. Policies can validate IaC templates, Kubernetes manifests, API requests, and application configurations. The same policy engine works across different domains, providing consistent governance.

# OPA Policy for Terraform AWS Security
package terraform.aws.security

import future.keywords.in
import future.keywords.if
import future.keywords.contains

# Deny S3 buckets without encryption
deny[msg] {
    resource := input.resource.aws_s3_bucket[name]
    not has_encryption(name)
    msg := sprintf("S3 bucket '%s' does not have encryption enabled", [name])
}

has_encryption(bucket_name) {
    encryption := input.resource.aws_s3_bucket_server_side_encryption_configuration[_]
    encryption.bucket == sprintf("${aws_s3_bucket.%s.id}", [bucket_name])
}

# Deny public S3 buckets
deny[msg] {
    resource := input.resource.aws_s3_bucket[name]
    not has_public_access_block(name)
    msg := sprintf("S3 bucket '%s' does not have public access block configured", [name])
}

has_public_access_block(bucket_name) {
    block := input.resource.aws_s3_bucket_public_access_block[_]
    block.bucket == sprintf("${aws_s3_bucket.%s.id}", [bucket_name])
    block.block_public_acls == true
    block.block_public_policy == true
    block.ignore_public_acls == true
    block.restrict_public_buckets == true
}

# Deny RDS without encryption
deny[msg] {
    resource := input.resource.aws_db_instance[name]
    resource.storage_encrypted != true
    msg := sprintf("RDS instance '%s' does not have storage encryption enabled", [name])
}

# Deny security groups with unrestricted ingress
deny[msg] {
    resource := input.resource.aws_security_group[name]
    rule := resource.ingress[_]
    unrestricted_ingress(rule)
    msg := sprintf("Security group '%s' has unrestricted ingress on port %d", [name, rule.from_port])
}

unrestricted_ingress(rule) {
    "0.0.0.0/0" in rule.cidr_blocks
}

unrestricted_ingress(rule) {
    "::/0" in rule.ipv6_cidr_blocks
}

# Require specific tags for cost management and compliance
required_tags := ["Environment", "Project", "Owner", "DataClassification"]

deny[msg] {
    resource := input.resource[resource_type][name]
    resource_type != "data"
    required_tag := required_tags[_]
    not resource.tags[required_tag]
    msg := sprintf("%s '%s' is missing required tag: %s", [resource_type, name, required_tag])
}

# Enforce naming conventions
deny[msg] {
    resource := input.resource[resource_type][name]
    resource_type != "data"
    not regex.match("^[a-z][a-z0-9-]*$", name)
    msg := sprintf("%s '%s' does not follow naming convention (lowercase, alphanumeric, hyphens only)", [resource_type, name])
}

# Cost control policies
deny[msg] {
    resource := input.resource.aws_instance[name]
    not resource.instance_type in allowed_instance_types
    msg := sprintf("EC2 instance '%s' uses non-approved instance type: %s", [name, resource.instance_type])
}

allowed_instance_types := [
    "t3.micro", "t3.small", "t3.medium",
    "t3a.micro", "t3a.small", "t3a.medium",
    "m5.large", "m5.xlarge"
]

Policy testing ensures policies work as intended before enforcement. Unit tests validate individual policy rules with example inputs. Integration tests verify policies work correctly with real IaC templates. Policy coverage metrics ensure all security requirements have corresponding rules. Continuous policy testing prevents policy regressions as requirements evolve.