feat(anthropic): optional SigV4 request signing behind a defaulted trait method - #2387
Open
awsmadi wants to merge 1 commit into
Open
feat(anthropic): optional SigV4 request signing behind a defaulted trait method#2387awsmadi wants to merge 1 commit into
awsmadi wants to merge 1 commit into
Conversation
…ait method
Lets the Anthropic provider talk to an Anthropic-compatible endpoint that sits
behind AWS SigV4, without changing anything for anyone who does not opt in.
Why not rig-bedrock
-------------------
rig-bedrock speaks the Bedrock Runtime InvokeModel API. This is the other
shape: an endpoint that accepts Anthropic's own /v1/messages request body, and
therefore carries Anthropic-shaped parameters, but authenticates with SigV4
instead of an x-api-key header. Different wire protocols against different
endpoints, so neither substitutes for the other.
What changed
------------
`AnthropicCompatibleProvider` gains one method with a default:
fn sigv4_region(&self) -> Option<&str> { None }
Returning `None` — the default — means no signing is attempted, so every
existing implementor is unaffected and none needs editing. `AnthropicExt`
overrides it; `AnthropicKey` gains a `SigV4 { region }` variant alongside
`ApiKey(String)`.
Going through the trait rather than a concrete field is deliberate: completions
run through `GenericCompletionModel<Ext, T>`, so a concrete field would not be
reachable from the generic path. This follows the provider-integration
checklist in CONTRIBUTING.md, which asks for wire-dialect differences to live
in the trait's hooks.
Signing happens in both request builders, immediately before the body is
attached and never earlier: the SigV4 payload hash covers the exact bytes
sent, so signing has to follow every mutation of the body. The SigV4 variant
emits no static header, because the signature covers the clock as well as the
body and so cannot be computed once at client construction.
Feature-gating
--------------
Behind a default-off `sigv4` feature. Default-off on purpose: the AWS
credential chain has no place in a wasm build, nor in builds that only ever
talk to api.anthropic.com.
Selecting SigV4 in a build without the feature is a hard error, not a silently
unsigned request — an unsigned request would 401 with a message about a
missing API key, which points at the wrong problem.
`#[cfg_attr(not(feature = "sigv4"), allow(unused_mut))]` on the request builder
keeps the no-feature build warning-free; without it the `mut` that only signing
needs would warn in every default build.
Two aws-config features are load-bearing, and both fail at RUNTIME rather than
at compile time, so `cargo check` cannot catch either. The workspace pins
aws-config with `default-features = false` (rig-bedrock supplies its own), while
`load_defaults` walks the full credential chain:
default-https-client — without it: "a http_client is required"
rt-tokio — without it: "An async sleep implementation is required
for retry to work"
aws-config was already a workspace dependency; aws-credential-types and
aws-sigv4 are added as bare-major floors, consistent with the documented policy
in the root manifest, since this code uses only long-stable API from both.
Verification
------------
cargo clippy -p rig-core --features sigv4 --all-targets 0 warnings
cargo test -p rig-core --features sigv4 --lib 1551 passed, 0 failed
cargo test -p rig-core --lib 1550 passed, 0 failed
The last line is the control that matters for existing users: it proves the
default-off path is unaffected. The +1 with the feature on is the new
`signed_headers_never_include_host` test, which asserts the signer does not
emit a `host` header — the HTTP client sets its own, and sending both breaks
the signature. That test carries its own anti-vacuity assertion, because
"no host header" passes trivially if nothing was produced at all.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2386
Adds optional AWS SigV4 authentication to the Anthropic provider, so it can
reach an Anthropic-compatible endpoint that sits behind AWS. Behind a
default-off
sigv4feature; nothing changes for anyone who doesn't opt in.Implementation
AnthropicCompatibleProvidergains one method with a default:Nonemeans no signing is attempted, so every existing implementor isunaffected and none needed editing.
AnthropicExtoverrides it;AnthropicKeygains aSigV4 { region }variant alongsideApiKey(String).Going through the trait rather than a concrete field is deliberate — completions
run through
GenericCompletionModel<Ext, T>, so a concrete field isn'treachable from the generic path. This follows the provider-integration
checklist in
CONTRIBUTING.md: wire-dialect differences belong in the trait'shooks.
Signing happens in both request builders, immediately before the body is
attached and never earlier. The SigV4 payload hash covers the exact bytes sent,
so signing has to follow every mutation of the body — and the SigV4 variant
emits no static header, because the signature covers the clock as well as the
body and can't be computed once at client construction.
Selecting SigV4 in a build without the feature is a hard error, not a silently
unsigned request: an unsigned request 401s with a message about a missing API
key, which points at the wrong problem.
Two things worth a reviewer's attention
The
sigv4feature enables twoaws-configfeatures, and both areload-bearing. The workspace pins
aws-configwithdefault-features = false(rig-bedrock supplies its own), while
load_defaultswalks the full credentialchain:
default-https-clienta http_client is requiredrt-tokioAn async sleep implementation is required for retry to workBoth fail at runtime, not compile time, so
cargo checkcan't catch either— I only found them by running the tests. Note also that feature unification
means enabling
sigv4givesrig-bedrock'saws-configthose features too.That's additive, but it's a real consequence and I'd rather flag it than have it
discovered later.
aws-credential-typesandaws-sigv4are new workspace dependencies, addedas bare-major floors per the documented policy in the root manifest, since this
code uses only long-stable API from both.
aws-configwas already there.Verification
The last line is the control that matters for existing users — it's what proves
the default-off path is unaffected.
The +1 with the feature on is
signed_headers_never_include_host, which assertsthe signer doesn't emit a
hostheader: the HTTP client sets its own, andsending both breaks the signature. It carries an anti-vacuity assertion too,
because "no host header" passes trivially if nothing was produced at all.
Not included
No cassette-backed regression test. Signing is request-shaping rather than a
response-parsing behaviour, so the assertion that matters is on the emitted
header list, which is what the included test does. Happy to add one if you'd
prefer the coverage there instead.