AWS Builder Center

IAM Least Privilege in Practice: How to Tighten an Over-Permissive Policy Step by Step

Most AWS accounts start with broad permissions like AdministratorAccess or "Action": "*" just to get things working, and never tighten them. This guide shows how to take a real over-permissive policy and reduce it to least privilege using IAM Access Analyzer, last-accessed data, and policy validation.

When you’re learning or moving fast, it’s tempting to attach a wide policy and move on. The problem is that a leaked key or a compromised role with broad permissions can do far more damage than one scoped to a single job. Least privilege means granting only the permissions needed, on only the resources needed. Here’s a practical process I follow.
The starting point: a typical over-permissive policy
json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "dynamodb:*",
"Resource": "*"
}
]
}
This lets the role do anything in S3 and DynamoDB, on every bucket and table in the account, including deleting them.
Step 1: Find out what the role actually does
  • Open the role in the IAM console and check the Last accessed tab. It shows which services (and for many services, which actions) were used and when.
  • Make sure CloudTrail is enabled, since it records the API calls the role makes.
  • Let the workload run through a normal cycle first, including monthly or occasional jobs, so you don’t remove something that’s used rarely.
Step 2: Generate a policy from real activity
IAM Access Analyzer can generate a policy based on the CloudTrail activity of a role.
  1. In IAM, open the role and choose Generate policy under the permissions section.
  2. Select the time period and the CloudTrail trail to analyze.
  3. Review the generated policy, which includes only the actions it saw.
Treat the output as a starting draft, not a final answer. Some actions may not appear in CloudTrail logs (certain data events, for example), so you may need to add them manually.
Step 3: Scope the resources
Replace "Resource": "*" with specific ARNs.
json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-bucket/uploads/*"
},
{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query"],
"Resource": "arn:aws:dynamodb:ap-south-1:123456789012:table/Orders"
}
]
}
Now the role can only read and write objects under one prefix and use three actions on one table.
Step 4: Add conditions where they make sense
Conditions narrow permissions further. Common examples:
  • Restrict access to a specific Region with aws:RequestedRegion
  • Require requests to come over HTTPS with aws:SecureTransport
  • Limit access by tag, such as aws:ResourceTag/project
Step 5: Validate before you apply
  • Use IAM Access Analyzer policy validation to catch syntax errors, overly broad statements, and security warnings.
  • Use the IAM Policy Simulator to test whether specific actions are allowed or denied.
  • Apply the new policy in a non-production environment first and run the application’s tests.
Step 6: Roll out safely
  1. Attach the new, tighter policy alongside the old one.
  2. Watch CloudTrail and application logs for AccessDenied errors.
  3. Fix any missing permissions.
  4. Detach the old broad policy once everything works.
Keeping a rollback path (the old policy, ready to reattach) makes this much less stressful.
Step 7: Make it a habit
  • Turn on unused access findings in IAM Access Analyzer to spot unused roles, users, and permissions.
  • Use permission boundaries or service control policies (with AWS Organizations) as guardrails so nobody can grant more than allowed.
  • Prefer IAM roles with temporary credentials over long-lived access keys.
  • Review permissions on a schedule, such as quarterly.
Common mistakes
  • Using "Action": "*" or "Resource": "*" “temporarily” and never fixing it
  • Tightening a policy without testing, then breaking production
  • Forgetting rarely used jobs when analyzing activity
  • Granting iam:* or iam:PassRole broadly, which can allow privilege escalation
Key takeaways
  • Start from real usage, not guesswork.
  • Access Analyzer can draft a policy from CloudTrail activity, but always review it.
  • Scope both actions and resources, and add conditions where useful.
  • Validate, test, then roll out gradually with a rollback plan.
  • Keep reviewing, because permissions drift over time.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article