fix(bedrock): accept BEDROCK_API_KEY as a bearer-token alias

Bedrock's bearer key can now be supplied under either `AWS_BEARER_TOKEN_BEDROCK`
(AWS's canonical name, also read by the AWS SDKs/CLI) or `BEDROCK_API_KEY`
(Fabro's `<PROVIDER>_API_KEY` convention). The AWS-canonical name stays primary,
so existing setups are unchanged; resolution is env (either name) → vault
(either name) → SigV4.

Wired through the env-var registry, the optional-vault secret registry, both
Bedrock providers' catalog credential lists, and the server's worker env
passthrough (so an ambient `BEDROCK_API_KEY` reaches workflow workers like the
existing bearer var). Gitleaks needs no change — its rules match the key value
format (`bedrock-api-key-...`), not the env var name. Docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Werner 2026-06-15 20:51:21 -04:00
parent c0c5562d3d
commit 879ac3ff8b
7 changed files with 36 additions and 15 deletions

View file

@ -37,10 +37,12 @@ The SigV4 signing region is derived from `base_url` — change it to your Region
Two auth modes, tried in order:
**Bedrock API key** (simplest): store the key and Fabro sends it as a bearer token.
**Bedrock API key** (simplest): store the key and Fabro sends it as a bearer token. The key is read from either `AWS_BEARER_TOKEN_BEDROCK` (AWS's canonical name, also honored by the AWS SDKs and CLI) or `BEDROCK_API_KEY` (Fabro's `<PROVIDER>_API_KEY` convention) — use whichever you prefer.
```bash
fabro secret set AWS_BEARER_TOKEN_BEDROCK bedrock-api-key-...
# or, equivalently
fabro secret set BEDROCK_API_KEY bedrock-api-key-...
# or for standalone local runs
export AWS_BEARER_TOKEN_BEDROCK=bedrock-api-key-...
```
@ -49,10 +51,10 @@ export AWS_BEARER_TOKEN_BEDROCK=bedrock-api-key-...
```toml
[llm.providers.bedrock.auth]
credentials = ["env:AWS_BEARER_TOKEN_BEDROCK", "vault:AWS_BEARER_TOKEN_BEDROCK", "aws_sigv4"]
credentials = ["env:AWS_BEARER_TOKEN_BEDROCK", "env:BEDROCK_API_KEY", "vault:AWS_BEARER_TOKEN_BEDROCK", "vault:BEDROCK_API_KEY", "aws_sigv4"]
```
The key resolves from the process environment first, then the server vault (`fabro secret set`), then falls back to SigV4 — so on a server, prefer `secret set`. To select a non-default AWS profile for SigV4, set `AWS_PROFILE` (it, and the rest of the AWS credential-chain variables, are passed through to workflow workers).
The key resolves from the process environment first (either name), then the server vault (`fabro secret set`), then falls back to SigV4 — so on a server, prefer `secret set`. To select a non-default AWS profile for SigV4, set `AWS_PROFILE` (it, and the rest of the AWS credential-chain variables, are passed through to workflow workers).
<Warning>
**Bearer-vs-SigV4 precedence.** Because the bearer key is tried before SigV4, setting `AWS_BEARER_TOKEN_BEDROCK` makes the `bedrock` (Converse) provider authenticate with that key too — not just the `bedrock-openai` mantle provider below. If your key is valid only for mantle (it lacks `bedrock:InvokeModel*` on the runtime), every Converse model then fails with *"Authentication failed."* To run Converse models on SigV4 while using a mantle-only bearer key for GPT-5.x, pin the Converse provider to SigV4 explicitly:
@ -132,7 +134,7 @@ Bedrock-specific request fields pass through verbatim via `provider_options.bedr
## Troubleshooting
**"no AWS credentials provider found"** — Neither an API key nor any AWS chain source resolved. Set `AWS_BEARER_TOKEN_BEDROCK`, or configure standard AWS credentials.
**"no AWS credentials provider found"** — Neither an API key nor any AWS chain source resolved. Set `AWS_BEARER_TOKEN_BEDROCK` (or `BEDROCK_API_KEY`), or configure standard AWS credentials.
**`AccessDeniedException` / 403** — The IAM principal lacks `bedrock:InvokeModel*` for the model, or model access has not been granted in the Bedrock console for your Region (Claude needs the per-Region use-case approval; third-party models need `aws-marketplace:Subscribe`).

View file

@ -2007,11 +2007,14 @@ enabled = true
provider.base_url.as_deref(),
Some("https://bedrock-runtime.us-east-1.amazonaws.com")
);
// Bearer key first (env then vault, like every other provider), SigV4
// chain as the fallback.
// Bearer key first (env then vault, like every other provider), under
// either the AWS-canonical name or Fabro's `<PROVIDER>_API_KEY`
// convention; SigV4 chain as the fallback.
assert_eq!(provider.auth.as_ref().unwrap().credentials, vec![
CredentialRef::Env("AWS_BEARER_TOKEN_BEDROCK".to_string()),
CredentialRef::Env("BEDROCK_API_KEY".to_string()),
CredentialRef::Vault("AWS_BEARER_TOKEN_BEDROCK".to_string()),
CredentialRef::Vault("BEDROCK_API_KEY".to_string()),
CredentialRef::AwsSigv4,
]);

View file

@ -7,7 +7,12 @@ priority = 19
enabled = false
[providers.bedrock-openai.auth]
credentials = ["env:AWS_BEARER_TOKEN_BEDROCK", "vault:AWS_BEARER_TOKEN_BEDROCK"]
credentials = [
"env:AWS_BEARER_TOKEN_BEDROCK",
"env:BEDROCK_API_KEY",
"vault:AWS_BEARER_TOKEN_BEDROCK",
"vault:BEDROCK_API_KEY",
]
# OpenAI's frontier models on Bedrock (GPT-5.5/5.4) are served ONLY by the
# bedrock-mantle endpoint's OpenAI Responses API — they are not reachable

View file

@ -8,14 +8,19 @@ enabled = false
[providers.bedrock.auth]
# An explicit Bedrock API key wins (from the process env, or the server
# vault via `fabro secret set AWS_BEARER_TOKEN_BEDROCK`, matching every
# other provider's env-then-vault order); SigV4 (the AWS default credential
# chain, resolved at request time) is the fallback. `aws_sigv4` always
# resolves, which is why this provider ships disabled: enabling it is the
# operator's statement that AWS credentials are expected to work.
# vault via `fabro secret set <name>`, matching every other provider's
# env-then-vault order); SigV4 (the AWS default credential chain, resolved
# at request time) is the fallback. `aws_sigv4` always resolves, which is
# why this provider ships disabled: enabling it is the operator's statement
# that AWS credentials are expected to work. The key is read from either
# `AWS_BEARER_TOKEN_BEDROCK` (the AWS-canonical name, also honored by the
# AWS SDKs/CLI) or `BEDROCK_API_KEY` (Fabro's `<PROVIDER>_API_KEY`
# convention); the env names are checked before the vault.
credentials = [
"env:AWS_BEARER_TOKEN_BEDROCK",
"env:BEDROCK_API_KEY",
"vault:AWS_BEARER_TOKEN_BEDROCK",
"vault:BEDROCK_API_KEY",
"aws_sigv4",
]
@ -26,8 +31,8 @@ credentials = [
# base_url = "https://bedrock-runtime.<your-region>.amazonaws.com"
#
# The signing region is derived from the base_url. Authenticate with
# either a Bedrock API key (AWS_BEARER_TOKEN_BEDROCK) or any AWS default
# credential chain source (env keys, profile, IMDS, IRSA, SSO).
# either a Bedrock API key (AWS_BEARER_TOKEN_BEDROCK or BEDROCK_API_KEY) or
# any AWS default credential chain source (env keys, profile, IMDS, IRSA, SSO).
#
# Model ids use cross-region inference profiles (`us.` / `global.`
# prefixes) where on-demand access requires them. Pricing rows are

View file

@ -23,7 +23,8 @@ const WORKER_ENV_ALLOWLIST: &[&str] = &[
// AWS chain on every request so STS/SSO/IRSA sessions can refresh, which
// means the chain's *inputs* must survive `env_clear()` in the worker, not
// a snapshot taken at launch. We pass the identity surface only (static
// keys, session token, the Bedrock bearer key, profile/region selectors,
// keys, session token, the Bedrock bearer key under either accepted name,
// profile/region selectors,
// and the web-identity/ECS role vars); HOME already carries the shared
// `~/.aws` config + SSO cache. Endpoint/metadata overrides
// (AWS_ENDPOINT_*, AWS_METADATA_ENDPOINT, AWS_IMDSV1_FALLBACK) are
@ -33,6 +34,7 @@ const WORKER_ENV_ALLOWLIST: &[&str] = &[
EnvVars::AWS_SECRET_ACCESS_KEY,
EnvVars::AWS_SESSION_TOKEN,
EnvVars::AWS_BEARER_TOKEN_BEDROCK,
EnvVars::BEDROCK_API_KEY,
EnvVars::AWS_PROFILE,
EnvVars::AWS_REGION,
EnvVars::AWS_DEFAULT_REGION,

View file

@ -42,6 +42,7 @@ impl EnvVars {
pub const ANTHROPIC_API_KEY: &'static str = "ANTHROPIC_API_KEY";
pub const AWS_BEARER_TOKEN_BEDROCK: &'static str = "AWS_BEARER_TOKEN_BEDROCK";
pub const ANTHROPIC_BASE_URL: &'static str = "ANTHROPIC_BASE_URL";
pub const BEDROCK_API_KEY: &'static str = "BEDROCK_API_KEY";
pub const BRAVE_SEARCH_API_KEY: &'static str = "BRAVE_SEARCH_API_KEY";
pub const CHATGPT_ACCOUNT_ID: &'static str = "CHATGPT_ACCOUNT_ID";
pub const GEMINI_API_KEY: &'static str = "GEMINI_API_KEY";
@ -184,6 +185,7 @@ mod tests {
EnvVars::ANTHROPIC_API_KEY,
EnvVars::ANTHROPIC_BASE_URL,
EnvVars::AWS_BEARER_TOKEN_BEDROCK,
EnvVars::BEDROCK_API_KEY,
EnvVars::BRAVE_SEARCH_API_KEY,
EnvVars::CHATGPT_ACCOUNT_ID,
EnvVars::GEMINI_API_KEY,

View file

@ -17,6 +17,7 @@ const BOOTSTRAP_SECRETS: &[&str] = &[
const OPTIONAL_VAULT_SECRETS: &[&str] = &[
EnvVars::ANTHROPIC_API_KEY,
EnvVars::AWS_BEARER_TOKEN_BEDROCK,
EnvVars::BEDROCK_API_KEY,
EnvVars::BRAVE_SEARCH_API_KEY,
EnvVars::FABRO_SLACK_APP_TOKEN,
EnvVars::FABRO_SLACK_BOT_TOKEN,
@ -88,6 +89,7 @@ mod tests {
EnvVars::BRAVE_SEARCH_API_KEY,
EnvVars::ANTHROPIC_API_KEY,
EnvVars::AWS_BEARER_TOKEN_BEDROCK,
EnvVars::BEDROCK_API_KEY,
EnvVars::GEMINI_API_KEY,
EnvVars::INCEPTION_API_KEY,
EnvVars::KIMI_API_KEY,