fix(bedrock): surface web identity token aud/iss on InvalidIdentityToken#31412
Conversation
|
|
Greptile SummaryThis PR improves the debugging experience for Bedrock OIDC auth failures by surfacing the JWT's
Confidence Score: 5/5Safe to merge. The change is purely additive on the failure path and cannot affect the happy path. The new code only runs when STS has already rejected the token, so it cannot change credentials-retrieval behavior on success. The JWT decode is guarded by a broad except (ValueError, ValidationError) with a None fallback, meaning any malformed or non-JWT token degrades silently to the original error message. aud and iss are public JWT claims; no signature material or secret is exposed. The four new unit tests are fully mocked and directly exercise the claims-surfacing logic end-to-end. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/llms/bedrock/base_aws_llm.py | Adds _unverified_web_identity_audience to extract aud/iss claims from the JWT payload (no signature read), then wraps the assume_role_with_web_identity call to catch InvalidIdentityTokenException and raise an AwsAuthError(401) that includes those claims. Implementation is correct: base64url decoding with padding fix, Pydantic validation, graceful None fallback on any parse error. |
| tests/test_litellm/llms/bedrock/test_web_identity_session_policy.py | Adds TestInvalidIdentityTokenSurfacesAudience with four focused unit tests that mock boto3 and get_secret. Tests verify the 401 status, presence of aud, iss, and the original STS reason in the error. No real network calls; fully mock-based. |
Reviews (1): Last reviewed commit: "fix(bedrock): surface web identity token..." | Re-trigger Greptile
Greptile SummaryWhen
Confidence Score: 5/5Safe to merge — the change only affects the error path; the happy path is untouched and carries no new overhead. The new code is entirely on the failure branch of No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/llms/bedrock/base_aws_llm.py | Adds _unverified_web_identity_audience helper to decode public JWT claims for diagnostics, and wraps the STS call to catch InvalidIdentityTokenException and raise a structured AwsAuthError(401) that includes the token's aud/iss. Logic is correct, gracefully degrades for non-JWT tokens, and is failure-path only (zero overhead on the happy path). |
| tests/test_litellm/llms/bedrock/test_web_identity_session_policy.py | Adds TestInvalidIdentityTokenSurfacesAudience with four focused mock-only unit tests covering the aud/iss surfacing, original STS reason preservation, and 401 status code. Uses patch for boto3.client and get_secret — no real network calls, consistent with the existing test pattern in this file. |
Reviews (2): Last reviewed commit: "fix(bedrock): surface web identity token..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
When STS rejects a web identity token with InvalidIdentityToken (the "Incorrect token audience" case), litellm propagated the raw botocore error, which never names the aud LiteLLM actually sent. Diagnosing an audience mismatch then required enabling LITELLM_LOG=DEBUG on the prod instance, which degrades performance. _auth_with_web_identity_token now catches InvalidIdentityTokenException, decodes the public aud/iss claims of the resolved JWT without verifying its signature (no secret is read), and raises an AwsAuthError that preserves the STS reason and names the token audience and issuer, so the mismatch is visible from the error alone. Resolves LIT-4026
7efa354 to
dc2e2fd
Compare
Relevant issues
Resolves LIT-4026
Linear ticket
LIT-4026
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewScreenshots / Proof of Fix
A customer connecting Bedrock through an AWS IAM role with GCP OIDC (
AssumeRoleWithWebIdentity) hitInvalidIdentityToken: Incorrect token audience. The whole investigation stalled on one question: whataudis LiteLLM actually putting in the token? The only way to find out wasLITELLM_LOG=DEBUGon the prod instance, which degrades performance, so the answer stayed unknown for weeks.This surfaces the token's
audandissin the error itself, no debug logging required. The proof below runs a live proxy that makes a realAssumeRoleWithWebIdentitycall against AWS STS (the call needs no AWS credentials; STS validates the token itself), so the sameInvalidIdentityTokenExceptionclass and the samebase_aws_llm.pycode path as the customer's traceback are exercised end to end. The token here carriesaud=https://guidepoint.litellm-prod.ai, the value the customer configured.Config used (
aws_web_identity_tokenresolves a JWT whoseaudishttps://guidepoint.litellm-prod.ai):Command (identical before and after):
Before (opaque; raw botocore error, no audience anywhere, returned as a 500
APIConnectionError):After (the STS reason is preserved and the token now names its own audience and issuer, as a 401
AuthenticationError):The crafted token here is not validly Google-signed, so AWS reports the verification-key subreason rather than the audience subreason; the exception class, the code path and the audience surfacing are exactly what the customer's "Incorrect token audience" case exercises. An operator now reads the
audstraight off the failure and compares it to the IAM OIDC provider'sclient_id_listand the role trust policy condition, with no debug logging on prod.Type
🐛 Bug Fix
Changes
_auth_with_web_identity_tokenwraps theassume_role_with_web_identitycall and catchesInvalidIdentityTokenException. On rejection it decodes the publicaud/issclaims of the already-resolved JWT (base64url of the payload segment only; the signature is never read, so no secret is exposed) and raises anAwsAuthErrorthat keeps the original STS reason and appends the token's audience and issuer. The decode is failure-path only, so there is no cost on the happy path; opaque or non-JWT tokens degrade gracefully to the original reason with no audience appended.This is the single funnel for the bedrock OIDC auth flow: chat (converse and invoke), embeddings, image and rerank all reach it through
get_credentials, so they are all covered.