Why AWS feels so stubborn
Every request you make to AWS starts out denied. Nothing is allowed until a policy says so. That sounds strict, and it is, but it also means "denied" is the normal answer and the interesting question is always which check said no.
AWS asks the same questions in the same order every time. Once you know the order, an AccessDenied stops being a mystery and becomes a checklist. Two of the checks, resource control policies (RCPs) and service control policies (SCPs), are set for a whole AWS organization rather than for one user or bucket, so you may not be able to see them unless you administer the organization.
1. Any explicit Deny?. AWS looks at every policy that applies. If one Deny matches, the answer is no, whatever else says yes.
Across two accounts there is one more rule: both sides must say yes. Your own IAM policy must allow the action, and the bucket (or other resource) in the other account must allow you. A bucket policy that names your role is not enough by itself.
The two phrases worth memorising
Most AccessDenied messages end with one of these:
- with an explicit deny in a service control policy (or identity-based policy, resource-based policy, permissions boundary, session policy). Something said no on purpose. Find that statement and narrow it. Adding another Allow will never override it.
- because no identity-based policy allows the s3:GetObject action (or any other type and action). Nothing said yes. Add the Allow, and check its Resource carefully: listing a bucket and reading an object in it are actions on different ARNs (Amazon Resource Names): one for the bucket itself and one for the objects inside it.
One catch: when several policy types deny the same request, the message names only one of them. Fix it, and the next one may appear. That is not AWS being difficult; it is the next check in the list.
Check who you are before anything else
aws sts get-caller-identity
This needs no permissions at all, so it works even when everything else is denied. Compare the ARN it prints with the principal your policies actually name. A leftover AWS_PROFILE, an access key in your environment or an instance role you forgot about is a very common answer.
Then test the decision without making the request again:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/YourRole \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::your-bucket/your-object
The simulator includes permissions boundaries and SCPs. It does not evaluate RCPs, and it does not fetch the resource's own policy for you, so a "yes" from it is not the whole story for a bucket in another account.
Where to go next
The AWS IAM guide has an explorer: set each policy to allow, deny or say nothing, and watch the decision walk this same path, with the exact message S3 would return. If the trouble is GitHub Actions assuming a role, the OIDC trust policy checker shows which condition rejects the job.
Mentioned in this article
- Amazon S3 explainedAWS guide
- Not authorized to perform sts:AssumeRoleWithWebIdentity — GitHub ActionsProblem · GitHub Actions
Tagged with
Enjoyed this article?
Get more DevOps insights delivered to your inbox.
Get new posts by email
Subscribe to get an email when a new blog post is published. Skip anytime.
No spam, unsubscribe anytime.
Related Posts
Discussion
0 comments
Sign in to join the conversation.
Be the first to comment
Start a conversation about this post



