Customer-created IAM roles carrying AdministratorAccess
What does ZopNight detect here?
ZopNight flags an IAM role when the AWS-managed AdministratorAccess policy is attached, derived from iam:ListAttachedRolePolicies at discovery time. Roles AWS provisions with admin by design (2 reserved path prefixes plus Control Tower, Identity Center, QuickSetup and StackSets name prefixes) are deliberately excluded, so only customer-created admin roles surface.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-081 |
| Category | compliance |
| Severity | high |
| Metric | none — pure configuration read |
| Source | iam_admin_role.go |
Where it applies
How admin access is derived, not assumed
At discovery time ZopNight lists each role’s attached managed policies and looks for the
AWS-managed policy ARN arn:aws:iam::aws:policy/AdministratorAccess. The result is stamped
onto the role in both polarities (has admin, or does not), so the rule always works from an
observed policy attachment, never from a naming convention or a guess. If that policy
listing fails for a role, no verdict is stamped and the rule abstains for it: a
high-severity least-privilege finding must not fire on missing evidence.
An operator-applied tag of has_admin_access set to true is honoured as a last-resort
fallback, so a team can deliberately mark a role they know is over-privileged even before
discovery re-runs.
The roles AWS built to be admin
Not every AdministratorAccess attachment is a mistake. Two IAM path prefixes are reserved
for roles AWS itself provisions and manages: /aws-service-role/ (service-linked roles)
and /aws-reserved/ (IAM Identity Center). Several name prefixes also mark landing-zone
automation that ships with admin by design: AWSControlTower, aws-controltower-,
AWS-QuickSetup-, AWSReservedSSO_ and stacksets-exec-.
The rule abstains on all of these. Detaching admin from a Control Tower execution role or an Identity Center permission-set role does not harden anything. It breaks the AWS service that owns the role. The path check runs first because it is provider-authoritative; the name-prefix check covers older discovery data that lacks the role path.
Enumerating AdministratorAccess holders
aws iam list-entities-for-policy \ --policy-arn arn:aws:iam::aws:policy/AdministratorAccess \ --entity-filter Role --query 'PolicyRoles[].RoleName' --output tableOne call answers the whole account: every role currently holding the managed admin policy, which you can then sieve through the allowlist above by eye.
Swapping AdministratorAccess for scoped policies
The finding is high severity because an admin role is a blast-radius multiplier: any workload or person who can assume it holds the entire account. The path down is incremental: work out which services the role genuinely calls, let IAM Access Analyzer generate a least-privilege policy from its CloudTrail activity, attach that, and only then detach the admin ARN. Doing the detach first is how outage tickets get opened.