@@ -18,87 +18,97 @@ use tracing::debug;
1818use tracing:: error;
1919
2020pub async fn resolve_secrets ( config : Arc < Config > , aws_config : Arc < AwsConfig > ) -> Option < String > {
21- let api_key_candidate =
22- if !config. api_key_secret_arn . is_empty ( ) || !config. kms_api_key . is_empty ( ) {
23- let before_decrypt = Instant :: now ( ) ;
21+ let api_key_candidate = if !config. api_key_secret_arn . is_empty ( )
22+ || !config. kms_api_key . is_empty ( )
23+ || !config. api_key_ssm_arn . is_empty ( )
24+ {
25+ let before_decrypt = Instant :: now ( ) ;
26+
27+ let builder = match create_reqwest_client_builder ( ) {
28+ Ok ( builder) => builder,
29+ Err ( err) => {
30+ error ! ( "Error creating reqwest client builder: {}" , err) ;
31+ return None ;
32+ }
33+ } ;
2434
25- let builder = match create_reqwest_client_builder ( ) {
26- Ok ( builder ) => builder ,
27- Err ( err) => {
28- error ! ( "Error creating reqwest client builder : {}" , err) ;
29- return None ;
30- }
31- } ;
35+ let client = match builder . build ( ) {
36+ Ok ( client ) => client ,
37+ Err ( err) => {
38+ error ! ( "Error creating reqwest client: {}" , err) ;
39+ return None ;
40+ }
41+ } ;
3242
33- let client = match builder. build ( ) {
34- Ok ( client) => client,
43+ let mut aws_credentials = AwsCredentials :: from_env ( ) ;
44+
45+ if aws_credentials. aws_secret_access_key . is_empty ( )
46+ && aws_credentials. aws_access_key_id . is_empty ( )
47+ && !aws_credentials
48+ . aws_container_credentials_full_uri
49+ . is_empty ( )
50+ && !aws_credentials. aws_container_authorization_token . is_empty ( )
51+ {
52+ // We're in Snap Start
53+ let credentials = match get_snapstart_credentials ( & aws_credentials, & client) . await {
54+ Ok ( credentials) => credentials,
3555 Err ( err) => {
36- error ! ( "Error creating reqwest client : {}" , err) ;
56+ error ! ( "Error getting Snap Start credentials : {}" , err) ;
3757 return None ;
3858 }
3959 } ;
60+ aws_credentials. aws_access_key_id = credentials[ "AccessKeyId" ]
61+ . as_str ( )
62+ . unwrap_or_default ( )
63+ . to_string ( ) ;
64+ aws_credentials. aws_secret_access_key = credentials[ "SecretAccessKey" ]
65+ . as_str ( )
66+ . unwrap_or_default ( )
67+ . to_string ( ) ;
68+ aws_credentials. aws_session_token = credentials[ "Token" ]
69+ . as_str ( )
70+ . unwrap_or_default ( )
71+ . to_string ( ) ;
72+ }
4073
41- let mut aws_credentials = AwsCredentials :: from_env ( ) ;
42-
43- if aws_credentials. aws_secret_access_key . is_empty ( )
44- && aws_credentials. aws_access_key_id . is_empty ( )
45- && !aws_credentials
46- . aws_container_credentials_full_uri
47- . is_empty ( )
48- && !aws_credentials. aws_container_authorization_token . is_empty ( )
49- {
50- // We're in Snap Start
51- let credentials = match get_snapstart_credentials ( & aws_credentials, & client) . await {
52- Ok ( credentials) => credentials,
53- Err ( err) => {
54- error ! ( "Error getting Snap Start credentials: {}" , err) ;
55- return None ;
56- }
57- } ;
58- aws_credentials. aws_access_key_id = credentials[ "AccessKeyId" ]
59- . as_str ( )
60- . unwrap_or_default ( )
61- . to_string ( ) ;
62- aws_credentials. aws_secret_access_key = credentials[ "SecretAccessKey" ]
63- . as_str ( )
64- . unwrap_or_default ( )
65- . to_string ( ) ;
66- aws_credentials. aws_session_token = credentials[ "Token" ]
67- . as_str ( )
68- . unwrap_or_default ( )
69- . to_string ( ) ;
70- }
71-
72- let decrypted_key = if config. kms_api_key . is_empty ( ) {
73- decrypt_aws_sm (
74- & client,
75- config. api_key_secret_arn . clone ( ) ,
76- aws_config,
77- & aws_credentials,
78- )
79- . await
80- } else {
81- decrypt_aws_kms (
82- & client,
83- config. kms_api_key . clone ( ) ,
84- aws_config,
85- & aws_credentials,
86- )
87- . await
88- } ;
74+ let decrypted_key = if !config. kms_api_key . is_empty ( ) {
75+ decrypt_aws_kms (
76+ & client,
77+ config. kms_api_key . clone ( ) ,
78+ aws_config,
79+ & aws_credentials,
80+ )
81+ . await
82+ } else if !config. api_key_secret_arn . is_empty ( ) {
83+ decrypt_aws_sm (
84+ & client,
85+ config. api_key_secret_arn . clone ( ) ,
86+ aws_config,
87+ & aws_credentials,
88+ )
89+ . await
90+ } else {
91+ decrypt_aws_ssm (
92+ & client,
93+ config. api_key_ssm_arn . clone ( ) ,
94+ aws_config,
95+ & aws_credentials,
96+ )
97+ . await
98+ } ;
8999
90- debug ! ( "Decrypt took {} ms" , before_decrypt. elapsed( ) . as_millis( ) ) ;
100+ debug ! ( "Decrypt took {} ms" , before_decrypt. elapsed( ) . as_millis( ) ) ;
91101
92- match decrypted_key {
93- Ok ( key) => Some ( key) ,
94- Err ( err) => {
95- error ! ( "Error decrypting key: {}" , err) ;
96- None
97- }
102+ match decrypted_key {
103+ Ok ( key) => Some ( key) ,
104+ Err ( err) => {
105+ error ! ( "Error decrypting key: {}" , err) ;
106+ None
98107 }
99- } else {
100- Some ( config. api_key . clone ( ) )
101- } ;
108+ }
109+ } else {
110+ Some ( config. api_key . clone ( ) )
111+ } ;
102112
103113 clean_api_key ( api_key_candidate)
104114}
@@ -215,6 +225,39 @@ async fn decrypt_aws_sm(
215225 }
216226}
217227
228+ async fn decrypt_aws_ssm (
229+ client : & Client ,
230+ parameter_arn : String ,
231+ aws_config : Arc < AwsConfig > ,
232+ aws_credentials : & AwsCredentials ,
233+ ) -> Result < String , Box < dyn std:: error:: Error + Send + Sync > > {
234+ let json_body = & serde_json:: json!( { "Name" : parameter_arn, "WithDecryption" : true } ) ;
235+ let parameter_region = parameter_arn
236+ . split ( ':' )
237+ . nth ( 3 )
238+ . unwrap_or ( & aws_config. region )
239+ . to_string ( ) ;
240+ let headers = build_get_secret_signed_headers (
241+ aws_config,
242+ aws_credentials,
243+ parameter_region,
244+ RequestArgs {
245+ service : "ssm" . to_string ( ) ,
246+ body : json_body,
247+ time : Utc :: now ( ) ,
248+ x_amz_target : "AmazonSSM.GetParameter" . to_string ( ) ,
249+ } ,
250+ ) ;
251+
252+ let v = request ( json_body, headers?, client) . await ?;
253+ if let Some ( parameter) = v[ "Parameter" ] . as_object ( ) {
254+ if let Some ( value) = parameter[ "Value" ] . as_str ( ) {
255+ return Ok ( value. to_string ( ) ) ;
256+ }
257+ }
258+ Err ( Error :: new ( std:: io:: ErrorKind :: InvalidData , v. to_string ( ) ) . into ( ) )
259+ }
260+
218261async fn get_snapstart_credentials (
219262 aws_credentials : & AwsCredentials ,
220263 client : & Client ,
@@ -483,4 +526,122 @@ mod tests {
483526 assert_eq ! ( headers. get( k) . expect( "cannot get header" ) , v) ;
484527 }
485528 }
529+
530+ #[ test]
531+ #[ allow( clippy:: unwrap_used) ]
532+ fn test_ssm_parameter_headers ( ) {
533+ let time = Utc . from_utc_datetime (
534+ & NaiveDateTime :: parse_from_str ( "2024-05-30 09:10:11" , "%Y-%m-%d %H:%M:%S" ) . unwrap ( ) ,
535+ ) ;
536+ let headers = build_get_secret_signed_headers (
537+ Arc :: new ( AwsConfig {
538+ region : "us-east-1" . to_string ( ) ,
539+ aws_lwa_proxy_lambda_runtime_api : Some ( "***" . into ( ) ) ,
540+ function_name : "arn:some-function" . to_string ( ) ,
541+ sandbox_init_time : Instant :: now ( ) ,
542+ runtime_api : String :: new ( ) ,
543+ exec_wrapper : None ,
544+ } ) ,
545+ & AwsCredentials {
546+ aws_access_key_id : "AKIDEXAMPLE" . to_string ( ) ,
547+ aws_secret_access_key : "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" . to_string ( ) ,
548+ aws_session_token : "AQoDYXdzEJr...<remainder of session token>" . to_string ( ) ,
549+ aws_container_authorization_token : String :: new ( ) ,
550+ aws_container_credentials_full_uri : String :: new ( ) ,
551+ } ,
552+ "us-east-1" . to_string ( ) ,
553+ RequestArgs {
554+ service : "ssm" . to_string ( ) ,
555+ body : & serde_json:: json!( { "Name" : "arn:aws:ssm:us-east-1:account-id:parameter/my-parameter" , "WithDecryption" : true } ) ,
556+ time,
557+ x_amz_target : "AmazonSSM.GetParameter" . to_string ( ) ,
558+ } ,
559+ ) . unwrap ( ) ;
560+
561+ let mut expected_headers = HeaderMap :: new ( ) ;
562+ expected_headers. insert ( "authorization" , HeaderValue :: from_str ( "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20240530/us-east-1/ssm/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=f45eebacb9f2f575cf1a235c65c97adc1c0d6ca21174044baa0c6457d4518d64" ) . unwrap ( ) ) ;
563+ expected_headers. insert (
564+ "host" ,
565+ HeaderValue :: from_str ( "ssm.us-east-1.amazonaws.com" ) . unwrap ( ) ,
566+ ) ;
567+ expected_headers. insert (
568+ "content-type" ,
569+ HeaderValue :: from_str ( "application/x-amz-json-1.1" ) . unwrap ( ) ,
570+ ) ;
571+ expected_headers. insert (
572+ "x-amz-date" ,
573+ HeaderValue :: from_str ( "20240530T091011Z" ) . unwrap ( ) ,
574+ ) ;
575+ expected_headers. insert (
576+ "x-amz-target" ,
577+ HeaderValue :: from_str ( "AmazonSSM.GetParameter" ) . unwrap ( ) ,
578+ ) ;
579+ expected_headers. insert (
580+ "x-amz-security-token" ,
581+ HeaderValue :: from_str ( "AQoDYXdzEJr...<remainder of session token>" ) . unwrap ( ) ,
582+ ) ;
583+
584+ for ( k, v) in & expected_headers {
585+ assert_eq ! ( headers. get( k) . expect( "cannot get header" ) , v) ;
586+ }
587+ }
588+
589+ #[ test]
590+ #[ allow( clippy:: unwrap_used) ]
591+ fn test_cross_region_ssm_parameter ( ) {
592+ let time = Utc . from_utc_datetime (
593+ & NaiveDateTime :: parse_from_str ( "2024-05-30 09:10:11" , "%Y-%m-%d %H:%M:%S" ) . unwrap ( ) ,
594+ ) ;
595+ let headers = build_get_secret_signed_headers (
596+ Arc :: new ( AwsConfig {
597+ region : "us-east-1" . to_string ( ) ,
598+ aws_lwa_proxy_lambda_runtime_api : Some ( "***" . into ( ) ) ,
599+ function_name : "arn:some-function" . to_string ( ) ,
600+ sandbox_init_time : Instant :: now ( ) ,
601+ runtime_api : String :: new ( ) ,
602+ exec_wrapper : None ,
603+ } ) ,
604+ & AwsCredentials {
605+ aws_access_key_id : "AKIDEXAMPLE" . to_string ( ) ,
606+ aws_secret_access_key : "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" . to_string ( ) ,
607+ aws_session_token : "AQoDYXdzEJr...<remainder of session token>" . to_string ( ) ,
608+ aws_container_authorization_token : String :: new ( ) ,
609+ aws_container_credentials_full_uri : String :: new ( ) ,
610+ } ,
611+ "eu-west-1" . to_string ( ) ,
612+ RequestArgs {
613+ service : "ssm" . to_string ( ) ,
614+ body : & serde_json:: json!( { "Name" : "arn:aws:ssm:eu-west-1:account-id:parameter/my-parameter" , "WithDecryption" : true } ) ,
615+ time,
616+ x_amz_target : "AmazonSSM.GetParameter" . to_string ( ) ,
617+ } ,
618+ ) . unwrap ( ) ;
619+
620+ let mut expected_headers = HeaderMap :: new ( ) ;
621+ expected_headers. insert ( "authorization" , HeaderValue :: from_str ( "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20240530/eu-west-1/ssm/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=4bdd2a27b632981c73a70b52ea84e92d3e26fe2c768e9cf18b96630188748b7a" ) . unwrap ( ) ) ;
622+ expected_headers. insert (
623+ "host" ,
624+ HeaderValue :: from_str ( "ssm.eu-west-1.amazonaws.com" ) . unwrap ( ) ,
625+ ) ;
626+ expected_headers. insert (
627+ "content-type" ,
628+ HeaderValue :: from_str ( "application/x-amz-json-1.1" ) . unwrap ( ) ,
629+ ) ;
630+ expected_headers. insert (
631+ "x-amz-date" ,
632+ HeaderValue :: from_str ( "20240530T091011Z" ) . unwrap ( ) ,
633+ ) ;
634+ expected_headers. insert (
635+ "x-amz-target" ,
636+ HeaderValue :: from_str ( "AmazonSSM.GetParameter" ) . unwrap ( ) ,
637+ ) ;
638+ expected_headers. insert (
639+ "x-amz-security-token" ,
640+ HeaderValue :: from_str ( "AQoDYXdzEJr...<remainder of session token>" ) . unwrap ( ) ,
641+ ) ;
642+
643+ for ( k, v) in & expected_headers {
644+ assert_eq ! ( headers. get( k) . expect( "cannot get header" ) , v) ;
645+ }
646+ }
486647}
0 commit comments