AWS Builder Center

Why Your IAM Allow Isn't Working: A Hands-On Lab in Policy Evaluation

support

You attached a policy with s3:GetObject. You still get AccessDenied. Most people respond by adding broader permissions, which is the wrong fix. The real issue is usually that they don't know how AWS decides. This lab walks through the evaluation logic by creating each failure on purpose.
Level: Beginner to intermediate Time: about 40 minutes Cost: effectively free (IAM, one small S3 bucket). Delete everything afterward.

The decision logic in four rules

  1. Everything is denied by default (implicit deny).
  2. An explicit Allow can override the implicit deny.
  3. An explicit Deny always wins, from any policy.
  4. Guardrails (permission boundaries, SCPs, session policies) limit what can be allowed. They never grant anything by themselves.
For a request to succeed, there must be an allow, and no explicit deny, and nothing in a guardrail layer cutting it off.
Policy typeGrants permissions?Acts as a limit?
Identity-based (user, group, role)YesNo
Resource-based (bucket policy, etc.)YesNo
Permission boundaryNoYes, on one user or role
SCP (AWS Organizations)NoYes, on accounts
Session policyNoYes, on one session

Lab setup

  1. Create a test S3 bucket and upload one small file.
  2. Create an IAM user (or an Identity Center permission set) with no permissions, and configure a CLI profile for it, named lab.
  3. Run:
bash
1
aws s3 ls s3://YOUR-BUCKET --profile lab
Expected result: AccessDenied. This is the implicit deny.

Experiment 1: add an allow

Attach a policy allowing s3:ListBucket on the bucket ARN:
json
1
2
3
4
5
6
7
8
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::YOUR-BUCKET"
}]
}
Run the list command again. It works.
Now try downloading the file:
bash
1
aws s3 cp s3://YOUR-BUCKET/file.txt . --profile lab
Expected result: AccessDenied again. Listing and reading are different actions, and they target different resources: ListBucket applies to the bucket, GetObject applies to objects (arn:aws:s3:::YOUR-BUCKET/*).
Observation: this resource-ARN mismatch is one of the most common causes of "my allow isn't working."

Experiment 2: fix it with the correct resource

Add s3:GetObject with the resource arn:aws:s3:::YOUR-BUCKET/*. The download now works.

Experiment 3: an explicit deny beats the allow

Add a second statement denying s3:GetObject on the same objects. Run the download again.
Expected result: AccessDenied, even though the allow is still attached. Remove the deny to restore access.
Observation: this is why a stray deny (often in a group policy or bucket policy) is so hard to find. The allow looks perfect.

Experiment 4: a permission boundary limits the allow

  1. Create a managed policy allowing only s3:ListBucket.
  2. Set it as the user's permission boundary.
  3. Keep the identity policy that allows GetObject.
  4. Try the download.
Expected result: AccessDenied. Effective permissions are the intersection of the identity policy and the boundary.
Observation: boundaries are how you let people create roles without letting them exceed a ceiling. Many teams confuse this with granting.

How to debug instead of guess

  • Read the error message. Newer AccessDenied messages often say what type of policy caused the denial, such as an explicit deny in an identity-based or resource-based policy, or a missing allow.
  • Use the IAM Policy Simulator. Test an action against a principal and see which statement matched.
  • Check CloudTrail. Find the denied event and read the errorCode and errorMessage fields.
  • Check every layer. Identity policy, resource policy, boundary, SCP, and session policy.
One caveat: SCPs do not apply to the Organizations management account, and the policy simulator may not fully model every guardrail type in every scenario. Treat it as a strong hint, then confirm with a real call.

A short troubleshooting checklist

SymptomFirst thing to check
List works, download failsObject-level resource ARN (/*)
Allow exists but still deniedAn explicit deny in any attached policy
Admin-like role can't do somethingPermission boundary or SCP
Works in console, fails in CLIDifferent principal or session policy
Cross-account access failsBoth sides must allow it

Clean up

Delete the IAM user and policies, remove the CLI profile and any access keys, empty and delete the bucket.

Key takeaways

  • Default is deny. Explicit deny always wins.
  • Boundaries and SCPs limit, they do not grant.
  • The action and resource ARN must match, so object actions need /*.
  • Debug with evidence: error text, Policy Simulator, and CloudTrail.
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