Why Access Key Rotation Is Still a Problem
IAM access keys are long-lived credentials that don't expire by default. A key created in 2019 to give a developer access to an S3 bucket is still valid in 2026 unless someone explicitly rotated or deleted it. If that key was accidentally committed to a GitHub repository, posted in Slack, or included in a build artifact, it's been available to attackers for years.
Access key rotation is one of the most frequently cited gaps in AWS security audits, one of the most frequently ignored recommendations, and one of the most preventable causes of credential compromise. This guide covers both the tactical question (how to rotate keys) and the strategic question (how to eliminate the need for long-lived keys in most cases).
The Case Against Long-Lived Access Keys
Before covering rotation, it's worth understanding why IAM access keys are a security liability in the first place. Access keys are:
- Static: They don't change automatically and can be used indefinitely
- Portable: They can be used from anywhere on the internet, not just from your infrastructure
- Copyable: Once generated, they can be copied by anyone who sees them
- Often over-privileged: Keys created for a specific task often end up with broader permissions than needed
The best rotation strategy is often elimination: use IAM roles instead of access keys wherever possible. EC2 instances, Lambda functions, ECS tasks, and almost all AWS compute services support IAM roles that provide temporary credentials (which rotate automatically every hour). Only create long-lived access keys when absolutely required — typically for applications outside AWS that need to call AWS APIs.
Auditing Your Current Access Key Posture
Start with a complete inventory. The IAM credential report provides exactly what you need:
aws iam generate-credential-report
aws iam get-credential-report --output text --query Content | base64 -d | python3 -c "
import csv, sys
from datetime import datetime, timezone
reader = csv.DictReader(sys.stdin)
old_keys = []
for row in reader:
for key_num in ['1', '2']:
key_field = f'access_key_{key_num}_last_rotated'
if row.get(key_field) and row[key_field] != 'N/A':
rotated = datetime.fromisoformat(row[key_field].replace('Z', '+00:00'))
age_days = (datetime.now(timezone.utc) - rotated).days
if age_days > 90:
print(f'{row["user"]}: key{key_num} is {age_days} days old')
"
This shows every IAM user with an access key older than 90 days. Run this audit before implementing rotation policies — you may find keys that have been inactive for years.
AWS Config Rule for Key Age
AWS Config provides a managed rule that enforces key rotation:
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "access-keys-rotated",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "ACCESS_KEYS_ROTATED"
},
"InputParameters": "{"maxAccessKeyAge":"90"}"
}'
This rule marks any IAM user with a key older than 90 days as non-compliant. For compliance frameworks requiring key rotation evidence (PCI DSS, SOC 2), this Config rule provides continuous, auditable evidence that your rotation policy is being enforced. See our compliance automation guide for how this fits into your broader evidence collection strategy.
The Rotation Workflow
Rotating an IAM access key without breaking applications requires a careful sequence:
- Create a new key (each user can have 2 active keys)
- Update all applications using the old key to use the new key
- Verify applications are working with the new key
- Deactivate the old key (not delete — this lets you re-activate if something breaks)
- Monitor for any failures caused by applications still using the old key
- Delete the old key after a safe window (1-7 days)
The most common rotation mistake is deleting the old key before verifying all applications have been updated. Always deactivate before deleting, and wait at least 24 hours before deletion to catch any scheduled jobs using the old key.
Automating Rotation with Lambda
For teams with many IAM users, manual rotation doesn't scale. An automated rotation Lambda:
import boto3
from datetime import datetime, timezone, timedelta
def rotate_access_key(user_name):
iam = boto3.client('iam')
# Get current keys
keys = iam.list_access_keys(UserName=user_name)['AccessKeyMetadata']
old_keys = [k for k in keys if k['Status'] == 'Active']
if len(old_keys) >= 2:
# Already has 2 keys - cannot create a new one
# Must delete oldest first, but only if we can verify it's not in use
send_alert(user_name, "Has 2 active keys - manual intervention required")
return
# Create new key
new_key = iam.create_access_key(UserName=user_name)['AccessKey']
# Store new key in Secrets Manager for the user to retrieve
secrets = boto3.client('secretsmanager')
secrets.put_secret_value(
SecretId=f'iam/access-keys/{user_name}',
SecretString=json.dumps({
'AccessKeyId': new_key['AccessKeyId'],
'SecretAccessKey': new_key['SecretAccessKey']
})
)
# Notify user that new key is available
send_rotation_notification(user_name, new_key['AccessKeyId'])
return new_key['AccessKeyId']
For storing the new key securely, see our Secrets Manager guide.
Detecting Leaked Keys with GuardDuty
Rotation prevents compromise of old keys; GuardDuty detects when keys are actively being abused. Relevant findings:
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS— EC2 instance credentials used from outside AWSUnauthorizedAccess:IAMUser/MaliciousIPCaller— API calls from known malicious IPsUnauthorizedAccess:IAMUser/TorIPCaller— API calls from TOR exit nodesStealth:IAMUser/CloudTrailLoggingDisabled— Attempt to disable audit logging (indicator of compromise)
If GuardDuty detects any of these patterns, immediately disable the compromised key and begin incident response. See our account compromise response guide for the full workflow.
GitHub Secrets Scanning
Access keys are frequently leaked through GitHub. AWS partners with GitHub to scan public repositories for AWS access key patterns and automatically notify AWS when they're found. Enable secret scanning on your GitHub organization, and configure the AWS integration to automatically quarantine exposed keys.
For your own repositories, add a pre-commit hook that scans for credential patterns before commits:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
Additionally, AWS CloudTrail captures GetCredentialReport and key management events, providing an audit trail of rotation activity. See our CloudTrail guide.
Service Account Key Management
The hardest keys to rotate are service account keys — access keys used by applications rather than humans. These keys are often embedded in CI/CD pipelines, application configurations, and deployment scripts. Best practices:
- Tag all service account keys with the application they serve:
Application: myapp,Environment: prod - Use Secrets Manager to store keys and enable rotation
- Prefer OIDC-based authentication for CI/CD: GitHub Actions, GitLab CI, and most modern CI systems support assuming IAM roles without long-lived keys
- For applications running outside AWS, consider AWS IAM Roles Anywhere, which lets on-premises servers assume IAM roles using PKI certificates
Enforcing Key Rotation with SCPs
Service Control Policies in AWS Organizations can prevent the creation of new long-lived access keys for human users, forcing role-based access instead:
{
"Effect": "Deny",
"Action": "iam:CreateAccessKey",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalTag/AllowAccessKeys": "true"
}
}
}
This SCP denies key creation unless the IAM user has the tag AllowAccessKeys=true. You apply this tag only to service accounts that genuinely need long-lived keys, blocking key creation for all human users. See our SCPs guide for the full SCP architecture.
FAQ
What's the recommended rotation interval?
90 days is the industry standard, recommended by PCI DSS and SOC 2 frameworks. NIST recommends considering shorter intervals for high-privilege accounts. AWS IAM best practices recommend 90 days as a maximum. Many security teams enforce 60 days for production service accounts.
How do I handle keys that can't be rotated without downtime?
For applications that can't tolerate a key change, each IAM user can have 2 active keys simultaneously. Create the new key, deploy the updated configuration, verify it's working, then deactivate and delete the old key. This zero-downtime rotation requires coordination but eliminates the maintenance window requirement.
Can I use AWS Config to automatically remediate old keys?
Config can detect non-compliance but automated remediation for key rotation is risky — you could break applications. Use Config for detection and notification, with manual or Lambda-assisted rotation rather than fully automated deletion. See our Config remediation guide for safe automation patterns.
Protect your AWS accounts before it's too late
Vigilare monitors your AWS accounts for suspension risks — billing anomalies, IAM issues, GuardDuty findings, and more — and alerts you before AWS takes action.
Written by Vigilare Engineering
Platform Team