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.