Tools · Cloud security · 05 / 24
Find old and unused AWS access keys with the IAM credential report
Generate the credential report, find keys older than 90 days or never used, rotate keys without downtime, and replace long-lived keys with roles.
Long-lived keys leak
IAM user access keys never expire on their own. They end up in laptops, CI systems, shell history, and code repositories, and automated scanners search public repositories for them constantly. A leaked key with broad permissions is one of the most common starting points for cloud incidents, often leading to crypto-mining on the victim’s bill.
The best key is one that does not exist: use roles with temporary credentials wherever possible.
The credential report
The credential report is a CSV listing every IAM user with password and key status: access_key_1_active, access_key_1_last_rotated, access_key_1_last_used_date, and the same for key 2, plus MFA status. Parse it to find keys older than 90 days, keys never used, and users with console passwords but no MFA.
aws iam get-access-key-last-used shows the last service and region for a single key.
aws iam generate-credential-report >/dev/null; sleep 5
aws iam get-credential-report --query Content --output text | base64 -d > cred.csv
python3 - <<'EOF'
import csv, datetime as dt
now = dt.datetime.now(dt.timezone.utc)
for r in csv.DictReader(open('cred.csv')):
for k in ('1', '2'):
if r[f'access_key_{k}_active'] == 'true':
age = (now - dt.datetime.fromisoformat(r[f'access_key_{k}_last_rotated'])).days
used = r[f'access_key_{k}_last_used_date']
if age > 90 or used == 'N/A':
print(r['user'], f'key{k}', f'{age}d old', 'last used', used)
EOFLab: rotate without downtime
IAM allows two keys per user, which makes rotation safe. Create a second key, update the application to use it, confirm the old key’s last-used date stops advancing, deactivate the old key, wait a few days, and then delete it. Deactivating first lets you reactivate if something you forgot still depends on it.
Practice on a sandbox user with a small script that calls aws sts get-caller-identity.
aws iam create-access-key --user-name report-bot # update the app/secret store with the new key, then: aws iam get-access-key-last-used --access-key-id AKIAOLDKEYEXAMPLE aws iam update-access-key --user-name report-bot --access-key-id AKIAOLDKEYEXAMPLE --status Inactive aws iam delete-access-key --user-name report-bot --access-key-id AKIAOLDKEYEXAMPLE
Replace keys with roles
EC2, Lambda, ECS, and EKS workloads should use roles that supply short-lived credentials automatically. CI systems such as GitHub Actions can use OpenID Connect federation to assume a role without storing any key. People should use IAM Identity Center for console and CLI access.
If a key leaks, deactivate it immediately, review CloudTrail for its activity, and check for new users, keys, or roles the attacker may have created.
Diagram
Zero-downtime key rotation
- Create
Make a second key for the same user.
- Switch
Update the application or secret store.
- Deactivate
Mark the old key Inactive after its last-used date stops moving.
- Delete
Remove it after a short grace period.
Check yourself
1. Why rotate by creating a second key first?
2. Which report shows each key’s age and last use?
3. How should GitHub Actions authenticate to AWS ideally?