feat: add Bedrock API key as a managed auth mode (#27443)

## Why

Codex needs to manage Amazon Bedrock API key credentials through the
existing auth lifecycle instead of introducing a separate auth manager
or provider-specific credential file. Treating Bedrock API key login as
a primary auth mode gives it the same persistence, keyring, reload, and
logout behavior as the existing OpenAI API key and ChatGPT modes.

The credential is valid only for the `amazon-bedrock` model provider.
OpenAI-compatible providers must reject this auth mode rather than
treating the Bedrock key as an OpenAI bearer token.

## What changed

- Added `bedrockApiKey` as an app-server `AuthMode` and
`CodexAuth::BedrockApiKey` as a primary `AuthManager` mode.
- Added `BedrockApiKeyAuth`, containing the API key and AWS region, to
the existing `AuthDotJson` payload stored in `$CODEX_HOME/auth.json` or
the configured keyring backend.
- Added `login_with_bedrock_api_key(...)`, parallel to
`login_with_api_key(...)`, which replaces the current stored login with
Bedrock credentials.
- Reused generic auth reload and logout behavior instead of adding a
Bedrock-specific auth manager or logout path.
- Updated login restrictions, status reporting, diagnostics, telemetry
classification, generated app-server schemas, and auth fixtures for the
new mode.
- Added explicit errors when Bedrock API key auth is selected with an
OpenAI-compatible model provider.

This PR establishes managed storage and auth-mode behavior. Routing the
managed key and region into Amazon Bedrock requests will be in follow-up
PRs.
This commit is contained in:
Celia Chen
2026-06-10 20:42:38 -07:00
committed by GitHub
parent 87ab01834a
commit 06afd63f4a
30 changed files with 426 additions and 15 deletions
+30
View File
@@ -8,11 +8,15 @@ use codex_api::SharedAuthProvider;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider_info::ModelProviderInfo;
use codex_protocol::error::CodexErr;
use http::HeaderMap;
use http::HeaderValue;
use crate::bearer_auth_provider::BearerAuthProvider;
const BEDROCK_API_KEY_UNSUPPORTED_MESSAGE: &str =
"Bedrock API key auth is only supported by the Amazon Bedrock model provider";
#[derive(Clone, Debug)]
struct AgentIdentityAuthProvider {
auth: codex_login::auth::AgentIdentityAuth,
@@ -79,6 +83,12 @@ pub(crate) fn resolve_provider_auth(
auth: Option<&CodexAuth>,
provider: &ModelProviderInfo,
) -> codex_protocol::error::Result<SharedAuthProvider> {
if matches!(auth, Some(CodexAuth::BedrockApiKey(_))) {
return Err(CodexErr::UnsupportedOperation(
BEDROCK_API_KEY_UNSUPPORTED_MESSAGE.to_string(),
));
}
if let Some(auth) = bearer_auth_for_provider(provider)? {
return Ok(Arc::new(auth));
}
@@ -109,6 +119,7 @@ pub fn auth_provider_from_auth(auth: &CodexAuth) -> SharedAuthProvider {
CodexAuth::AgentIdentity(auth) => {
Arc::new(AgentIdentityAuthProvider { auth: auth.clone() })
}
CodexAuth::BedrockApiKey(_) => unreachable!("{BEDROCK_API_KEY_UNSUPPORTED_MESSAGE}"),
CodexAuth::ApiKey(_)
| CodexAuth::Chatgpt(_)
| CodexAuth::ChatgptAuthTokens(_)
@@ -122,8 +133,10 @@ pub fn auth_provider_from_auth(auth: &CodexAuth) -> SharedAuthProvider {
#[cfg(test)]
mod tests {
use codex_login::auth::BedrockApiKeyAuth;
use codex_model_provider_info::WireApi;
use codex_model_provider_info::create_oss_provider_with_base_url;
use pretty_assertions::assert_eq;
use super::*;
@@ -135,4 +148,21 @@ mod tests {
assert!(auth.to_auth_headers().is_empty());
}
#[test]
fn openai_provider_rejects_bedrock_api_key_auth() {
let provider = ModelProviderInfo::create_openai_provider(/*base_url*/ None);
let auth = CodexAuth::BedrockApiKey(BedrockApiKeyAuth {
api_key: "bedrock-api-key-test".to_string(),
region: "us-east-1".to_string(),
});
match resolve_provider_auth(Some(&auth), &provider) {
Err(CodexErr::UnsupportedOperation(message)) => {
assert_eq!(message, BEDROCK_API_KEY_UNSUPPORTED_MESSAGE);
}
Err(err) => panic!("unexpected auth error: {err:?}"),
Ok(_) => panic!("Bedrock API key auth should be rejected"),
}
}
}
+31
View File
@@ -51,6 +51,7 @@ pub struct ProviderAccountState {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderAccountError {
MissingChatgptAccountDetails,
UnsupportedBedrockApiKeyAuth,
}
impl fmt::Display for ProviderAccountError {
@@ -62,6 +63,12 @@ impl fmt::Display for ProviderAccountError {
"email and plan type are required for chatgpt authentication"
)
}
Self::UnsupportedBedrockApiKeyAuth => {
write!(
f,
"Bedrock API key auth is only supported by the Amazon Bedrock model provider"
)
}
}
}
}
@@ -232,6 +239,9 @@ impl ModelProvider for ConfiguredModelProvider {
})
.map(|auth| match &auth {
CodexAuth::ApiKey(_) => Ok(ProviderAccount::ApiKey),
CodexAuth::BedrockApiKey(_) => {
Err(ProviderAccountError::UnsupportedBedrockApiKeyAuth)
}
CodexAuth::Chatgpt(_)
| CodexAuth::ChatgptAuthTokens(_)
| CodexAuth::AgentIdentity(_)
@@ -287,6 +297,7 @@ impl ModelProvider for ConfiguredModelProvider {
mod tests {
use std::num::NonZeroU64;
use codex_login::auth::BedrockApiKeyAuth;
use codex_model_provider_info::ModelProviderAwsAuthInfo;
use codex_model_provider_info::WireApi;
use codex_models_manager::manager::RefreshStrategy;
@@ -374,6 +385,13 @@ mod tests {
.expect("valid model")
}
fn bedrock_api_key_auth() -> CodexAuth {
CodexAuth::BedrockApiKey(BedrockApiKeyAuth {
api_key: "bedrock-api-key-test".to_string(),
region: "us-east-1".to_string(),
})
}
#[test]
fn configured_provider_uses_default_capabilities() {
let provider = create_model_provider(
@@ -491,6 +509,19 @@ mod tests {
);
}
#[test]
fn openai_provider_rejects_bedrock_api_key_account_state() {
let provider = create_model_provider(
ModelProviderInfo::create_openai_provider(/*base_url*/ None),
Some(AuthManager::from_auth_for_testing(bedrock_api_key_auth())),
);
assert_eq!(
provider.account_state(),
Err(ProviderAccountError::UnsupportedBedrockApiKeyAuth)
);
}
#[test]
fn custom_non_openai_provider_returns_no_account_state() {
let provider = create_model_provider(