@@ -8,6 +8,7 @@ use askama::Template;
88use aws_config:: BehaviorVersion ;
99use aws_sdk_sesv2:: Client as SesClient ;
1010use aws_sdk_sesv2:: types:: { Body as SesBody , Content , Destination , EmailContent , Message } ;
11+ use aws_sdk_ssm:: Client as SsmClient ;
1112use email_address:: EmailAddress ;
1213use hndigest:: storage_adapter:: StorageAdapter ;
1314use hndigest:: strategies:: DigestStrategy ;
@@ -68,19 +69,6 @@ struct TurnstileVerifyResponse {
6869 success : bool ,
6970}
7071
71- /// Response from the AWS Parameters and Secrets Lambda Extension.
72- #[ derive( Debug , Deserialize ) ]
73- struct SsmExtensionResponse {
74- #[ serde( rename = "Parameter" ) ]
75- parameter : SsmParameterValue ,
76- }
77-
78- #[ derive( Debug , Deserialize ) ]
79- struct SsmParameterValue {
80- #[ serde( rename = "Value" ) ]
81- value : String ,
82- }
83-
8472// ============================================================================
8573// HTTP Response Helpers
8674// ============================================================================
@@ -160,18 +148,28 @@ async fn main() -> Result<(), Error> {
160148 . map_err ( |_| Error :: from ( "EMAIL_REPLY_TO environment variable must be set" ) ) ?;
161149 let base_url = env:: var ( "BASE_URL" )
162150 . map_err ( |_| Error :: from ( "BASE_URL environment variable must be set" ) ) ?;
151+
152+ // I'd love to pass this as an environment variable, but using AWS secrets manager is expensive
153+ // and this is effectively free
154+ let ssm_client = SsmClient :: new ( & config) ;
163155 let turnstile_secret_key_param = env:: var ( "TURNSTILE_SECRET_KEY_PARAM" )
164156 . map_err ( |_| Error :: from ( "TURNSTILE_SECRET_KEY_PARAM environment variable must be set" ) ) ?;
157+ let turnstile_secret_key = ssm_client
158+ . get_parameter ( )
159+ . name ( & turnstile_secret_key_param)
160+ . with_decryption ( true )
161+ . send ( )
162+ . await ?
163+ . parameter ( )
164+ . and_then ( |p| p. value . clone ( ) )
165+ . ok_or_else ( || {
166+ anyhow:: format_err!(
167+ "SSM parameter value not found for name {}" ,
168+ turnstile_secret_key_param
169+ )
170+ } ) ?;
165171
166172 let http_client = reqwest:: Client :: new ( ) ;
167- let turnstile_secret_key = fetch_ssm_parameter ( & http_client, & turnstile_secret_key_param)
168- . await
169- . map_err ( |e| {
170- Error :: from ( format ! (
171- "Failed to fetch SSM parameter {}: {}" ,
172- turnstile_secret_key_param, e
173- ) )
174- } ) ?;
175173 let storage = Arc :: new ( StorageAdapter :: new ( dynamodb_client, dynamodb_table) ) ;
176174 let state = Arc :: new ( AppState {
177175 storage,
@@ -305,27 +303,6 @@ async fn handle_unsubscribe_post(
305303 }
306304}
307305
308- /// Fetch a parameter from SSM via the AWS Parameters and Secrets Lambda Extension.
309- /// The extension runs as a Lambda layer and serves requests on localhost:2773.
310- async fn fetch_ssm_parameter (
311- http_client : & reqwest:: Client ,
312- parameter_name : & str ,
313- ) -> Result < String , Box < dyn std:: error:: Error > > {
314- let session_token = env:: var ( "AWS_SESSION_TOKEN" ) ?;
315- let response: SsmExtensionResponse = http_client
316- . get ( format ! (
317- "http://localhost:2773/systemsmanager/parameters/get?name={}&withDecryption=true" ,
318- urlencoding:: encode( parameter_name)
319- ) )
320- . header ( "X-Aws-Parameters-Secrets-Token" , & session_token)
321- . send ( )
322- . await ?
323- . error_for_status ( ) ?
324- . json ( )
325- . await ?;
326- Ok ( response. parameter . value )
327- }
328-
329306/// Verify a Cloudflare Turnstile CAPTCHA token.
330307async fn verify_turnstile_token (
331308 http_client : & reqwest:: Client ,
@@ -385,6 +362,7 @@ async fn handle_subscribe_post(state: &Arc<AppState>, body: &str) -> Response<Bo
385362 warn ! ( "Missing Turnstile CAPTCHA token" ) ;
386363 return json_response ( 400 , r#"{"error": "CAPTCHA verification required"}"# ) ;
387364 }
365+
388366 match verify_turnstile_token (
389367 & state. http_client ,
390368 & state. turnstile_secret_key ,
0 commit comments