AWS Error Guide: 'AccessDenied: User is not authorized to perform' IAM Permission Failures
Fix the AWS AccessDenied 'is not authorized to perform' error: diagnose missing IAM permissions, explicit denies, SCPs, permissions boundaries, and resource policies.
- #aws
- #troubleshooting
- #errors
- #iam
Overview
An AccessDenied error means IAM evaluated the request and the requesting principal was not granted the action on the resource. AWS denies by default: every API call must be explicitly allowed by an identity-based or resource-based policy, and must not be blocked by an explicit Deny, a Service Control Policy (SCP), or a permissions boundary. When the evaluation comes up short, the API returns AccessDenied.
The message names the principal, the action, and the resource:
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:iam::123456789012:user/build-bot is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::prod-artifacts/release.tar.gz" because no identity-based policy allows the s3:GetObject action
Newer messages append the reason — because no identity-based policy allows..., with an explicit deny in a service control policy, or with an explicit deny in an identity-based policy — which tells you exactly which part of the evaluation chain failed.
It occurs on any API call: the CLI, an SDK, a Lambda execution role, an EC2 instance profile, or a cross-account assumed role.
Symptoms
- A CLI command or SDK call fails with
AccessDeniednaming a specificservice:Action. - The same call works for an admin user but fails for a scoped role.
- Console shows “You don’t have permissions” with an encoded authorization message.
- A Lambda or ECS task logs
AccessDeniedagainst its execution role.
aws s3api get-object --bucket prod-artifacts --key release.tar.gz /tmp/r.tar.gz
An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:iam::123456789012:user/build-bot is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::prod-artifacts/release.tar.gz"
aws sts get-caller-identity
{
"UserId": "AIDAEXAMPLE4ZXBUILD",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/build-bot"
}
Common Root Causes
1. No policy grants the action
The principal simply has no Allow for the action/resource. This is the default state and the most common cause.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/build-bot \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::prod-artifacts/release.tar.gz \
--query 'EvaluationResults[0].[EvalActionName,EvalDecision]' --output text
s3:GetObject implicitDeny
implicitDeny (no matching Allow) confirms a missing grant — attach a policy with that action.
2. An explicit Deny is overriding the Allow
An explicit Deny anywhere always wins, even if another policy allows the action. Often a “guardrail” policy denying actions outside an allowed region or on protected resources.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/build-bot \
--action-names s3:DeleteObject \
--resource-arns arn:aws:s3:::prod-artifacts/release.tar.gz \
--query 'EvaluationResults[0].[EvalDecision,MatchedStatements[0].SourcePolicyId]' --output text
explicitDeny DenyProdDeletes
explicitDeny plus the policy name (DenyProdDeletes) points straight at the blocking statement.
3. A Service Control Policy (SCP) blocks it at the org level
In AWS Organizations an SCP caps what any principal in the account can do. The action may be allowed by IAM but denied by the SCP — the message reads with an explicit deny in a service control policy.
aws organizations list-policies-for-target \
--target-id 123456789012 --filter SERVICE_CONTROL_POLICY \
--query 'Policies[].Name' --output text
FullAWSAccess DenyOutsideUSRegions
If DenyOutsideUSRegions denies the action’s region, no IAM policy can grant it.
4. A permissions boundary caps the role
A permissions boundary sets the maximum permissions an identity can have. Even with an admin policy attached, the effective permissions are the intersection of the policy and the boundary.
aws iam get-role --role-name build-bot-role \
--query 'Role.PermissionsBoundary' --output json
{
"PermissionsBoundaryType": "Policy",
"PermissionsBoundaryArn": "arn:aws:iam::123456789012:policy/DevBoundary"
}
If DevBoundary does not allow s3:GetObject, the action is denied regardless of the attached policy.
5. A resource-based policy denies or omits the principal
For S3 buckets, KMS keys, SQS queues, etc., the resource’s own policy participates in the decision. A bucket policy with an explicit Deny (e.g. requiring aws:SecureTransport or a VPC endpoint) blocks the call even with full IAM rights.
aws s3api get-bucket-policy --bucket prod-artifacts \
--query Policy --output text | python3 -m json.tool
{
"Statement": [
{"Effect": "Deny", "Principal": "*", "Action": "s3:*",
"Condition": {"Bool": {"aws:SecureTransport": "false"}}}
]
}
An HTTP (non-TLS) request hits this Deny. Cross-account access additionally requires the resource policy to name the principal.
6. A condition key does not match (MFA, source IP, tags)
The Allow exists but its Condition is not satisfied — missing MFA, wrong source IP, or a tag mismatch — so the statement does not apply and the request falls through to implicit deny.
aws iam get-user-policy --user-name build-bot --policy-name S3Access \
--query 'PolicyDocument.Statement[].Condition' --output json
[
{"Bool": {"aws:MultiFactorAuthPresent": "true"}}
]
Long-lived access keys never carry MultiFactorAuthPresent=true, so this Allow is skipped.
Diagnostic Workflow
Step 1: Read the full error message
aws s3api get-object --bucket prod-artifacts --key release.tar.gz /tmp/r 2>&1
Note the principal ARN, the exact service:Action, the resource ARN, and — crucially — the trailing because ... clause that names the failing evaluation stage.
Step 2: Confirm which identity is actually calling
aws sts get-caller-identity
A common surprise: the call uses an instance profile or assumed role, not the user you expected. Diagnose permissions for that ARN.
Step 3: Simulate the action against the real identity
aws iam simulate-principal-policy \
--policy-source-arn <PRINCIPAL_ARN> \
--action-names <SERVICE:ACTION> \
--resource-arns <RESOURCE_ARN>
implicitDeny means add a policy; explicitDeny means find and remove/adjust the denying statement reported in MatchedStatements.
Step 4: Decode any console authorization message
aws sts decode-authorization-message --encoded-message <ENCODED_MESSAGE> \
--query DecodedMessage --output text | python3 -m json.tool
The decoded JSON shows allowed: false and the matched/failed statements, including SCP and boundary context.
Step 5: Check org-level and resource-level controls
aws organizations list-policies-for-target --target-id <ACCOUNT_ID> \
--filter SERVICE_CONTROL_POLICY --query 'Policies[].Name' --output text
aws iam get-role --role-name <ROLE> --query 'Role.PermissionsBoundary'
aws s3api get-bucket-policy --bucket <BUCKET> --query Policy --output text
Rule out an SCP, a permissions boundary, and a resource-based Deny before concluding the fix is “add an IAM policy”.
Example Root Cause Analysis
A CI job running as build-bot-role started failing on kms:Decrypt for the artifact bucket’s CMK, with AccessDenied. The role had an attached policy granting kms:Decrypt on the key ARN, so the team was confused.
Simulating the action revealed the real story:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/build-bot-role \
--action-names kms:Decrypt \
--resource-arns arn:aws:kms:us-east-1:123456789012:key/abcd-1234 \
--query 'EvaluationResults[0].[EvalDecision,MatchedStatements[0].SourcePolicyId]' --output text
implicitDeny
implicitDeny despite the attached policy meant the policy was not actually in effect for this resource. The key was a customer-managed KMS key, and KMS keys require the key policy (a resource-based policy) to grant access too — the IAM policy alone is insufficient unless the key policy delegates to IAM.
aws kms get-key-policy --key-id abcd-1234 --policy-name default \
--query Policy --output text | grep -c build-bot-role
0
The key policy never referenced the role and did not delegate to account IAM. Fix: add a statement to the key policy allowing kms:Decrypt for arn:aws:iam::123456789012:role/build-bot-role. The next CI run decrypted successfully.
Prevention Best Practices
- Use
aws iam simulate-principal-policyin CI before granting access — it showsimplicitDenyvsexplicitDenyso you fix the right layer instead of over-granting. - Grant least privilege with specific action and resource ARNs; avoid
"Action": "*"which masks real requirements and triggers SCP denies later. - Document SCPs and permissions boundaries centrally so engineers know the org-level ceiling before debugging an IAM policy that “looks correct”.
- Remember resource-based policies (S3 buckets, KMS keys, SQS, Secrets Manager) participate in the decision — grant on both sides for cross-account and KMS access.
- Read the trailing
because ...clause in the error; it names the failing stage and saves a round of guessing. - For fast triage of opaque denials, the free incident assistant can map an
AccessDeniedmessage to the likely missing grant or blocking policy. More walkthroughs live in the AWS guides.
Quick Command Reference
# Who is actually calling?
aws sts get-caller-identity
# Simulate the exact action against the real principal
aws iam simulate-principal-policy \
--policy-source-arn <PRINCIPAL_ARN> \
--action-names <SERVICE:ACTION> \
--resource-arns <RESOURCE_ARN>
# Decode a console authorization failure
aws sts decode-authorization-message --encoded-message <MSG> \
--query DecodedMessage --output text | python3 -m json.tool
# Check org SCPs and permissions boundary
aws organizations list-policies-for-target --target-id <ACCOUNT_ID> \
--filter SERVICE_CONTROL_POLICY --query 'Policies[].Name' --output text
aws iam get-role --role-name <ROLE> --query 'Role.PermissionsBoundary'
# Resource-based policies (S3 / KMS)
aws s3api get-bucket-policy --bucket <BUCKET> --query Policy --output text
aws kms get-key-policy --key-id <KEY_ID> --policy-name default --query Policy --output text
Conclusion
An AccessDenied “is not authorized to perform” error means the IAM evaluation produced a deny. The usual root causes:
- No identity-based policy grants the action (implicit deny).
- An explicit
Denyin an IAM policy overrides everyAllow. - An Organizations SCP blocks the action at the account level.
- A permissions boundary caps the role below the attached policy.
- A resource-based policy (bucket, KMS key, queue) denies or omits the principal.
- A
Condition(MFA, source IP, tag) on theAllowis not satisfied.
Always confirm the calling identity, then simulate the exact action — implicitDeny vs explicitDeny tells you whether to add a grant or hunt down a blocking policy.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.