Tarek Cheikh
Founder & AWS Security Expert
AWS Identity and Access Management (IAM) is the gatekeeper of your entire cloud environment. Every API call, every console login, every service interaction flows through IAM. If IAM is misconfigured, nothing else you do matters -- an attacker with the right credentials and overly broad permissions can access, modify, or delete any resource in your account in minutes.
According to the 2025 Verizon DBIR, compromised credentials remain among the leading causes of data breaches. In November 2025, attackers used compromised admin-like IAM credentials to deploy crypto miners across an AWS environment in under 10 minutes, leveraging Lambda functions and new IAM users for persistence. Getting IAM right is the single highest-impact security investment you can make.
This guide covers 12 battle-tested IAM best practices, each with real AWS CLI commands, audit procedures, and the latest 2025-2026 updates from AWS.
The root account has unrestricted access to all AWS resources, billing, and account closure. It bypasses all IAM policies and SCPs. A compromised root account is a total account takeover.
# Check for root access keys (should return empty)
aws iam generate-credential-report
aws iam get-credential-report --query "Content" --output text | base64 -d | head -2
# Verify root MFA status from the credential report
# Look for: mfa_active = true, access_key_1_active = false
CIS Benchmark: Control 1.4 (no root access keys), Control 1.5 (root MFA enabled), Control 1.6 (hardware MFA for root).
Credentials alone are insufficient. The November 2025 crypto mining campaign demonstrated that compromised IAM credentials without MFA enabled allowed attackers to deploy miners within 10 minutes of initial access.
Attach this policy to all IAM users. It denies every action except MFA self-management until the user configures MFA:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllExceptSelfManageWithoutMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {"aws:MultiFactorAuthPresent": "true"}
}
}]
}
# Generate credential report and check for users without MFA
aws iam generate-credential-report
aws iam get-credential-report --query "Content" --output text | base64 -d > report.csv
# Parse CSV: look for rows where mfa_active = false and password_enabled = true
2025-2026 Update: AWS now supports passkeys (FIDO2 WebAuthn) as a phishing-resistant MFA option. CIS Benchmark v5.0 explicitly recommends passkeys or hardware security keys over TOTP authenticator apps.
IAM users use long-lived credentials (passwords and access keys) that never automatically expire and are easily leaked. Multiple industry articles in late 2025 and early 2026 declare "IAM users are dead" for human access.
IAM Identity Center provides:
# Create a permission set
aws sso-admin create-permission-set --instance-arn arn:aws:sso:::instance/ssoins-EXAMPLE --name "ReadOnlyAccess" --session-duration "PT8H"
# Attach an AWS managed policy to the permission set
aws sso-admin attach-managed-policy-to-permission-set --instance-arn arn:aws:sso:::instance/ssoins-EXAMPLE --permission-set-arn arn:aws:sso:::permissionSet/ssoins-EXAMPLE/ps-EXAMPLE --managed-policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
Migration strategy: Enable Identity Center alongside existing IAM users. Create equivalent permission sets for your IAM groups. Migrate users in phases, then decommission IAM users.
Overly permissive policies increase the blast radius of any credential compromise. IAM Access Analyzer combines external access detection and unused access analysis into a single tool.
# Create an external access analyzer
aws accessanalyzer create-analyzer --analyzer-name my-external-analyzer --type ACCOUNT
# Create an unused access analyzer (organization level)
aws accessanalyzer create-analyzer --analyzer-name my-unused-access-analyzer --type ORGANIZATION_UNUSED_ACCESS --configuration '{"unusedAccess": {"unusedAccessAge": 90}}'
# Generate a policy based on actual CloudTrail usage
aws accessanalyzer start-policy-generation --policy-generation-details '{"principalArn":"arn:aws:iam::123456789012:role/MyRole"}' --cloud-trail-details '{"trails":[{"cloudTrailArn":"arn:aws:cloudtrail:us-east-1:123456789012:trail/my-trail","allRegions":true}],"accessRole":"arn:aws:iam::123456789012:role/AccessAnalyzerRole","startTime":"2025-01-01T00:00:00Z","endTime":"2025-03-01T00:00:00Z"}'
# Validate a policy against best practices
aws accessanalyzer validate-policy --policy-document file://policy.json --policy-type IDENTITY_POLICY
2025 Update: Access Analyzer now provides actionable policy recommendations with specific steps to refine permissions. Custom tracking periods (1-365 days) were introduced.
Permission boundaries let centralized security teams safely delegate IAM role creation to developers. The boundary sets the maximum permissions ceiling -- even if a developer attaches AdministratorAccess, the effective permissions are the intersection of the identity policy and the boundary.
# Create a permission boundary policy
aws iam create-policy --policy-name DeveloperBoundary --policy-document file://boundary-policy.json
# Create a role with the boundary applied at creation time
aws iam create-role --role-name app-lambda-role --assume-role-policy-document file://trust-policy.json --permissions-boundary arn:aws:iam::123456789012:policy/DeveloperBoundary
Best practice: Always set the boundary at role creation time so the role is never without a boundary, even momentarily. Maximum one boundary per role/user.
SCPs set hard permission boundaries at the AWS Organizations level. Deny statements in SCPs cannot be overridden by any IAM policy in member accounts.
Major 2025 Enhancement: As of September 2025, SCPs now support the full IAM policy language, including NotResource, conditions in Allow statements, and individual resource ARNs.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyUnapprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "eu-west-1", "eu-central-1"]
},
"ArnNotLike": {
"aws:PrincipalARN": "arn:aws:iam::*:role/OrganizationAdmin"
}
}
}]
}
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyDisablingSecurityTools",
"Effect": "Deny",
"Action": [
"guardduty:DeleteDetector",
"securityhub:DisableSecurityHub",
"config:StopConfigurationRecorder",
"cloudtrail:StopLogging",
"access-analyzer:DeleteAnalyzer"
],
"Resource": "*"
}]
}
Best practice: Use a deny-list approach. Start with the default FullAWSAccess SCP, then add targeted Deny statements. Always test in a dedicated test OU before production.
Access keys are the single most common attack vector. In November 2025, attackers found credentials in public S3 buckets and used LLMs to automate reconnaissance and lateral movement across 19 AWS principals in under 10 minutes.
# List all access keys for a user
aws iam list-access-keys --user-name example-user
# Create a new key (rotate)
aws iam create-access-key --user-name example-user
# Deactivate the old key after updating applications
aws iam update-access-key --user-name example-user --access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive
# Delete the old key after confirming
aws iam delete-access-key --user-name example-user --access-key-id AKIAIOSFODNN7EXAMPLE
# Detect keys older than 90 days
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "access-keys-rotated",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "ACCESS_KEYS_ROTATED"
},
"InputParameters": "{"maxAccessKeyAge":"90"}"
}'
Best practice: Prefer IAM roles with temporary credentials over access keys entirely. For CI/CD, use OIDC federation (e.g., GitHub Actions OIDC provider) to eliminate keys completely.
Condition keys add contextual controls beyond identity -- restricting by IP, MFA status, encryption requirements, and organization membership.
Require MFA for sensitive operations:
{
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
Restrict by organization (data perimeter):
{
"Condition": {
"StringNotEquals": {
"aws:PrincipalOrgID": "o-EXAMPLE"
}
}
}
Require KMS encryption on S3 uploads:
{
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
}
Caveat: aws:SourceIp provides limited security because attackers can pivot through VPNs. It should be one layer in a defense-in-depth strategy, not the sole control.
IAM roles provide temporary credentials via STS that automatically rotate, eliminating the risk of key leakage.
# Create an EC2 instance profile with a role
aws iam create-role --role-name MyEC2AppRole --assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
aws iam create-instance-profile --instance-profile-name MyEC2AppProfile
aws iam add-role-to-instance-profile --instance-profile-name MyEC2AppProfile --role-name MyEC2AppRole
# For CI/CD: use OIDC federation (GitHub Actions example)
aws iam create-open-id-connect-provider --url https://token.actions.githubusercontent.com --client-id-list sts.amazonaws.com --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
Use instance profiles for EC2, execution roles for Lambda, task roles for ECS, and IRSA/Pod Identity for EKS.
Credential reports provide a snapshot of all IAM users and their credential status -- passwords, access keys, MFA, last usage dates.
# Generate and download the report
aws iam generate-credential-report
aws iam get-credential-report --query "Content" --output text | base64 -d > iam-report.csv
mfa_active -- must be true for all password-enabled usersaccess_key_1_active / access_key_2_active -- identify active keysaccess_key_1_last_used_date -- identify unused keys for removalpassword_last_used -- identify dormant accounts (>90 days)access_key_1_last_rotated -- identify keys needing rotationCIS Benchmark: Control 1.12 (disable credentials unused for 45+ days), Control 1.13 (only one active access key per user), Control 1.14 (rotate keys every 90 days).
AWS Security Hub CSPM added support for CIS AWS Foundations Benchmark v5.0 in October 2025, providing automated, continuous evaluation of your IAM posture.
# Enable CIS Benchmark v5.0 in Security Hub
aws securityhub batch-enable-standards --standards-subscription-requests '[{
"StandardsArn": "arn:aws:securityhub:::standards/cis-aws-foundations-benchmark/v/5.0.0"
}]'
*:* admin policies attachedAn open-source MCP server and CLI tool that analyzes application code locally to generate baseline IAM policies. Integrates with AI coding assistants (Claude Code, Kiro, Cursor). Supports Python, TypeScript, and Go.
Enables AWS workloads to securely authenticate with third-party cloud providers and SaaS platforms using short-lived JWTs, eliminating long-term credentials for cross-cloud access.
SCPs now support NotResource, conditions in Allow statements, and individual resource ARNs -- enabling far more granular organizational guardrails than before.
| Misconfiguration | Risk | Detection |
|---|---|---|
| Root account with access keys | Total account takeover | Credential report: access_key_1_active = true on root |
| IAM users without MFA | Credential compromise leads to full access | Credential report: mfa_active = false |
*:* admin policies on IAM users |
No blast radius containment | Access Analyzer findings |
| Stale access keys (>90 days) | Long-lived credentials increase exposure window | AWS Config: access-keys-rotated |
| Overly permissive role trust policies | Cross-account privilege escalation | Access Analyzer external access findings |
| # | Practice | Priority |
|---|---|---|
| 1 | Eliminate root account usage | Critical |
| 2 | Enforce MFA universally | Critical |
| 3 | Adopt IAM Identity Center | High |
| 4 | Implement least privilege with Access Analyzer | High |
| 5 | Use permission boundaries | High |
| 6 | Deploy SCPs as guardrails | High |
| 7 | Rotate/eliminate access keys | Critical |
| 8 | Use IAM condition keys | Medium |
| 9 | Use roles for applications | Critical |
| 10 | Review credential reports regularly | Medium |
| 11 | Enable CIS Benchmark in Security Hub | High |
| 12 | Leverage new IAM features | Medium |
This article is just the start. Get the full picture with our free whitepaper - 8 chapters covering IAM, S3, VPC, monitoring, agentic AI security, compliance, and a prioritized action plan with 50+ CLI commands.
Comprehensive guide to securing Amazon S3. Covers Block Public Access, encryption (SSE-KMS, SSE-C deprecation), Object Lock, MFA Delete, VPC endpoints, presigned URLs, and GuardDuty S3 Protection.
Comprehensive guide to securing AWS EC2 instances. Covers IMDSv2 enforcement, security groups, EBS encryption, SSM Session Manager, private subnets, VPC Flow Logs, Amazon Inspector, and AMI hardening.
Comprehensive guide to securing AWS Lambda functions. Covers execution role least privilege, Function URL authentication, VPC placement, code signing, environment variable encryption, Secrets Manager integration, and SnapStart security considerations.