🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

AWS IAM AssumeRole Access Denied: Fix in 5 Minutes

sts:AssumeRole failing with AccessDenied even though the role exists and the policy looks right? Here is exactly how to diagnose trust policy, permission boundary, session policy, and external ID causes.

Shubham4 min read
Share:Tweet

AssumeRole failures are confusing because there are two separate policies involved — the trust policy on the role you're assuming, and the permissions policy on the identity doing the assuming — and AWS gives the same generic AccessDenied for failures in either.

Step 1: Get the Exact Error

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/deploy-role \
  --role-session-name test-session
 
# An error occurred (AccessDenied) when calling the AssumeRole operation:
# User: arn:aws:iam::111111111111:user/ci-user is not authorized to perform:
# sts:AssumeRole on resource: arn:aws:iam::123456789012:role/deploy-role

This tells you who is trying (ci-user) and what they're trying to assume (deploy-role) — but not which of the two policies is blocking it. Check both.

Cause 1: Trust Policy Doesn't Allow the Caller

The trust policy on the target role decides who is allowed to assume it — this is the most common cause.

bash
aws iam get-role --role-name deploy-role --query 'Role.AssumeRolePolicyDocument'
json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::123456789012:role/other-role"    // Only this identity is trusted
    },
    "Action": "sts:AssumeRole"
  }]
}

If ci-user isn't listed as a trusted Principal, the assume fails regardless of what permissions ci-user itself has.

Fix — add the caller as a trusted principal:

json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "AWS": [
        "arn:aws:iam::111111111111:user/ci-user",
        "arn:aws:iam::123456789012:role/other-role"
      ]
    },
    "Action": "sts:AssumeRole"
  }]
}
bash
aws iam update-assume-role-policy --role-name deploy-role --policy-document file://trust-policy.json

Cause 2: Caller Lacks sts:AssumeRole Permission

The calling identity also needs its own IAM policy granting sts:AssumeRole on the target role's ARN — the trust policy alone isn't enough.

bash
aws iam list-attached-user-policies --user-name ci-user
aws iam get-user-policy --user-name ci-user --policy-name inline-policy-name
json
// Caller's own IAM policy must include this
{
  "Effect": "Allow",
  "Action": "sts:AssumeRole",
  "Resource": "arn:aws:iam::123456789012:role/deploy-role"
}

Both directions have to say yes: the target role's trust policy must name the caller, AND the caller's own permissions must allow assuming that specific role ARN.

Cause 3: External ID Required but Not Provided (Cross-Account)

json
// Trust policy with a required ExternalId — common for third-party access patterns
{
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::111111111111:root"},
  "Action": "sts:AssumeRole",
  "Condition": {
    "StringEquals": {
      "sts:ExternalId": "unique-partner-id-12345"
    }
  }
}

If the trust policy has an ExternalId condition and you don't pass it, the assume fails with the same generic AccessDenied.

Fix — pass the external ID:

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/deploy-role \
  --role-session-name test-session \
  --external-id unique-partner-id-12345

Cause 4: MFA Required by the Trust Policy

json
{
  "Condition": {
    "Bool": {"aws:MultiFactorAuthPresent": "true"}
  }
}

If this condition exists and the caller's credentials weren't obtained through an MFA-authenticated session, the assume fails.

bash
# Get MFA-authenticated temporary credentials first
aws sts get-session-token \
  --serial-number arn:aws:iam::111111111111:mfa/ci-user \
  --token-code 123456
 
# Then use those temporary credentials for the AssumeRole call

This is a common source of confusion in CI pipelines — a service account or CI role usually cannot provide MFA, so if the target role's trust policy requires it, CI-based assumption will always fail by design. Either exempt CI roles from the MFA condition or use a separate CI-specific role without it.

Cause 5: Permission Boundary Blocks the Assumed Role's Actions

Even after a successful assume, actions taken with the assumed role's credentials can fail if a permission boundary is attached.

bash
aws iam get-role --role-name deploy-role --query 'Role.PermissionsBoundary'
json
{
  "PermissionsBoundaryType": "Policy",
  "PermissionsBoundaryArn": "arn:aws:iam::123456789012:policy/deploy-boundary"
}

A permission boundary caps what the role can do regardless of its attached policies — if the boundary doesn't include an action, the role can't do it even with an explicit Allow in its own policy. This shows up as AccessDenied on the next API call after a successful AssumeRole, not on the AssumeRole call itself — easy to misdiagnose as a different issue.

Diagnostic Checklist

bash
aws sts get-caller-identity                                          # confirm who you actually are
aws iam get-role --role-name deploy-role --query 'Role.AssumeRolePolicyDocument'   # trust policy
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111111111111:user/ci-user \
  --action-names sts:AssumeRole \
  --resource-arns arn:aws:iam::123456789012:role/deploy-role    # simulates the exact call
aws iam get-role --role-name deploy-role --query 'Role.PermissionsBoundary'

More AWS IAM troubleshooting? Read our AWS ECR push denied authentication fix and AWS S3 403 Forbidden error fix.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments