How to Apply AWS IAM Least Privilege to an Over-Permissive Policy
An IAM policy that grants every action on every resource gives an attacker the keys to your whole AWS account if a credential leaks. Least privilege means scoping each policy to exactly what the role needs - one action, one resource, nothing more.
The problem with wildcard IAM policies
A policy like this is common in early setups and terrifying in production:
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
If the credentials attached to this policy are ever exposed - in a log file, an environment variable, a misconfigured S3 bucket - an attacker has full control of the account. AWS Access Analyzer and security audits flag this immediately.
Write a least-privilege policy in Terraform
The deployer role only needs to read objects from one S3 bucket. Scope the policy to exactly that:
resource "aws_iam_policy" "deployer" {
name = "deployer-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "s3:GetObject"
Resource = "arn:aws:s3:::app-artifacts/*"
}]
})
}
Action is now a single string (s3:GetObject) instead of "*", and
Resource is the specific bucket prefix ARN instead of "*". The deployer
can do exactly one thing on exactly one resource.
Verify the policy after applying
After terraform apply, confirm the live policy document matches what you
intended:
terraform apply -auto-approve
aws iam get-policy-version \
--policy-arn $(terraform output -raw policy_arn) \
--version-id v1 \
--query 'PolicyVersion.Document'
The output should show "Action": "s3:GetObject" and the specific ARN -
not wildcards.
How to scope any IAM policy
- Start from zero - list what the workload actually does, then add only
those permissions. Never start from
"*"and prune. - Scope the Resource ARN - use the full ARN of the specific bucket,
table, secret, or queue. Append
/*only when item-level access is needed (S3 objects, not the bucket itself). - Use IAM Access Advisor - in the AWS console, the Access Advisor tab on a role shows which services it actually used in the last 90 days. Anything not listed is a candidate to remove.
- Use AWS Access Analyzer - generates a least-privilege policy from CloudTrail logs so you can iterate: deploy permissive, log real usage, tighten to what was actually called.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Replacing wildcard Action and Resource with specific permissions in Terraform
- Verifying the live IAM policy document with aws iam get-policy-version
- Using Access Advisor and Access Analyzer to identify unused permissions
FAQ
What does least privilege mean in AWS IAM?
Least privilege means each IAM policy grants only the specific actions on the specific resources that the workload actually needs - no wildcards, no extra permissions. It minimizes blast radius if credentials are compromised.
How do I scope an IAM policy to a specific S3 bucket?
Set Resource to the full bucket ARN with a path prefix - e.g. 'arn:aws:s3:::my-bucket/*' for objects inside the bucket, and 'arn:aws:s3:::my-bucket' for bucket-level actions like ListBucket. Use both if you need both.
How do I find which IAM permissions a role actually uses?
Check the IAM Access Advisor tab in the AWS console for service-level usage over the last 90 days. For action-level detail, use AWS Access Analyzer to generate a least-privilege policy from CloudTrail logs.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →