intermediate30 min readcloud-providersUpdated: 2024-06-25

AWS Policy Implementation

Comprehensive guide to implementing policies for AWS resources using CloudFormation Guard and OPA

๐Ÿ“‹ Prerequisites

  • AWS account with appropriate permissions
  • AWS CLI configured
  • Basic understanding of CloudFormation or Terraform
  • Familiarity with JSON/YAML
  • Read: What is Policy-as-Code?

๐ŸŽฏ What You'll Learn

  • Setting up CloudFormation Guard for policy validation
  • Writing Guard rules for common AWS security patterns
  • Using OPA with Conftest for Terraform and CloudFormation
  • Integrating policies into CI/CD pipelines
  • Runtime compliance monitoring with AWS Config
  • Best practices for AWS policy governance

๐Ÿท๏ธ Topics Covered

aws cloudformation policy validationcloudformation guard rules examplesaws config rules with policy as codes3 bucket policy validation examplesaws resource policy enforcementcloudformation security scanning

AWS CloudFormation Policy Validation: Complete Implementation Guide

AWS provides multiple tools and services for implementing policy-as-code across your cloud infrastructure. This guide covers the most effective approaches for ensuring your AWS resources meet security, compliance, and operational standards through automated policy enforcement.

๐Ÿ›ก๏ธ CloudFormation Guard

AWS's native policy validation tool for CloudFormation templates and configuration files

๐Ÿ” Open Policy Agent (OPA)

Versatile policy engine that works with any AWS configuration format

โš™๏ธ AWS Config

Runtime monitoring and compliance assessment of AWS resources

CloudFormation Guard Rules Examples: Security Scanning Tutorial

CloudFormation Guard is AWS's purpose-built tool for validating CloudFormation templates and AWS configuration. It uses a domain-specific language that's designed to be readable and maintainable.

Installation and Setup

Install CloudFormation Guard

# Install via Homebrew (macOS/Linux)
brew install aws/tap/cfn-guard

# Install via Cargo (Rust)
cargo install cfn-guard

# Download binary directly
curl -LO https://github.com/aws-cloudformation/cloudformation-guard/releases/latest/download/cfn-guard-v3-ubuntu-latest.tar.gz
tar xf cfn-guard-v3-ubuntu-latest.tar.gz

# Verify installation
cfn-guard --version

Your First Guard Rule

Let's create a rule that ensures all S3 buckets have encryption enabled:

s3-encryption.guard

# S3 bucket encryption rule
rule s3_bucket_encryption_enabled {
    # Select all S3 bucket resources
    AWS::S3::Bucket {
        # Ensure encryption configuration exists
        Properties {
            BucketEncryption exists
            BucketEncryption {
                ServerSideEncryptionConfiguration exists
                ServerSideEncryptionConfiguration[*] {
                    ServerSideEncryptionByDefault exists
                    ServerSideEncryptionByDefault {
                        SSEAlgorithm in ["AES256", "aws:kms"]
                    }
                }
            }
        }
    }
}

# Custom violation message
rule s3_bucket_encryption_enabled {
    AWS::S3::Bucket {
        Properties {
            BucketEncryption exists
            <<
                VIOLATION: S3 bucket must have server-side encryption enabled
                FIX: Add BucketEncryption property with ServerSideEncryptionConfiguration
            >>
        }
    }
}

Testing Your Rules

Create a sample CloudFormation template to test against:

test-template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Test S3 bucket for Guard validation'

Resources:
  # Non-compliant bucket (will fail)
  BadBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-test-bucket-bad
  
  # Compliant bucket (will pass)
  GoodBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-test-bucket-good
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

Run Guard Validation

# Validate template against rules
cfn-guard validate --rules s3-encryption.guard --data test-template.yaml

# Output will show:
# test-template.yaml Status = FAIL
# FAILED rules
# s3-encryption.guard/s3_bucket_encryption_enabled    FAIL
# --
# Rule = s3_bucket_encryption_enabled, Status = FAIL
# Violation: S3 bucket must have server-side encryption enabled

Advanced Guard Patterns

๐Ÿ”ง 1. EC2 Instance Security Groups

ec2-security.guard

# Ensure EC2 instances don't allow SSH from anywhere
rule ec2_no_ssh_from_anywhere {
    AWS::EC2::SecurityGroup {
        Properties {
            SecurityGroupIngress[*] {
                when IpProtocol == "tcp" {
                    when FromPort <= 22 {
                        when ToPort >= 22 {
                            CidrIp != "0.0.0.0/0"
                            CidrIpv6 != "::/0"
                        }
                    }
                }
            }
        }
    }
}

๐Ÿ”ง 2. RDS Database Encryption

rds-encryption.guard

# Ensure RDS instances have encryption enabled
rule rds_encryption_enabled {
    AWS::RDS::DBInstance {
        Properties {
            StorageEncrypted == true
            <<
                VIOLATION: RDS instance must have storage encryption enabled
                FIX: Set StorageEncrypted to true
            >>
        }
    }
}

# Ensure RDS instances are not publicly accessible
rule rds_not_public {
    AWS::RDS::DBInstance {
        Properties {
            PubliclyAccessible == false
            <<
                VIOLATION: RDS instance must not be publicly accessible
                FIX: Set PubliclyAccessible to false
            >>
        }
    }
}

๐Ÿ”ง 3. Lambda Function Configuration

lambda-security.guard

# Ensure Lambda functions have proper timeout and memory limits
rule lambda_timeout_limit {
    AWS::Lambda::Function {
        Properties {
            Timeout <= 300
            <<
                VIOLATION: Lambda timeout must not exceed 300 seconds
                FIX: Set Timeout to 300 or less
            >>
        }
    }
}

# Ensure Lambda functions use supported runtime
rule lambda_supported_runtime {
    AWS::Lambda::Function {
        Properties {
            Runtime in [
                "python3.9", "python3.10", "python3.11",
                "nodejs18.x", "nodejs20.x",
                "java11", "java17",
                "dotnet6", "dotnet8"
            ]
            <<
                VIOLATION: Lambda must use a supported runtime version
                FIX: Update Runtime to a supported version
            >>
        }
    }
}

AWS Resource Policy Enforcement: OPA Conftest Integration

Open Policy Agent with Conftest provides more flexibility for complex policies and works excellently with Terraform configurations and other infrastructure tools.

Setup Conftest

Install Conftest

# Install via Homebrew
brew install conftest

# Install via Go
go install github.com/open-policy-agent/conftest@latest

# Download binary
curl -L https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_linux_x86_64.tar.gz | tar xz
sudo mv conftest /usr/local/bin

# Verify installation
conftest --version

Terraform AWS Policies with OPA

policy/aws_s3.rego

package aws.s3

import future.keywords.in

# METADATA
# title: S3 Security Policies
# description: Comprehensive S3 bucket security validation
# authors:
# - PolicyAsCode

# Deny S3 buckets without encryption
deny[msg] {
    resource := input.planned_values.root_module.resources[_]
    resource.type == "aws_s3_bucket"
    
    not has_encryption(resource)
    
    msg := sprintf("S3 bucket '%s' must have server-side encryption enabled", [resource.name])
}

# Deny S3 buckets with public read access
deny[msg] {
    resource := input.planned_values.root_module.resources[_]
    resource.type == "aws_s3_bucket_public_access_block"
    
    values := resource.values
    any([
        values.block_public_acls == false,
        values.block_public_policy == false,
        values.ignore_public_acls == false,
        values.restrict_public_buckets == false
    ])
    
    msg := sprintf("S3 bucket '%s' must block all public access", [values.bucket])
}

# Deny S3 buckets without versioning
deny[msg] {
    resource := input.planned_values.root_module.resources[_]
    resource.type == "aws_s3_bucket_versioning"
    
    resource.values.versioning_configuration[_].status != "Enabled"
    
    msg := sprintf("S3 bucket versioning must be enabled for bucket '%s'", [resource.values.bucket])
}

# Helper function to check encryption
has_encryption(resource) {
    encryption := input.planned_values.root_module.resources[_]
    encryption.type == "aws_s3_bucket_server_side_encryption_configuration"
    encryption.values.bucket == resource.values.id
}

# Warn about S3 buckets without lifecycle policies
warn[msg] {
    resource := input.planned_values.root_module.resources[_]
    resource.type == "aws_s3_bucket"
    
    not has_lifecycle_policy(resource)
    
    msg := sprintf("Consider adding lifecycle policy to S3 bucket '%s' for cost optimization", [resource.name])
}

# Helper function to check lifecycle policy
has_lifecycle_policy(resource) {
    lifecycle := input.planned_values.root_module.resources[_]
    lifecycle.type == "aws_s3_bucket_lifecycle_configuration"
    lifecycle.values.bucket == resource.values.id
}

EC2 Security Policies

policy/aws_ec2.rego

package aws.ec2

import future.keywords.in

# Deny EC2 instances without encryption
deny[msg] {
    resource := input.planned_values.root_module.resources[_]
    resource.type == "aws_instance"
    
    not all_ebs_encrypted(resource)
    
    msg := sprintf("EC2 instance '%s' must have all EBS volumes encrypted", [resource.name])
}

# Deny security groups with overly permissive ingress
deny[msg] {
    resource := input.planned_values.root_module.resources[_]
    resource.type == "aws_security_group"
    
    rule := resource.values.ingress[_]
    has_wide_cidr(rule)
    has_sensitive_port(rule)
    
    msg := sprintf("Security group '%s' allows %s access from 0.0.0.0/0 on port %d", 
        [resource.name, rule.protocol, rule.from_port])
}

# Deny instances without IMDSv2
deny[msg] {
    resource := input.planned_values.root_module.resources[_]
    resource.type == "aws_instance"
    
    metadata := resource.values.metadata_options[_]
    metadata.http_tokens != "required"
    
    msg := sprintf("EC2 instance '%s' must require IMDSv2 (set http_tokens to 'required')", [resource.name])
}

# Check if all EBS volumes are encrypted
all_ebs_encrypted(instance) {
    # Check root block device
    instance.values.root_block_device[_].encrypted == true
    
    # Check additional EBS volumes
    all_additional_encrypted(instance)
}

all_additional_encrypted(instance) {
    count(instance.values.ebs_block_device) == 0
} {
    instance.values.ebs_block_device[_].encrypted == true
}

# Check for wide CIDR blocks
has_wide_cidr(rule) {
    rule.cidr_blocks[_] in ["0.0.0.0/0", "::/0"]
}

# Check for sensitive ports
has_sensitive_port(rule) {
    sensitive_ports := [22, 3389, 1433, 3306, 5432, 6379, 27017]
    rule.from_port <= sensitive_ports[_]
    rule.to_port >= sensitive_ports[_]
}

Testing Terraform Configurations

Generate and Test Terraform Plan

# Generate Terraform plan in JSON format
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json

# Test with Conftest
conftest verify --policy policy/ tfplan.json

# Run specific policy package
conftest verify --policy policy/ --namespace aws.s3 tfplan.json

# Generate detailed report
conftest verify --policy policy/ --output json tfplan.json > policy-results.json

AWS Config Rules with Policy as Code: Compliance Monitoring

AWS Config provides continuous monitoring of your AWS resources against policy rules, enabling you to detect configuration drift and non-compliance in real-time.

Setting Up AWS Config Rules

config-rules.yaml

# CloudFormation template for AWS Config rules
AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS Config rules for policy compliance'

Resources:
  # S3 bucket encryption rule
  S3BucketSSLRequestsOnlyRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: s3-bucket-ssl-requests-only
      Source:
        Owner: AWS
        SourceIdentifier: S3_BUCKET_SSL_REQUESTS_ONLY
      DependsOn: ConfigurationRecorder

  # RDS encryption rule  
  RDSStorageEncryptedRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: rds-storage-encrypted
      Source:
        Owner: AWS
        SourceIdentifier: RDS_STORAGE_ENCRYPTED
      DependsOn: ConfigurationRecorder

  # EC2 security group rule
  EC2SecurityGroupAttachedToENIRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: ec2-security-group-attached-to-eni
      Source:
        Owner: AWS
        SourceIdentifier: EC2_SECURITY_GROUP_ATTACHED_TO_ENI
      DependsOn: ConfigurationRecorder

  # Custom rule using Guard
  CustomS3EncryptionRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: custom-s3-encryption-check
      Source:
        Owner: AWS
        SourceIdentifier: CLOUDFORMATION_GUARD_RULE
        SourceDetails:
          - EventSource: aws.config
            MessageType: ConfigurationItemChangeNotification
      InputParameters: |
        {
          "guard-rule": "rule s3_encrypted { AWS::S3::Bucket { Properties { BucketEncryption exists } } }"
        }

Custom Config Rules with Lambda

custom-config-rule.py

import json
import boto3
from datetime import datetime

def lambda_handler(event, context):
    """
    Custom AWS Config rule to check S3 bucket encryption
    """
    
    # Extract configuration item from event
    config_item = event['configurationItem']
    
    # Check if this is an S3 bucket
    if config_item['resourceType'] != 'AWS::S3::Bucket':
        return build_evaluation(
            config_item['resourceId'],
            'NOT_APPLICABLE',
            'Resource is not an S3 bucket'
        )
    
    # Check encryption configuration
    compliance_type = 'NON_COMPLIANT'
    annotation = 'S3 bucket does not have encryption enabled'
    
    if is_bucket_encrypted(config_item):
        compliance_type = 'COMPLIANT'
        annotation = 'S3 bucket has encryption enabled'
    
    # Build evaluation result
    evaluation = build_evaluation(
        config_item['resourceId'],
        compliance_type,
        annotation
    )
    
    # Send evaluation to AWS Config
    config_client = boto3.client('config')
    config_client.put_evaluations(
        Evaluations=[evaluation],
        ResultToken=event['resultToken']
    )
    
    return {
        'statusCode': 200,
        'body': json.dumps('Config rule evaluation completed')
    }

def is_bucket_encrypted(config_item):
    """Check if S3 bucket has encryption enabled"""
    configuration = config_item.get('configuration', {})
    
    # Check for bucket encryption configuration
    bucket_encryption = configuration.get('bucketEncryption')
    if not bucket_encryption:
        return False
    
    # Verify encryption algorithm is present
    sse_config = bucket_encryption.get('serverSideEncryptionConfiguration', [])
    for rule in sse_config:
        if rule.get('serverSideEncryptionByDefault', {}).get('sseAlgorithm'):
            return True
    
    return False

def build_evaluation(resource_id, compliance_type, annotation):
    """Build Config evaluation result"""
    return {
        'ComplianceResourceType': 'AWS::S3::Bucket',
        'ComplianceResourceId': resource_id,
        'ComplianceType': compliance_type,
        'Annotation': annotation,
        'OrderingTimestamp': datetime.utcnow()
    }

CloudFormation Security Scanning: GitHub Actions Integration

Integrating AWS policy validation into your CI/CD pipelines ensures that policy violations are caught early, before resources are deployed to your AWS environment.

GitHub Actions Pipeline

.github/workflows/aws-policy-check.yml

name: AWS Policy Validation

on:
  pull_request:
    paths:
      - '**.tf'
      - '**.yaml'
      - '**.yml'
      - 'policy/**'

env:
  AWS_REGION: us-east-1

jobs:
  policy-validation:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v3
      with:
        terraform_version: 1.6.0
    
    - name: Setup CloudFormation Guard
      run: |
        curl -LO https://github.com/aws-cloudformation/cloudformation-guard/releases/latest/download/cfn-guard-v3-ubuntu-latest.tar.gz
        tar xf cfn-guard-v3-ubuntu-latest.tar.gz
        chmod +x cfn-guard
        sudo mv cfn-guard /usr/local/bin/
    
    - name: Setup Conftest
      run: |
        curl -L https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_linux_x86_64.tar.gz | tar xz
        chmod +x conftest
        sudo mv conftest /usr/local/bin/
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}
    
    - name: Terraform Init
      run: terraform init
    
    - name: Terraform Plan
      run: |
        terraform plan -out=tfplan
        terraform show -json tfplan > tfplan.json
    
    - name: Validate with Conftest
      run: |
        echo "๐Ÿ” Running Conftest validation..."
        conftest verify --policy policy/ tfplan.json
    
    - name: Validate CloudFormation templates
      run: |
        echo "๐Ÿ›ก๏ธ Running CloudFormation Guard validation..."
        find . -name "*.yaml" -o -name "*.yml" | grep -E "(cloudformation|cfn)" | while read template; do
          echo "Validating $template"
          cfn-guard validate --rules guard-rules/ --data "$template"
        done
    
    - name: Policy Compliance Report
      if: always()
      run: |
        echo "## ๐Ÿ“Š Policy Compliance Report" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY
        
        # Conftest results
        echo "### Conftest Results" >> $GITHUB_STEP_SUMMARY
        if conftest verify --policy policy/ tfplan.json --output json > conftest-results.json; then
          echo "โœ… All Conftest policies passed" >> $GITHUB_STEP_SUMMARY
        else
          echo "โŒ Conftest policy violations found" >> $GITHUB_STEP_SUMMARY
          cat conftest-results.json >> $GITHUB_STEP_SUMMARY
        fi
        
        echo "" >> $GITHUB_STEP_SUMMARY
        echo "### Resources Validated" >> $GITHUB_STEP_SUMMARY
        jq -r '.planned_values.root_module.resources[].type' tfplan.json | sort | uniq -c >> $GITHUB_STEP_SUMMARY

AWS Policy Best Practices

๐ŸŽฏ

Start with High-Impact Rules

Focus on encryption at rest/transit, public access prevention, IAM least privilege, and network security first.

๐Ÿ“Š

Organize Policies by Domain

Structure policies into security, compliance, cost, and operational domains for better management.

๐Ÿงช

Test Thoroughly

Create positive and negative test cases, use realistic samples, and validate helpful error messages.

๐Ÿ”„

Gradual Rollout

Start with warning-only mode, enable in dev first, monitor for false positives before production.

๐Ÿ“š

Documentation

Provide clear descriptions, compliant examples, remediation guidance, and exception processes.

โšก

Performance Optimization

Use efficient patterns, avoid complex conditions, cache results, and monitor execution time.

Troubleshooting Common Issues

๐Ÿ”ง CloudFormation Guard Issues

โŒ Rule not matching expected resources

  • Check resource type spelling (case-sensitive)
  • Verify property path structure
  • Use cfn-guard parse-tree to debug
  • Test with minimal template first

โŒ Complex conditions not working

  • Break complex rules into smaller parts
  • Use intermediate variables
  • Check operator precedence
  • Test each condition separately

๐Ÿ”ง OPA/Conftest Issues

โŒ Terraform plan JSON structure changes

  • Use defensive programming with has_key()
  • Create helper functions for common patterns
  • Test against multiple Terraform versions
  • Use generic selectors where possible

โŒ Undefined value errors

  • Check existence before accessing values
  • Use helper functions for validation
  • Implement null-safe operations
  • Test with edge cases and missing data

Example: Safe Value Access Pattern

# Bad - will fail if encryption doesn't exist
deny[msg] {
    bucket := input.resource_changes[_]
    bucket.type == "aws_s3_bucket"
    bucket.change.after.server_side_encryption_configuration == null
}

# Good - check existence first
deny[msg] {
    bucket := input.resource_changes[_]
    bucket.type == "aws_s3_bucket"
    not has_encryption(bucket)
}

has_encryption(bucket) {
    bucket.change.after.server_side_encryption_configuration
    bucket.change.after.server_side_encryption_configuration != null
}

๐ŸŽ‰ Congratulations!

AWS Policy Governance Mastery

You now have the knowledge to implement comprehensive AWS policy governance using:

โœ…

CloudFormation Guard

Master AWS's native validation tool for CloudFormation templates and configuration files.

โœ…

OPA/Conftest Integration

Implement flexible policy enforcement for Terraform and complex multi-tool environments.

โœ…

AWS Config Monitoring

Set up runtime compliance monitoring with continuous drift detection and remediation.

โœ…

CI/CD Integration

Automate policy checks in your deployment pipelines for early violation detection.

โœ…

Enterprise Best Practices

Apply proven patterns for enterprise-scale AWS governance and policy management.

โœ…

Troubleshooting Skills

Debug and resolve common policy issues with confidence and systematic approaches.

Next Steps