Skip to content
fixerror.dev
AWS HTTP 403 permission

AWS Error: AccessDeniedException — IAM Permission Denied

stderr text
An error occurred (AccessDeniedException) when calling the GetObject operation:
User: arn:aws:iam::123456789012:user/deploy-bot is not authorized to perform:
s3:GetObject on resource: "arn:aws:s3:::my-prod-bucket/config.json"
because no identity-based policy allows the s3:GetObject action
Every AccessDeniedException tells you the principal, the action, and the resource. Read it before guessing.

AccessDeniedException is AWS’s universal “your IAM principal cannot do this” response. It’s HTTP 403 with a structured message that almost always names the principal, the action, and the resource. The fix is rarely to grant *:* — it’s to read the message, find the gap, and add a tightly-scoped Allow for the exact action the caller needs.

The single most useful debugging habit is to log aws sts get-caller-identity at the start of every failing code path. It tells you the actual principal ARN AWS is evaluating, which is often different from what you expected — especially when running on EC2 (instance profile), Lambda (execution role), or after AssumeRole chains.

Why this happens

  • No identity-based policy grants the action. The IAM user or role attached to the request has no policy that includes an `Allow` for the specific action (e.g., `s3:GetObject`) on the specific resource. AWS denies by default — silence is denial.
  • Explicit Deny in an SCP, permission boundary, or session policy. Even if the identity policy says Allow, an Organizations SCP, a permission boundary, or an `AssumeRole` session policy can override with an explicit Deny. The error message will say `with an explicit deny in a service control policy` or similar — read it carefully.
  • Resource policy blocks the principal. S3 bucket policies, KMS key policies, SNS topic policies, and Lambda resource policies can deny access independent of the caller's IAM. Cross-account calls require BOTH the caller's IAM AND the resource policy to allow the action.
  • Wrong region or partition in the resource ARN. Policies that use `arn:aws:s3:::bucket/*` won't match `arn:aws-cn:s3:::bucket/*` (China) or `arn:aws-us-gov:...` (GovCloud). For regional services, an ARN with the wrong region in a `Resource` block silently fails to match.
  • Condition keys not satisfied. Conditions like `aws:SourceVpc`, `aws:PrincipalTag`, `s3:RequestObjectTag`, or MFA requirements (`aws:MultiFactorAuthPresent`) can cause an Allow to evaluate to false. The denied request matched the action and resource but failed a condition.

How to fix it

Fixes are ordered by likelihood. Start with the first one that matches your context.

1. Read the error message — it names the missing permission

Modern AWS error messages include the principal ARN, the exact action, the resource ARN, and often the policy type that caused the deny (`identity-based policy`, `resource-based policy`, `permission boundary`, `service control policy`). Copy those three values straight into a new policy statement.

deploy-bot-policy.json json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowConfigRead",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-prod-bucket/config.json"
    }
  ]
}

2. Use IAM Access Analyzer or the policy simulator to confirm

Before redeploying, run the IAM Policy Simulator or `aws accessanalyzer` against your draft policy with the exact principal, action, and resource. It surfaces SCP and boundary denies that the console hides.

simulate.sh bash
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/deploy-bot \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::my-prod-bucket/config.json

3. Check resource policies for cross-account or service-to-service calls

Cross-account S3, KMS, and SNS calls need both sides to allow. Add the calling principal to the resource's policy, and don't forget KMS — encrypted S3 objects require `kms:Decrypt` on the key as well as `s3:GetObject` on the bucket.

bucket-policy.json json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCrossAccountRead",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:role/deploy-bot" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-prod-bucket/*"
    }
  ]
}

4. Inspect the assumed role's session policy

`sts:AssumeRole` can attach a session policy that scopes down the role's permissions. If your role has `s3:*` but you assumed it with a session policy of `s3:ListBucket` only, GetObject will deny. Log the session policy at the call site to debug.

5. Turn on CloudTrail and search for the exact denied call

CloudTrail event `errorCode: AccessDenied` records the principal, action, resource, and the policy type that denied the call. Filter by `userIdentity.arn` and `eventName` to find the exact deny event and confirm whether it was identity-based, resource-based, or SCP-based.

Detection and monitoring in production

Alarm on `AccessDenied` events in CloudTrail at higher than baseline rates. A spike usually means a recent IAM change, a new SCP, or a rotated role that lost a policy attachment. Tag your CloudWatch alarm with the principal ARN so you know which service is degraded.

Related errors

Frequently asked questions

What's the difference between AccessDenied and AccessDeniedException? +
`AccessDenied` is the S3-specific REST error code; `AccessDeniedException` is the standardised exception used by most other AWS services (Lambda, DynamoDB, KMS, etc.). Both indicate the same underlying problem — IAM evaluation returned Deny — and both have HTTP status 403.
Why does the AccessDeniedException message sometimes hide the reason? +
For sensitive resources, AWS may suppress the explanatory portion (e.g., it won't say which SCP denied you across an Organizations boundary). Enable CloudTrail data events and check the `errorMessage` field there — it often contains more detail than the API response.
I added the policy but still get AccessDeniedException. What''s going on? +
Three usual culprits: (1) IAM is eventually consistent — wait 30-60 seconds after attaching, (2) a permission boundary or SCP is denying despite your Allow, or (3) the policy is attached to a different principal than the one making the call. Print the caller identity (`aws sts get-caller-identity`) at the call site to confirm.
Does `Effect: Deny` always win over `Effect: Allow`? +
Yes. AWS policy evaluation is "explicit deny wins, then default deny, then explicit allow." A single Deny anywhere in the evaluation chain (identity policy, resource policy, SCP, boundary, session policy) blocks the request even with multiple Allows.
Can `s3:*` in my policy still produce AccessDeniedException for s3:GetObject? +
Yes — if the bucket policy or KMS key policy denies, if your permission boundary doesn't include s3:GetObject, or if the object is encrypted with a KMS key your role can't decrypt. `s3:*` only grants S3 actions; it does not grant `kms:Decrypt`.
How do I debug an SCP-based AccessDeniedException without org-admin access? +
You can't see SCP contents without org-admin, but you can confirm an SCP is the cause: the error message will explicitly say `with an explicit deny in a service control policy`. Escalate to your AWS Organizations admin with the principal ARN, action, and resource to get the SCP exception added.
Why do I get AccessDeniedException calling KMS even though my role has full S3 access? +
S3 server-side encryption with KMS (SSE-KMS) requires `kms:Decrypt` on the KMS key — separate from S3 permissions. The KMS key policy must also allow your principal. Add `kms:Decrypt` and `kms:GenerateDataKey` for write paths.
Are there tools that automatically suggest the missing IAM policy? +
Yes. IAM Access Analyzer's policy generation feature watches CloudTrail for 90 days and emits a least-privilege policy. The IAM console's "last accessed" data shows which actions a role actually used. Both are better than guessing or copying overly broad templates.

When to escalate to AWS support

Escalate to your AWS account or org admin if (a) the error message points at an SCP you don't control, (b) a permission boundary you didn't create blocks the call, or (c) you've reproduced the deny with the IAM Policy Simulator and confirmed all your policies allow but CloudTrail still shows Deny — that's a service-side bug worth a support case with the request ID.