An AWS AccessDenied error tells you that a request failed authorisation, but the fix depends on which of up to eight policy types said no, and guessing usually ends with an over-broad Allow. This guide shows how to read the denial message, which policy type to inspect for each message form, how to decode encoded authorisation messages, and how to confirm the fix in CloudTrail and the IAM policy simulator. It covers cross-account requests, assumed-role sessions, S3 and KMS. It is written for cloud engineers, platform teams and identity practitioners who own IAM for humans and for machine identities such as roles, pipelines and workloads.
Key takeaways
- Read the tail of the message first. "with an explicit deny in a ... policy" and "because no ... policy allows" name the policy type, and for explicit denies often the policy ARN, which tells you exactly where to look.
- Explicit and implicit denies have opposite fixes. An implicit deny needs a missing Allow added in the named policy type; an explicit deny needs a Deny statement found and narrowed, never an extra Allow, because an Allow cannot override a Deny.
- Identify the real caller before reading any policy. Most "the policy is correct but it still fails" cases are the wrong principal: a different role session, a stale credential in the chain, or a pipeline running as a role nobody expected.
- Cross-account and KMS have extra gates. Cross-account requests need an Allow in both accounts, and KMS needs the key policy to allow the caller or to delegate to IAM before any IAM policy has effect.
- Fix narrowly and prove it. Reproduce with the simulator or a test call, change one statement, confirm the CloudTrail
errorCodedisappears, and never close a ticket with"Action": "*".
How AWS reaches a deny
The default is deny. A request succeeds only when some policy allows it, nothing explicitly denies it, and every filtering policy also permits it. The relationships that matter for troubleshooting:
- Explicit deny wins everywhere: a
Denyin any applicable policy overrides anyAllow, including Deny statements in SCPs, RCPs, permissions boundaries, identity-based and resource-based policies. - Identity and resource policies are a union in the same account: within one account, an Allow in either the identity-based policy or the resource-based policy is enough.
- Boundaries, session policies, SCPs and RCPs are intersections: they grant nothing. They only limit what identity-based policies can grant, so the action must be allowed by the identity policy and by each filter.
- Cross-account is two evaluations: the trusted account (where the principal lives) and the trusting account (where the resource lives) are evaluated separately, and the request is allowed only if both return Allow. A Deny in either account denies the request.
- Temporary credentials cannot exceed their source: an assumed-role session has the intersection of the role's identity-based policies and any session policies passed at
AssumeRoletime (inlinePolicyor up to 10PolicyArns).
Reading the error message
Most AWS services return a message in this shape: User: <principal ARN> is not authorized to perform: <action> on resource: <resource ARN> because <context>. The "on resource" part is not always present, and AWS documents that some services do not use this format at all. Where the context is present, it takes one of two forms, quoted here from the IAM troubleshooting page:
- Explicit deny: "with an explicit deny in a service control policy", optionally followed by the policy ARN. Inline policies have no ARN, so the type is named without one.
- Implicit deny: "because no identity-based policy allows the codecommit:ListRepositories action", with the policy type substituted.
Two limits matter. If several policy types deny the request, only one type appears in the message. If several policies of the same type deny it, the message does not say how many. Fixing the named policy can therefore reveal a second denial from another type, which is normal and not a sign the first fix failed.
Message to policy type reference
| Phrase in the message | Policy type | Where to look | Typical fix |
|---|---|---|---|
| no identity-based policy allows | Identity-based (user, group or role) | Policies attached to the principal; for a session, the role's policies | Add a scoped Allow for the action and resource ARN |
| explicit deny in an identity-based policy | Identity-based | Deny statements, often with conditions such as aws:SourceIp or aws:RequestedRegion | Correct the condition or exempt the specific case |
| no resource-based policy allows | Resource-based (bucket, queue, topic, secret, key) | The resource's policy in the resource account | Name the principal and action; required for cross-account |
| no permissions boundary allows | Permissions boundary | The managed policy set as the principal's boundary | Extend the boundary, or confirm the action is meant to be out of bounds |
| no session policy allows | Session policy | The Policy or PolicyArns passed by the caller or broker | Change what the broker passes at assume time |
| service control policy | SCP (AWS Organizations) | SCPs on the account, its OUs and the root | Request a scoped exception from the organisation owners |
| resource control policy | RCP (AWS Organizations) | RCPs on the resource's account path | As above; RCPs act on the resource side |
| VPC endpoint policy | VPC endpoint policy | The endpoint the request travelled through | Add the action or resource to the endpoint policy |
no role trust policy allows sts:AssumeRole | Role trust policy | The target role's trust relationship | Name the caller as principal; check conditions such as sts:ExternalId |
When the message tells you nothing
- Encoded authorisation message: some operations, notably EC2 calls failing with
Client.UnauthorizedOperation(HTTP 403), return an encoded message instead. Decode it withaws sts decode-authorization-message --encoded-message <value>. The output shows whether the deny was explicit, the matched statements, the principal, action, resource and the condition key values in the request context. The caller needssts:DecodeAuthorizationMessage; AWS encodes the message deliberately because it can reveal privileged details, so grant that permission to operators, not to every workload. The decode fails withInvalidAuthorizationMessageif the value has expired or contains line breaks. - Bare "Access Denied": Amazon S3 returns only a generic message for cross-account requests from outside your organisation, for directory buckets, and for some VPC endpoint cases. Work from CloudTrail and the policy checklist below.
Step-by-step diagnosis
- Capture the exact request. Record the full error text, the API action (for example
s3:GetObject, not "S3 read"), the resource ARN, the region and the request ID (x-amz-request-idfor S3). The action name in the error is the IAM action to grant, which is not always the API call name the SDK shows. - Confirm the caller. Run
aws sts get-caller-identityfrom the same environment, process and profile. Compare the returned ARN with the principal in the error. Anassumed-role/<role>/<session>ARN means the role's policies apply; a user ARN where you expected a role means the credential chain found static keys first (environment variables or a credentials file). See the AWS Access Keys Security Guide. - Classify the denial. Use the message to policy type table. If the message names a type and an ARN, open that policy and search for Deny statements that match the action. If it names a type with no ARN, search every policy of that type (all SCPs in the account path, all inline policies on the role).
- Check conditions, not just actions. Many denials come from condition blocks:
aws:SourceIpfails when the call comes through a VPC endpoint or an AWS service,aws:RequestedRegionblocks a region,aws:PrincipalOrgIDexcludes a partner account, and tag conditions (aws:ResourceTag/,aws:RequestTag/) fail when a tag is missing. AWS notes that condition key names are case-insensitive, so two keys differing only by case can collide. - Check the resource side. For services with resource-based policies (S3, SQS, SNS, Secrets Manager, KMS, Lambda), read the resource policy. Same account: either side may grant. Cross-account: both must grant, and the resource policy must name the principal or its account.
- Check the guardrails. Permissions boundary on the principal, session policies passed by the broker, SCPs and RCPs on the account path, and the VPC endpoint policy if the request used an endpoint. AWS notes that when a VPC endpoint policy denial is not logged in CloudTrail, the endpoint owner may be a different account from the caller or the target.
- Reproduce before changing anything. Use the IAM policy simulator (
aws iam simulate-principal-policy) with the same action, resource ARN and context keys. Then make one narrow change and re-run the original request. - Confirm in CloudTrail. The failed event carries
errorCodeanderrorMessage; the fix is proven when the sameeventNamefrom the same principal succeeds and no new denial appears from another policy type.
Service-specific causes: S3 and KMS
Amazon S3
| Cause | How to recognise it | Fix |
|---|---|---|
| Bucket policy Deny | Explicit deny; common conditions are aws:SecureTransport, aws:SourceVpce, encryption headers | Satisfy the condition (HTTPS, correct endpoint, required header) rather than remove the Deny |
| Object owned by another account | Bucket owner gets denied on objects uploaded cross-account while ACLs are enabled | Set Object Ownership to Bucket owner enforced (ACLs disabled) after moving ACL grants into the bucket policy |
| Block Public Access | Message ends "because public policies are prevented by the BlockPublicPolicy setting in S3 Block Public Access" (or the BlockPublicAcls equivalent) | Use a non-public grant (named principals, CloudFront origin access control); all four settings are on by default for new buckets since April 2023 |
| SSE-KMS key permissions | Upload or download fails although S3 permissions look right | Grant kms:GenerateDataKey for uploads and kms:Decrypt for downloads and multipart uploads, in both IAM and the key policy; the AWS managed key aws/s3 only works for principals in the key's own account |
AWS KMS
- Key policy first: the key policy is the primary control. IAM policies only have effect if the key policy contains a statement allowing the account root principal (the default "enable IAM policies" statement). If that statement was removed, no IAM policy can grant access to the key.
- Cross-account keys: the key policy in the key's account must allow the caller's account, and an IAM policy in the caller's account must also allow the role to use the key. Both are needed.
- Grants and guardrails: check grants on the key (
aws kms list-grants) and remember that SCPs and VPC endpoint policies can still deny a request that the key policy and IAM allow. - Condition keys:
kms:ViaServicerestricts use to calls made through a named service, andkms:EncryptionContext:keys require a specific encryption context; a direct SDK call or a different context then fails. See the AWS KMS Security Guide.
Assumed roles, sessions and cross-account requests
Machine identities have layered permissions: trust policy, role policy, boundary, session policy and organisation guardrails.
- Cannot assume the role: "no role trust policy allows the sts:AssumeRole action" means the target role's trust policy does not name the caller, or a condition (
sts:ExternalId,aws:PrincipalOrgID,aws:SourceIdentity) is not met. The caller also needssts:AssumeRoleon the role ARN in its own identity policy when the accounts differ. See the AWS STS and AssumeRole Guide. - Assumed but still denied: check for session policies. Brokers, CI systems and identity platforms often pass a session policy to scope down a role; the session can then do less than the role.
- Federated workloads: errors at the token exchange step (
InvalidIdentityToken,IDPRejectedClaim) are not AccessDenied problems. Use the Multi-Cloud Workload Identity Troubleshooting Guide for those. - Cross-account resource access: the principal needs an identity-based Allow in its account and the resource needs a resource-based Allow naming that principal in the other account. Where a service has no resource-based policy, assume a role in the resource account instead.
- Organisation guardrails across accounts: SCPs restrict principals in member accounts, and RCPs restrict resources in member accounts. A cross-account call can therefore be blocked by the caller's SCP or the resource owner's RCP. See the AWS Organizations and SCPs Guide and the AWS Resource Control Policies and Data Perimeter Guide.
Tools for diagnosis: choosing the right one
| Tool | What it shows | Limits | Use it when |
|---|---|---|---|
| Error message | Policy type, sometimes policy ARN | Only one type shown; some services give no context | Always first |
sts decode-authorization-message | Explicit or implicit, matched statements, request context | Only for operations that return an encoded message; needs a sensitive permission | EC2 and other encoded 403s |
| IAM policy simulator | Allow or deny per action and resource, matched statement | No RCP support; differs from live results for VPC endpoint policies, role chaining and multiple resource policies; only auto-fills some context keys | Testing a change before applying it |
| CloudTrail | errorCode, errorMessage, userIdentity, sourceIPAddress, vpcEndpointId | Data events (S3 object, Lambda invoke) must be enabled to see those denials | Finding the real caller and confirming the fix |
Small teams can resolve most cases with the error message, get-caller-identity and CloudTrail Event history. Large organisations should group denials by principal, action and policy type in a SIEM, because one broken guardrail usually hits many roles. Never loosen an SCP, RCP or boundary to unblock one workload: request a scoped exception tested on a sandbox OU. See the AWS CloudTrail Guide and the IAM Access Analyzer Guide.
Fixing safely: change, test, roll back
- Write the smallest grant: the exact action from the error, the specific resource ARN, and any conditions the rest of your estate relies on. Validate the policy with Access Analyzer policy validation before saving.
- Test in a lower environment or with the simulator: run
simulate-principal-policywith the original action, resource and context, including keys such asaws:RequestedRegion; the simulator denies when a referenced key has no value. - Apply as a new policy version: customer managed policies keep up to five versions, so a bad change can be rolled back by setting the previous version as default. Inline policies have no history, which is one reason to avoid them for production roles.
- Verify and record: confirm the CloudTrail event now succeeds, log the ticket with the policy type and the change, and schedule a review to remove the grant if it was temporary.
Where identity fits
AccessDenied is an identity signal, not only a support ticket. For machine identities a denial means a deployment change, drift (a guardrail tightened) or stolen credentials probing what they can reach. Repeated denials across many services from one access key or role, especially List* and Describe* calls, match discovery behaviour such as ATT&CK T1580 Cloud Infrastructure Discovery and T1526 Cloud Service Discovery performed with valid cloud accounts (T1078.004). Treat a burst of denials from a workload identity that normally makes a stable set of calls as a detection, not a fix request. See the How to detect AWS access key use from unusual locations guide and the How to Find IAM Privilege Escalation Paths in AWS guide.
Detection and measurement
- Denial events: alert on CloudTrail events with
errorCodeofAccessDenied,AccessDeniedExceptionorClient.UnauthorizedOperation, grouped byuserIdentity.arn, and flag principals with denials across many distincteventSourcevalues in a short window. - Guardrail hits: track denials whose message names an SCP or RCP; a spike after an organisation policy change shows the blast radius.
| Metric | What it shows | Healthy direction |
|---|---|---|
| Median time to resolve an AccessDenied ticket | Whether teams can read denials without escalation | Falling |
| Share of fixes that add a wildcard action or resource | Whether fixes are creating over-privilege | Near zero |
| Workload roles with denials outside their normal API set | Possible credential misuse or unreviewed code changes | Investigated within one day |
| Temporary grants removed on schedule | Whether break-glass fixes are cleaned up | 100 percent |
Common failure patterns
- Fixing with a wildcard: adding
"Action": "s3:*"or"Resource": "*"to make the error go away. Broad role permissions turn a single foothold into a data breach, as the Capital One breach showed when a firewall's role could list and read far more buckets than it needed. - Adding an Allow against an explicit Deny: it never works, and leaves a useless grant behind. Find the Deny and fix its condition.
- Debugging the wrong principal: editing the role the team expects while the process is using an old access key or a different profile. Always run
get-caller-identityfrom the failing context. - Forgetting the KMS key: S3, Secrets Manager or EBS permissions are fixed but the customer managed key policy does not allow the role, or the key policy no longer delegates to IAM.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev 5 | AC-3 Access Enforcement; AC-6 Least Privilege | Policy evaluation is access enforcement; narrow fixes keep least privilege |
| NIST SP 800-53 Rev 5 | AU-6 Audit Record Review, Analysis, and Reporting | Reviewing CloudTrail denials for misuse |
| NIST CSF 2.0 | PR.AA-05; DE.CM-03 | Permissions managed with least privilege; monitoring of account activity |
| MITRE ATT&CK v20 | T1078.004 Cloud Accounts; T1580 Cloud Infrastructure Discovery; T1526 Cloud Service Discovery | Denial bursts from valid credentials during discovery |
Practitioner checklist
- Record the exact action, resource ARN, region, request ID and full error text before changing anything.
- Run
aws sts get-caller-identityfrom the failing context and compare it with the principal in the error. - Classify the denial as explicit or implicit and identify the policy type from the message.
- For encoded 403s, decode with
sts decode-authorization-messageusing an operator role, not the workload. - Check conditions (
aws:SourceIp,aws:RequestedRegion,aws:PrincipalOrgID, tag keys) as well as actions. - For cross-account and KMS requests, confirm both sides grant access and the key policy delegates to IAM.
- Reproduce with the IAM policy simulator, keeping its RCP and VPC endpoint limits in mind.
- Apply the smallest grant as a new managed policy version so you can roll back.
- Monitor denial bursts from workload identities as a security signal.
Standards and references
- AWS: Troubleshoot access denied error messages
- AWS: Policy evaluation logic
- AWS: Cross-account policy evaluation logic
- AWS: DecodeAuthorizationMessage API reference
- AWS: Troubleshoot access denied (403 Forbidden) errors in Amazon S3
- AWS: Troubleshooting AWS KMS key access
- AWS: Testing IAM policies with the IAM policy simulator
- AWS: CloudTrail record contents
Related NHI Mgmt Group resources: AWS IAM Policies and Roles Guide · AWS STS and AssumeRole Guide · AWS Organizations and SCPs Guide · Kubernetes RBAC Forbidden Errors Troubleshooting Guide