Least privilege: the basics
What makes an identity "over-permissioned"?
Least privilege means an identity can do exactly what its job requires and nothing more. In IAM that shows up in three shapes: how broad each policy is, how policies are attached, and which broad managed grants are handed out. A policy that says s3:* allows every API call S3 exposes, including DeleteBucket and PutBucketPolicy, not the two reads the workload was written for. A policy attached straight to a user hides the intent that a named group would make obvious. A blanket grant like AWSCloudShellFullAccess bundles a privileged, internet-connected shell under one identity.
AWS Security Hub turns each of these into its own control. IAM.21 flags customer-managed policies that allow full administrative or service-level wildcards. IAM.2 flags policies attached directly to IAM users rather than through groups. IAM.27 flags identities carrying the broad AWSCloudShellFullAccess managed policy. They look like separate findings, but they are one capability: bounding what each identity is authorised to do.
Most over-permission is drift, not intent. A wildcard slips past code review because nobody enumerated what was actually called; a policy gets attached to a user "just for today" and never removed; a broad grant rides in on an onboarding template and copies forward for years. The job is to find every over-broad permission, scope it to what the workload genuinely uses, anchor access to groups and roles so intent is auditable, and then prevent the patterns from creeping back in.
In this lesson you will learn how AWS expresses over-permission across policy breadth, attachment structure and broad managed grants, how to find every over-broad identity in an account using the credential and policy data plus IAM Access Analyzer, and how to scope them down without breaking the workloads that depend on them. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.
The Code Spaces lesson
In 2014 a code-hosting startup called Code Spaces was put out of business in twelve hours. An attacker phished an AWS access key whose policy included Action: "*", used it to delete every S3 bucket, every EBS snapshot, every instance and every backup, then demanded a ransom. There were no backups left to restore from. The wildcard was not the only failure, but it is the one that turned a compromise into an extinction event. The mirror image shows up in over-permissioned individual identities: in one later audit a single contractor user had accumulated 47 directly-attached managed policies, including an AdministratorAccess added "temporarily" during a migration and never removed, on a contract that had ended 18 months earlier.
Finding over-permission across an estate
Priya is auditing IAM after Security Hub fires a batch of findings overnight: several customer-managed policies with service wildcards, a stack of users with policies attached directly, and a group carrying AWSCloudShellFullAccess.
Rather than rewrite anything from scratch, she starts by enumerating what each over-broad policy actually grants and which identities carry it, so she can separate genuine drift from the handful of deliberate exceptions before changing anything.
Pull a wildcard customer-managed policy to confirm exactly how broad it is before scoping it down.
Service wildcards on a production policy are the highest-value target in this group. Enumerate real usage, then scope to it.
How AWS evaluates over-permissiondeep dive
IAM.21 runs against the AWS Config managed rule iam-policy-no-statements-with-admin-access. It parses every customer-managed policy and fails any statement with Effect: Allow plus an Action matching * or service:* on Resource *. It only inspects customer-managed policies, the ones you create, not AWS-managed policies like AdministratorAccess, and it is change-triggered, so it re-evaluates within minutes of a CreatePolicy or CreatePolicyVersion call. IAM Access Analyzer is the right tool to fix it: its policy generator reads CloudTrail for a principal over a window and emits a deny-by-default policy listing only the actions actually called.
IAM.2 evaluates the IAM data model and counts each user that has either a non-empty attached-managed-policies list or a non-empty inline-policies list. Group memberships and assumed roles are not counted, which is the entire point: from the evaluation engine's view a policy on a user and the same policy on a group containing that user are identical at request time, but a group name documents intent and makes offboarding a single removal. There is also a hard ceiling, a user can hold at most 10 attached managed policies and 10 inline policies, which groups raise effectively to unlimited.
IAM.27 is backed by the Config rule iam-policy-blacklisted-check, parameterised with the AWSCloudShellFullAccess ARN, and evaluates users, groups and roles. The danger is the bundle: the policy grants the CloudShell session and file-transfer actions, and the environment itself provides sudo and outbound internet, which together is a textbook data-exfiltration path. The least-privilege replacement is a scoped customer-managed policy that allows the session actions but denies the file upload and download actions, keeping the convenience without the exfiltration leg.
What is the impact of leaving identities over-permissioned?
The direct impact is blast radius. A key with s3:* does not just read data; the same key can delete every bucket, rewrite policies to make data public, and disable versioning to defeat your last line of defence. A user with a broad CloudShell grant has a sudo-capable, internet-connected shell that can stage and exfiltrate anything its other permissions can reach. A workload meant to do one narrow thing has the authority to wipe customer data, and the attacker does not need to escalate, the policy already gave them everything.
The second-order impact is detection. Tight policies generate AccessDenied events the moment something unusual happens, which GuardDuty notices and the on-call gets paged. Wildcard and broad policies silently allow whatever the attacker does, so the first signal is the exfiltration itself, often days later. Least privilege is not only about prevention; it is about giving yourself something to detect.
On the compliance side, SOC 2, ISO 27001, HIPAA and PCI DSS all require role-based access aligned with least privilege, and evidence of it. A finding flagging s3:* on a production role, a user with eleven direct policies nobody can explain, or a long list of broad CloudShell grants is the paper trail an auditor finds quickly, and it becomes a finding in the report whether or not you were ever breached. It is also harder to wave away than most findings, because the failing identities frequently hold the broadest permissions.
How do you scope permissions down safely?
Work the capability as one loop rather than chasing individual findings. The order matters: measure what is actually used before you tighten, anchor access to groups and roles, and prevent the patterns from creeping back in.
1. Inventory over-permission across the estate
List every customer-managed policy with a service or full wildcard (IAM.21), every IAM user with directly-attached or inline policies (IAM.2), and every user, group and role carrying AWSCloudShellFullAccess (IAM.27). Group findings by environment and by what each identity can reach. A single over-broad group fans out across many users, so the real count of decisions is usually far smaller than the count of failing identities.
2. Enumerate real usage, then scope to it
For wildcards, run IAM Access Analyzer policy generation over at least 35 days of CloudTrail for the principal, then replace service:* with the enumerated actions scoped to specific ARNs rather than Resource: "*". For CloudShell, replace the broad grant with a scoped policy that allows the session actions but denies file upload and download. Do not guess; the only reliable way to know what an identity needs is to look at what it actually called.
3. Anchor access to groups and roles
For IAM.2, migrate each direct policy to a named group in the safe order, attach the policy to the group and add the user before detaching from the user, so nobody loses access mid-change. Cluster users who share the same policy set into a purpose-named group. Long-term, move humans off IAM users entirely and onto IAM Identity Center, keeping one documented break-glass exception.
4. Prevent recurrence
Enable the relevant AWS Config rules so a new wildcard, direct attachment or broad grant is flagged within minutes, and attach Service Control Policies that deny creating catch-all policies and re-attaching AWSCloudShellFullAccess. Make least-privilege sign-off a gate on new production deployments, and keep a documented exception register with named owners and review dates for the deliberate exceptions.
# Generate a least-privilege policy from real usage, then promote it to default.
aws accessanalyzer start-policy-generation \
--policy-generation-details principalArn=arn:aws:iam::123456789012:role/DataPipelineWorker
aws iam create-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/DataPipelineWorkerPolicy \
--policy-document file://generated-policy.json --set-as-default
# Move a user's direct policy to a group: attach + add BEFORE detach, so there is no gap.
aws iam attach-group-policy --group-name developers \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam add-user-to-group --group-name developers --user-name nina
aws iam detach-user-policy --user-name nina \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Detach a broad CloudShell grant, then scope down anyone with a genuine need.
aws iam detach-group-policy --group-name developers \
--policy-arn arn:aws:iam::aws:policy/AWSCloudShellFullAccess Quick quiz
Question 1 of 5Security Hub shows wildcard policies, users with directly-attached policies, and a group carrying AWSCloudShellFullAccess. What is the most efficient way to think about them?
You scored
0 / 5
Keep learning
Go deeper on least-privilege policy design and the IAM tooling around it.
- IAM Access Analyzer: generating policies from CloudTrail How to enumerate the actions a principal actually uses and emit a least-privilege policy for IAM.21.
- IAM best practices: grant least privilege via groups Canonical AWS guidance on group-based access and the role of policies in the IAM data model behind IAM.2.
- AWS CloudShell security and IAM permissions How CloudShell sessions, file transfer and the managed policies map to IAM actions, the basis for a scoped IAM.27 replacement.
You can now treat least privilege as one capability rather than a scatter of findings: inventory every over-broad permission, enumerate real usage with Access Analyzer and scope policies to it, anchor access to named groups and roles so intent is auditable, replace broad managed grants with scoped ones, and prevent recurrence with Config rules and Service Control Policies. The Controls this lesson covers section below links every control in this group to its deep page and fix.
Back to the library