---
# === IDENTITY ===
id: software/security/aws-security-checklist/2026
canonical_question: "What is the AWS security checklist?"
aliases:
  - "AWS security best practices checklist"
  - "AWS cloud security hardening guide"
  - "secure AWS account configuration"
  - "AWS IAM S3 EC2 security checklist"
  - "CIS AWS Foundations Benchmark checklist"
  - "AWS Security Hub best practices"
  - "AWS Well-Architected security pillar checklist"
  - "how to secure my AWS account"
entity_type: software_reference
domain: software > security > AWS Security Checklist
region: global
jurisdiction: global
temporal_scope: 2025-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.93
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "CIS AWS Foundations Benchmark v3.0.0 (2024); AWS SSE-S3 default encryption for new buckets (Jan 2023); S3 Block Public Access default for new buckets (Apr 2023); IAM Access Analyzer expanded to external access (2024)"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER use AWS root account for daily operations -- enable MFA and lock it away"
  - "NEVER embed long-lived access keys in code, environment variables, or config files -- use IAM roles with temporary credentials"
  - "NEVER allow wildcard (*) Actions or Resources in IAM policies for production workloads"
  - "NEVER create S3 buckets without Block Public Access enabled at account level"
  - "CloudTrail MUST be enabled in all regions with log file validation and KMS encryption"
  - "Security Group rules MUST follow least-privilege -- no 0.0.0.0/0 ingress except on port 443 for public-facing load balancers"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need Azure or GCP security guidance, not AWS"
    use_instead: "software/security/azure-security-checklist/2026 or software/security/gcp-security-checklist/2026"
  - condition: "Need application-level security (XSS, SQLi, CSRF) not cloud infrastructure"
    use_instead: "software/security/xss-prevention/2026"
  - condition: "Need compliance-specific controls (HIPAA, PCI-DSS, SOC 2) not general AWS security"
    use_instead: "software/security/aws-compliance-frameworks/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "aws_service"
    question: "Which AWS service do you need to secure?"
    type: choice
    options: ["IAM", "S3", "EC2", "RDS", "Lambda", "VPC", "CloudTrail", "All services"]
  - key: "iac_tool"
    question: "What Infrastructure-as-Code tool are you using?"
    type: choice
    options: ["Terraform", "CloudFormation", "CDK", "Pulumi", "Console/CLI only"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/aws-security-checklist/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention Guide"
  solves: []
  alternative_to: []
  often_confused_with: []

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Security Pillar - AWS Well-Architected Framework"
    author: Amazon Web Services
    url: https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src2
    title: "CIS Amazon Web Services Foundations Benchmark"
    author: Center for Internet Security
    url: https://www.cisecurity.org/benchmark/amazon_web_services
    type: industry_report
    published: 2024-09-01
    reliability: authoritative
  - id: src3
    title: "AWS Foundational Security Best Practices Standard"
    author: Amazon Web Services
    url: https://docs.aws.amazon.com/securityhub/latest/userguide/fsbp-standard.html
    type: official_docs
    published: 2025-03-01
    reliability: authoritative
  - id: src4
    title: "Security Best Practices in AWS CloudTrail"
    author: Amazon Web Services
    url: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/best-practices-security.html
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src5
    title: "Security Best Practices for Amazon S3"
    author: Amazon Web Services
    url: https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-best-practices.html
    type: official_docs
    published: 2025-04-01
    reliability: authoritative
  - id: src6
    title: "Prowler - Open Source Cloud Security Platform"
    author: Prowler
    url: https://github.com/prowler-cloud/prowler
    type: community_resource
    published: 2025-10-01
    reliability: high
  - id: src7
    title: "Amazon GuardDuty Best Practices"
    author: Amazon Web Services
    url: https://aws.github.io/aws-security-services-best-practices/guides/guardduty/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
---

# AWS Security Checklist: Complete Cloud Infrastructure Hardening Guide

## TL;DR

- **Bottom line**: Secure AWS accounts by locking the root user with MFA, enforcing IAM least-privilege with roles (not long-lived keys), enabling CloudTrail + GuardDuty + Security Hub across all regions, blocking public S3 at the account level, and encrypting everything with KMS.
- **Key tool/command**: `aws securityhub enable-security-hub --enable-default-standards` enables Security Hub with CIS and FSBP standards in one command; run `prowler aws` for a comprehensive open-source audit.
- **Watch out for**: Wildcard IAM policies (`"Action": "*", "Resource": "*"`) -- the #1 root cause of privilege escalation in AWS. 83% of organizations experienced at least one cloud security incident in 2025, with 23% caused by misconfigurations.
- **Works with**: All AWS regions, all account types (standalone, AWS Organizations). Terraform AWS Provider 5.x, CloudFormation, CDK 2.x. CIS Benchmark v3.0.0, AWS Security Hub FSBP.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER use AWS root account for daily operations -- enable MFA and lock it away
- NEVER embed long-lived access keys in code, environment variables, or config files -- use IAM roles with temporary credentials
- NEVER allow wildcard (*) Actions or Resources in IAM policies for production workloads
- NEVER create S3 buckets without Block Public Access enabled at account level
- CloudTrail MUST be enabled in all regions with log file validation and KMS encryption
- Security Group rules MUST follow least-privilege -- no 0.0.0.0/0 ingress except on port 443 for public-facing load balancers

## Quick Reference

| # | AWS Service | Security Control | Risk if Missing | Config/Command |
|---|---|---|---|---|
| 1 | IAM | Enable MFA on root account | Full account takeover | Console > IAM > Root > Assign MFA |
| 2 | IAM | Use IAM roles, not long-lived access keys | Credential leaks, lateral movement | `aws iam create-role --assume-role-policy-document` |
| 3 | IAM | Enforce least-privilege policies (no wildcards) | Privilege escalation | IAM Access Analyzer + `aws accessanalyzer` |
| 4 | IAM | Enable IAM Access Analyzer | Undetected public/cross-account access | `aws accessanalyzer create-analyzer --type ACCOUNT` |
| 5 | S3 | Block Public Access at account level | Data breaches (most common AWS misconfiguration) | `aws s3control put-public-access-block --account-id` |
| 6 | S3 | Enable default SSE-KMS encryption | Data exposure at rest | `aws s3api put-bucket-encryption` |
| 7 | S3 | Disable ACLs (use bucket policies only) | Complex, error-prone permissions | `BucketOwnerEnforced` ownership setting |
| 8 | EC2 | Encrypt all EBS volumes by default | Data exposure if disk stolen/shared | `aws ec2 enable-ebs-encryption-by-default` |
| 9 | EC2 | Use private subnets for backends | Direct internet exposure | VPC with public/private subnet architecture |
| 10 | RDS | Enable encryption at rest and in transit | Database data exposure | `--storage-encrypted --kms-key-id` |
| 11 | RDS | Deploy in private subnets, no public access | Database internet exposure | `--no-publicly-accessible` |
| 12 | Lambda | Use IAM roles with minimal permissions | Over-privileged function compromise | Per-function execution role |
| 13 | Lambda | Store secrets in Secrets Manager, not env vars | Credential exposure in console/logs | `aws secretsmanager get-secret-value` |
| 14 | VPC | Enable VPC Flow Logs | No network visibility for forensics | `aws ec2 create-flow-logs` |
| 15 | VPC | Restrict security groups (no 0.0.0.0/0 ingress) | Unauthorized access from internet | `aws ec2 describe-security-groups` audit |
| 16 | CloudTrail | Enable multi-region trail with log validation | No audit trail for incident response | `aws cloudtrail create-trail --is-multi-region` |
| 17 | GuardDuty | Enable in all regions | No threat detection | `aws guardduty create-detector --enable` |
| 18 | Security Hub | Enable with CIS + FSBP standards | No centralized security posture view | `aws securityhub enable-security-hub` |
| 19 | KMS | Use CMKs with automatic rotation | Non-compliant encryption, manual key management | `aws kms enable-key-rotation` |
| 20 | Organizations | Use SCPs to block dangerous actions | No preventive guardrails | `aws organizations create-policy --type SERVICE_CONTROL_POLICY` |

## Decision Tree

```
START: What AWS security area needs attention?
├── Account-level security?
│   ├── YES → Lock root account with MFA, enable Organizations + SCPs, enable Security Hub
│   └── NO ↓
├── Identity & Access (IAM)?
│   ├── YES → Use IAM Identity Center (SSO), roles not keys, least-privilege, Access Analyzer
│   └── NO ↓
├── Data storage (S3/EBS/RDS)?
│   ├── YES → Block Public Access (S3), encrypt at rest (KMS), encrypt in transit (TLS 1.2+)
│   └── NO ↓
├── Compute (EC2/Lambda)?
│   ├── YES → Private subnets, security groups, per-function IAM roles, Secrets Manager
│   └── NO ↓
├── Network (VPC)?
│   ├── YES → VPC Flow Logs, restrict security groups, use VPC endpoints for AWS services
│   └── NO ↓
├── Monitoring & Detection?
│   ├── YES → CloudTrail (all regions), GuardDuty, Security Hub, Config Rules
│   └── NO ↓
├── Compliance audit?
│   ├── YES → Run Prowler, enable CIS Benchmark in Security Hub, AWS Config conformance packs
│   └── NO ↓
└── DEFAULT → Start with IAM + CloudTrail + GuardDuty + S3 Block Public Access (highest impact)
```

## Step-by-Step Guide

### 1. Lock down the root account

The root account has unrestricted access to all resources. Enable hardware MFA, delete root access keys, and never use root for daily operations. [src1]

```bash
# Check if root account has MFA enabled
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'

# Check for root access keys (should return empty)
aws iam list-access-keys --user-name root 2>/dev/null || echo "Use Console to check root keys"

# Enable virtual MFA (hardware MFA preferred -- use Console)
# Console: IAM > Dashboard > Security recommendations > Root user MFA
```

**Verify**: `aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'` --> expected output: `1`

### 2. Enforce IAM least-privilege with Access Analyzer

Enable IAM Access Analyzer to detect public and cross-account access, then audit all policies for wildcard permissions. [src2]

```bash
# Create IAM Access Analyzer
aws accessanalyzer create-analyzer \
  --analyzer-name account-analyzer \
  --type ACCOUNT

# List all findings (public/cross-account access)
aws accessanalyzer list-findings \
  --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/account-analyzer

# Generate a policy based on actual access activity (90-day window)
aws accessanalyzer start-policy-generation \
  --policy-generation-details '{"principalArn":"arn:aws:iam::123456789012:role/MyRole"}'
```

**Verify**: `aws accessanalyzer list-findings --analyzer-arn <arn> --query 'findings[?status==ACTIVE]'` --> expected: empty list (no active findings)

### 3. Block public S3 access at account level

S3 is the most commonly misconfigured AWS service. Block public access at the account level to prevent any bucket from being accidentally exposed. [src5]

```bash
# Block public access for entire account
aws s3control put-public-access-block \
  --account-id $(aws sts get-caller-identity --query Account --output text) \
  --public-access-block-configuration \
    BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

# Verify account-level block
aws s3control get-public-access-block \
  --account-id $(aws sts get-caller-identity --query Account --output text)
```

**Verify**: All four settings return `true` in the output.

### 4. Enable CloudTrail with KMS encryption

Enable a multi-region trail with log file validation and KMS encryption for tamper-proof audit logging. [src4]

```bash
# Create KMS key for CloudTrail encryption
KEY_ID=$(aws kms create-key --description "CloudTrail encryption key" \
  --query 'KeyMetadata.KeyId' --output text)
aws kms enable-key-rotation --key-id $KEY_ID

# Create multi-region trail
aws cloudtrail create-trail \
  --name organization-trail \
  --s3-bucket-name my-cloudtrail-logs \
  --is-multi-region-trail \
  --enable-log-file-validation \
  --kms-key-id $KEY_ID

aws cloudtrail start-logging --name organization-trail
```

**Verify**: `aws cloudtrail get-trail-status --name organization-trail --query 'IsLogging'` --> expected: `true`

### 5. Enable GuardDuty and Security Hub

GuardDuty provides continuous threat detection; Security Hub aggregates findings from GuardDuty, Inspector, Access Analyzer, and Config into a single dashboard. [src3] [src7]

```bash
# Enable GuardDuty
aws guardduty create-detector --enable --finding-publishing-frequency FIFTEEN_MINUTES

# Enable Security Hub with default standards (CIS + FSBP)
aws securityhub enable-security-hub --enable-default-standards

# List enabled standards
aws securityhub get-enabled-standards
```

**Verify**: `aws securityhub get-findings --query 'Findings[0:3].{Title:Title,Severity:Severity.Label}'` --> returns findings list (may take 24h for initial scan)

### 6. Encrypt EBS volumes by default

Enable default EBS encryption in every region to ensure all new volumes are automatically encrypted. [src1]

```bash
# Enable default EBS encryption in current region
aws ec2 enable-ebs-encryption-by-default

# Set a custom KMS key as default (optional)
aws ec2 modify-ebs-default-kms-key-id --kms-key-id $KEY_ID

# Verify
aws ec2 get-ebs-encryption-by-default
```

**Verify**: `aws ec2 get-ebs-encryption-by-default --query 'EbsEncryptionByDefault'` --> expected: `true`

### 7. Run Prowler for comprehensive audit

Prowler executes 240+ security checks based on CIS, NIST, PCI-DSS, HIPAA, and AWS best practices. [src6]

```bash
# Install Prowler
pip install prowler

# Run full AWS security assessment
prowler aws

# Run specific checks (e.g., CIS Benchmark only)
prowler aws --compliance cis_3.0_aws

# Generate HTML report
prowler aws -M html
```

**Verify**: Review the generated report in `output/` directory. Address all FAIL and HIGH severity findings first.

## Code Examples

### Terraform: Secure IAM Role with Least-Privilege Policy

> Full script: [terraform-secure-iam-role-with-least-privilege-pol.txt](scripts/terraform-secure-iam-role-with-least-privilege-pol.txt) (31 lines)

```hcl
# Input:  AWS account with Terraform configured
# Output: IAM role with minimal S3 read-only access and confused deputy protection
resource "aws_iam_role" "app_role" {
  name = "app-read-only"
  assume_role_policy = jsonencode({
# ... (see full script)
```

### Terraform: Secure S3 Bucket with Encryption and Public Access Block

> Full script: [terraform-secure-s3-bucket-with-encryption-and-pub.txt](scripts/terraform-secure-s3-bucket-with-encryption-and-pub.txt) (26 lines)

```hcl
# Input:  AWS account with Terraform configured
# Output: S3 bucket with encryption, versioning, public access block, and logging
resource "aws_s3_bucket" "secure_bucket" {
  bucket = "my-secure-data-bucket"
}
# ... (see full script)
```

### CloudFormation: VPC Security Group with Least-Privilege Ingress

> Full script: [cloudformation-vpc-security-group-with-least-privi.yml](scripts/cloudformation-vpc-security-group-with-least-privi.yml) (29 lines)

```yaml
# Input:  CloudFormation stack deployment
# Output: Security group allowing only HTTPS from specific CIDR, all egress
Resources:
  WebServerSG:
    Type: AWS::EC2::SecurityGroup
# ... (see full script)
```

### Terraform: Lambda Function with Minimal IAM and VPC Isolation

> Full script: [terraform-lambda-function-with-minimal-iam-and-vpc.txt](scripts/terraform-lambda-function-with-minimal-iam-and-vpc.txt) (63 lines)

```hcl
# Input:  AWS account with VPC and RDS configured
# Output: Lambda function in private subnet with Secrets Manager access
resource "aws_iam_role" "lambda_role" {
  name = "lambda-db-reader"
  assume_role_policy = jsonencode({
# ... (see full script)
```

## Anti-Patterns

### Wrong: Wildcard IAM policy

```json
// BAD -- grants full admin access to everything
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }]
}
```

### Correct: Scoped IAM policy with specific actions and resources

```json
// GOOD -- only the permissions the application actually needs
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:PutObject"],
    "Resource": "arn:aws:s3:::my-app-bucket/*"
  }]
}
```

### Wrong: S3 bucket with public ACL

```hcl
// BAD -- makes bucket contents publicly readable
resource "aws_s3_bucket_acl" "public" {
  bucket = aws_s3_bucket.data.id
  acl    = "public-read"
}
```

### Correct: S3 bucket with public access blocked

```hcl
// GOOD -- blocks all public access at bucket level
resource "aws_s3_bucket_public_access_block" "block" {
  bucket                  = aws_s3_bucket.data.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

### Wrong: Security group open to the world

```hcl
// BAD -- allows SSH from any IP address
resource "aws_security_group_rule" "ssh" {
  type              = "ingress"
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]  # Open to the entire internet
  security_group_id = aws_security_group.server.id
}
```

### Correct: Security group restricted to specific sources

```hcl
// GOOD -- SSH only from bastion host security group
resource "aws_security_group_rule" "ssh" {
  type                     = "ingress"
  from_port                = 22
  to_port                  = 22
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.bastion.id
  security_group_id        = aws_security_group.server.id
}
```

### Wrong: Hardcoded credentials in Lambda environment variables

```python
# BAD -- credentials visible in Lambda console, CloudWatch logs, and API responses
import os
DB_PASSWORD = os.environ['DB_PASSWORD']  # Stored as Lambda env var
```

### Correct: Retrieve secrets from AWS Secrets Manager at runtime

```python
# GOOD -- secrets never stored in code or environment variables
import boto3
import json

def get_db_credentials():
    client = boto3.client('secretsmanager')
    response = client.get_secret_value(SecretId='prod/db/credentials')
    return json.loads(response['SecretString'])
```

## Common Pitfalls

- **Root account used for daily work**: Root has unlimited access and cannot be restricted by IAM policies or SCPs. Fix: Create IAM users/roles, enable root MFA, delete root access keys. [src1]
- **Long-lived access keys in code repos**: Access keys committed to Git are scraped by bots within minutes. Fix: Use `git-secrets` pre-commit hook, rotate keys immediately if exposed, switch to IAM roles. [src2]
- **S3 bucket policies overriding account-level Block Public Access**: Individual bucket policies can still grant access if Block Public Access is only set at the bucket level, not the account level. Fix: Enable Block Public Access at the account level with `aws s3control put-public-access-block`. [src5]
- **CloudTrail only in one region**: Attackers operate in regions you are not monitoring. Fix: Create a multi-region trail with `--is-multi-region-trail`. [src4]
- **Security groups with 0.0.0.0/0 on SSH (port 22)**: Most common initial access vector for EC2 compromise. Fix: Restrict SSH to bastion host security group or use AWS Systems Manager Session Manager (no SSH needed). [src3]
- **RDS publicly accessible by default**: Some CloudFormation/Terraform defaults may leave `publicly_accessible = true`. Fix: Explicitly set `--no-publicly-accessible` and deploy in private subnets. [src2]
- **Lambda environment variables for secrets**: Environment variables are visible in the Lambda console, returned by `GetFunctionConfiguration` API, and may appear in CloudWatch logs. Fix: Use Secrets Manager with `GetSecretValue` at runtime. [src3]
- **KMS keys without rotation**: Manually managed keys that are never rotated increase the blast radius of a compromised key. Fix: Enable automatic rotation with `aws kms enable-key-rotation` (rotates annually). [src1]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (37 lines)

```bash
# Run comprehensive Prowler security assessment
prowler aws --compliance cis_3.0_aws -M json html
# Check IAM credential report (users, MFA, key age)
aws iam generate-credential-report && sleep 5
aws iam get-credential-report --query 'Content' --output text | base64 -d
# ... (see full script)
```

## Version History & Compatibility

| Standard/Tool | Version | Status | Key Changes |
|---|---|---|---|
| CIS AWS Foundations Benchmark | v3.0.0 | Current | Added IAM Access Analyzer checks, expanded S3 controls |
| CIS AWS Foundations Benchmark | v2.0.0 | Supported | Restructured sections, added Organizations controls |
| AWS Security Hub FSBP | 2025 | Current | 250+ controls across 30+ services |
| Prowler | 4.x | Current | Multi-cloud (AWS/Azure/GCP), 240+ AWS checks |
| Prowler | 3.x | Maintained | AWS-only, CIS v2.0 baseline |
| AWS Well-Architected Security Pillar | 2025 | Current | Updated identity, detection, and response guidance |
| Terraform AWS Provider | 5.x | Current | S3 bucket resources split into sub-resources |
| Terraform AWS Provider | 4.x | Maintained | Unified S3 bucket resource |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Setting up a new AWS account or organization | Using Azure, GCP, or multi-cloud | Cloud-specific security checklists |
| Preparing for a security audit (SOC 2, ISO 27001) | Need application-layer security (OWASP Top 10) | XSS/SQLi/CSRF prevention guides |
| Remediating Security Hub or Prowler findings | Need container/Kubernetes security | EKS security best practices |
| Hardening an existing AWS environment | Need compliance-specific controls (HIPAA, PCI) | AWS compliance-specific guides |
| Onboarding developers to AWS security practices | Managing on-premises infrastructure | On-prem security hardening guides |

## Important Caveats

- This checklist covers the most critical controls but is not exhaustive -- AWS has 200+ services, each with its own security surface. Use Security Hub and Prowler for comprehensive coverage.
- CIS Benchmark Level 1 vs Level 2: Level 1 controls are essential for all accounts. Level 2 adds stricter controls for regulated environments but may impact functionality (e.g., restricting regions via SCPs).
- AWS default settings have improved significantly (S3 Block Public Access, SSE-S3 default encryption for new buckets since Jan 2023) but older accounts and buckets may not have these defaults applied.
- Terraform AWS Provider 5.x changed S3 bucket configuration to separate resources (`aws_s3_bucket_versioning`, `aws_s3_bucket_encryption`, etc.). Provider 4.x used inline configuration blocks. Check your provider version before applying examples.
- SCPs in AWS Organizations affect all accounts in the OU but never restrict the management account itself -- the management account remains unrestricted.
- GuardDuty and Security Hub must be enabled per-region. Use `aws organizations` delegated administrator feature to enable across all accounts and regions automatically.

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention Guide](/software/security/xss-prevention/2026)
