[IAM.3] is an AWS Security Hub CSPM control that fails when an IAM user holds an active access key older than 90 days. To rotate AWS access keys without downtime, create a second key, move every application to it, deactivate the old key, then delete it. Where possible, replace the key with an IAM role instead.
There is no server to patch. The work is an inventory of keys, a map of where each one is used, and a swap that keeps a rollback path until the last step.
What the scanner is actually detecting
The finding comes from AWS Security Hub CSPM, the cloud security posture management service in AWS (if the category is new to you, see what CSPM is and what it can and cannot detect). The control is backed by an AWS Config managed rule, so the affected “asset” in the report is an IAM user ARN, not a host.
| Field | Value |
|---|---|
| Control | [IAM.3] IAM users’ access keys should be rotated every 90 days or less |
| Severity | Medium |
| Resource type | AWS::IAM::User |
| AWS Config rule | access-keys-rotated |
| Schedule | Periodic |
| Parameter | maxAccessKeyAge: 90 (not customizable) |
The same control appears under the CIS AWS Foundations Benchmark standard, mapped to requirement 1.13 in v5.0.0, 1.14 in v3.0.0 and v1.4.0, and 1.4 in v1.2.0. Three details matter when you read the report:
- Only active keys are evaluated. A key you have already set to Inactive does not fail the check.
- The rule does not apply to root user access keys. Those are reported by a separate control, IAM.4 (Critical).
- The same users often fail IAM.8 (credentials unused for 90 days) and IAM.22 (unused for 45 days). An old key that nobody uses fails several controls at once, and deleting it clears all of them.
Real-world risk, stated honestly
An access key pair is a long-term credential with no expiration. Anyone who copies the access key ID and secret can sign API requests as that IAM user, from anywhere, until the key is deactivated. Keys end up in shell profiles, CI variables, config files baked into images and old laptops, which is why their age matters.
An old key is not a compromised key, and rotation does not detect a leak: a key stolen last week works until the day you rotate it. What rotation does is put an upper limit on how long a forgotten copy stays useful. The real impact depends on the permissions behind the key, so a 400-day-old key on an administrator user deserves more urgency than one on a user that can only read a single bucket. If you are not sure what a user can do, pair this work with an audit of over-permissioned IAM roles and users.
How to confirm it in your account
Find old active keys with the credential report
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 -d > cred-report.csv
CUTOFF=$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%S)
awk -F, -v c="$CUTOFF" 'NR>1 && $2 !~ /:root$/ {
if (tolower($9)=="true" && $10 < c) print $1, "key1 created", $10, "last used", $11
if (tolower($14)=="true" && $15 < c) print $1, "key2 created", $15, "last used", $16
}' cred-report.csv
Repeat the generate command until it reports COMPLETE. In the CSV, columns 9 and 14 are access_key_1_active and access_key_2_active, columns 10 and 15 are the last_rotated timestamps, and columns 11 and 16 are the last_used dates. The date syntax is GNU date, as on most Linux distributions. IAM creates a new report at most once every four hours; inside that window you get the previous one.
Check a user directly, without the four-hour delay
for u in $(aws iam list-users --query "Users[].UserName" --output text); do
aws iam list-access-keys --user-name "$u"
--query "AccessKeyMetadata[?Status=='Active'].[UserName,AccessKeyId,CreateDate]"
--output text
done
Find out who is using the key
aws iam get-access-key-last-used --access-key-id AKIAIOSFODNN7EXAMPLE
aws cloudtrail lookup-events --region us-east-1
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE
--query "Events[].[EventTime,EventSource,EventName]" --output text
The first command returns the date, service and Region of the last request. Run the CloudTrail lookup in that Region; it covers management events from the last 90 days, and the CloudTrailEvent field of each result includes the source IP address and user agent, which usually point to the server or tool holding the key. On a suspected Linux host, aws configure list shows where the CLI is reading credentials from, and grep -rIl ‘AKIAIOSFODNN7EXAMPLE’ /etc /opt /srv /home 2>/dev/null finds files that contain that key ID.
How to fix it
Rotate a key that has to stay
This is the AWS documented sequence for a workload that genuinely needs a long-term key, such as a third-party tool or a server outside AWS with no other option:
# 1. Create the second key (the secret is shown only in this output)
aws iam create-access-key --user-name build-bot
# 2. Store the new pair where the application reads it, then restart the application
# 3. Confirm the old key has stopped being used
aws iam get-access-key-last-used --access-key-id AKIAIOSFODNN7EXAMPLE
# 4. Deactivate the old key, do not delete it yet
aws iam update-access-key --user-name build-bot
--access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive
# 5. After a quiet period with no errors, delete it
aws iam delete-access-key --user-name build-bot
--access-key-id AKIAIOSFODNN7EXAMPLE
Write the new secret directly into the secret store or configuration the application uses, not into a ticket or chat. AWS suggests waiting several days before step 3 and checking that the old key shows no new use, and even then deactivating rather than deleting. For jobs that run weekly or monthly, wait at least one full cycle before you delete.
Workloads running on AWS: replace the key with a role
AWS recommends IAM roles for anything that runs inside AWS, because roles deliver temporary credentials and there is nothing to rotate:
- EC2: attach an instance profile, for example aws ec2 associate-iam-instance-profile –instance-id i-0123456789abcdef0 –iam-instance-profile Name=app-server-profile.
- ECS: use a task role. Lambda: use the execution role.
- EKS: use IAM roles for service accounts (IRSA) or EKS Pod Identity instead of keys mounted as Kubernetes secrets.
Then remove the key from ~/.aws/credentials and from AWS_ environment variables on that host. The AWS CLI and SDKs check environment variables and the credentials file before container and instance profile credentials, so a leftover key silently keeps winning. aws sts get-caller-identity should now return an assumed-role ARN, not a user ARN. Deactivate, then delete the old key as in steps 4 and 5.
Workloads outside AWS
On-premises servers and other clouds can still use temporary credentials: IAM Roles Anywhere issues them in exchange for an X.509 certificate from your PKI, and CI/CD platforms that issue OIDC tokens can call AssumeRoleWithWebIdentity. Where neither is possible, rotate on a schedule as above.
People using the CLI
Human users do not need personal access keys. Move them to aws configure sso with IAM Identity Center, or to aws login in current AWS CLI versions, which issues short-term credentials from a console sign-in. Then delete their keys.
How to verify the fix and rescan
- Rerun the list-access-keys loop. Every remaining active key should have a CreateDate inside the last 90 days.
- After the four-hour window, regenerate the credential report and rerun the awk filter. It should print nothing.
- Check the control state in Security Hub CSPM, in the Region where you record global resources:
aws securityhub get-findings --filters '{"ComplianceSecurityControlId":[{"Value":"IAM.3","Comparison":"EQUALS"}],"ComplianceStatus":[{"Value":"FAILED","Comparison":"EQUALS"}],"RecordState":[{"Value":"ACTIVE","Comparison":"EQUALS"}]}'
--query "Findings[].Resources[0].Id" --output text
IAM.3 is periodic, and Security Hub CSPM runs periodic checks within 12 or 24 hours of the previous run, so a fixed user can stay FAILED for up to a day. When the control passes, Security Hub CSPM sets the finding’s workflow status to RESOLVED on its own.
What can break and how to roll back
- Something still uses the old key. It fails as soon as the key is Inactive. Roll back with aws iam update-access-key –user-name build-bot –access-key-id AKIAIOSFODNN7EXAMPLE –status Active, move that application to the new key, then deactivate again. This is why you deactivate before deleting.
- A deleted key cannot come back. The secret can only be retrieved when the key is created, so after deletion the only path is a new key deployed everywhere.
- Services that read the key at startup may keep the old credentials until restarted, so restart them after updating the configuration.
- The two-key limit. Each IAM user can have at most two access keys, and the quota is not adjustable. If a user already has two, create-access-key fails until you delete one, usually a stale inactive key.
- One key shared by several applications. All of them must move before you deactivate. Give each application its own user or role next time.
Common false positive reasons
- Fixed, but still FAILED: the periodic check has not run since the change. Wait up to 24 hours before investigating.
- The same user reported in many Regions: IAM users are global. AWS notes that if you record global resources in a single Region, you can disable IAM.3 in every other Region, and one fix clears every copy.
- Root user keys show up elsewhere: IAM.3 does not evaluate them. A root key is an IAM.4 finding with its own fix.
- A key that truly cannot rotate yet: this is not a false positive but an accepted risk. Record the owner and a date, and set the finding’s workflow status to SUPPRESSED with a note instead of ignoring it.
FAQ
Can I change the 90-day threshold for IAM.3?
No. In Security Hub CSPM the maxAccessKeyAge parameter is fixed at 90 and marked not customizable.
Does deactivating the old key clear the finding?
Yes, because the control only evaluates active keys. Delete it afterwards anyway, so nobody can quietly reactivate it.
Why does create-access-key fail for this user?
The user already has two access keys, which is the maximum. Delete the one that is no longer needed, then create the new key.
Does IAM.3 check root access keys?
No. The underlying rule excludes the root user. Root keys are covered by IAM.4 and should be deleted rather than rotated.
Tracking this finding across many accounts
With dozens of accounts, the hard part is keeping track of which users were rotated, which moved to roles, and which exceptions were approved. SITEY is a self-hosted vulnerability management platform that imports findings from 16 scanners, merges duplicates per scanner (not across scanners), and uses AI triage to suggest false positives with evidence for a human to decide. If you are considering it, check whether your cloud posture source is among its supported imports first.