Skip to content

feat(rust): port BaseAWSLLM auth (credential resolution + SigV4) to litellm-core as a base provider - #33888

Merged
ishaan-berri merged 6 commits into
litellm_internal_stagingfrom
litellm_rust_bedrock_auth
Jul 19, 2026
Merged

feat(rust): port BaseAWSLLM auth (credential resolution + SigV4) to litellm-core as a base provider#33888
ishaan-berri merged 6 commits into
litellm_internal_stagingfrom
litellm_rust_bedrock_auth

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Screenshots / Proof of Fix

This is PR 1 of the Rust migration's auth de-risking (Vertex is the follow-up). The question it answers is whether the boto3/botocore auth the Python Bedrock path depends on has a maintained Rust equivalent that behaves identically, so the Rust gateway does not have to call back into Python for credentials or signing. It does: the official AWS SDK for Rust.

Nothing is wired into a live route yet and the whole module is behind a default-off cargo feature, so there is no proxy endpoint to curl at this stage. The proof that matters here is that Rust and Python produce byte-identical SigV4 output, pinned by one shared golden vector that both a Rust test and a Python botocore test assert against, so drift on either side fails a test.

Same fixed inputs on both sides (POST to a Bedrock invoke URL, body {"input":"hello"}, region us-east-1, service bedrock, static creds with a session token, signing time frozen to 2024-01-02T03:04:05Z); both produce

Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20240102/us-east-1/bedrock/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token, Signature=55c027ef47527d3ad63f1735f9d099efdbc99f296ff914bd94e727e24ec0e464

Rust side (byte-parity, no-session-token, classification, cache-policy, same-role comparison, no-network resolution):

cd litellm-rust && cargo test -p litellm-core --features bedrock-auth
# 68 passed

Python side (recomputes the botocore signature for the identical inputs and asserts the same golden):

uv run --with boto3 --with pytest --no-sync \
  pytest tests/test_litellm/llms/bedrock/test_base_aws_llm.py -k sigv4_matches_rust_golden_vector
# 1 passed

A live signed Bedrock call against a real account will be added as a follow-up comment on this PR, captured at the head commit.

Type

🆕 New Feature

Changes

Ports the auth surface of Python's BaseAWSLLM (litellm/llms/bedrock/base_aws_llm.py) into the Rust workspace as a base provider at litellm-core/src/providers/bedrock/, so the Rust Bedrock path can resolve AWS credentials and SigV4-sign a request natively instead of shelling back into Python. It is gated behind a new, default-off bedrock-auth cargo feature on litellm-core; core still builds and clippies clean with the feature off, and the AWS dependencies are optional and only pulled in when it is enabled.

Placement note for reviewers: credential resolution reads env and calls STS, and signing is SDK work, which the core-purity guidance in litellm-rust/CLAUDE.md normally keeps out of litellm-core. Putting the base provider here (mirroring how Python's base provider class owns auth) is a deliberate, directed decision; the module carries a short note to that effect and the purity guidance will be reconciled separately rather than silently.

Credential-flow selection is a pure function over a typed AwsAuthConfig, with the same per-parameter AWS_* env fallback as Python, so branch order is unit-testable without the network

web_identity_token + role + session_name -> WebIdentity   (STS AssumeRoleWithWebIdentity)
role_name                                 -> AssumeRole    (STS AssumeRole)
profile_name                              -> Profile       (shared config/credentials file)
access_key + secret + session_token       -> SessionToken  (static, in-process)
access_key + secret + region              -> StaticKeys    (static, in-process)
otherwise                                 -> DefaultChain  (env, IMDS/ECS, SSO, ...)

classify_auth returns that tagged union; resolve_credentials executes the chosen flow through the AWS SDK providers, and sign_bedrock_post signs with aws-sigv4 and normalizes the emitted header casing to match botocore (Authorization, X-Amz-Date, X-Amz-Security-Token).

The port now includes the two behaviors mirrored from Python that the earlier draft had deferred. First, the process-wide IAM credential cache with matching TTLs: static access-key credentials cached for 59 minutes, the ambient default chain for 10 minutes, and the flows Python does not cache (AssumeRole, web identity, profile, explicit session-token) left uncached. Cache keys are SHA-256 of the resolved config plus flow, so no credential material appears in a key. Second, the AssumeRole "already running as this role" short-circuit: it compares the target role ARN's partition, account, and role name against the caller (via the IRSA env fast path, otherwise sts:GetCallerIdentity) and uses ambient credentials when they already match, falling through to a normal AssumeRole when the caller-identity lookup fails.

AWS SDK deps are pinned and configured rustls-only (default-features = false, rustls, rt-tokio); no OpenSSL enters the tree.

One SDK parity note: AssumeRoleWithWebIdentity has no ExternalId input in the AWS API (the Python path does not pass one there either), so aws_external_id applies only to the plain AssumeRole flow.

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/bdf99b89a6584cc0989b5605765aec03
Requested by: @ishaan-berri

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
@ishaan-berri ishaan-berri self-assigned this Jul 18, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR ports Python's BaseAWSLLM credential resolution and SigV4 signing into a new litellm-core Rust module (litellm-rust/crates/core/src/providers/bedrock/), gated behind a default-off bedrock-auth cargo feature. It includes a process-wide InMemoryCache<Credentials> (a 1:1 port of Python's in-memory cache), a typed classify_authresolve_credentials pipeline covering all six credential flows, the AssumeRole "already running as this role" short-circuit, and a golden-vector test that asserts byte-identical SigV4 output between Rust and botocore.

  • The InMemoryCache is well-tested and correctly implements heap-based expiration eviction with clock injection for deterministic tests.
  • The WebIdentity classifier requires session_name to be explicitly provided; when it is absent, the OIDC token is silently dropped and the code falls through to AssumeRole, which uses a different STS operation and may behave incorrectly.
  • AwsAuthConfig and AwsAuthFlow derive Debug without redacting sensitive fields, which risks credential leakage via future tracing or error formatting.

Confidence Score: 4/5

Safe to land behind the feature flag; the auth classifier has a behavioral gap that should be fixed before the module is wired to a live route.

The whole module is gated behind a default-off cargo feature and nothing is connected to a live Bedrock endpoint yet, which limits blast radius. The WebIdentity classifier silently ignores an explicitly-provided OIDC token whenever session_name is absent, routing the request through a different STS operation — a defect that will be hard to diagnose once the module is wired up. Everything else — the cache port, SigV4 golden-vector parity, IRSA short-circuit, and CI additions — looks correct and well-covered.

litellm-rust/crates/core/src/providers/bedrock/aws_base.rs — the WebIdentity classification arm and the Debug derivations on credential types.

Important Files Changed

Filename Overview
litellm-rust/crates/core/src/providers/bedrock/aws_base.rs Core Rust port of Python's BaseAWSLLM auth; contains a WebIdentity classification gap where an explicit OIDC token is silently dropped when session_name is absent, plus derived Debug on credential types exposes secrets
litellm-rust/crates/core/src/caching/in_memory_cache.rs Clean 1:1 port of Python's InMemoryCache; heap-based expiration eviction, 200-entry bound, clock injection for deterministic tests, well-covered by unit tests
litellm-rust/crates/core/Cargo.toml AWS SDK deps correctly pinned behind the optional bedrock-auth feature; rustls-only, no OpenSSL
.github/workflows/test-rust.yml Adds Clippy and cargo-test steps for the bedrock-auth feature flag; correctly runs in addition to the baseline workspace checks
litellm-rust/crates/core/src/providers/bedrock/constants.rs Litellm-namespaced AWS env-var constants; all map correctly to their Python counterparts

Reviews (2): Last reviewed commit: "fix(rust): preserve web identity credent..." | Re-trigger Greptile

Comment thread tests/test_litellm/llms/bedrock/test_base_aws_llm.py
Comment thread litellm-rust/crates/core/src/providers/bedrock/aws_base.rs
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

devin-ai-integration Bot and others added 2 commits July 19, 2026 00:05
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat(rust): feature-gated Bedrock AWS auth (credential resolution + SigV4) in ai-gateway feat(rust): port BaseAWSLLM auth (credential resolution + SigV4) to litellm-core as a base provider Jul 19, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_rust_bedrock_auth (b036e52) with litellm_internal_staging (595e724)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (b83c60b) during the generation of this report, so 595e724 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team July 19, 2026 00:31
devin-ai-integration Bot and others added 2 commits July 19, 2026 01:17
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Addressed both review comments in 1b7ce21 and b036e525

Removed the unused FrozenDateTime inner class from test_sigv4_matches_rust_golden_vector; the time freeze is handled by the patch("botocore.auth.get_current_datetime", ...) call

The WebIdentity flow now forwards the STS-returned expiration into Credentials::new instead of dropping it, so time-limited web-identity credentials carry their real TTL

Separately restructured the cache: the inline per-module cache was removed and Bedrock now uses a shared InMemoryCache in litellm-core that is a 1:1 port of Python's litellm/caching/in_memory_cache.py (heap-based expiration eviction, 200-entry bound, 600s default TTL), consumed through a process-wide OnceLock<Mutex<InMemoryCache<Credentials>>>

@greptileai please re-review

@ishaan-berri
ishaan-berri merged commit f2e340c into litellm_internal_staging Jul 19, 2026
78 checks passed
@ishaan-berri
ishaan-berri deleted the litellm_rust_bedrock_auth branch July 19, 2026 02:12
Comment on lines +150 to +165
if let (Some(token), Some(role), Some(session_name)) = (
config.web_identity_token.clone(),
config.role_name.clone(),
config.session_name.clone(),
) {
return AwsAuthFlow::WebIdentity {
token,
role,
session_name,
};
}
if let Some(role) = config.role_name.clone() {
return AwsAuthFlow::AssumeRole {
role,
session_name: config.session_name.clone(),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 WebIdentity token silently dropped when session_name is absent

The WebIdentity arm requires all three of web_identity_token, role_name, and session_name to be Some. When a caller provides web_identity_token + role_name but omits session_name (a common case when AWS_SESSION_NAME is not set), the pattern doesn't match and the code falls through to the AssumeRole arm — silently discarding the OIDC token and issuing a plain sts:AssumeRole instead of sts:AssumeRoleWithWebIdentity. The two STS operations require different IAM policies, so the wrong one may fail or succeed in surprising ways.

The AssumeRole arm already handles an absent session_name by calling default_session_name() in resolve_credentials. The same auto-generation should apply to WebIdentity — either by loosening the classifier pattern to not require session_name, or by accepting Option<String> in the WebIdentity variant and generating a default in resolve_credentials.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants