1- //! AWS cloud-based URI resolvers (S3, Secrets Manager, SSM Parameter Store).
1+ //! TLS-dependent URI resolvers (HTTPS, S3, Secrets Manager, SSM Parameter Store).
22//!
33//! This module is only compiled when the `tls` feature is enabled.
44
55use crate :: uri_resolver:: SettingsInput ;
66use crate :: uri_resolver:: { ResolverResult , UriResolver , MAX_SIZE_BYTES } ;
77use async_trait:: async_trait;
8+ use reqwest:: Url ;
89use snafu:: { ensure, OptionExt , ResultExt , Snafu } ;
910use std:: convert:: TryFrom ;
1011
@@ -13,6 +14,68 @@ use aws_sdk_s3;
1314use aws_sdk_secretsmanager;
1415use aws_sdk_ssm;
1516
17+ pub struct HttpsUri {
18+ url : Url ,
19+ }
20+
21+ impl TryFrom < & SettingsInput > for HttpsUri {
22+ type Error = crate :: uri_resolver:: ResolverError ;
23+ fn try_from ( input : & SettingsInput ) -> ResolverResult < Self > {
24+ use tls_resolver_error:: * ;
25+ let url = input. parsed_url . clone ( ) . context ( InvalidHttpsUriSnafu {
26+ input_source : input. input . clone ( ) ,
27+ } ) ?;
28+ ensure ! (
29+ url. scheme( ) == "https" ,
30+ InvalidHttpsUriSnafu {
31+ input_source: url. to_string( )
32+ }
33+ ) ;
34+ Ok ( HttpsUri { url } )
35+ }
36+ }
37+
38+ #[ async_trait]
39+ impl UriResolver for HttpsUri {
40+ async fn resolve ( & self ) -> ResolverResult < String > {
41+ use crate :: uri_resolver:: resolver_error:: * ;
42+ let uri_str = self . url . to_string ( ) ;
43+ let resp = reqwest:: get ( self . url . clone ( ) )
44+ . await
45+ . context ( HttpRequestSnafu { uri : & uri_str } ) ?;
46+ ensure ! (
47+ resp. status( ) . is_success( ) ,
48+ HttpStatusSnafu {
49+ uri: & uri_str,
50+ status: resp. status( ) ,
51+ }
52+ ) ;
53+ if let Some ( content_length) = resp. content_length ( ) {
54+ ensure ! (
55+ content_length < MAX_SIZE_BYTES ,
56+ HttpObjectTooLargeSnafu {
57+ size: content_length,
58+ max_size: MAX_SIZE_BYTES ,
59+ uri: & uri_str,
60+ }
61+ ) ;
62+ }
63+ let bytes = resp
64+ . bytes ( )
65+ . await
66+ . context ( HttpBodySnafu { uri : & uri_str } ) ?;
67+ ensure ! (
68+ bytes. len( ) as u64 <= MAX_SIZE_BYTES ,
69+ HttpObjectTooLargeSnafu {
70+ size: bytes. len( ) as u64 ,
71+ max_size: MAX_SIZE_BYTES ,
72+ uri: & uri_str,
73+ }
74+ ) ;
75+ String :: from_utf8 ( bytes. to_vec ( ) ) . context ( Utf8DecodeSnafu { uri : uri_str } )
76+ }
77+ }
78+
1679struct Arn {
1780 service : String ,
1881 region : String ,
@@ -53,7 +116,7 @@ pub struct S3Uri {
53116impl TryFrom < & SettingsInput > for S3Uri {
54117 type Error = crate :: uri_resolver:: ResolverError ;
55118 fn try_from ( input : & SettingsInput ) -> ResolverResult < Self > {
56- use cloud_error :: * ;
119+ use tls_resolver_error :: * ;
57120 const PREFIX : & str = "s3://" ;
58121 let uri_str = input. input . as_str ( ) ;
59122 let remainder = uri_str. strip_prefix ( PREFIX ) . context ( S3UriSchemeSnafu {
@@ -76,7 +139,7 @@ impl TryFrom<&SettingsInput> for S3Uri {
76139#[ async_trait]
77140impl UriResolver for S3Uri {
78141 async fn resolve ( & self ) -> ResolverResult < String > {
79- use cloud_error :: * ;
142+ use tls_resolver_error :: * ;
80143 let cfg = aws_config:: load_defaults ( aws_config:: BehaviorVersion :: latest ( ) ) . await ;
81144 let client = aws_sdk_s3:: Client :: new ( & cfg) ;
82145
@@ -91,10 +154,12 @@ impl UriResolver for S3Uri {
91154 key : self . key . clone ( ) ,
92155 } ) ?;
93156
94- let size = head_resp. content_length . context ( S3MissingContentLengthSnafu {
95- bucket : self . bucket . clone ( ) ,
96- key : self . key . clone ( ) ,
97- } ) ? as u64 ;
157+ let size = head_resp
158+ . content_length
159+ . context ( S3MissingContentLengthSnafu {
160+ bucket : self . bucket . clone ( ) ,
161+ key : self . key . clone ( ) ,
162+ } ) ? as u64 ;
98163
99164 ensure ! (
100165 size < MAX_SIZE_BYTES ,
@@ -122,9 +187,11 @@ impl UriResolver for S3Uri {
122187 key : self . key . clone ( ) ,
123188 } ) ?;
124189
125- String :: from_utf8 ( bytes. to_vec ( ) ) . context ( crate :: uri_resolver:: resolver_error:: Utf8DecodeSnafu {
126- uri : format ! ( "s3://{}/{}" , self . bucket, self . key) ,
127- } )
190+ String :: from_utf8 ( bytes. to_vec ( ) ) . context (
191+ crate :: uri_resolver:: resolver_error:: Utf8DecodeSnafu {
192+ uri : format ! ( "s3://{}/{}" , self . bucket, self . key) ,
193+ } ,
194+ )
128195 }
129196}
130197
@@ -137,7 +204,7 @@ impl TryFrom<&SettingsInput> for SecretsManagerArn {
137204 type Error = crate :: uri_resolver:: ResolverError ;
138205 fn try_from ( input : & SettingsInput ) -> ResolverResult < Self > {
139206 use crate :: uri_resolver:: resolver_error:: InvalidArnFormatSnafu ;
140- use cloud_error :: SecretsManagerArnSnafu ;
207+ use tls_resolver_error :: SecretsManagerArnSnafu ;
141208 let arn = Arn :: parse ( input. input . as_str ( ) ) ?;
142209 ensure ! (
143210 arn. parts == 7 ,
@@ -162,9 +229,11 @@ impl TryFrom<&SettingsInput> for SecretsManagerArn {
162229#[ async_trait]
163230impl UriResolver for SecretsManagerArn {
164231 async fn resolve ( & self ) -> ResolverResult < String > {
165- use cloud_error :: * ;
232+ use tls_resolver_error :: * ;
166233 let cfg = aws_config:: defaults ( aws_config:: BehaviorVersion :: latest ( ) )
167- . region ( aws_sdk_secretsmanager:: config:: Region :: new ( self . region . clone ( ) ) )
234+ . region ( aws_sdk_secretsmanager:: config:: Region :: new (
235+ self . region . clone ( ) ,
236+ ) )
168237 . load ( )
169238 . await ;
170239 let client = aws_sdk_secretsmanager:: Client :: new ( & cfg) ;
@@ -196,12 +265,14 @@ pub struct SecretsManagerUri {
196265impl TryFrom < & SettingsInput > for SecretsManagerUri {
197266 type Error = crate :: uri_resolver:: ResolverError ;
198267 fn try_from ( input : & SettingsInput ) -> ResolverResult < Self > {
199- use cloud_error :: * ;
268+ use tls_resolver_error :: * ;
200269 const PREFIX : & str = "secretsmanager://" ;
201270 let uri_str = input. input . as_str ( ) ;
202- let remainder = uri_str. strip_prefix ( PREFIX ) . context ( SecretsManagerUriSnafu {
203- input_source : input. input . clone ( ) ,
204- } ) ?;
271+ let remainder = uri_str
272+ . strip_prefix ( PREFIX )
273+ . context ( SecretsManagerUriSnafu {
274+ input_source : input. input . clone ( ) ,
275+ } ) ?;
205276 ensure ! (
206277 !remainder. is_empty( ) ,
207278 SecretsManagerUriSnafu {
@@ -217,7 +288,7 @@ impl TryFrom<&SettingsInput> for SecretsManagerUri {
217288#[ async_trait]
218289impl UriResolver for SecretsManagerUri {
219290 async fn resolve ( & self ) -> ResolverResult < String > {
220- use cloud_error :: * ;
291+ use tls_resolver_error :: * ;
221292 let cfg = aws_config:: load_defaults ( aws_config:: BehaviorVersion :: latest ( ) ) . await ;
222293 let client = aws_sdk_secretsmanager:: Client :: new ( & cfg) ;
223294 let resp = client
@@ -246,7 +317,7 @@ impl TryFrom<&SettingsInput> for SsmArn {
246317 type Error = crate :: uri_resolver:: ResolverError ;
247318 fn try_from ( input : & SettingsInput ) -> ResolverResult < Self > {
248319 use crate :: uri_resolver:: resolver_error:: InvalidArnFormatSnafu ;
249- use cloud_error :: SsmArnSnafu ;
320+ use tls_resolver_error :: SsmArnSnafu ;
250321 let arn = Arn :: parse ( input. input . as_str ( ) ) ?;
251322 ensure ! (
252323 arn. parts == 6 ,
@@ -271,7 +342,7 @@ impl TryFrom<&SettingsInput> for SsmArn {
271342#[ async_trait]
272343impl UriResolver for SsmArn {
273344 async fn resolve ( & self ) -> ResolverResult < String > {
274- use cloud_error :: * ;
345+ use tls_resolver_error :: * ;
275346 let cfg = aws_config:: defaults ( aws_config:: BehaviorVersion :: latest ( ) )
276347 . region ( aws_sdk_ssm:: config:: Region :: new ( self . region . clone ( ) ) )
277348 . load ( )
@@ -307,7 +378,7 @@ pub struct SsmUri {
307378impl TryFrom < & SettingsInput > for SsmUri {
308379 type Error = crate :: uri_resolver:: ResolverError ;
309380 fn try_from ( input : & SettingsInput ) -> ResolverResult < Self > {
310- use cloud_error :: * ;
381+ use tls_resolver_error :: * ;
311382 const PREFIX : & str = "ssm://" ;
312383 let uri_str = input. input . as_str ( ) ;
313384 let remainder = uri_str. strip_prefix ( PREFIX ) . context ( SsmUriSnafu {
@@ -328,7 +399,7 @@ impl TryFrom<&SettingsInput> for SsmUri {
328399#[ async_trait]
329400impl UriResolver for SsmUri {
330401 async fn resolve ( & self ) -> ResolverResult < String > {
331- use cloud_error :: * ;
402+ use tls_resolver_error :: * ;
332403 let config = aws_config:: load_defaults ( aws_config:: BehaviorVersion :: latest ( ) ) . await ;
333404 let client = aws_sdk_ssm:: Client :: new ( & config) ;
334405 let resp = client
@@ -352,7 +423,10 @@ impl UriResolver for SsmUri {
352423
353424#[ derive( Debug , Snafu ) ]
354425#[ snafu( module) ]
355- pub enum CloudError {
426+ pub enum TlsResolverError {
427+ #[ snafu( display( "Given invalid https:// URI '{}'" , input_source) ) ]
428+ InvalidHttpsUri { input_source : String } ,
429+
356430 #[ snafu( display( "Failed to HEAD S3 object s3://{bucket}/{key}: {source}" ) ) ]
357431 S3Head {
358432 source : aws_sdk_s3:: error:: SdkError <
@@ -380,7 +454,11 @@ pub enum CloudError {
380454 source : aws_sdk_s3:: primitives:: ByteStreamError ,
381455 } ,
382456
383- #[ snafu( display( "Failed to fetch secret '{}' from Secrets Manager: {}" , secret_id, source) ) ]
457+ #[ snafu( display(
458+ "Failed to fetch secret '{}' from Secrets Manager: {}" ,
459+ secret_id,
460+ source
461+ ) ) ]
384462 SecretsManagerGet {
385463 secret_id : String ,
386464 source : aws_sdk_secretsmanager:: error:: SdkError <
@@ -391,11 +469,19 @@ pub enum CloudError {
391469 #[ snafu( display( "Failed to fetch parameter '{}' from SSM: {}" , parameter_name, source) ) ]
392470 SsmGetParameter {
393471 parameter_name : String ,
394- source : aws_sdk_ssm:: error:: SdkError < aws_sdk_ssm:: operation:: get_parameter:: GetParameterError > ,
472+ source :
473+ aws_sdk_ssm:: error:: SdkError < aws_sdk_ssm:: operation:: get_parameter:: GetParameterError > ,
395474 } ,
396475
397- #[ snafu( display( "S3 object s3://{bucket}/{key} is too large ({size} bytes, maximum is {max_size} bytes)" ) ) ]
398- S3ObjectTooLarge { size : u64 , max_size : u64 , bucket : String , key : String } ,
476+ #[ snafu( display(
477+ "S3 object s3://{bucket}/{key} is too large ({size} bytes, maximum is {max_size} bytes)"
478+ ) ) ]
479+ S3ObjectTooLarge {
480+ size : u64 ,
481+ max_size : u64 ,
482+ bucket : String ,
483+ key : String ,
484+ } ,
399485
400486 #[ snafu( display( "Invalid S3 URI scheme for '{}', expected s3://" , input_source) ) ]
401487 S3UriScheme { input_source : String } ,
@@ -409,16 +495,25 @@ pub enum CloudError {
409495 #[ snafu( display( "No Content-Length for S3 object {bucket}/{key}" ) ) ]
410496 S3MissingContentLength { bucket : String , key : String } ,
411497
412- #[ snafu( display( "Invalid Secrets Manager URI scheme for '{}', expected secretsmanager://" , input_source) ) ]
498+ #[ snafu( display(
499+ "Invalid Secrets Manager URI scheme for '{}', expected secretsmanager://" ,
500+ input_source
501+ ) ) ]
413502 SecretsManagerUri { input_source : String } ,
414503
415504 #[ snafu( display( "Secrets Manager secret '{}' did not return a string value" , secret_id) ) ]
416505 SecretsManagerStringMissing { secret_id : String } ,
417506
418- #[ snafu( display( "Invalid Secrets Manager ARN scheme for '{}', expected arn:aws:secretsmanager:…" , input_source) ) ]
507+ #[ snafu( display(
508+ "Invalid Secrets Manager ARN scheme for '{}', expected arn:aws:secretsmanager:…" ,
509+ input_source
510+ ) ) ]
419511 SecretsManagerArn { input_source : String } ,
420512
421- #[ snafu( display( "Invalid SSM ARN scheme for '{}', expected arn:aws:ssm:…" , input_source) ) ]
513+ #[ snafu( display(
514+ "Invalid SSM ARN scheme for '{}', expected arn:aws:ssm:…" ,
515+ input_source
516+ ) ) ]
422517 SsmArn { input_source : String } ,
423518
424519 #[ snafu( display( "SSM ARN parameter '{}' did not return a string value" , parameter_name) ) ]
@@ -430,3 +525,26 @@ pub enum CloudError {
430525 #[ snafu( display( "SSM parameter '{}' did not return a string value" , parameter_name) ) ]
431526 SsmParameterMissing { parameter_name : String } ,
432527}
528+
529+ #[ cfg( test) ]
530+ mod tests {
531+ use super :: * ;
532+ use test_case:: test_case;
533+
534+ #[ test_case( "https://example.com/foo" , "https://example.com/foo" ; "https_ok" ) ]
535+ fn parse_https ( input : & str , expected : & str ) {
536+ let settings = SettingsInput :: new ( input) ;
537+ let uri = HttpsUri :: try_from ( & settings) . expect ( "should parse HTTPS URI" ) ;
538+ assert_eq ! ( uri. url. as_str( ) , expected) ;
539+ }
540+
541+ #[ test_case( "http://example.com" ; "http_rejected" ) ]
542+ #[ test_case( "ftp://example.com" ; "unsupported_scheme" ) ]
543+ fn parse_https_fail ( input : & str ) {
544+ let settings = SettingsInput :: new ( input) ;
545+ assert ! (
546+ HttpsUri :: try_from( & settings) . is_err( ) ,
547+ "should reject non-HTTPS URI"
548+ ) ;
549+ }
550+ }
0 commit comments