From 879ac3ff8be5b49e44b10600ee01436615a2126f Mon Sep 17 00:00:00 2001 From: Scott Werner Date: Mon, 15 Jun 2026 20:51:21 -0400 Subject: [PATCH] fix(bedrock): accept BEDROCK_API_KEY as a bearer-token alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `_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) --- docs/public/integrations/bedrock.mdx | 10 ++++++---- lib/crates/fabro-model/src/catalog.rs | 7 +++++-- .../src/catalog/providers/bedrock-openai.toml | 7 ++++++- .../src/catalog/providers/bedrock.toml | 19 ++++++++++++------- lib/crates/fabro-server/src/spawn_env.rs | 4 +++- lib/crates/fabro-static/src/env_vars.rs | 2 ++ .../fabro-static/src/secret_registry.rs | 2 ++ 7 files changed, 36 insertions(+), 15 deletions(-) diff --git a/docs/public/integrations/bedrock.mdx b/docs/public/integrations/bedrock.mdx index 572ca1c0e..66a03c91c 100644 --- a/docs/public/integrations/bedrock.mdx +++ b/docs/public/integrations/bedrock.mdx @@ -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 `_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). **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`). diff --git a/lib/crates/fabro-model/src/catalog.rs b/lib/crates/fabro-model/src/catalog.rs index 8905b5de5..3bc427a39 100644 --- a/lib/crates/fabro-model/src/catalog.rs +++ b/lib/crates/fabro-model/src/catalog.rs @@ -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 `_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, ]); diff --git a/lib/crates/fabro-model/src/catalog/providers/bedrock-openai.toml b/lib/crates/fabro-model/src/catalog/providers/bedrock-openai.toml index d3f0b79fc..20b011b9c 100644 --- a/lib/crates/fabro-model/src/catalog/providers/bedrock-openai.toml +++ b/lib/crates/fabro-model/src/catalog/providers/bedrock-openai.toml @@ -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 diff --git a/lib/crates/fabro-model/src/catalog/providers/bedrock.toml b/lib/crates/fabro-model/src/catalog/providers/bedrock.toml index f5ea72799..b21c7ea2e 100644 --- a/lib/crates/fabro-model/src/catalog/providers/bedrock.toml +++ b/lib/crates/fabro-model/src/catalog/providers/bedrock.toml @@ -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 `, 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 `_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..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 diff --git a/lib/crates/fabro-server/src/spawn_env.rs b/lib/crates/fabro-server/src/spawn_env.rs index 968f52b05..7401da3c6 100644 --- a/lib/crates/fabro-server/src/spawn_env.rs +++ b/lib/crates/fabro-server/src/spawn_env.rs @@ -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, diff --git a/lib/crates/fabro-static/src/env_vars.rs b/lib/crates/fabro-static/src/env_vars.rs index 794e1462d..cd16892b2 100644 --- a/lib/crates/fabro-static/src/env_vars.rs +++ b/lib/crates/fabro-static/src/env_vars.rs @@ -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, diff --git a/lib/crates/fabro-static/src/secret_registry.rs b/lib/crates/fabro-static/src/secret_registry.rs index adc9a693f..6166a1d42 100644 --- a/lib/crates/fabro-static/src/secret_registry.rs +++ b/lib/crates/fabro-static/src/secret_registry.rs @@ -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,