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.
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
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-roleThis 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.
aws iam get-role --role-name deploy-role --query 'Role.AssumeRolePolicyDocument'{
"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:
{
"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"
}]
}aws iam update-assume-role-policy --role-name deploy-role --policy-document file://trust-policy.jsonCause 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.
aws iam list-attached-user-policies --user-name ci-user
aws iam get-user-policy --user-name ci-user --policy-name inline-policy-name// 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)
// 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:
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/deploy-role \
--role-session-name test-session \
--external-id unique-partner-id-12345Cause 4: MFA Required by the Trust Policy
{
"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.
# 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 callThis 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.
aws iam get-role --role-name deploy-role --query 'Role.PermissionsBoundary'{
"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
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
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
AWS IAM Permission Denied Errors — How to Fix Every Variant (2026)
Getting 'Access Denied' or 'is not authorized to perform' errors in AWS? Here's how to diagnose and fix every IAM permission issue — EC2, EKS, Lambda, S3, and CLI.
AWS IRSA Permission Denied in Kubernetes — Fix
Your Kubernetes pod can't access AWS services even though IRSA is configured. Here's every reason IRSA fails and exactly how to debug and fix each one.
AWS S3 403 Forbidden Error: Fix in 5 Minutes
Getting 403 Forbidden from S3 even though you're sure the bucket policy is right? Here is exactly how to diagnose IAM policy, bucket policy, ACL, block-public-access, and KMS key permission causes.