Back to Blogs

AWS CIS Compliance Checklist: A Comprehensive Security Guide

Introduction

The Center for Internet Security (CIS) AWS Foundations Benchmark provides a comprehensive set of security configuration best practices for AWS. Having implemented CIS compliance across healthcare, financial, and e-commerce platforms, I've compiled this practical checklist to help organizations achieve and maintain CIS compliance in their AWS environments.

Why CIS Compliance Matters

CIS benchmarks are globally recognized security standards that help organizations:

  • Reduce security vulnerabilities and attack surface
  • Meet regulatory compliance requirements
  • Establish security baselines and governance
  • Improve overall security posture

Section 1: Identity and Access Management

1.1 Root Account Security

resource "aws_cloudwatch_log_metric_filter" "root_usage" {
  name           = "root-account-usage"
  log_group_name = aws_cloudwatch_log_group.cloudtrail.name
  pattern        = "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }"

  metric_transformation {
    name      = "RootAccountUsageCount"
    namespace = "CISBenchmark"
    value     = "1"
  }
}

resource "aws_cloudwatch_metric_alarm" "root_usage_alarm" {
  alarm_name          = "root-account-usage-alarm"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "1"
  metric_name         = "RootAccountUsageCount"
  namespace           = "CISBenchmark"
  period              = "60"
  statistic           = "Sum"
  threshold           = "1"
  alarm_description   = "Root account usage detected"
  alarm_actions       = [aws_sns_topic.security_alerts.arn]
}
  • ✅ Avoid using root account for daily activities
  • ✅ Enable MFA on root account
  • ✅ Remove or deactivate root access keys
  • ✅ Set up root account monitoring alerts
  • ✅ Avoid inline policies - use managed policies instead

1.2 Multi-Factor Authentication

resource "aws_iam_policy" "require_mfa" {
  name        = "RequireMFAPolicy"
  description = "Policy to require MFA for all operations"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "DenyAllExceptUsersWithMFA"
        Effect = "Deny"
        NotAction = [
          "iam:CreateVirtualMFADevice",
          "iam:EnableMFADevice",
          "iam:GetUser",
          "iam:ListMFADevices",
          "iam:ListVirtualMFADevices",
          "iam:ResyncMFADevice",
          "sts:GetSessionToken"
        ]
        Resource = "*"
        Condition = {
          BoolIfExists = {
            "aws:MultiFactorAuthPresent" = "false"
          }
        }
      }
    ]
  })
}
  • ✅ Enable MFA for all IAM users
  • ✅ Require MFA for console access
  • ✅ Implement MFA for privileged operations
  • ✅ Avoid inline policies - use managed policies instead

1.3 Password Policy

resource "aws_iam_account_password_policy" "strict" {
  minimum_password_length        = 14
  require_lowercase_characters   = true
  require_numbers               = true
  require_uppercase_characters   = true
  require_symbols               = true
  allow_users_to_change_password = true
  max_password_age              = 90
  password_reuse_prevention     = 24
}

Section 2: Logging and Monitoring

2.1 CloudTrail Configuration

resource "aws_cloudtrail" "main" {
  name           = "main-cloudtrail"
  s3_bucket_name = aws_s3_bucket.cloudtrail.bucket
  
  include_global_service_events = true
  is_multi_region_trail        = true
  enable_logging               = true
  
  kms_key_id = aws_kms_key.cloudtrail.arn
  
  event_selector {
    read_write_type                 = "All"
    include_management_events       = true
    data_resource {
      type   = "AWS::S3::Object"
      values = ["arn:aws:s3:::*/*"]
    }
  }
}
  • ✅ Enable CloudTrail in all regions
  • ✅ Enable log file validation
  • ✅ Encrypt CloudTrail logs with KMS
  • ✅ Configure CloudTrail log file integrity validation

2.2 CloudWatch Monitoring

# Root account usage alarm
resource "aws_cloudwatch_metric_alarm" "root_usage" {
  alarm_name          = "root-account-usage"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "1"
  metric_name         = "RootAccountUsage"
  namespace           = "CISBenchmark"
  period              = "60"
  statistic           = "Sum"
  threshold           = "1"
  alarm_description   = "Root account usage detected"
  alarm_actions       = [aws_sns_topic.security_alerts.arn]
}

Section 3: Storage Security

3.1 S3 Bucket Security

resource "aws_s3_bucket" "secure" {
  bucket = "my-secure-bucket"
}

resource "aws_s3_bucket_public_access_block" "secure" {
  bucket = aws_s3_bucket.secure.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_server_side_encryption_configuration" "secure" {
  bucket = aws_s3_bucket.secure.id

  rule {
    apply_server_side_encryption_by_default {
      kms_master_key_id = aws_kms_key.s3.arn
      sse_algorithm     = "aws:kms"
    }
  }
}

resource "aws_s3_bucket_versioning" "secure" {
  bucket = aws_s3_bucket.secure.id
  versioning_configuration {
    status     = "Enabled"
    mfa_delete = "Enabled"
  }
}

resource "aws_s3_bucket_policy" "secure_transport" {
  bucket = aws_s3_bucket.secure.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "DenyInsecureConnections"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:*"
        Resource = [
          aws_s3_bucket.secure.arn,
          "${aws_s3_bucket.secure.arn}/*"
        ]
        Condition = {
          Bool = {
            "aws:SecureTransport" = "false"
          }
        }
      }
    ]
  })
}
  • ✅ Block public access to S3 buckets
  • ✅ Enable S3 bucket encryption
  • ✅ Enable S3 bucket logging
  • ✅ Enable S3 bucket versioning
  • ✅ Configure S3 bucket MFA delete
  • ✅ Enforce secure transport policy

Section 4: Network Security

4.1 VPC Security

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "main-vpc"
  }
}

resource "aws_flow_log" "vpc_flow_log" {
  iam_role_arn    = aws_iam_role.flow_log.arn
  log_destination = aws_cloudwatch_log_group.vpc_flow_log.arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.main.id
}

resource "aws_cloudwatch_log_group" "vpc_flow_log" {
  name              = "/aws/vpc/flowlogs"
  retention_in_days = 30
  kms_key_id        = aws_kms_key.logs.arn
}

resource "aws_iam_role" "flow_log" {
  name = "flowlogsRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "vpc-flow-logs.amazonaws.com"
        }
      }
    ]
  })
}
  • ✅ Remove default VPCs
  • ✅ Restrict security group rules
  • ✅ Enable VPC Flow Logs
  • ✅ Use NACLs for additional security

4.2 Security Group Best Practices

resource "aws_security_group" "web" {
  name_prefix = "web-sg"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS from internet"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "web-security-group"
  }
}

Section 5: Compute Security

5.1 EC2 Instance Security

  • ✅ Disable unused services and ports
  • ✅ Enable detailed monitoring
  • ✅ Use IAM roles instead of access keys
  • ✅ Enable EBS encryption
  • ✅ Regular security patching

5.2 EBS Encryption by Default

resource "aws_ebs_encryption_by_default" "enable" {
  enabled = true
}

resource "aws_ebs_default_kms_key" "main" {
  key_id = aws_kms_key.ebs.arn
}

resource "aws_kms_key" "ebs" {
  description             = "KMS key for EBS encryption"
  deletion_window_in_days = 7
  enable_key_rotation     = true

  tags = {
    Name = "ebs-encryption-key"
  }
}

resource "aws_kms_alias" "ebs" {
  name          = "alias/ebs-encryption"
  target_key_id = aws_kms_key.ebs.key_id
}

Section 6: Database Security

6.1 RDS Security

resource "aws_db_instance" "secure" {
  identifier = "secure-database"
  
  engine         = "postgres"
  engine_version = "14.9"
  instance_class = "db.t3.micro"
  
  allocated_storage     = 20
  max_allocated_storage = 100
  storage_type          = "gp3"
  storage_encrypted     = true
  kms_key_id           = aws_kms_key.rds.arn
  
  db_name  = "securedb"
  username = "dbadmin"
  password = var.db_password
  
  backup_retention_period = 30
  backup_window          = "03:00-04:00"
  maintenance_window     = "sun:04:00-sun:05:00"
  
  multi_az = true
  
  enabled_cloudwatch_logs_exports = ["postgresql"]
  monitoring_interval             = 60
  monitoring_role_arn            = aws_iam_role.rds_enhanced_monitoring.arn
  
  publicly_accessible = false
  
  vpc_security_group_ids = [aws_security_group.rds.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name
  
  deletion_protection = true
  
  tags = {
    Environment = "production"
    Compliance  = "CIS"
  }
}
  • ✅ Enable RDS encryption at rest
  • ✅ Enable RDS backup encryption
  • ✅ Disable public accessibility
  • ✅ Enable RDS logging
  • ✅ Use SSL/TLS for connections
  • ✅ Set backup retention to 30 days
  • ✅ Enable Multi-AZ deployment

Automated Compliance Checking

AWS Config Rules

resource "aws_config_config_rule" "root_mfa_enabled" {
  name = "root-mfa-enabled"

  source {
    owner             = "AWS"
    source_identifier = "ROOT_MFA_ENABLED"
  }

  depends_on = [aws_config_configuration_recorder.main]
}

resource "aws_config_config_rule" "s3_bucket_public_read_prohibited" {
  name = "s3-bucket-public-read-prohibited"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_READ_PROHIBITED"
  }

  depends_on = [aws_config_configuration_recorder.main]
}

Security Hub Integration

resource "aws_securityhub_account" "main" {}

resource "aws_securityhub_standards_subscription" "cis" {
  standards_arn = "arn:aws:securityhub:::ruleset/finding-format/aws-foundational-security-standard/v/1.0.0"
  depends_on    = [aws_securityhub_account.main]
}

resource "aws_securityhub_standards_subscription" "aws_foundational" {
  standards_arn = "arn:aws:securityhub:${data.aws_region.current.name}::standard/aws-foundational-security-standard/v/1.0.0"
  depends_on    = [aws_securityhub_account.main]
}

Compliance Monitoring Script

#!/bin/bash

# CIS Compliance Check Script
echo "🔍 Running CIS Compliance Checks..."

# Check 1: Root account MFA
echo "Checking root account MFA..."
root_mfa=$(aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled' --output text)
if [ "$root_mfa" == "1" ]; then
    echo "✅ Root MFA enabled"
else
    echo "❌ Root MFA not enabled"
fi

# Check 2: Password policy
echo "Checking password policy..."
min_length=$(aws iam get-account-password-policy --query 'PasswordPolicy.MinimumPasswordLength' --output text 2>/dev/null)
if [ "$min_length" -ge "14" ]; then
    echo "✅ Password policy compliant"
else
    echo "❌ Password policy needs update"
fi

# Check 3: CloudTrail enabled
echo "Checking CloudTrail..."
trails=$(aws cloudtrail describe-trails --query 'trailList[?IsMultiRegionTrail==`true`]' --output text)
if [ -n "$trails" ]; then
    echo "✅ Multi-region CloudTrail enabled"
else
    echo "❌ Multi-region CloudTrail not configured"
fi

# Check 4: Default VPCs
echo "Checking for default VPCs..."
default_vpcs=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query 'Vpcs[*].VpcId' --output text)
if [ -z "$default_vpcs" ]; then
    echo "✅ No default VPCs found"
else
    echo "❌ Default VPCs exist: $default_vpcs"
fi

echo "🏁 CIS compliance check completed"

Remediation Automation

resource "aws_lambda_function" "cis_remediation" {
  filename         = "cis_remediation.zip"
  function_name    = "cis-auto-remediation"
  role            = aws_iam_role.lambda_role.arn
  handler         = "index.handler"
  runtime         = "python3.9"
  timeout         = 300

  environment {
    variables = {
      SNS_TOPIC = aws_sns_topic.security_alerts.arn
    }
  }
}

resource "aws_cloudwatch_event_rule" "config_compliance" {
  name        = "config-compliance-change"
  description = "Trigger remediation on compliance changes"

  event_pattern = jsonencode({
    source      = ["aws.config"]
    detail-type = ["Config Rules Compliance Change"]
    detail = {
      newEvaluationResult = {
        complianceType = ["NON_COMPLIANT"]
      }
    }
  })
}

resource "aws_cloudwatch_event_target" "lambda" {
  rule      = aws_cloudwatch_event_rule.config_compliance.name
  target_id = "TriggerRemediation"
  arn       = aws_lambda_function.cis_remediation.arn
}

CIS Compliance Checklist Summary

Identity and Access Management

  • ✅ Root account security and MFA
  • ✅ IAM password policy
  • ✅ MFA for all users
  • ✅ Access key rotation
  • ✅ Unused credentials removal

Logging and Monitoring

  • ✅ CloudTrail multi-region
  • ✅ CloudTrail log encryption
  • ✅ CloudWatch monitoring
  • ✅ Security alerts and notifications

Storage and Data Protection

  • ✅ S3 bucket security
  • ✅ EBS encryption
  • ✅ RDS encryption
  • ✅ Data backup and retention

Network Security

  • ✅ VPC security configuration
  • ✅ Security group restrictions
  • ✅ Network ACLs
  • ✅ VPC Flow Logs

Conclusion

Implementing CIS compliance is an ongoing process that requires continuous monitoring, regular audits, and automated remediation. The checklist and automation scripts provided here will help you establish a strong security foundation and maintain compliance over time.

Remember that compliance is not a one-time achievement but a continuous journey. Regular reviews, updates to security policies, and staying current with the latest CIS benchmark updates are essential for maintaining a secure AWS environment.