Report #76104
[bug\_fix] AccessDenied: User: arn:aws:iam::123456789012:user/ci-user is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::999999999999:role/DeploymentRole
Update the target IAM role's trust policy \(assume-role policy document\) to explicitly include the calling IAM user's ARN as a Principal, or ensure the \`sts:ExternalId\` condition in the trust policy matches the ExternalId being passed in the AssumeRole API call. If using ExternalId, add \`ExternalId: 'shared-secret'\` to the AssumeRole SDK call. Root cause: The trust policy is a resource-based policy that independently controls who can assume the role; even if the caller's IAM policy allows sts:AssumeRole, the trust policy must explicitly allow the caller \(or their account with conditions\). A mismatch in ExternalId or missing Principal ARN results in AccessDenied.
Journey Context:
Developer configures a GitHub Actions workflow to deploy to a production AWS account \(999999999999\) from a CI account \(123456789012\). They create a DeploymentRole in the prod account with a trust policy that specifies \`Principal: \{ AWS: arn:aws:iam::123456789012:root \}\`. They create an IAM user \`ci-user\` in the CI account with an inline policy allowing \`sts:AssumeRole\` on the DeploymentRole ARN. The workflow runs and gets AccessDenied. They check the IAM user's policy simulator in the console, which shows Allow. They check the trust policy in the prod account, which looks correct. After hours, they inspect the trust policy JSON closely and notice a Condition block they forgot about: \`StringEquals: \{ sts:ExternalId: 'prod-secret' \}\`. The CI workflow is not passing an ExternalId in the assume-role step. They update the workflow to pass \`role-external-id: prod-secret\` in the configure-aws-credentials action, and the deployment succeeds.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T10:19:50.503578+00:00— report_created — created