Report #24421
[bug\_fix] sts:AssumeRole access denied due to missing ExternalId \(AWS\)
Include the \`ExternalId\` parameter in the \`sts:AssumeRole\` API call, matching the external ID configured in the target role's trust policy. The root cause is that the target IAM role's trust policy includes a condition requiring a specific ExternalId \(a shared secret\) to prevent the confused deputy problem, and the STS AssumeRole request lacks this parameter, failing the condition key evaluation.
Journey Context:
Developer is building a multi-tenant SaaS platform that needs to read resources from customer AWS accounts. They have configured an IAM role in the customer's account with a trust policy allowing the SaaS AWS account to assume it. The developer writes code using AWS SDK for Python \(boto3\) calling \`sts.assume\_role\(RoleArn='arn:aws:iam::CUSTOMER:role/SaaSRole', RoleSessionName='ingestion'\)\`, but it fails with \`AccessDenied: User: arn:aws:sts::SAAS:assumed-role/ServiceRole is not authorized to perform: sts:AssumeRole on resource\`. They first check the IAM policy simulator using the customer's account, which shows the action should be allowed. They verify the trust policy Principal is set to their SaaS account ID. They enable CloudTrail in the customer account and see the AssumeRole event with error 'AccessDenied' and a condition key 'sts:ExternalId' showing 'null'. They search online for 'sts:AssumeRole ExternalId' and find AWS documentation explaining the confused deputy problem. They contact the customer \(or check their own documentation\) and find that the trust policy includes \`Condition: \{'StringEquals': \{'sts:ExternalId': 'unique-secret-123'\}\}\`. They modify their Python code to include \`ExternalId='unique-secret-123'\` in the assume\_role call. The fix works because the trust policy condition is now satisfied, proving the caller knows the shared secret and preventing a rogue AWS account from assuming the role even if they know the ARN.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T19:24:16.708328+00:00— report_created — created