The short answer
Least privilege in AWS is best achieved from access data rather than from first principles: IAM Access Analyzer can generate a policy from the actions a role has actually used over a period. Roll changes out by monitoring first, then applying in non-production, then production — and use permissions boundaries to cap what roles can grant themselves.
Key takeaways
- Generate policies from observed usage, not from imagination
- Wildcard actions and resources are where privilege accumulates
- Permissions boundaries cap privilege escalation
- Roll out via monitor → non-prod → prod, never all at once
Part of our guide to Cloud security: the configuration is the attack surface.
Every AWS estate of any age has roles carrying permissions nobody can justify. It happens the same way each time: something did not work, a broader policy fixed it, and nobody came back to narrow it once the deadline passed.
Find where privilege actually sits
Start with data rather than a review meeting. Three sources matter:
IAM Access Analyzer generates a policy based on the actions a role has actually called over a period, using CloudTrail. This is the single most useful input — it turns a subjective argument into a concrete diff.
Last-accessed information shows which services a role has and has not touched. Services never used in ninety days are strong candidates for removal.
Credential reports surface unused users, keys that have never been rotated, and accounts without MFA.
What to look for
- Wildcards in actions.
s3:*grants deletion and policy modification alongside the read access someone actually needed. - Wildcards in resources. An action scoped to
*applies to every bucket, table or key in the account. - Managed policies attached wholesale. AWS-managed policies are convenient and deliberately broad. Several are far wider than any single application needs.
- Roles that can modify IAM. Permission to create roles or attach policies is permission to grant yourself anything. Treat it as administrative.
- Long-lived access keys. Every one is a credential that can leak. Replace with roles and federation.
Roll changes out without an outage
Tightening IAM is one of the easier ways to cause a production incident, because the failure appears at whatever moment the removed permission was next needed — which may be month-end, not deployment day.
A safe sequence:
- Observe. Let Access Analyzer collect a period long enough to cover monthly and quarterly jobs. Ninety days is safer than thirty for anything with periodic work.
- Generate and review. Produce the candidate policy and have the owning team check it against what they know the system does. Automated generation misses paths not exercised during the window.
- Apply in non-production and run a full cycle, including batch jobs.
- Apply in production during a period when someone is watching, with the previous policy version ready to restore.
Do a handful of roles at a time. Tightening fifty roles in one change makes it impossible to attribute the resulting failure.
Prevent the drift returning
Permissions boundaries cap the maximum privilege a role can have, regardless of what policies are attached. They are the most effective control against privilege escalation, and they let you delegate role creation to teams safely.
Service control policies set organisation-wide limits nothing inside an account can exceed.
Policy checks in CI. Since IAM should be defined in Terraform, scan those definitions before they are applied. Catching a wildcard at review is far cheaper than finding it in an audit.
Realistic expectations
Perfect least privilege is not the goal — the effort curve gets steep and the marginal risk reduction small. Removing administrative permissions from application roles, eliminating static keys, and scoping resource wildcards to specific ARNs captures most of the benefit for a fraction of the work.