Skip to content

Commit fedfd15

Browse files
authored
Standardise credentials API (#4223) (#4163) (apache#4225)
* Standardise credentials API (#4223) (#4163) * Clippy * Allow HTTP metadata endpoint
1 parent 59bad6a commit fedfd15

File tree

10 files changed

+461
-412
lines changed

10 files changed

+461
-412
lines changed

src/aws/client.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
// under the License.
1717

1818
use crate::aws::checksum::Checksum;
19-
use crate::aws::credential::{AwsCredential, CredentialExt, CredentialProvider};
20-
use crate::aws::{STORE, STRICT_PATH_ENCODE_SET};
19+
use crate::aws::credential::{AwsCredential, CredentialExt};
20+
use crate::aws::{AwsCredentialProvider, STORE, STRICT_PATH_ENCODE_SET};
2121
use crate::client::list::ListResponse;
2222
use crate::client::pagination::stream_paginated;
2323
use crate::client::retry::RetryExt;
@@ -135,7 +135,7 @@ pub struct S3Config {
135135
pub endpoint: String,
136136
pub bucket: String,
137137
pub bucket_endpoint: String,
138-
pub credentials: Box<dyn CredentialProvider>,
138+
pub credentials: AwsCredentialProvider,
139139
pub retry_config: RetryConfig,
140140
pub client_options: ClientOptions,
141141
pub sign_payload: bool,

src/aws/credential.rs

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
use crate::aws::{STORE, STRICT_ENCODE_SET};
1919
use crate::client::retry::RetryExt;
2020
use crate::client::token::{TemporaryToken, TokenCache};
21+
use crate::client::TokenProvider;
2122
use crate::util::hmac_sha256;
2223
use crate::{Result, RetryConfig};
24+
use async_trait::async_trait;
2325
use bytes::Buf;
2426
use chrono::{DateTime, Utc};
25-
use futures::future::BoxFuture;
26-
use futures::TryFutureExt;
2727
use percent_encoding::utf8_percent_encode;
2828
use reqwest::header::{HeaderMap, HeaderValue};
2929
use reqwest::{Client, Method, Request, RequestBuilder, StatusCode};
@@ -41,10 +41,14 @@ static EMPTY_SHA256_HASH: &str =
4141
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
4242
static UNSIGNED_PAYLOAD_LITERAL: &str = "UNSIGNED-PAYLOAD";
4343

44-
#[derive(Debug)]
44+
/// A set of AWS security credentials
45+
#[derive(Debug, Eq, PartialEq)]
4546
pub struct AwsCredential {
47+
/// AWS_ACCESS_KEY_ID
4648
pub key_id: String,
49+
/// AWS_SECRET_ACCESS_KEY
4750
pub secret_key: String,
51+
/// AWS_SESSION_TOKEN
4852
pub token: Option<String>,
4953
}
5054

@@ -291,49 +295,31 @@ fn canonicalize_headers(header_map: &HeaderMap) -> (String, String) {
291295
(signed_headers, canonical_headers)
292296
}
293297

294-
/// Provides credentials for use when signing requests
295-
pub trait CredentialProvider: std::fmt::Debug + Send + Sync {
296-
fn get_credential(&self) -> BoxFuture<'_, Result<Arc<AwsCredential>>>;
297-
}
298-
299-
/// A static set of credentials
300-
#[derive(Debug)]
301-
pub struct StaticCredentialProvider {
302-
pub credential: Arc<AwsCredential>,
303-
}
304-
305-
impl CredentialProvider for StaticCredentialProvider {
306-
fn get_credential(&self) -> BoxFuture<'_, Result<Arc<AwsCredential>>> {
307-
Box::pin(futures::future::ready(Ok(Arc::clone(&self.credential))))
308-
}
309-
}
310-
311298
/// Credentials sourced from the instance metadata service
312299
///
313300
/// <https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html>
314301
#[derive(Debug)]
315302
pub struct InstanceCredentialProvider {
316303
pub cache: TokenCache<Arc<AwsCredential>>,
317-
pub client: Client,
318-
pub retry_config: RetryConfig,
319304
pub imdsv1_fallback: bool,
320305
pub metadata_endpoint: String,
321306
}
322307

323-
impl CredentialProvider for InstanceCredentialProvider {
324-
fn get_credential(&self) -> BoxFuture<'_, Result<Arc<AwsCredential>>> {
325-
Box::pin(self.cache.get_or_insert_with(|| {
326-
instance_creds(
327-
&self.client,
328-
&self.retry_config,
329-
&self.metadata_endpoint,
330-
self.imdsv1_fallback,
331-
)
308+
#[async_trait]
309+
impl TokenProvider for InstanceCredentialProvider {
310+
type Credential = AwsCredential;
311+
312+
async fn fetch_token(
313+
&self,
314+
client: &Client,
315+
retry: &RetryConfig,
316+
) -> Result<TemporaryToken<Arc<AwsCredential>>> {
317+
instance_creds(client, retry, &self.metadata_endpoint, self.imdsv1_fallback)
318+
.await
332319
.map_err(|source| crate::Error::Generic {
333320
store: STORE,
334321
source,
335322
})
336-
}))
337323
}
338324
}
339325

@@ -342,31 +328,34 @@ impl CredentialProvider for InstanceCredentialProvider {
342328
/// <https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html>
343329
#[derive(Debug)]
344330
pub struct WebIdentityProvider {
345-
pub cache: TokenCache<Arc<AwsCredential>>,
346331
pub token_path: String,
347332
pub role_arn: String,
348333
pub session_name: String,
349334
pub endpoint: String,
350-
pub client: Client,
351-
pub retry_config: RetryConfig,
352335
}
353336

354-
impl CredentialProvider for WebIdentityProvider {
355-
fn get_credential(&self) -> BoxFuture<'_, Result<Arc<AwsCredential>>> {
356-
Box::pin(self.cache.get_or_insert_with(|| {
357-
web_identity(
358-
&self.client,
359-
&self.retry_config,
360-
&self.token_path,
361-
&self.role_arn,
362-
&self.session_name,
363-
&self.endpoint,
364-
)
365-
.map_err(|source| crate::Error::Generic {
366-
store: STORE,
367-
source,
368-
})
369-
}))
337+
#[async_trait]
338+
impl TokenProvider for WebIdentityProvider {
339+
type Credential = AwsCredential;
340+
341+
async fn fetch_token(
342+
&self,
343+
client: &Client,
344+
retry: &RetryConfig,
345+
) -> Result<TemporaryToken<Arc<AwsCredential>>> {
346+
web_identity(
347+
client,
348+
retry,
349+
&self.token_path,
350+
&self.role_arn,
351+
&self.session_name,
352+
&self.endpoint,
353+
)
354+
.await
355+
.map_err(|source| crate::Error::Generic {
356+
store: STORE,
357+
source,
358+
})
370359
}
371360
}
372361

src/aws/mod.rs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ use url::Url;
4848
pub use crate::aws::checksum::Checksum;
4949
use crate::aws::client::{S3Client, S3Config};
5050
use crate::aws::credential::{
51-
AwsCredential, CredentialProvider, InstanceCredentialProvider,
52-
StaticCredentialProvider, WebIdentityProvider,
51+
AwsCredential, InstanceCredentialProvider, WebIdentityProvider,
5352
};
5453
use crate::client::header::header_meta;
55-
use crate::client::ClientConfigKey;
54+
use crate::client::{
55+
ClientConfigKey, CredentialProvider, StaticCredentialProvider,
56+
TokenCredentialProvider,
57+
};
5658
use crate::config::ConfigValue;
5759
use crate::multipart::{CloudMultiPartUpload, CloudMultiPartUploadImpl, UploadPart};
5860
use crate::{
@@ -83,6 +85,8 @@ const STRICT_PATH_ENCODE_SET: percent_encoding::AsciiSet = STRICT_ENCODE_SET.rem
8385

8486
const STORE: &str = "S3";
8587

88+
type AwsCredentialProvider = Arc<dyn CredentialProvider<Credential = AwsCredential>>;
89+
8690
/// Default metadata endpoint
8791
static METADATA_ENDPOINT: &str = "http://169.254.169.254";
8892

@@ -1001,13 +1005,12 @@ impl AmazonS3Builder {
10011005
let credentials = match (self.access_key_id, self.secret_access_key, self.token) {
10021006
(Some(key_id), Some(secret_key), token) => {
10031007
info!("Using Static credential provider");
1004-
Box::new(StaticCredentialProvider {
1005-
credential: Arc::new(AwsCredential {
1006-
key_id,
1007-
secret_key,
1008-
token,
1009-
}),
1010-
}) as _
1008+
let credential = AwsCredential {
1009+
key_id,
1010+
secret_key,
1011+
token,
1012+
};
1013+
Arc::new(StaticCredentialProvider::new(credential)) as _
10111014
}
10121015
(None, Some(_), _) => return Err(Error::MissingAccessKeyId.into()),
10131016
(Some(_), None, _) => return Err(Error::MissingSecretAccessKey.into()),
@@ -1031,15 +1034,18 @@ impl AmazonS3Builder {
10311034
.with_allow_http(false)
10321035
.client()?;
10331036

1034-
Box::new(WebIdentityProvider {
1035-
cache: Default::default(),
1037+
let token = WebIdentityProvider {
10361038
token_path,
10371039
session_name,
10381040
role_arn,
10391041
endpoint,
1042+
};
1043+
1044+
Arc::new(TokenCredentialProvider::new(
1045+
token,
10401046
client,
1041-
retry_config: self.retry_config.clone(),
1042-
}) as _
1047+
self.retry_config.clone(),
1048+
)) as _
10431049
}
10441050
_ => match self.profile {
10451051
Some(profile) => {
@@ -1049,19 +1055,20 @@ impl AmazonS3Builder {
10491055
None => {
10501056
info!("Using Instance credential provider");
10511057

1052-
// The instance metadata endpoint is access over HTTP
1053-
let client_options =
1054-
self.client_options.clone().with_allow_http(true);
1055-
1056-
Box::new(InstanceCredentialProvider {
1058+
let token = InstanceCredentialProvider {
10571059
cache: Default::default(),
1058-
client: client_options.client()?,
1059-
retry_config: self.retry_config.clone(),
10601060
imdsv1_fallback: self.imdsv1_fallback.get()?,
10611061
metadata_endpoint: self
10621062
.metadata_endpoint
10631063
.unwrap_or_else(|| METADATA_ENDPOINT.into()),
1064-
}) as _
1064+
};
1065+
1066+
Arc::new(TokenCredentialProvider::new(
1067+
token,
1068+
// The instance metadata endpoint is access over HTTP
1069+
self.client_options.clone().with_allow_http(true).client()?,
1070+
self.retry_config.clone(),
1071+
)) as _
10651072
}
10661073
},
10671074
},
@@ -1114,11 +1121,8 @@ fn profile_region(profile: String) -> Option<String> {
11141121
}
11151122

11161123
#[cfg(feature = "aws_profile")]
1117-
fn profile_credentials(
1118-
profile: String,
1119-
region: String,
1120-
) -> Result<Box<dyn CredentialProvider>> {
1121-
Ok(Box::new(profile::ProfileProvider::new(
1124+
fn profile_credentials(profile: String, region: String) -> Result<AwsCredentialProvider> {
1125+
Ok(Arc::new(profile::ProfileProvider::new(
11221126
profile,
11231127
Some(region),
11241128
)))
@@ -1133,7 +1137,7 @@ fn profile_region(_profile: String) -> Option<String> {
11331137
fn profile_credentials(
11341138
_profile: String,
11351139
_region: String,
1136-
) -> Result<Box<dyn CredentialProvider>> {
1140+
) -> Result<AwsCredentialProvider> {
11371141
Err(Error::MissingProfileFeature.into())
11381142
}
11391143

src/aws/profile.rs

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@
1717

1818
#![cfg(feature = "aws_profile")]
1919

20+
use async_trait::async_trait;
2021
use aws_config::meta::region::ProvideRegion;
2122
use aws_config::profile::profile_file::ProfileFiles;
2223
use aws_config::profile::ProfileFileCredentialsProvider;
2324
use aws_config::profile::ProfileFileRegionProvider;
2425
use aws_config::provider_config::ProviderConfig;
2526
use aws_credential_types::provider::ProvideCredentials;
2627
use aws_types::region::Region;
27-
use futures::future::BoxFuture;
2828
use std::sync::Arc;
2929
use std::time::Instant;
3030
use std::time::SystemTime;
3131

32-
use crate::aws::credential::CredentialProvider;
3332
use crate::aws::AwsCredential;
3433
use crate::client::token::{TemporaryToken, TokenCache};
34+
use crate::client::CredentialProvider;
3535
use crate::Result;
3636

3737
#[cfg(test)]
@@ -91,38 +91,43 @@ impl ProfileProvider {
9191
}
9292
}
9393

94+
#[async_trait]
9495
impl CredentialProvider for ProfileProvider {
95-
fn get_credential(&self) -> BoxFuture<'_, Result<Arc<AwsCredential>>> {
96-
Box::pin(self.cache.get_or_insert_with(move || async move {
97-
let region = self.region.clone().map(Region::new);
98-
99-
let config = ProviderConfig::default().with_region(region);
100-
101-
let credentials = ProfileFileCredentialsProvider::builder()
102-
.configure(&config)
103-
.profile_name(&self.name)
104-
.build();
105-
106-
let c = credentials.provide_credentials().await.map_err(|source| {
107-
crate::Error::Generic {
108-
store: "S3",
109-
source: Box::new(source),
110-
}
111-
})?;
112-
let t_now = SystemTime::now();
113-
let expiry = c
114-
.expiry()
115-
.and_then(|e| e.duration_since(t_now).ok())
116-
.map(|ttl| Instant::now() + ttl);
117-
118-
Ok(TemporaryToken {
119-
token: Arc::new(AwsCredential {
120-
key_id: c.access_key_id().to_string(),
121-
secret_key: c.secret_access_key().to_string(),
122-
token: c.session_token().map(ToString::to_string),
123-
}),
124-
expiry,
96+
type Credential = AwsCredential;
97+
98+
async fn get_credential(&self) -> Result<Arc<AwsCredential>> {
99+
self.cache
100+
.get_or_insert_with(move || async move {
101+
let region = self.region.clone().map(Region::new);
102+
103+
let config = ProviderConfig::default().with_region(region);
104+
105+
let credentials = ProfileFileCredentialsProvider::builder()
106+
.configure(&config)
107+
.profile_name(&self.name)
108+
.build();
109+
110+
let c = credentials.provide_credentials().await.map_err(|source| {
111+
crate::Error::Generic {
112+
store: "S3",
113+
source: Box::new(source),
114+
}
115+
})?;
116+
let t_now = SystemTime::now();
117+
let expiry = c
118+
.expiry()
119+
.and_then(|e| e.duration_since(t_now).ok())
120+
.map(|ttl| Instant::now() + ttl);
121+
122+
Ok(TemporaryToken {
123+
token: Arc::new(AwsCredential {
124+
key_id: c.access_key_id().to_string(),
125+
secret_key: c.secret_access_key().to_string(),
126+
token: c.session_token().map(ToString::to_string),
127+
}),
128+
expiry,
129+
})
125130
})
126-
}))
131+
.await
127132
}
128133
}

0 commit comments

Comments
 (0)