Misconfigured IAM policies are one of the most common causes of cloud security breaches. I've seen production buckets left public, access keys hardcoded in GitHub, and root accounts without MFA — all catastrophic mistakes. Here's the complete checklist to lock down your AWS environment the right way.
1. Lock Down the Root Account
- Enable MFA on the root account immediately — use a hardware key (YubiKey) or authenticator app
- Delete all root account access keys — they should never exist
- Never use root for daily tasks — create an admin IAM user instead
- Set a complex, unique password stored in a password manager
# Verify root MFA via AWS CLI (requires IAM permissions)
aws iam get-account-summary | grep "AccountMFAEnabled"
# Should return: "AccountMFAEnabled": 1
2. Enforce the Principle of Least Privilege
Every user, role, and service should have only the permissions they need — nothing more. Start by granting no permissions, then add incrementally:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-specific-bucket/*"
}
]
}
Use AWS IAM Access Analyzer to automatically detect overly permissive policies and external access they wouldn't allow.
3. Use IAM Roles, Not Long-Term Keys
Access keys are a security liability — they can be leaked, stolen, or forgotten. Instead:
- Assign IAM Roles to EC2 instances (instance profiles) — credentials rotate automatically
- Use AWS STS AssumeRole for cross-account access
- For GitHub Actions / CI-CD, use OIDC federation — no keys needed at all
- If you absolutely must use access keys, rotate them every 90 days and audit with
aws iam generate-credential-report
aws iam simulate-principal-policy to test whether a user/role can perform a
specific action before granting access. No guessing required.
4. Require MFA for All Human Users
Add an IAM policy that forces MFA before any action can be performed:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyWithoutMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevice"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}]
}
5. Enable CloudTrail and GuardDuty
You can't secure what you can't see:
- CloudTrail — logs every API call across all regions. Enable it in all regions with S3 log storage
- GuardDuty — ML-powered threat detection, automatically detects unusual behavior like API calls from Tor exit nodes
- Config Rules — enforce compliance: flag public S3 buckets, unencrypted EBS volumes, etc.
- Security Hub — centralized security findings from GuardDuty, Inspector, Macie
# Enable GuardDuty via CLI
aws guardduty create-detector --enable
# Check for public S3 buckets (Config rule)
aws configservice describe-compliance-by-rule \
--rule-names s3-bucket-public-read-prohibited
6. Enforce a Strong Password Policy
aws iam update-account-password-policy \
--minimum-password-length 14 \
--require-symbols \
--require-numbers \
--require-uppercase-characters \
--require-lowercase-characters \
--max-password-age 90 \
--password-reuse-prevention 5
Quick Checklist
- ✅ Root MFA enabled, no root access keys
- ✅ All human users have MFA enforced
- ✅ No wildcard
*in Action or Resource unless absolutely necessary - ✅ EC2/Lambda use IAM roles, not access keys
- ✅ CloudTrail enabled in all regions
- ✅ GuardDuty enabled
- ✅ S3 Block Public Access enabled account-wide
- ✅ Password policy enforced
- ✅ Unused IAM users/roles reviewed quarterly