CIS Amazon Web Services Foundations Benchmark

Secure configuration guidelines for AWS cloud platform

v4.0.1 March 2025

Overview

▶

This CIS Benchmark provides prescriptive guidance for configuring security options for a subset of Amazon Web Services with an emphasis on foundational, testable, and architecture-agnostic settings.

~100Recommendations
5Sections
2Profile Levels
Scope: Covers AWS account-level settings and foundational services (IAM, S3, CloudTrail, CloudWatch, VPC). Application workloads and service-specific benchmarks are addressed in separate CIS documents.
SectionAreaFocus
1Identity and Access ManagementRoot account, IAM users/roles, MFA, access keys, credential reports
2StorageS3 bucket policies, encryption, public access, EBS volumes
3LoggingCloudTrail, S3 access logging, CloudWatch Logs
4MonitoringCloudWatch metric filters and alarms for security events
5NetworkingVPC, security groups, NACLs, flow logs

Profile Definitions

▶
ProfileDescriptionIntended Use
L1Level 1Practical security baseline for all AWS accounts. May cause minimal service impact.
L2Level 2Defense-in-depth for security-sensitive environments. May affect cost or complexity.

1 — Identity and Access Management

▶

AWS IAM configuration covering root account security, user policies, MFA, access keys, and credential hygiene.

1.1 Root Account

▶
1.1.1 Ensure MFA is Enabled for the Root User Account (Automated)
L1 Auto
Description

The root user has unrestricted access to all AWS resources. Enable hardware or virtual MFA for the root account to add a second layer of authentication.

Rationale

Failure to mFA is Enabled for the Root User Account may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'
# Should return 1
Remediation

Sign in as root > IAM Dashboard > Security credentials > Multi-factor authentication (MFA) > Assign MFA device. Use a hardware/FIDO2 key for maximum security.

CIS Controls
ControlDescriptionIG
6.5Require MFA for Administrative AccessIG1
1.1.2 Ensure No Root User Access Keys Exist (Automated)
L1 Auto
Description

The root account should not have access keys. Access keys provide programmatic access and, when associated with the root account, grant unrestricted access to all resources. Use IAM users or roles instead.

Rationale

The absence of No Root User Access Keys leaves the AWS cloud platform without an important security control. Verifying its presence ensures the system meets the minimum security baseline required by the CIS benchmark.

Audit
aws iam get-account-summary --query 'SummaryMap.AccountAccessKeysPresent'
# Should return 0
Remediation

Delete all root access keys: IAM Console > Root security credentials > Access keys > Delete.

1.1.3 Ensure Hardware MFA is Enabled for the Root Account (Automated)
L2 Auto
Description

Use a hardware MFA device (FIDO2 security key or hardware TOTP token) for the root account rather than a virtual MFA application. Hardware tokens are resistant to phishing and SIM-swap attacks.

Rationale

Failure to hardware MFA is Enabled for the Root Account may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws iam list-virtual-mfa-devices --query 'VirtualMFADevices[?SerialNumber==`arn:aws:iam::<account-id>:mfa/root-account-mfa-device`]'
# Should return empty (no virtual MFA for root = hardware is used)
Remediation

Replace virtual MFA with a FIDO2 hardware security key for the root account.

1.2 IAM Users & Policies

▶
1.2.1 Ensure IAM Password Policy Requires Minimum Length of 14 or Greater (Automated)
L1 Auto
Description

Set the IAM password policy minimum length to 14 characters or more. Longer passwords provide significantly more entropy and resistance to brute-force attacks.

Rationale

Failure to iAM Password Policy Requires Minimum Length of 14 or Greater may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws iam get-account-password-policy --query 'PasswordPolicy.MinimumPasswordLength'
# Should be 14 or greater
Remediation
aws iam update-account-password-policy --minimum-password-length 14
1.2.2 Ensure IAM Policies Are Not Attached Directly to Users (Automated)
L1 Auto
Description

Assign IAM policies to groups or roles, not directly to users. This simplifies access management and ensures consistent permission assignment.

Rationale

Failure to iAM Policies Are Not Attached Directly to Users may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws iam list-users --query 'Users[*].UserName' --output text | while read user; do
  policies=$(aws iam list-attached-user-policies --user-name "$user" --query 'AttachedPolicies')
  inline=$(aws iam list-user-policies --user-name "$user" --query 'PolicyNames')
  echo "$user: attached=$policies inline=$inline"
done
# Users should have no directly attached policies
Remediation

Create IAM groups, attach policies to groups, and add users to appropriate groups. Remove direct user policy attachments.

1.2.3 Ensure MFA is Enabled for All IAM Users with Console Access (Automated)
L1 Auto
Description

All IAM users with console (password) access must have MFA enabled. This protects against credential compromise from phishing, keylogging, or password reuse.

Rationale

Failure to mFA is Enabled for All IAM Users with Console Access may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws iam generate-credential-report > /dev/null 2>&1
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
  awk -F, '$4=="true" && $8=="false" {print $1, "MFA_NOT_ENABLED"}'
# Should return no results
Remediation

Each user should enable MFA via IAM Console > Users > Security credentials > Assign MFA device.

1.3 Access Keys & Credentials

▶
1.3.1 Ensure Access Keys Are Rotated Every 90 Days or Less (Automated)
L1 Auto
Description

Access keys should be rotated at least every 90 days to reduce the window of exposure if keys are compromised. Consider using IAM roles with temporary credentials instead of long-lived access keys.

Rationale

Failure to access Keys Are Rotated Every 90 Days or Less may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws iam generate-credential-report > /dev/null 2>&1
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
  awk -F, 'NR>1 && $9=="true" {split($10,d,"T"); print $1, d[1]}' | \
  while read user date; do
    age=$(( ($(date +%s) - $(date -d "$date" +%s)) / 86400 ))
    [[ $age -gt 90 ]] && echo "$user: key1 age=${age}d EXCEEDS 90 DAYS"
  done
Remediation
# Create new key, update applications, then deactivate old key
aws iam create-access-key --user-name <user>
# After updating apps:
aws iam update-access-key --user-name <user> --access-key-id <old-key> --status Inactive
aws iam delete-access-key --user-name <user> --access-key-id <old-key>
1.3.2 Ensure Unused Credentials Are Disabled After 45 Days (Automated)
L1 Auto
Description

Disable IAM user credentials (password and access keys) that have not been used within 45 days. Unused credentials are an attack vector for credential compromise.

Rationale

Failure to unused Credentials Are Disabled After 45 Days may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
  awk -F, 'NR>1 {print $1, "pwd_last_used:" $5, "key1_last_used:" $11, "key2_last_used:" $16}'
Remediation
# Disable console access
aws iam delete-login-profile --user-name <user>
# Deactivate unused access key
aws iam update-access-key --user-name <user> --access-key-id <key> --status Inactive

2 — Storage

▶

Configuration of AWS storage services including S3 bucket security and EBS volume encryption.

2.1 S3 Buckets

▶
2.1.1 Ensure S3 Bucket Policy Does Not Grant Public Access (Automated)
L1 Auto
Description

S3 bucket policies should not allow public access (Principal: "*") unless the bucket is explicitly intended for public content. Enable S3 Block Public Access at the account level.

Rationale

Failure to s3 Bucket Policy Does Not Grant Public Access may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
# Check account-level block public access
aws s3control get-public-access-block --account-id <account-id>
# All four settings should be true:
# BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets

# Check per-bucket
aws s3api get-bucket-policy-status --bucket <bucket> --query 'PolicyStatus.IsPublic'
# Should be false
Remediation
aws s3control put-public-access-block --account-id <account-id> \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
2.1.2 Ensure S3 Bucket Server-Side Encryption is Enabled (Automated)
L1 Auto
Description

Enable default server-side encryption (SSE-S3 or SSE-KMS) for all S3 buckets. As of January 2023, S3 encrypts all new objects by default with SSE-S3, but custom KMS keys provide additional control.

Rationale

Without S3 Bucket Server-Side Encryption enabled, the AWS cloud platform may lack critical protections against known attack vectors. Enabling this control mitigates risk and aligns the deployment with industry-accepted security baselines.

Audit
aws s3api get-bucket-encryption --bucket <bucket> \
  --query 'ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault'
Remediation
aws s3api put-bucket-encryption --bucket <bucket> \
  --server-side-encryption-configuration '{
    "Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "aws:kms"}}]
  }'
2.1.3 Ensure S3 Bucket Versioning is Enabled (Automated)
L1 Auto
Description

Enable versioning on S3 buckets to preserve, retrieve, and restore every version of every object. This protects against accidental deletion and enables recovery from application failures.

Rationale

Without S3 Bucket Versioning enabled, the AWS cloud platform may lack critical protections against known attack vectors. Enabling this control mitigates risk and aligns the deployment with industry-accepted security baselines.

Audit
aws s3api get-bucket-versioning --bucket <bucket> --query 'Status'
# Should be "Enabled"
Remediation
aws s3api put-bucket-versioning --bucket <bucket> \
  --versioning-configuration Status=Enabled
2.1.4 Ensure MFA Delete is Enabled on S3 Buckets (Manual)
L2
Description

Enable MFA Delete on S3 buckets so that an additional MFA authentication is required to permanently delete object versions or change versioning state. This protects against accidental or malicious deletion.

Rationale

Failure to mFA Delete is Enabled on S3 Buckets may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws s3api get-bucket-versioning --bucket <bucket> --query 'MFADelete'
# Should be "Enabled"
Remediation

MFA Delete can only be enabled by the root account. Use the root user with MFA to enable it: aws s3api put-bucket-versioning --bucket <bucket> --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa 'arn:aws:iam::<account>:mfa/root-account-mfa-device <mfa-code>'

2.2 EBS Volumes

▶
2.2.1 Ensure EBS Volume Encryption is Enabled by Default (Automated)
L1 Auto
Description

Enable EBS encryption by default in each region to ensure all new EBS volumes and snapshots are encrypted automatically without requiring per-volume configuration.

Rationale

Failure to eBS Volume Encryption is Enabled by Default may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws ec2 get-ebs-encryption-by-default --query 'EbsEncryptionByDefault'
# Should be true (check each region)
Remediation
aws ec2 enable-ebs-encryption-by-default
# Optionally set a custom KMS key:
aws ec2 modify-ebs-default-kms-key-id --kms-key-id <key-arn>

3 — Logging

▶

AWS logging configuration covering CloudTrail, S3 access logs, and CloudWatch integration.

3.1 CloudTrail

▶
3.1.1 Ensure CloudTrail is Enabled in All Regions (Automated)
L1 Auto
Description

CloudTrail should be configured as a multi-region trail to capture API calls across all AWS regions. This ensures visibility into activity in regions not normally used, which could indicate unauthorized access.

Rationale

Failure to cloudTrail is Enabled in All Regions may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws cloudtrail describe-trails --query 'trailList[*].{Name:Name, IsMultiRegion:IsMultiRegionTrail, IsOrg:IsOrganizationTrail}'
# At least one trail should have IsMultiRegionTrail=true

aws cloudtrail get-trail-status --name <trail> --query 'IsLogging'
# Should be true
Remediation
aws cloudtrail update-trail --name <trail> --is-multi-region-trail
3.1.2 Ensure CloudTrail Log File Validation is Enabled (Automated)
L1 Auto
Description

Enable log file validation to ensure CloudTrail logs have not been tampered with after delivery. CloudTrail creates a hash (digest file) for every log file, enabling tamper detection.

Rationale

Without CloudTrail Log File Validation enabled, the AWS cloud platform may lack critical protections against known attack vectors. Enabling this control mitigates risk and aligns the deployment with industry-accepted security baselines.

Audit
aws cloudtrail describe-trails --query 'trailList[*].{Name:Name, LogFileValidation:LogFileValidationEnabled}'
# Should be true for all trails
Remediation
aws cloudtrail update-trail --name <trail> --enable-log-file-validation
3.1.3 Ensure CloudTrail Logs are Encrypted at Rest Using KMS CMKs (Automated)
L2 Auto
Description

Configure CloudTrail to encrypt log files using a customer-managed KMS key (CMK). This provides an additional layer of protection and allows key access auditing through CloudTrail and KMS logs.

Rationale

Without encryption, CloudTrail Logs may transmit or store sensitive information in cleartext, exposing it to interception, eavesdropping, or tampering. Encryption is a critical control for data confidentiality and integrity.

Audit
aws cloudtrail describe-trails --query 'trailList[*].{Name:Name, KmsKeyId:KmsKeyId}'
# KmsKeyId should be set
Remediation
aws cloudtrail update-trail --name <trail> --kms-key-id <kms-key-arn>

3.2 CloudWatch

▶
3.2.1 Ensure CloudTrail is Integrated with CloudWatch Logs (Automated)
L1 Auto
Description

Configure CloudTrail to deliver log events to a CloudWatch Logs group. This enables real-time analysis, metric filters, and alarms based on API activity.

Rationale

Failure to cloudTrail is Integrated with CloudWatch Logs may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws cloudtrail describe-trails --query 'trailList[*].{Name:Name, CWLogGroup:CloudWatchLogsLogGroupArn}'
# CloudWatchLogsLogGroupArn should be set

aws cloudtrail get-trail-status --name <trail> --query 'LatestCloudWatchLogsDeliveryTime'
# Should be recent (within last 24 hours)
Remediation
aws cloudtrail update-trail --name <trail> \
  --cloud-watch-logs-log-group-arn <log-group-arn> \
  --cloud-watch-logs-role-arn <role-arn>

4 — Monitoring

▶

CloudWatch metric filters and alarms for detecting unauthorized or security-relevant API activity.

4.1 Metric Filters & Alarms

▶
4.1.1 Ensure a Log Metric Filter and Alarm Exist for Unauthorized API Calls (Automated)
L1 Auto
Description

Create a metric filter and alarm to detect unauthorized API calls (AccessDenied, UnauthorizedAccess). This can indicate misconfigured applications, credential probing, or attempted lateral movement.

Rationale

Failure to a Log Metric Filter and Alarm Exist for Unauthorized API Calls may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws logs describe-metric-filters --log-group-name <cloudtrail-log-group> \
  --query 'metricFilters[?filterPattern!=`null`].{Name:filterName, Pattern:filterPattern}'
# Look for filter pattern containing:
#   { ($.errorCode = "*UnauthorizedAccess*") || ($.errorCode = "AccessDenied*") }
Remediation

Create a metric filter on the CloudTrail log group with the appropriate filter pattern, then create a CloudWatch alarm that triggers an SNS notification.

4.1.2 Ensure a Log Metric Filter and Alarm Exist for Console Sign-In Without MFA (Automated)
L1 Auto
Description

Create a metric filter and alarm to detect AWS Console sign-in events without MFA. This detects potential credential compromise or MFA policy bypass.

Rationale

Failure to a Log Metric Filter and Alarm Exist for Console Sign-In Without MFA may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
# Look for metric filter with pattern:
#   { ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }
aws logs describe-metric-filters --log-group-name <cloudtrail-log-group>
Remediation

Create a metric filter, publish a custom metric, and create a CloudWatch alarm linked to an SNS topic for notification.

4.1.3 Ensure a Log Metric Filter and Alarm Exist for Root Account Usage (Automated)
L1 Auto
Description

Create a metric filter and alarm for root account usage. The root account should rarely be used and any activity should be investigated promptly.

Rationale

Failure to a Log Metric Filter and Alarm Exist for Root Account Usage may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
# Look for metric filter with pattern:
#   { $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }
aws logs describe-metric-filters --log-group-name <cloudtrail-log-group>
Remediation

Create the metric filter, custom metric, CloudWatch alarm, and SNS notification for root account usage.

4.1.4 Ensure a Log Metric Filter and Alarm Exist for IAM Policy Changes (Automated)
L1 Auto
Description

Monitor IAM policy changes including CreatePolicy, DeletePolicy, AttachUserPolicy, DetachUserPolicy, AttachRolePolicy, DetachRolePolicy, AttachGroupPolicy, DetachGroupPolicy, and PutGroupPolicy events.

Rationale

Failure to a Log Metric Filter and Alarm Exist for IAM Policy Changes may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
# Look for filter pattern covering IAM policy change events:
#   { ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || 
#     ($.eventName=AttachRolePolicy) || ... }
Remediation

Create a comprehensive metric filter for all IAM policy change API calls and attach an alarm.

5 — Networking

▶

VPC, security group, and network ACL configuration for secure AWS networking.

5.1 VPC & Security Groups

▶
5.1.1 Ensure No Security Group Allows Ingress from 0.0.0.0/0 to Port 22 (Automated)
L1 Auto
Description

No security group should allow unrestricted SSH access (0.0.0.0/0 to port 22). Restrict SSH to known IP ranges or use AWS Systems Manager Session Manager for shell access.

Rationale

Failure to no Security Group Allows Ingress from 0.0.0.0/0 to Port 22 may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws ec2 describe-security-groups --filters \
  Name=ip-permission.from-port,Values=22 \
  Name=ip-permission.to-port,Values=22 \
  Name=ip-permission.cidr,Values='0.0.0.0/0' \
  --query 'SecurityGroups[*].{ID:GroupId, Name:GroupName}'
# Should return empty
Remediation
aws ec2 revoke-security-group-ingress --group-id <sg-id> \
  --protocol tcp --port 22 --cidr 0.0.0.0/0
5.1.2 Ensure No Security Group Allows Ingress from 0.0.0.0/0 to Port 3389 (Automated)
L1 Auto
Description

No security group should allow unrestricted RDP access (0.0.0.0/0 to port 3389). Restrict RDP to specific IP ranges or use Systems Manager for remote access.

Rationale

Failure to no Security Group Allows Ingress from 0.0.0.0/0 to Port 3389 may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws ec2 describe-security-groups --filters \
  Name=ip-permission.from-port,Values=3389 \
  Name=ip-permission.to-port,Values=3389 \
  Name=ip-permission.cidr,Values='0.0.0.0/0' \
  --query 'SecurityGroups[*].{ID:GroupId, Name:GroupName}'
# Should return empty
Remediation
aws ec2 revoke-security-group-ingress --group-id <sg-id> \
  --protocol tcp --port 3389 --cidr 0.0.0.0/0
5.1.3 Ensure the Default Security Group Restricts All Traffic (Automated)
L2 Auto
Description

The default security group in every VPC should be configured to restrict all inbound and outbound traffic. Resources should use custom security groups with specific rules.

Rationale

Failure to the Default Security Group Restricts All Traffic may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
aws ec2 describe-security-groups --filters Name=group-name,Values='default' \
  --query 'SecurityGroups[*].{VPC:VpcId, Ingress:IpPermissions, Egress:IpPermissionsEgress}'
# Both Ingress and Egress should be empty lists
Remediation

Remove all inbound and outbound rules from the default security group in each VPC.

5.2 VPC Flow Logs

▶
5.2.1 Ensure VPC Flow Logging is Enabled in All VPCs (Automated)
L2 Auto
Description

Enable VPC Flow Logs for all VPCs to capture IP traffic information. Flow logs are critical for network monitoring, troubleshooting, and security analysis.

Rationale

Failure to vPC Flow Logging is Enabled in All VPCs may leave the AWS cloud platform vulnerable to attack or non-compliant with organizational security policies. This control helps establish a consistent, hardened configuration baseline.

Audit
# List all VPCs
aws ec2 describe-vpcs --query 'Vpcs[*].VpcId' --output text | tr '\t' '\n' | while read vpc; do
  flowlogs=$(aws ec2 describe-flow-logs --filter "Name=resource-id,Values=$vpc" --query 'FlowLogs[0].FlowLogId' --output text)
  echo "$vpc: $flowlogs"
done
# Each VPC should have at least one flow log
Remediation
aws ec2 create-flow-logs --resource-type VPC --resource-ids <vpc-id> \
  --traffic-type ALL --log-destination-type cloud-watch-logs \
  --log-group-name vpc-flow-logs --deliver-logs-permission-arn <role-arn>