IAM Access Analyzer Unused Access in Organizations
IAM Access Analyzer unused access is a paid org analyzer, not the free external-access findings. Delegated admin, service-linked noise, tracking-period traps, and turning findings into permission boundaries.
External Access Analyzer is free and tells you a bucket policy lets another account in. IAM Access Analyzer unused access is a different analyzer type: it watches IAM users and roles for unused roles, unused passwords, unused access keys, and unused permissions over a tracking period you choose (1–365 days). It is closer to CIEM last-used than to CSPM.
This page is org enablement and the findings that waste a quarter. It is not a general IAM primer (AWS security best practices) and not how to rank a public resource (how to prioritize cloud vulnerabilities).
External access vs unused access
| External (and internal) access | Unused access | |
|---|---|---|
| Zone | Resource policies, public/* |
IAM users and roles |
| Question | Who outside (or inside) the zone can use this resource? | What standing identity unused? |
| Regional? | Yes — enable per Region | No — one org analyzer is enough |
| Price | External: included | Per user/role per analyzer per month |
| Finding types | ExternalAccess / InternalAccess |
UnusedIAMRole, UnusedIAMUserAccessKey, UnusedIAMUserPassword, UnusedPermission |
Create unused access with the organization as the selected scope, tracking period typically 90 days:
In the IAM console (delegated admin account): Access Analyzer → create analyzer → unused access → organization → tracking period. Or the Access Analyzer API CreateAnalyzer with type ORGANIZATION_UNUSED_ACCESS and unused-access age. Do not invent a second unused analyzer “for prod only” unless you like paying twice for the same roles.
External access still matters: a role can be used daily and still be assumable by a foreign account. Unused access will stay silent. Run both.
Delegated analyzer admin
The management account can register a member as delegated administrator for Access Analyzer (access-analyzer.amazonaws.com in Organizations delegated services). That member creates organization analyzers so findings are not trapped in the management account.
Rules that burn people:
- Changing the delegated admin disables organization analyzers created in the old account. Findings in that account go dark until you point delegation back or the new admin creates new analyzers.
- The management account still sees org findings; do not also create unused analyzers there.
- Access Analyzer uses the service-linked role
AWSServiceRoleForAccessAnalyzerin accounts it analyzes. Do not try to “least-privilege” that SLR.
Tag exclusions on unused analyzers (skip access-analyzer:exclude=true or your own key) are how you keep break-glass roles out of the ticket mill. Document the tag; otherwise every unused-role finding gets an exception in Jira instead.
Findings that lie (service-linked)
Unused permissions are computed from identity-based policies versus last-accessed. Action-level coverage is only the services in IAM’s last-accessed list; everything else is service-level. A role that “never used iam:PassRole” might have used a subset the report cannot see.
Service-linked roles (AWSServiceRoleFor*, /aws-service-role/) ship with managed policies sized for the worst-case AWS service. They will show unused actions forever. Do not “right-size” an SLR. Archive those findings with a rule on path prefix.
Other liars:
- Automation roles used every 91 days (quarterly job) with a 90-day window
- SSO permission sets provisioned as roles that look unused in accounts nobody logs into—but
AdministratorAccessis still standing - Roles younger than the tracking period — absent, not healthy
- Unused IAM user password on a user that only uses access keys (still delete the password)
Filter in the analyzer or in your ticketing join: findingType = UnusedPermission AND resource not like aws-service-role. What remains is CIEM work.
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:111122223333:analyzer/unused-org \
--filter '{"findingType":[{"eq":["UnusedIAMRole"]}]}'
Region in the ARN is the analyzer’s home Region; unused findings are still org-global.
From finding to permission boundary
Do not start by deleting the role. Order:
- UnusedIAMUserAccessKey / Password — disable, wait, delete. Humans should be on Identity Center anyway.
- UnusedIAMRole — confirm no trust from a service you forgot (EventBridge, IdP). Then delete or deny
sts:AssumeRolein a sandbox first. - UnusedPermission on a customer role that attaches
AdministratorAccessor a 4k-line inline policy — attach a permissions boundary that is the intended ceiling (e.g. noiam:*, noorganizations:*, nos3:PutBucketPolicy), then shrink the identity policy in a second PR.
A boundary is the intersection with identity policy. It survives the next engineer attaching PowerUserAccess “temporarily.” SCPs remain the org ceiling; boundaries are per-role (AWS security best practices for SCP vs management account).
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BoundaryNoIamEscalation",
"Effect": "Allow",
"Action": [
"s3:*",
"logs:*",
"cloudwatch:*",
"ec2:Describe*"
],
"Resource": "*"
}
]
}
Attach as the role’s permissions boundary, not as the identity policy. The unused-permission finding should then track the effective set. Re-run after the boundary exists; many UnusedPermission rows die because last-accessed never included iam:CreateUser you just made impossible.
Graph context (the graph, OpenSourceOM): an unused admin role with a trust policy from the internet is not an unused-access cleanup—it is an attack path even at zero last-used. Last-used is not reachability.
Checklist
- One org unused-access analyzer in the delegated admin account; none in management
- External-access analyzers still exist in every active Region
- Tracking period matches how often break-glass and quarterly jobs run
- Tag exclusions for true break-glass; SLRs archived, not “fixed”
- Unused keys/passwords disabled then deleted; users moved to Identity Center
- Customer admin roles: permission boundary first, identity policy shrink second
- Unused + assumable from outside the org treated as a path, not a hygiene ticket