Report #26326
[bug\_fix] AccessDenied: User is not authorized to perform: sts:AssumeRole on resource \(Missing ExternalId in cross-account AssumeRole\)
Add the \`ExternalId\` parameter to the STS AssumeRole API call \(or AWS CLI \`--external-id\` flag\), ensuring the value matches the \`ExternalId\` condition set in the target role's trust policy. Also verify the trust policy allows the source account or role ARN.
Journey Context:
A developer is building a SaaS data connector that needs to read S3 buckets in customers' AWS accounts. Following AWS best practices for third-party access, the developer requires customers to create a role with a trust policy that includes an \`sts:ExternalId\` condition to prevent the confused deputy problem. The developer writes Python code using boto3 to assume the role: \`sts.assume\_role\(RoleArn=customer\_role\_arn, RoleSessionName='saas-connector'\)\`. The call fails with \`AccessDenied\`. The developer checks the IAM policy of the calling Lambda execution role \(it has \`sts:AssumeRole\` permission on \`\*\`\). The developer checks the customer's trust policy: it explicitly lists the source account ID and the exact ExternalId string 'saas-123'. The developer realizes the code never passes the ExternalId parameter. After updating the code to include \`ExternalId='saas-123'\` in the \`assume\_role\` call, the assumption succeeds. The fix works because the trust policy condition \`sts:ExternalId\` is only satisfied when the AssumeRole request includes the matching ExternalId parameter, proving the caller is the intended third-party and not a malicious actor attempting to exploit the trust relationship.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T22:35:23.725718+00:00— report_created — created