How to read an AWS access denied message
Most AWS services write access denied errors in one format: User: PRINCIPAL is not authorized to perform: ACTION on resource: RESOURCE because CONTEXT. The principal is the ARN of the user, role or role session that made the request; the action is the IAM action that was checked, which is not always named like the API call; the resource is the ARN the action was checked against. The context says which type of policy denied the request, and how:
- Explicit deny -
with an explicit deny in a TYPE policy, sometimes followed by the policy's ARN. ADenystatement matched the request; noAllowanywhere can override it. - Implicit deny -
because no TYPE policy allows the ACTION action. Nothing denied the request, but no statement of that policy type allowed it, and every request is denied unless something allows it.
When several policies deny a request, the message names only one policy type and does not say how many policies denied it - after fixing one, the same call can fail on the next. Some services do not use this format at all; the decoder then goes through the policy types in the order worth checking.
Where to fix each policy type
| Policy type in the message | What it is | Who can fix it |
|---|---|---|
| identity-based | Policies attached to the user or role, directly, through a group or inline | Whoever manages the account's IAM; for IAM Identity Center roles, the permission set |
| resource-based | The resource's own policy: bucket, key, secret, queue, topic or function policy | The resource's owner |
| role trust | The trust policy of the role being assumed (sts:AssumeRole) | The role's account |
| permissions boundary | The maximum permissions set on the user or role | Whoever set the boundary, usually the account's administrators |
| session | A policy passed when the temporary credentials were created (AssumeRole's Policy or PolicyArns, GetFederationToken) | Whatever creates the session: a CI system, a broker, a script |
| service control | An SCP of AWS Organizations on the account, its OUs or the root | The organization's management account |
| resource control | An RCP of AWS Organizations on the resource's account | The organization's management account |
| VPC endpoint | The policy of the VPC endpoint the request went through | The endpoint's owner |
A role session such as arn:aws:sts::111122223333:assumed-role/app-role/i-0abc123 gets its permissions from the role arn:aws:iam::111122223333:role/app-role: fix the role's policies, and name the role, not the session, in a resource or trust policy. From another account, both the resource's policy and the caller's identity-based policy must allow the request.
Amazon S3's access denied messages
Amazon S3 uses the same format, with the resource in quotes and the policy after , with policy ARN: - but only for requests within the same account or AWS organization. A request from another account outside the organization, to a directory bucket, or denied by a VPC endpoint policy gets a bare Access Denied. Instead of a policy type, S3 can also give a reason: public ACLs or public policies blocked by S3 Block Public Access, or uploads with customer-provided keys (SSE-C) blocked by the bucket. Other frequent causes of a bare Access Denied: an object encrypted with a KMS key the caller cannot use, a Requester Pays bucket, and a missing object when the caller has no s3:ListBucket - S3 then answers 403 instead of 404.
Encoded authorization messages (UnauthorizedOperation)
Amazon EC2 and a few other services return UnauthorizedOperation with the details encoded, because they can contain information the caller should not see. Anyone with the sts:DecodeAuthorizationMessage permission can decode it; paste the message into the decoder to get the command, then paste its output back:
aws sts decode-authorization-message --encoded-message ENCODED-MESSAGE --query DecodedMessage --output text The decoded message says whether a Deny matched (explicitDeny), which statements matched, the principal, action and resource, and the value of every condition key in the request - such as the instance type or Region - which is what to compare with the conditions of your policies. It does not say which policy the statement is in.
Frequently asked questions
Is the error message sent anywhere?
No. The message is decoded in your browser: nothing you paste is uploaded, processed on a server or stored. The page only counts that the decoder was used, with what kind of message and which diagnosis, never the text.
My policy allows the action - why is it still denied?
An explicit deny somewhere else wins over any Allow: in another identity-based policy, the resource's policy, an SCP or RCP, a permissions boundary or a session policy. For an implicit deny, the Allow may not match this request: another resource ARN (a bucket instead of its objects), a condition on tags, Region or network, or a policy type that has to allow the action too - a permissions boundary, an SCP, or the resource's policy across accounts.
How is this different from the IAM Permissions Finder?
The IAM Permissions Finder starts from a CLI command or SDK call and lists every action it needs, with a least-privilege policy. The decoder starts from the error: it tells which policy type stopped one request and what to check in it, including the policy types no Allow of yours can fix.
Why does the message name an action I did not call?
IAM actions are not API calls: aws lambda invoke is authorized by lambda:InvokeFunction, and one call can need actions of other services, such as iam:PassRole or kms:Decrypt. The message names the action that was denied, not the call.
References
Troubleshoot access denied error messages
Troubleshoot access denied (403 Forbidden) errors in Amazon S3
AWS STS DecodeAuthorizationMessage
Policy evaluation logic
Cross-account policy evaluation logic
IAM identifiers