diff --git a/bottlecap/src/bin/bottlecap/main.rs b/bottlecap/src/bin/bottlecap/main.rs index f6f600a44..80045922c 100644 --- a/bottlecap/src/bin/bottlecap/main.rs +++ b/bottlecap/src/bin/bottlecap/main.rs @@ -13,7 +13,7 @@ use bottlecap::{ base_url, config::{ self, - aws::{build_lambda_function_arn, AwsConfig}, + aws::{build_lambda_function_arn, AwsConfig, AwsCredentials}, Config, }, event_bus::bus::EventBus, @@ -302,7 +302,7 @@ async fn register(client: &Client) -> Result { #[tokio::main] async fn main() -> Result<()> { let start_time = Instant::now(); - let (mut aws_config, config) = load_configs(start_time); + let (aws_config, mut aws_credentials, config) = load_configs(start_time); enable_logging_subsystem(&config); log_fips_status(&aws_config.region); @@ -329,7 +329,9 @@ async fn main() -> Result<()> { .await .map_err(|e| Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?; - if let Some(resolved_api_key) = resolve_secrets(Arc::clone(&config), &mut aws_config).await { + if let Some(resolved_api_key) = + resolve_secrets(Arc::clone(&config), &aws_config, &mut aws_credentials).await + { match extension_loop_active( &aws_config, &config, @@ -357,9 +359,10 @@ async fn main() -> Result<()> { } } -fn load_configs(start_time: Instant) -> (AwsConfig, Arc) { +fn load_configs(start_time: Instant) -> (AwsConfig, AwsCredentials, Arc) { // First load the AWS configuration let aws_config = AwsConfig::from_env(start_time); + let aws_credentials = AwsCredentials::from_env(); let lambda_directory: String = env::var("LAMBDA_TASK_ROOT").unwrap_or_else(|_| "/var/task".to_string()); let config = match config::get_config(Path::new(&lambda_directory)) { @@ -370,7 +373,7 @@ fn load_configs(start_time: Instant) -> (AwsConfig, Arc) { } }; - (aws_config, config) + (aws_config, aws_credentials, config) } fn enable_logging_subsystem(config: &Arc) { diff --git a/bottlecap/src/config/aws.rs b/bottlecap/src/config/aws.rs index 49067dd2c..b45fc0085 100644 --- a/bottlecap/src/config/aws.rs +++ b/bottlecap/src/config/aws.rs @@ -15,11 +15,6 @@ const AWS_LAMBDA_EXEC_WRAPPER: &str = "AWS_LAMBDA_EXEC_WRAPPER"; #[derive(Debug, Clone)] pub struct AwsConfig { pub region: String, - pub aws_access_key_id: String, - pub aws_secret_access_key: String, - pub aws_session_token: String, - pub aws_container_credentials_full_uri: String, - pub aws_container_authorization_token: String, pub aws_lwa_proxy_lambda_runtime_api: Option, pub function_name: String, pub runtime_api: String, @@ -32,6 +27,29 @@ impl AwsConfig { pub fn from_env(start_time: Instant) -> Self { Self { region: env::var(AWS_DEFAULT_REGION).unwrap_or("us-east-1".to_string()), + aws_lwa_proxy_lambda_runtime_api: env::var(AWS_LWA_LAMBDA_RUNTIME_API_PROXY).ok(), + function_name: env::var(AWS_LAMBDA_FUNCTION_NAME).unwrap_or_default(), + runtime_api: env::var(AWS_LAMBDA_RUNTIME_API).unwrap_or_default(), + sandbox_init_time: start_time, + exec_wrapper: env::var(AWS_LAMBDA_EXEC_WRAPPER).ok(), + } + } +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone)] +pub struct AwsCredentials { + pub aws_access_key_id: String, + pub aws_secret_access_key: String, + pub aws_session_token: String, + pub aws_container_credentials_full_uri: String, + pub aws_container_authorization_token: String, +} + +impl AwsCredentials { + #[must_use] + pub fn from_env() -> Self { + Self { aws_access_key_id: env::var(AWS_ACCESS_KEY_ID).unwrap_or_default(), aws_secret_access_key: env::var(AWS_SECRET_ACCESS_KEY).unwrap_or_default(), aws_session_token: env::var(AWS_SESSION_TOKEN).unwrap_or_default(), @@ -39,11 +57,6 @@ impl AwsConfig { .unwrap_or_default(), aws_container_authorization_token: env::var(AWS_CONTAINER_AUTHORIZATION_TOKEN) .unwrap_or_default(), - aws_lwa_proxy_lambda_runtime_api: env::var(AWS_LWA_LAMBDA_RUNTIME_API_PROXY).ok(), - function_name: env::var(AWS_LAMBDA_FUNCTION_NAME).unwrap_or_default(), - runtime_api: env::var(AWS_LAMBDA_RUNTIME_API).unwrap_or_default(), - sandbox_init_time: start_time, - exec_wrapper: env::var(AWS_LAMBDA_EXEC_WRAPPER).ok(), } } } diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index a4f89af49..5c2de928e 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -966,11 +966,6 @@ mod tests { fn setup() -> Processor { let aws_config = AwsConfig { region: "us-east-1".into(), - aws_access_key_id: "***".into(), - aws_secret_access_key: "***".into(), - aws_session_token: "***".into(), - aws_container_credentials_full_uri: "***".into(), - aws_container_authorization_token: "***".into(), aws_lwa_proxy_lambda_runtime_api: Some("***".into()), function_name: "test-function".into(), sandbox_init_time: Instant::now(), diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index d6ae707d6..49d0f2f06 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -480,11 +480,6 @@ mod tests { let aws_config = AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "".to_string(), - aws_secret_access_key: "".to_string(), - aws_session_token: "".to_string(), - aws_container_credentials_full_uri: "".to_string(), - aws_container_authorization_token: "".to_string(), aws_lwa_proxy_lambda_runtime_api: Some("".to_string()), runtime_api: "".to_string(), function_name: "".to_string(), diff --git a/bottlecap/src/proxy/interceptor.rs b/bottlecap/src/proxy/interceptor.rs index 252c2ffce..ce3b3c052 100644 --- a/bottlecap/src/proxy/interceptor.rs +++ b/bottlecap/src/proxy/interceptor.rs @@ -400,13 +400,8 @@ mod tests { let aws_config = AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "AKIDEXAMPLE".to_string(), - aws_secret_access_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".to_string(), - aws_session_token: "AQoDYXdzEJr...".to_string(), function_name: "arn:some-function".to_string(), sandbox_init_time: Instant::now(), - aws_container_credentials_full_uri: String::new(), - aws_container_authorization_token: String::new(), runtime_api: aws_lambda_runtime_api.to_string(), aws_lwa_proxy_lambda_runtime_api: Some(aws_lwa_lambda_runtime_api.to_string()), exec_wrapper: None, diff --git a/bottlecap/src/proxy/mod.rs b/bottlecap/src/proxy/mod.rs index ce424ab27..48c673a12 100644 --- a/bottlecap/src/proxy/mod.rs +++ b/bottlecap/src/proxy/mod.rs @@ -37,11 +37,6 @@ mod tests { }); let aws_config = AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "".to_string(), - aws_secret_access_key: "".to_string(), - aws_session_token: "".to_string(), - aws_container_credentials_full_uri: "".to_string(), - aws_container_authorization_token: "".to_string(), aws_lwa_proxy_lambda_runtime_api: Some("127.0.0.1:12345".to_string()), function_name: "".to_string(), runtime_api: "".to_string(), @@ -55,11 +50,6 @@ mod tests { let config = Arc::new(Config::default()); let aws_config = AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "".to_string(), - aws_secret_access_key: "".to_string(), - aws_session_token: "".to_string(), - aws_container_credentials_full_uri: "".to_string(), - aws_container_authorization_token: "".to_string(), // LWA proxy is set, so we should start the proxy aws_lwa_proxy_lambda_runtime_api: Some("127.0.0.1:12345".to_string()), function_name: "".to_string(), @@ -79,11 +69,6 @@ mod tests { }); let aws_config = AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "".to_string(), - aws_secret_access_key: "".to_string(), - aws_session_token: "".to_string(), - aws_container_credentials_full_uri: "".to_string(), - aws_container_authorization_token: "".to_string(), aws_lwa_proxy_lambda_runtime_api: None, function_name: "".to_string(), runtime_api: "".to_string(), @@ -102,11 +87,6 @@ mod tests { }); let aws_config = AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "".to_string(), - aws_secret_access_key: "".to_string(), - aws_session_token: "".to_string(), - aws_container_credentials_full_uri: "".to_string(), - aws_container_authorization_token: "".to_string(), aws_lwa_proxy_lambda_runtime_api: None, function_name: "".to_string(), runtime_api: "".to_string(), @@ -125,11 +105,6 @@ mod tests { }); let aws_config = AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "".to_string(), - aws_secret_access_key: "".to_string(), - aws_session_token: "".to_string(), - aws_container_credentials_full_uri: "".to_string(), - aws_container_authorization_token: "".to_string(), aws_lwa_proxy_lambda_runtime_api: None, function_name: "".to_string(), runtime_api: "".to_string(), diff --git a/bottlecap/src/secrets/decrypt.rs b/bottlecap/src/secrets/decrypt.rs index 0f474eaf3..d1d11de2a 100644 --- a/bottlecap/src/secrets/decrypt.rs +++ b/bottlecap/src/secrets/decrypt.rs @@ -1,4 +1,7 @@ -use crate::config::{aws::AwsConfig, Config}; +use crate::config::{ + aws::{AwsConfig, AwsCredentials}, + Config, +}; use crate::fips::compute_aws_api_host; use base64::prelude::*; use chrono::{DateTime, Utc}; @@ -14,7 +17,11 @@ use std::time::Instant; use tracing::debug; use tracing::error; -pub async fn resolve_secrets(config: Arc, aws_config: &mut AwsConfig) -> Option { +pub async fn resolve_secrets( + config: Arc, + aws_config: &AwsConfig, + aws_credentials: &mut AwsCredentials, +) -> Option { let api_key_candidate = if !config.api_key_secret_arn.is_empty() || !config.kms_api_key.is_empty() { let before_decrypt = Instant::now(); @@ -35,37 +42,51 @@ pub async fn resolve_secrets(config: Arc, aws_config: &mut AwsConfig) -> } }; - if aws_config.aws_secret_access_key.is_empty() - && aws_config.aws_access_key_id.is_empty() - && !aws_config.aws_container_credentials_full_uri.is_empty() - && !aws_config.aws_container_authorization_token.is_empty() + if aws_credentials.aws_secret_access_key.is_empty() + && aws_credentials.aws_access_key_id.is_empty() + && !aws_credentials + .aws_container_credentials_full_uri + .is_empty() + && !aws_credentials.aws_container_authorization_token.is_empty() { // We're in Snap Start - let credentials = match get_snapstart_credentials(aws_config, &client).await { + let credentials = match get_snapstart_credentials(aws_credentials, &client).await { Ok(credentials) => credentials, Err(err) => { error!("Error getting Snap Start credentials: {}", err); return None; } }; - aws_config.aws_access_key_id = credentials["AccessKeyId"] + aws_credentials.aws_access_key_id = credentials["AccessKeyId"] .as_str() .unwrap_or_default() .to_string(); - aws_config.aws_secret_access_key = credentials["SecretAccessKey"] + aws_credentials.aws_secret_access_key = credentials["SecretAccessKey"] .as_str() .unwrap_or_default() .to_string(); - aws_config.aws_session_token = credentials["Token"] + aws_credentials.aws_session_token = credentials["Token"] .as_str() .unwrap_or_default() .to_string(); } let decrypted_key = if config.kms_api_key.is_empty() { - decrypt_aws_sm(&client, config.api_key_secret_arn.clone(), aws_config).await + decrypt_aws_sm( + &client, + config.api_key_secret_arn.clone(), + aws_config, + aws_credentials, + ) + .await } else { - decrypt_aws_kms(&client, config.kms_api_key.clone(), aws_config).await + decrypt_aws_kms( + &client, + config.kms_api_key.clone(), + aws_config, + aws_credentials, + ) + .await }; debug!("Decrypt took {}ms", before_decrypt.elapsed().as_millis()); @@ -106,6 +127,7 @@ async fn decrypt_aws_kms( client: &Client, kms_key: String, aws_config: &AwsConfig, + aws_credentials: &AwsCredentials, ) -> Result> { // When the API key is encrypted using the AWS console, the function name is added as an // encryption context. When the API key is encrypted using the AWS CLI, no encryption context @@ -117,6 +139,7 @@ async fn decrypt_aws_kms( let headers = build_get_secret_signed_headers( aws_config, + aws_credentials, aws_config.region.clone(), RequestArgs { service: "kms".to_string(), @@ -139,6 +162,7 @@ async fn decrypt_aws_kms( let headers = build_get_secret_signed_headers( aws_config, + aws_credentials, aws_config.region.clone(), RequestArgs { service: "kms".to_string(), @@ -163,6 +187,7 @@ async fn decrypt_aws_sm( client: &Client, secret_arn: String, aws_config: &AwsConfig, + aws_credentials: &AwsCredentials, ) -> Result> { let json_body = &serde_json::json!({ "SecretId": secret_arn}); // Supports cross-region secrets @@ -173,6 +198,7 @@ async fn decrypt_aws_sm( .to_string(); let headers = build_get_secret_signed_headers( aws_config, + aws_credentials, secret_region, RequestArgs { service: "secretsmanager".to_string(), @@ -192,17 +218,17 @@ async fn decrypt_aws_sm( } async fn get_snapstart_credentials( - aws_config: &AwsConfig, + aws_credentials: &AwsCredentials, client: &Client, ) -> Result> { let mut headers = HeaderMap::new(); headers.insert( "Authorization", - HeaderValue::from_str(&aws_config.aws_container_authorization_token)?, + HeaderValue::from_str(&aws_credentials.aws_container_authorization_token)?, ); let req = client - .get(&aws_config.aws_container_credentials_full_uri) + .get(&aws_credentials.aws_container_credentials_full_uri) .headers(headers); let body = req.send().await?.text().await?; let v: Value = serde_json::from_str(&body)?; @@ -229,6 +255,7 @@ async fn request( fn build_get_secret_signed_headers( aws_config: &AwsConfig, + aws_credentials: &AwsCredentials, region: String, header_values: RequestArgs, ) -> Result> { @@ -247,7 +274,7 @@ fn build_get_secret_signed_headers( let canonical_querystring = ""; let canonical_headers = format!( "content-type:application/x-amz-json-1.1\nhost:{}\nx-amz-date:{}\nx-amz-security-token:{}\nx-amz-target:{}", - host, amz_date, aws_config.aws_session_token, header_values.x_amz_target); + host, amz_date, aws_credentials.aws_session_token, header_values.x_amz_target); let signed_headers = "content-type;host;x-amz-date;x-amz-security-token;x-amz-target"; let payload_hash = Sha256::digest(header_values.body.to_string().as_bytes()); @@ -270,7 +297,7 @@ fn build_get_secret_signed_headers( ); let signing_key = get_aws4_signature_key( - &aws_config.aws_secret_access_key, + &aws_credentials.aws_secret_access_key, &date_stamp, region.as_str(), header_values.service.as_str(), @@ -280,7 +307,7 @@ fn build_get_secret_signed_headers( let authorization_header = format!( "{} Credential={}/{}, SignedHeaders={}, Signature={}", - algorithm, aws_config.aws_access_key_id, credential_scope, signed_headers, signature + algorithm, aws_credentials.aws_access_key_id, credential_scope, signed_headers, signature ); let mut headers = HeaderMap::new(); headers.insert( @@ -299,7 +326,7 @@ fn build_get_secret_signed_headers( ); headers.insert( "x-amz-security-token", - HeaderValue::from_str(&aws_config.aws_session_token)?, + HeaderValue::from_str(&aws_credentials.aws_session_token)?, ); Ok(headers) } @@ -347,19 +374,21 @@ mod tests { &NaiveDateTime::parse_from_str("2024-05-30 09:10:11", "%Y-%m-%d %H:%M:%S").unwrap(), ); let headers = build_get_secret_signed_headers( - &AwsConfig{ + &AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "AKIDEXAMPLE".to_string(), - aws_secret_access_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".to_string(), - aws_session_token: "AQoDYXdzEJr...".to_string(), - aws_container_authorization_token: String::new(), - aws_container_credentials_full_uri: String::new(), aws_lwa_proxy_lambda_runtime_api: Some("***".into()), function_name: "arn:some-function".to_string(), sandbox_init_time: Instant::now(), runtime_api: String::new(), exec_wrapper: None, }, + &AwsCredentials{ + aws_access_key_id: "AKIDEXAMPLE".to_string(), + aws_secret_access_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".to_string(), + aws_session_token: "AQoDYXdzEJr...".to_string(), + aws_container_authorization_token: String::new(), + aws_container_credentials_full_uri: String::new(), + }, "us-east-1".to_string(), RequestArgs { service: "secretsmanager".to_string(), @@ -404,19 +433,21 @@ mod tests { &NaiveDateTime::parse_from_str("2024-05-30 09:10:11", "%Y-%m-%d %H:%M:%S").unwrap(), ); let headers = build_get_secret_signed_headers( - &AwsConfig{ + &AwsConfig { region: "us-east-1".to_string(), - aws_access_key_id: "AKIDEXAMPLE".to_string(), - aws_secret_access_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".to_string(), - aws_session_token: "AQoDYXdzEJr...".to_string(), - aws_container_authorization_token: String::new(), - aws_container_credentials_full_uri: String::new(), aws_lwa_proxy_lambda_runtime_api: Some("***".into()), function_name: "arn:some-function".to_string(), sandbox_init_time: Instant::now(), runtime_api: String::new(), exec_wrapper: None, }, + &AwsCredentials{ + aws_access_key_id: "AKIDEXAMPLE".to_string(), + aws_secret_access_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".to_string(), + aws_session_token: "AQoDYXdzEJr...".to_string(), + aws_container_authorization_token: String::new(), + aws_container_credentials_full_uri: String::new(), + }, "us-west-2".to_string(), RequestArgs { service: "secretsmanager".to_string(),