If you run an AWS account that is a few years old, an IAM security audit is the quickest way to find access that should no longer exist. IAM, short for Identity and Access Management, is the AWS service that controls which people and programs can do what in your account. Over time, forgotten access piles up. Access keys, the long-lived credentials that scripts and tools sign in with, go years without being replaced. Some belong to people who left long ago. Users hold AdministratorAccess, the built-in policy that allows everything, because it was quicker on the day. Policies written during an incident still say "Action": "*", which means any action at all. And someone still signs in as the root user, the all-powerful login created with the account, for routine work. IAM already includes the reports you need, so the work is mostly reading them and acting on what they show. The steps below follow the order that surfaces the most dangerous findings first.
What’s happening
IAM sprawl is the default outcome of normal work. Every integration, contractor, and deadline adds a user, a key, or a policy, and nothing ever removes them — deleting access feels risky and nobody owns the follow-up, so entitlements only grow. The result is an account where the effective attack surface is defined by the oldest forgotten credential, not the newest carefully-scoped role. A leaked access key from a laptop backup in 2022 has the same power today as the day it was created, and static keys are exactly what shows up in credential-stuffing and repo-scanning attacks.
The audit inverts this: instead of asking “who needs access?” it asks “what access exists, is it used, and can we prove it?”
AWS provides three data sources that make this answerable:
- the credential report (every user’s password and key age and last-use);
- service last-accessed data (which permissions a principal actually exercised);
- IAM Access Analyzer (what is reachable from outside the account, and — with unused access analysis, which is a paid feature — which permissions have gone unexercised).
The reference material for all three lives in the IAM documentation.
What to check first
- Root user hygiene. MFA enabled, no access keys on root, and no sign-ins for routine work. A root access key is finding number one, every time.
- Access keys older than 90 days, and keys never used. Both come straight out of the credential report.
- Users versus roles. Every long-lived IAM user for a human is a candidate for retirement in favour of SSO; every key baked into an EC2 instance should be an instance profile instead.
- Direct
AdministratorAccessattachments. Count them; the number is usually higher than anyone guesses. - Anything public or cross-account. Access Analyzer findings for S3 buckets, KMS keys, roles trusting unknown accounts.
How to verify
Start with the credential report — one command gives you every user’s credential state:
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d > cred-report.csv
Open the CSV and sort by access_key_1_last_rotated and access_key_1_last_used_date. Keys never used, or unused for months, are your deletion list. The same report is in the console at IAM > Credential report.
Then chase down what specific keys are doing before you touch them:
# Who owns this key and when was it last used, and for what service?
aws iam get-access-key-last-used --access-key-id AKIAEXAMPLEKEYID
# All keys for a user
aws iam list-access-keys --user-name legacy-deploy-user
Measure privilege against usage:
# What has this user actually touched? (returns a JobId, then fetch it)
aws iam generate-service-last-accessed-details \
--arn arn:aws:iam::111122223333:user/legacy-deploy-user
aws iam get-service-last-accessed-details --job-id <job-id-from-above>
And check external exposure via IAM > Access analyzer or:
aws accessanalyzer list-analyzers
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:ca-central-1:111122223333:analyzer/account-analyzer \
--filter '{"status":{"eq":["ACTIVE"]}}'
CloudTrail is the companion to all of this — before deleting any credential, ninety days of CloudTrail history tells you whether it is genuinely dead or quietly load-bearing. If CloudTrail is not on, fix that first; the CloudTrail and GuardDuty baseline is the prerequisite for auditing anything.
The runbook
- Lock down root. Enable MFA, delete any root access keys, set the root email to a monitored company address, and create a CloudTrail-based alarm on root sign-in. Nothing else on this list matters as much.
- Delete dead credentials, deactivate the doubtful ones. Keys never used: delete. Keys unused 90+ days: deactivate (
aws iam update-access-key --status Inactive), wait two weeks for screams, then delete. Deactivation is reversible in seconds, which makes it politically easy. - Retire departed humans. Cross-reference the user list against the HR roster. Departed users lose console access and keys the same day; this belongs in the offboarding checklist permanently.
- Replace embedded keys with roles. Keys inside EC2 instances, containers, or Lambda environment variables become instance profiles and execution roles — the pattern and its pitfalls are in IAM roles on EC2. Keys in third-party SaaS configs get rotated onto dedicated minimal users, or better, OIDC federation where the vendor supports it.
- Cut privilege to observed usage. For each
AdministratorAccessholder, use service last-accessed data to draft a policy covering what they actually used in 90 days, apply it, and keep admin behind a separate break-glass role with alerting. - Move humans to SSO. IAM Identity Center (or your IdP federated in) replaces long-lived users with short-lived sessions. New rule going forward: humans get SSO, machines get roles, and new IAM users require a written justification.
- Write down the exceptions. Some static keys will survive the audit (legacy vendor integrations, mostly). List them, name an owner, set a rotation date. An exception with an owner is a managed risk; an unlisted one is next year’s finding.
When this is the wrong move
If you suspect an active compromise — an unfamiliar admin user, GuardDuty credential findings, resources you did not create — stop auditing and start incident response: rotating one key at a time tips off an attacker who may have several. If the account is weeks from being replaced by a new landing zone, spend the effort on clean IAM in the destination instead of polishing the condemned account — but still kill root keys and departed users; that is triage, not audit. And do not run the cleanup as a single big-bang change set; deleting forty credentials in one afternoon is how audits cause outages.
Common mistakes
- Deleting instead of deactivating. Deactivation gives you an instant undo when the “unused” key turns out to run month-end billing.
- Auditing users but not roles. Overly broad trust policies —
"Principal": {"AWS": "*"}with a weak condition, or trust to an account nobody recognizes — are rarer than old keys but far worse. - Trusting the key’s description over its usage data. The key labelled “temporary — for testing” has been in production for three years. Only last-used data and CloudTrail tell the truth.
- Running the audit once and declaring victory. Entitlements regrow immediately; a one-time cleanup without a recurring cadence is a photograph, not a control.
Prevention
- Put the credential report on a monthly calendar slot with a named owner; the review takes fifteen minutes when done monthly.
- Alarm on root usage and on
iam:CreateAccessKeyfor privileged users. - Enforce a key-age policy mechanically (a scheduled script or Config rule that deactivates keys past 90 days after warning).
- Require SSO for humans and roles for workloads in your provisioning standards, so the audit stops finding new static keys.
- Keep Access Analyzer enabled in every region you use and treat new external-access findings as tickets, not dashboard decoration.
The IAM audit is a two-day job the first time and a monthly fifteen-minute habit afterwards — the difficulty was never technical. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.