Back to blog
AWSBest PracticesCloud SecurityIdentity & AccessOperations & Compliance

Access Key Older Than 1 Year: Rotating Stale AWS IAM Credentials

Stale IAM access keys are a top target for credential leaks. Learn how to detect, rotate, and prevent AWS access keys older than 365 days with CLI and policy-as-code.

TL;DR

This check flags any active IAM access key that hasn't been rotated in over 365 days. Stale long-lived keys are a prime target for credential leaks and lateral movement. Rotate the key, delete the old one, and ideally move workloads to short-lived credentials so there's nothing to rotate.

Long-lived AWS access keys are convenient, which is exactly why they pile up. Someone creates a key for a script, the script keeps running, the key keeps working, and a year later it's still sitting in a config file somewhere with the same secret it had on day one. This check exists to catch that drift before it turns into an incident.


What this check detects

The user_accesskeyage check looks at every IAM user in your account and inspects their access keys. If a key has an Active status and its creation date is more than 365 days in the past, the check fails for that key.

Each IAM user can have up to two access keys, each made of an access key ID (like AKIAIOSFODNN7EXAMPLE) and a secret access key. These are long-lived credentials: unlike a session token from STS, they don't expire on their own. They keep working until someone deactivates or deletes them.

Note: Age is measured from the key's creation date, not its last use. A key created 400 days ago that was used five minutes ago still fails this check. Rotation is about replacing the secret periodically, regardless of how active the key is.


Why it matters

The longer a credential lives, the more chances it has to leak. A year is plenty of time for an access key to end up somewhere it shouldn't:

  • Committed to a Git repo and pushed to a public GitHub fork
  • Baked into a Docker image layer that gets shared
  • Pasted into a Slack message, a support ticket, or a CI log
  • Left on a laptop that gets lost or compromised
  • Stored in a ~/.aws/credentials file on a machine that changes hands

If a key never rotates, a leaked secret stays valid indefinitely. Attackers know this. Automated scanners crawl public repositories looking for AWS keys, and a working key can be abused within minutes of being exposed. The classic outcome is a spike of expensive EC2 instances spun up for cryptomining, but a key tied to a privileged user can do far worse: read S3 buckets, dump databases, or create new IAM users to maintain persistence.

Rotation doesn't stop a leak, but it shrinks the window. A key rotated every 90 days that leaks on day 80 is only useful to an attacker for 10 days. A key that's a year old and never rotates is useful forever.

Warning: A failing key here is often a symptom of a bigger problem. Keys that go a year without rotation usually belong to "service accounts" that nobody owns and nobody monitors. Treat a stale key as a prompt to ask who actually uses it and whether it needs to exist at all.


How to fix it

The safe rotation pattern is: create a new key, update everything that uses the old one, verify the new key works, then deactivate and delete the old key. Don't delete first, or you'll break whatever depends on it.

Step 1: Find the offending key

aws iam list-access-keys --user-name deploy-bot

This returns each key with its CreateDate and Status. Confirm which one is the stale, active key before you touch anything.

Step 2: Create a new access key

aws iam create-access-key --user-name deploy-bot

The response includes the new AccessKeyId and SecretAccessKey. This is the only time AWS will show you the secret, so capture it now and store it in your secrets manager.

Step 3: Roll the new key into every consumer

Update wherever the old credentials live: your CI/CD secret store, environment variables, Kubernetes secrets, application config, or whatever else depends on it. Then test that the new key actually works:

AWS_ACCESS_KEY_ID=AKIANEWKEYEXAMPLE \
AWS_SECRET_ACCESS_KEY=newsecret... \
aws sts get-caller-identity

If that returns the expected user ARN, the new key is good.

Step 4: Deactivate the old key (don't delete yet)

Deactivating instead of deleting gives you a quick rollback if something was still using the old key. Watch CloudTrail or the key's last-used data for a few days.

aws iam update-access-key \
  --user-name deploy-bot \
  --access-key-id AKIAOLDKEYEXAMPLE \
  --status Inactive

Step 5: Delete the old key

Danger: Deleting an access key is irreversible. Make absolutely sure nothing still uses it. Check the key's last-used timestamp first, and confirm it shows no activity since you deactivated it.

# Confirm it's been quiet
aws iam get-access-key-last-used --access-key-id AKIAOLDKEYEXAMPLE

# Then delete
aws iam delete-access-key \
  --user-name deploy-bot \
  --access-key-id AKIAOLDKEYEXAMPLE

From the console, the same flow lives under IAM → Users → [user] → Security credentials, where you can create, deactivate, and delete keys for the same two-key rotation.

Tip: The best fix is to not have a long-lived key at all. For workloads on EC2, ECS, Lambda, or EKS, use IAM roles instead. For developer access, use IAM Identity Center (SSO) with short-lived credentials. A key that doesn't exist can't go stale and can't leak.


How to prevent it from happening again

Rotation that depends on someone remembering will fail. Build it into your tooling and policies instead.

Block long-lived keys with a service control policy

If you run AWS Organizations, you can deny key creation outright for accounts that shouldn't use them, pushing teams toward roles and SSO:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "iam:CreateAccessKey",
      "Resource": "*"
    }
  ]
}

Catch stale keys in CI with a credential report

IAM can generate a CSV report of every user and the age of their keys. Pull it in a scheduled job and fail the pipeline if anything crosses your threshold:

aws iam generate-credential-report
aws iam get-credential-report \
  --query 'Content' --output text | base64 -d > report.csv

The access_key_1_last_rotated and access_key_2_last_rotated columns tell you exactly when each key was last replaced. A short script comparing those dates against today is enough to gate a build or fire an alert.

Enforce rotation policy as code

Manage IAM users and keys through Terraform so age is visible in review, and pair it with a policy-as-code tool. For example, a Lensix scan or an OPA/Conftest rule in your pipeline can reject any plan that leaves a key in place beyond your rotation window. The point is to make a stale key impossible to ignore rather than something discovered a year later.

Note: AWS Config has a managed rule, access-keys-rotated, that continuously flags keys older than a number of days you set. It's a solid backstop if you want continuous detection without writing your own scheduled job.


Best practices

  • Prefer roles over keys. Almost every compute workload on AWS can assume a role and get short-lived credentials automatically. Reserve access keys for the rare cases that genuinely can't.
  • Use IAM Identity Center for humans. Developers should never hold long-lived keys. SSO gives them temporary credentials tied to their session.
  • Set a rotation window and enforce it. Ninety days is a common standard. Pick a number, automate the check, and don't make exceptions without an owner.
  • One key per purpose. The two-key limit exists so you can rotate without downtime. Don't share a single key across multiple apps, or rotating it means coordinating an outage across all of them.
  • Track key ownership. Every access key should map to a known person or system. Keys with no clear owner are the ones that go stale and the ones nobody dares to delete.
  • Clean up inactive keys. If get-access-key-last-used shows a key hasn't been touched in months, it probably shouldn't exist. Deactivate it, wait, then delete it.

A failing user_accesskeyage check is rarely just about one old credential. It's a signal that part of your access model is running on autopilot. Rotate the key to clear the finding, but use the moment to ask whether that key should be a role instead, and put automation in place so the next one never reaches a year.