AWS security compliance IAM audit

AWS security audit checklist: a practical guide for DevOps teams

AWS environment security audit procedure, from IAM review, through network configuration, to encryption and monitoring. A ready-to-use checklist for recurring assessments.

·
Your AWS environment is growing, and nobody has done a security review in months. The root account has API keys, Security Groups have open ports, and CloudTrail logs to a bucket nobody monitors. Time for a systematic audit.

If you need external support running a cloud security audit, check our DevOps and cloud security consulting service.

When to run an audit

  • Before migrating production to a new AWS account
  • After a security incident or suspected breach
  • On a quarterly cycle (every six months at minimum)
  • Before a compliance audit (SOC2, ISO 27001, NIS2)
  • After significant infrastructure changes (new VPC, new account in the organisation)

Automation tools

Before you start a manual review, run automated scanners:

# AWS Security Hub - native compliance check
aws securityhub get-findings --filters '{"RecordState":[{"Value":"ACTIVE","Comparison":"EQUALS"}]}' \
  --query 'Findings[?Severity.Label==`CRITICAL`].[Title,Resources[0].Id]' --output table

# Prowler - open-source AWS audit (install: pip install prowler)
prowler aws --severity critical high --output-formats json-ocsf html

# ScoutSuite - multi-cloud security auditing
scout aws --report-dir ./audit-$(date +%Y%m%d)

# AWS Trusted Advisor (requires Business/Enterprise support)
aws support describe-trusted-advisor-checks --language en \
  --query 'checks[?category==`security`].name'

Prowler covers over 300 checks from CIS AWS Benchmark, PCI-DSS, and GDPR. If you can only run one tool, pick Prowler.

Checklist: IAM and access

Root account

# Check if root has API keys (it should NOT)
aws iam get-account-summary --query 'SummaryMap.AccountAccessKeysPresent'
# Expected result: 0

# Check if MFA is enabled on root
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'
# Expected result: 1
CheckpointExpected stateHow to check
Root account has no API keysAccountAccessKeysPresent = 0aws iam get-account-summary
MFA on root accountAccountMFAEnabled = 1aws iam get-account-summary
Root account unused for >90 daysNo recent loginCredential Report
MFA on all IAM users with console access100% coverageaws iam generate-credential-report
No inline policies on usersAll through groups/rolesprowler aws -c iam_inline_policy_no_administrative_privileges
API key rotation <90 daysNo stale keysCredential Report column access_key_last_rotated

Policies and roles

# Find users with AdministratorAccess
aws iam list-entities-for-policy \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
  --query '[PolicyUsers[].UserName, PolicyGroups[].GroupName, PolicyRoles[].RoleName]'

# Find policies with wildcard (*) on actions and resources
aws iam list-policies --scope Local --query 'Policies[].Arn' --output text | \
  xargs -I {} aws iam get-policy-version --policy-arn {} \
    --version-id $(aws iam get-policy --policy-arn {} --query 'Policy.DefaultVersionId' --output text) \
    --query 'PolicyVersion.Document'

# Unused roles (no assume in last 90 days)
aws iam generate-service-last-accessed-details --arn arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME

Checklist: Network and access

# Security Groups with open 0.0.0.0/0 on ports other than 80/443
aws ec2 describe-security-groups \
  --filters Name=ip-permission.cidr,Values='0.0.0.0/0' \
  --query 'SecurityGroups[].{Name:GroupName,ID:GroupId,Rules:IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]].{Port:FromPort,Proto:IpProtocol}}' \
  --output table

# Public subnets without NAT Gateway (potential attack vector)
aws ec2 describe-route-tables --query 'RouteTables[].Routes[?GatewayId!=null && DestinationCidrBlock==`0.0.0.0/0`]'

# VPC Flow Logs - check if enabled on all VPCs
aws ec2 describe-flow-logs --query 'FlowLogs[].{VPC:ResourceId,Status:FlowLogStatus,Dest:LogDestination}'
CheckpointExpected state
No SG with 0.0.0.0/0 on SSH (22) or RDP (3389)Zero results
VPC Flow Logs enabled on every VPC100%
No public IPs on database instancesZero results
NACLs blocking known malicious IP rangesConfigured
No default VPC in use for productionDeleted or empty

Checklist: Encryption and storage

# S3 buckets without default encryption
aws s3api list-buckets --query 'Buckets[].Name' --output text | tr '\t' '\n' | while read bucket; do
  enc=$(aws s3api get-bucket-encryption --bucket "$bucket" 2>&1)
  if echo "$enc" | grep -q "ServerSideEncryptionConfigurationNotFoundError"; then
    echo "NO ENCRYPTION: $bucket"
  fi
done

# S3 buckets with public access
aws s3api list-buckets --query 'Buckets[].Name' --output text | tr '\t' '\n' | while read bucket; do
  status=$(aws s3api get-public-access-block --bucket "$bucket" 2>&1)
  if echo "$status" | grep -q "NoSuchPublicAccessBlockConfiguration"; then
    echo "NO PUBLIC ACCESS BLOCK: $bucket"
  fi
done

# EBS volumes without encryption
aws ec2 describe-volumes --filters Name=encrypted,Values=false \
  --query 'Volumes[].{ID:VolumeId,Size:Size,State:State}' --output table

# RDS without at-rest encryption
aws rds describe-db-instances \
  --query 'DBInstances[?StorageEncrypted==`false`].{ID:DBInstanceIdentifier,Engine:Engine}' --output table

Checklist: Logging and monitoring

# CloudTrail - check if active and multi-region
aws cloudtrail describe-trails --query 'trailList[].{Name:Name,MultiRegion:IsMultiRegionTrail,Logging:HasCustomEventSelectors}'
aws cloudtrail get-trail-status --name default --query '{IsLogging:IsLogging,LatestDelivery:LatestDeliveryTime}'

# Config - is AWS Config recording
aws configservice describe-configuration-recorders \
  --query 'ConfigurationRecorders[].{Name:name,Recording:recording}'

# GuardDuty - is it enabled
aws guardduty list-detectors --query 'DetectorIds'

# Alarms on key events (root login, IAM policy changes)
aws cloudwatch describe-alarms --alarm-name-prefix "Security-" \
  --query 'MetricAlarms[].{Name:AlarmName,State:StateValue}'
CheckpointExpected state
CloudTrail active, multi-regionIsLogging: true, Multi-region: true
CloudTrail logs in a separate account (log archive)Bucket in a different account
AWS Config recording changesRecording: true
GuardDuty enabled in every regionDetectorId in each region
Alarm on root loginConfigured and active
Alarm on CreateUser/AttachPolicyConfigured and active

Reporting results

After running Prowler, generate a report:

# Full report with prioritization
prowler aws --severity critical high medium \
  --output-formats html csv json-ocsf \
  --output-directory ./audit-report-$(date +%Y%m%d)

# Compare with previous audit
prowler aws --severity critical high \
  --output-formats csv \
  | diff previous-audit.csv - > delta-findings.txt

The report should categorize findings:

  1. Critical - fix within 24 hours (open root keys, public databases)
  2. High - fix within a week (missing MFA, missing encryption)
  3. Medium - plan for next sprint (key rotation, unused roles)

Automating recurring audits

Schedule the audit as a recurring pipeline (e.g., weekly in CI/CD):

# .github/workflows/security-audit.yml
name: Weekly AWS Security Audit
on:
  schedule:
    - cron: '0 6 * * 1'  # Monday 6:00 UTC
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Prowler
        run: pip install prowler
      - name: Run audit
        run: prowler aws --severity critical high --output-formats json-ocsf html
        env:
          AWS_ACCESS_KEY_ID: $
          AWS_SECRET_ACCESS_KEY: $
          AWS_DEFAULT_REGION: eu-central-1
      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: security-audit-$
          path: output/

The IAM role for auditing should have the SecurityAudit managed policy + ViewOnlyAccess, no write permissions.

Next steps

  • Fix critical findings immediately
  • Implement AWS Config Rules for continuous monitoring
  • Consider AWS Organisations with SCPs (Service Control Policies) as guardrails
  • Set up Security Hub with automated findings aggregation

If you need help running an audit or implementing security automation, get in touch.