@@ -36,10 +36,14 @@ use md5::Digest;
3636use md5:: Md5 ;
3737use reqsign:: AwsAssumeRoleLoader ;
3838use reqsign:: AwsConfig ;
39+ use reqsign:: AwsCredential ;
3940use reqsign:: AwsCredentialLoad ;
4041use reqsign:: AwsDefaultLoader ;
4142use reqsign:: AwsV4Signer ;
4243use reqwest:: Url ;
44+ use serde:: Deserialize ;
45+ use serde_json;
46+ use tokio:: time:: Duration ;
4347
4448use super :: core:: * ;
4549use super :: delete:: S3Deleter ;
@@ -83,6 +87,174 @@ impl Configurator for S3Config {
8387 }
8488}
8589
90+ /// Container credentials returned by ECS/EKS credential endpoints
91+ #[ derive( Debug , Deserialize ) ]
92+ #[ serde( rename_all = "PascalCase" ) ]
93+ #[ allow( dead_code) ]
94+ struct ContainerCredentials {
95+ access_key_id : String ,
96+ secret_access_key : String ,
97+ token : String ,
98+ expiration : String ,
99+ }
100+
101+ impl From < ContainerCredentials > for AwsCredential {
102+ fn from ( creds : ContainerCredentials ) -> Self {
103+ Self {
104+ access_key_id : creds. access_key_id ,
105+ secret_access_key : creds. secret_access_key ,
106+ session_token : Some ( creds. token ) ,
107+ expires_in : None , // Container credentials expiration is handled by the endpoint
108+ }
109+ }
110+ }
111+
112+ /// ECS Task credential provider
113+ ///
114+ /// Implements AWS ECS task IAM roles credential retrieval
115+ /// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
116+ #[ derive( Debug ) ]
117+ #[ allow( dead_code) ]
118+ struct EcsCredentialProvider {
119+ client : reqwest:: Client ,
120+ uri : String ,
121+ }
122+
123+ #[ allow( dead_code) ]
124+ impl EcsCredentialProvider {
125+ fn new ( client : reqwest:: Client , relative_uri : String ) -> Self {
126+ Self {
127+ client,
128+ uri : format ! ( "http://169.254.170.2{relative_uri}" ) ,
129+ }
130+ }
131+
132+ async fn get_credentials ( & self ) -> Result < AwsCredential > {
133+ let resp = self
134+ . client
135+ . get ( & self . uri )
136+ . timeout ( Duration :: from_secs ( 30 ) )
137+ . send ( )
138+ . await
139+ . map_err ( |e| {
140+ Error :: new ( ErrorKind :: Unexpected , "failed to get ECS task credentials" )
141+ . set_source ( e)
142+ } ) ?;
143+
144+ if !resp. status ( ) . is_success ( ) {
145+ return Err ( Error :: new (
146+ ErrorKind :: Unexpected ,
147+ format ! (
148+ "ECS task credentials request failed with status: {}" ,
149+ resp. status( )
150+ ) ,
151+ ) ) ;
152+ }
153+
154+ let body = resp. text ( ) . await . map_err ( |e| {
155+ Error :: new (
156+ ErrorKind :: Unexpected ,
157+ "failed to read ECS task credentials response" ,
158+ )
159+ . set_source ( e)
160+ } ) ?;
161+
162+ let creds: ContainerCredentials = serde_json:: from_str ( & body) . map_err ( |e| {
163+ Error :: new (
164+ ErrorKind :: Unexpected ,
165+ "failed to parse ECS task credentials response" ,
166+ )
167+ . set_source ( e)
168+ } ) ?;
169+
170+ Ok ( creds. into ( ) )
171+ }
172+ }
173+
174+ // TODO: Implement AwsCredentialLoad trait properly
175+ // impl AwsCredentialLoad for EcsCredentialProvider {}
176+
177+ /// EKS Pod credential provider
178+ ///
179+ /// Implements AWS EKS pod identity credential retrieval
180+ /// https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html
181+ #[ derive( Debug ) ]
182+ #[ allow( dead_code) ]
183+ struct EksCredentialProvider {
184+ client : reqwest:: Client ,
185+ uri : String ,
186+ token_file : String ,
187+ }
188+
189+ #[ allow( dead_code) ]
190+ impl EksCredentialProvider {
191+ fn new ( client : reqwest:: Client , uri : String , token_file : String ) -> Self {
192+ Self {
193+ client,
194+ uri,
195+ token_file,
196+ }
197+ }
198+
199+ async fn get_credentials ( & self ) -> Result < AwsCredential > {
200+ // Read the authorization token from file
201+ let token = tokio:: fs:: read_to_string ( & self . token_file )
202+ . await
203+ . map_err ( |e| {
204+ Error :: new (
205+ ErrorKind :: ConfigInvalid ,
206+ format ! ( "failed to read EKS token file '{}': {}" , self . token_file, e) ,
207+ )
208+ . set_source ( e)
209+ } ) ?;
210+
211+ let token = token. trim ( ) ;
212+
213+ // Make request with authorization header
214+ let resp = self
215+ . client
216+ . get ( & self . uri )
217+ . header ( "Authorization" , token)
218+ . timeout ( Duration :: from_secs ( 30 ) )
219+ . send ( )
220+ . await
221+ . map_err ( |e| {
222+ Error :: new ( ErrorKind :: Unexpected , "failed to get EKS pod credentials" ) . set_source ( e)
223+ } ) ?;
224+
225+ if !resp. status ( ) . is_success ( ) {
226+ return Err ( Error :: new (
227+ ErrorKind :: Unexpected ,
228+ format ! (
229+ "EKS pod credentials request failed with status: {}" ,
230+ resp. status( )
231+ ) ,
232+ ) ) ;
233+ }
234+
235+ let body = resp. text ( ) . await . map_err ( |e| {
236+ Error :: new (
237+ ErrorKind :: Unexpected ,
238+ "failed to read EKS pod credentials response" ,
239+ )
240+ . set_source ( e)
241+ } ) ?;
242+
243+ let creds: ContainerCredentials = serde_json:: from_str ( & body) . map_err ( |e| {
244+ Error :: new (
245+ ErrorKind :: Unexpected ,
246+ "failed to parse EKS pod credentials response" ,
247+ )
248+ . set_source ( e)
249+ } ) ?;
250+
251+ Ok ( creds. into ( ) )
252+ }
253+ }
254+
255+ // TODO: Implement AwsCredentialLoad trait properly
256+ // impl AwsCredentialLoad for EksCredentialProvider {}
257+
86258/// Aws S3 and compatible services (including minio, digitalocean space, Tencent Cloud Object Storage(COS) and so on) support.
87259/// For more information about s3-compatible services, refer to [Compatible Services](#compatible-services).
88260#[ doc = include_str ! ( "docs.md" ) ]
@@ -216,6 +388,39 @@ impl S3Builder {
216388 self
217389 }
218390
391+ /// Set the container credentials relative URI when used in ECS.
392+ ///
393+ /// See [AWS ECS task IAM roles](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html)
394+ /// for more information.
395+ pub fn container_credentials_relative_uri ( mut self , uri : & str ) -> Self {
396+ if !uri. is_empty ( ) {
397+ self . config . container_credentials_relative_uri = Some ( uri. to_string ( ) ) ;
398+ }
399+ self
400+ }
401+
402+ /// Set the container credentials full URI when used in EKS.
403+ ///
404+ /// See [AWS container credentials](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html)
405+ /// for more information.
406+ pub fn container_credentials_full_uri ( mut self , uri : & str ) -> Self {
407+ if !uri. is_empty ( ) {
408+ self . config . container_credentials_full_uri = Some ( uri. to_string ( ) ) ;
409+ }
410+ self
411+ }
412+
413+ /// Set the authorization token file when used in EKS to authenticate with ContainerCredentialsFullUri.
414+ ///
415+ /// See [AWS container credentials](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html)
416+ /// for more information.
417+ pub fn container_authorization_token_file ( mut self , token_file : & str ) -> Self {
418+ if !token_file. is_empty ( ) {
419+ self . config . container_authorization_token_file = Some ( token_file. to_string ( ) ) ;
420+ }
421+ self
422+ }
423+
219424 /// Set default storage_class for this backend.
220425 ///
221426 /// Available values:
@@ -844,6 +1049,21 @@ impl Builder for S3Builder {
8441049 loader = Some ( v) ;
8451050 }
8461051
1052+ // TODO: Container credentials support implementation
1053+ // For now, container credentials would need to be loaded by custom credential loaders
1054+ // This is a placeholder for the container credentials feature
1055+ if let Some ( relative_uri) = & self . config . container_credentials_relative_uri {
1056+ debug ! ( "ECS container credentials URI configured: {relative_uri}" ) ;
1057+ debug ! ( "Note: Container credentials will be supported in a future update" ) ;
1058+ }
1059+ if let ( Some ( full_uri) , Some ( token_file) ) = (
1060+ & self . config . container_credentials_full_uri ,
1061+ & self . config . container_authorization_token_file ,
1062+ ) {
1063+ debug ! ( "EKS container credentials configured - URI: {full_uri}, token file: {token_file}" ) ;
1064+ debug ! ( "Note: Container credentials will be supported in a future update" ) ;
1065+ }
1066+
8471067 // If role_arn is set, we must use AssumeRoleLoad.
8481068 if let Some ( role_arn) = self . config . role_arn {
8491069 // use current env as source credential loader.
0 commit comments