feat(goose): add support for AWS_BEARER_TOKEN_BEDROCK environment variable (#6739)

Signed-off-by: Andre Silva <andreswebs@pm.me>
This commit is contained in:
Andre Silva
2026-01-29 04:12:09 -03:00
committed by GitHub
parent 4d49fd5423
commit 5290f61a24
2 changed files with 148 additions and 26 deletions
+119 -17
View File
@@ -64,8 +64,34 @@ impl BedrockProvider {
} }
}; };
let filtered_secrets = config.all_secrets().map(|map| {
map.into_iter()
.filter(|(key, _)| key != "AWS_BEARER_TOKEN_BEDROCK")
.collect()
});
set_aws_env_vars(config.all_values()); set_aws_env_vars(config.all_values());
set_aws_env_vars(config.all_secrets()); set_aws_env_vars(filtered_secrets);
// Check for bearer token first to determine if region is required
let bearer_token = match config.get_secret::<String>("AWS_BEARER_TOKEN_BEDROCK") {
Ok(token) => {
let token = token.trim().to_string();
if token.is_empty() {
None
} else {
Some(token)
}
}
Err(_) => None,
};
// Get AWS_REGION from config if explicitly set (optional - SDK can resolve from other sources)
let region = match config.get_param::<String>("AWS_REGION") {
Ok(r) if !r.is_empty() => Some(r),
Ok(_) => None,
Err(_) => None,
};
// Use load_defaults() which supports AWS SSO, profiles, and environment variables // Use load_defaults() which supports AWS SSO, profiles, and environment variables
let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()); let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest());
@@ -76,24 +102,37 @@ impl BedrockProvider {
} }
} }
// Check for AWS_REGION configuration // Apply region to loader if explicitly configured
if let Ok(region) = config.get_param::<String>("AWS_REGION") { if let Some(ref region) = region {
if !region.is_empty() { loader = loader.region(aws_config::Region::new(region.clone()));
loader = loader.region(aws_config::Region::new(region));
}
} }
let sdk_config = loader.load().await; let sdk_config = loader.load().await;
// Validate credentials or return error back up // Validate region requirement for bearer token auth after SDK config is loaded
sdk_config // This allows region to be resolved from ~/.aws/config, AWS_DEFAULT_REGION, etc.
.credentials_provider() if bearer_token.is_some() && sdk_config.region().is_none() {
.ok_or_else(|| anyhow::anyhow!("No AWS credentials provider configured"))? return Err(anyhow::anyhow!(
.provide_credentials() "AWS region is required when using AWS_BEARER_TOKEN_BEDROCK authentication. \
.await Set AWS_REGION, AWS_DEFAULT_REGION, or configure region in your AWS profile."
.map_err(|e| anyhow::anyhow!("Failed to load AWS credentials: {}. Make sure to run 'aws sso login --profile <your-profile>' if using SSO", e))?; ));
}
let client = Client::new(&sdk_config); let client = if let Some(bearer_token) = bearer_token {
// Build from sdk_config to inherit all settings (endpoint overrides, timeouts, etc.)
// then override authentication with bearer token
let bedrock_config = aws_sdk_bedrockruntime::Config::new(&sdk_config)
.to_builder()
.bearer_token(aws_sdk_bedrockruntime::config::Token::new(
bearer_token,
None,
))
.build();
Client::from_conf(bedrock_config)
} else {
Self::create_client_with_credentials(&sdk_config).await?
};
let retry_config = Self::load_retry_config(config); let retry_config = Self::load_retry_config(config);
@@ -105,6 +144,22 @@ impl BedrockProvider {
}) })
} }
async fn create_client_with_credentials(sdk_config: &aws_config::SdkConfig) -> Result<Client> {
sdk_config
.credentials_provider()
.ok_or_else(|| anyhow::anyhow!("No AWS credentials provider configured"))?
.provide_credentials()
.await
.map_err(|e| {
anyhow::anyhow!(
"Failed to load AWS credentials: {}. Make sure to run 'aws sso login --profile <your-profile>' if using SSO",
e
)
})?;
Ok(Client::new(sdk_config))
}
fn load_retry_config(config: &crate::config::Config) -> RetryConfig { fn load_retry_config(config: &crate::config::Config) -> RetryConfig {
let max_retries = config let max_retries = config
.get_param::<usize>("BEDROCK_MAX_RETRIES") .get_param::<usize>("BEDROCK_MAX_RETRIES")
@@ -212,13 +267,14 @@ impl Provider for BedrockProvider {
ProviderMetadata::new( ProviderMetadata::new(
"aws_bedrock", "aws_bedrock",
"Amazon Bedrock", "Amazon Bedrock",
"Run models through Amazon Bedrock. Supports AWS SSO profiles - run 'aws sso login --profile <profile-name>' before using. Configure with AWS_PROFILE and AWS_REGION, or use environment variables/credentials.", "Run models through Amazon Bedrock. Supports AWS SSO profiles - run 'aws sso login --profile <profile-name>' before using. Configure with AWS_PROFILE and AWS_REGION, use environment variables/credentials, or use AWS_BEARER_TOKEN_BEDROCK for bearer token authentication. Region is required for bearer token auth (can be set via AWS_REGION, AWS_DEFAULT_REGION, or AWS profile).",
BEDROCK_DEFAULT_MODEL, BEDROCK_DEFAULT_MODEL,
BEDROCK_KNOWN_MODELS.to_vec(), BEDROCK_KNOWN_MODELS.to_vec(),
BEDROCK_DOC_LINK, BEDROCK_DOC_LINK,
vec![ vec![
ConfigKey::new("AWS_PROFILE", true, false, Some("default")), ConfigKey::new("AWS_PROFILE", false, false, Some("default")),
ConfigKey::new("AWS_REGION", true, false, None), ConfigKey::new("AWS_REGION", false, false, None),
ConfigKey::new("AWS_BEARER_TOKEN_BEDROCK", false, true, None),
], ],
) )
} }
@@ -276,3 +332,49 @@ impl Provider for BedrockProvider {
Ok((message, provider_usage)) Ok((message, provider_usage))
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metadata_config_keys_have_expected_flags() {
let meta = BedrockProvider::metadata();
let aws_profile = meta
.config_keys
.iter()
.find(|k| k.name == "AWS_PROFILE")
.expect("AWS_PROFILE config key should exist");
assert!(!aws_profile.required, "AWS_PROFILE should not be required");
assert!(
!aws_profile.secret,
"AWS_PROFILE should not be marked as secret"
);
let aws_region = meta
.config_keys
.iter()
.find(|k| k.name == "AWS_REGION")
.expect("AWS_REGION config key should exist");
assert!(!aws_region.required, "AWS_REGION should not be required");
assert!(
!aws_region.secret,
"AWS_REGION should not be marked as secret"
);
let bearer_token = meta
.config_keys
.iter()
.find(|k| k.name == "AWS_BEARER_TOKEN_BEDROCK")
.expect("AWS_BEARER_TOKEN_BEDROCK config key should exist");
assert!(
!bearer_token.required,
"AWS_BEARER_TOKEN_BEDROCK should not be required"
);
assert!(
bearer_token.secret,
"AWS_BEARER_TOKEN_BEDROCK should be marked as secret"
);
}
}
+29 -9
View File
@@ -395,6 +395,15 @@ async fn test_provider(
load_env(); load_env();
// Check required_vars BEFORE applying env_modifications to avoid
// leaving the environment mutated when skipping
let missing_vars = required_vars.iter().any(|var| std::env::var(var).is_err());
if missing_vars {
println!("Skipping {} tests - credentials not configured", name);
TEST_REPORT.record_skip(name);
return Ok(());
}
let mut original_env = HashMap::new(); let mut original_env = HashMap::new();
for &var in required_vars { for &var in required_vars {
if let Ok(val) = std::env::var(var) { if let Ok(val) = std::env::var(var) {
@@ -418,13 +427,6 @@ async fn test_provider(
} }
} }
let missing_vars = required_vars.iter().any(|var| std::env::var(var).is_err());
if missing_vars {
println!("Skipping {} tests - credentials not configured", name);
TEST_REPORT.record_skip(name);
return Ok(());
}
original_env original_env
}; };
@@ -488,7 +490,7 @@ async fn test_azure_provider() -> Result<()> {
#[tokio::test] #[tokio::test]
async fn test_bedrock_provider_long_term_credentials() -> Result<()> { async fn test_bedrock_provider_long_term_credentials() -> Result<()> {
test_provider( test_provider(
"Bedrock", "aws_bedrock",
BEDROCK_DEFAULT_MODEL, BEDROCK_DEFAULT_MODEL,
&["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], &["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
None, None,
@@ -502,7 +504,7 @@ async fn test_bedrock_provider_aws_profile_credentials() -> Result<()> {
HashMap::from_iter([("AWS_ACCESS_KEY_ID", None), ("AWS_SECRET_ACCESS_KEY", None)]); HashMap::from_iter([("AWS_ACCESS_KEY_ID", None), ("AWS_SECRET_ACCESS_KEY", None)]);
test_provider( test_provider(
"Bedrock", "aws_bedrock",
BEDROCK_DEFAULT_MODEL, BEDROCK_DEFAULT_MODEL,
&["AWS_PROFILE"], &["AWS_PROFILE"],
Some(env_mods), Some(env_mods),
@@ -510,6 +512,24 @@ async fn test_bedrock_provider_aws_profile_credentials() -> Result<()> {
.await .await
} }
#[tokio::test]
async fn test_bedrock_provider_bearer_token() -> Result<()> {
// Clear standard AWS credentials to ensure bearer token auth is used
let env_mods = HashMap::from_iter([
("AWS_ACCESS_KEY_ID", None),
("AWS_SECRET_ACCESS_KEY", None),
("AWS_PROFILE", None),
]);
test_provider(
"aws_bedrock",
BEDROCK_DEFAULT_MODEL,
&["AWS_BEARER_TOKEN_BEDROCK", "AWS_REGION"],
Some(env_mods),
)
.await
}
#[tokio::test] #[tokio::test]
async fn test_databricks_provider() -> Result<()> { async fn test_databricks_provider() -> Result<()> {
test_provider( test_provider(