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.
- In IAM, open the role and choose Generate policy under the permissions section.
- Select the time period and the CloudTrail trail to analyze.
- 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
- Attach the new, tighter policy alongside the old one.
- Watch CloudTrail and application logs for
AccessDeniederrors. - Fix any missing permissions.
- 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:*oriam:PassRolebroadly, 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.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article