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:
@@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user