mirror of https://github.com/smithy-lang/smithy-rs
Create Profile File Provider for Region (#682)
* Create Profile File Provider for Region As part of this work, I unified handling of provider configuration into `ProviderConfig`. This simplifies and removes boilerplate from the credential provider builders. * Fix profile name bug, add test * More cleanups around connection management * Lots of cleanups around connector handling and crate features * Update Changelog * Remove unecessary sleep module * Add builder convenience method * CR feedback * Update aws/rust-runtime/aws-config/src/default_provider.rs Co-authored-by: John DiSanti <jdisanti@amazon.com> Co-authored-by: John DiSanti <jdisanti@amazon.com>
This commit is contained in:
parent
0242158fe1
commit
f649d559a9
|
@ -18,7 +18,7 @@ Current Credential Provider Support:
|
|||
- [x] Assume role from source profile
|
||||
- [x] Static credentials source profile
|
||||
- [x] WebTokenIdentity provider
|
||||
- [ ] Region
|
||||
- [x] Region
|
||||
- [ ] IMDS
|
||||
- [ ] ECS
|
||||
|
||||
|
@ -155,7 +155,7 @@ impl ProvideCredentials for CustomCreds {
|
|||
**New this week**
|
||||
|
||||
- (When complete) Add Event Stream support (#653, #xyz)
|
||||
- (When complete) Add profile file provider for region (#594, #xyz)
|
||||
- Add profile file provider for region (#594, #682)
|
||||
- Improve documentation on collection-aware builders (#664)
|
||||
- Add support for Transcribe `StartStreamTranscription` and S3 `SelectObjectContent` operations (#667)
|
||||
- Add support for shared configuration between multiple services (#673)
|
||||
|
|
|
@ -7,11 +7,12 @@ exclude = ["test-data/*"]
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
default-provider = ["profile", "imds", "meta", "sts"]
|
||||
profile = ["sts", "web-identity-token"]
|
||||
default-provider = ["profile", "imds", "meta", "sts", "environment"]
|
||||
profile = ["sts", "web-identity-token", "meta", "environment"]
|
||||
# note: IMDS currently unsupported
|
||||
imds = []
|
||||
meta = ["tokio/sync"]
|
||||
environment = ["meta"]
|
||||
sts = ["aws-sdk-sts", "aws-hyper"]
|
||||
web-identity-token = ["sts"]
|
||||
sso = []
|
||||
|
|
|
@ -9,13 +9,76 @@
|
|||
pub mod region {
|
||||
|
||||
use crate::environment::region::EnvironmentVariableRegionProvider;
|
||||
use crate::meta::region::ProvideRegion;
|
||||
use crate::meta::region::{ProvideRegion, RegionProviderChain};
|
||||
use crate::profile;
|
||||
|
||||
use crate::provider_config::ProviderConfig;
|
||||
|
||||
use aws_types::region::Region;
|
||||
|
||||
/// Default Region Provider chain
|
||||
///
|
||||
/// This provider will load region from environment variables.
|
||||
/// This provider will check the following sources in order:
|
||||
/// 1. [Environment variables](EnvironmentVariableRegionProvider)
|
||||
/// 2. [Profile file](crate::profile::region::ProfileFileRegionProvider)
|
||||
pub fn default_provider() -> impl ProvideRegion {
|
||||
EnvironmentVariableRegionProvider::new()
|
||||
Builder::default().build()
|
||||
}
|
||||
|
||||
/// Default region provider chain
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultRegionChain(RegionProviderChain);
|
||||
|
||||
impl DefaultRegionChain {
|
||||
/// Load a region from this chain
|
||||
pub async fn region(&self) -> Option<Region> {
|
||||
self.0.region().await
|
||||
}
|
||||
|
||||
/// Builder for [`DefaultRegionChain`]
|
||||
pub fn builder() -> Builder {
|
||||
Builder::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [DefaultRegionChain]
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
env_provider: EnvironmentVariableRegionProvider,
|
||||
profile_file: profile::region::Builder,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
#[doc(hidden)]
|
||||
/// Configure the default chain
|
||||
///
|
||||
/// Exposed for overriding the environment when unit-testing providers
|
||||
pub fn configure(mut self, configuration: &ProviderConfig) -> Self {
|
||||
self.env_provider =
|
||||
EnvironmentVariableRegionProvider::new_with_env(configuration.env());
|
||||
self.profile_file = self.profile_file.configure(configuration);
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the profile name used by this provider
|
||||
pub fn profile_name(mut self, name: &str) -> Self {
|
||||
self.profile_file = self.profile_file.profile_name(name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a [DefaultRegionChain]
|
||||
pub fn build(self) -> DefaultRegionChain {
|
||||
DefaultRegionChain(
|
||||
RegionProviderChain::first_try(self.env_provider)
|
||||
.or_else(self.profile_file.build()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProvideRegion for DefaultRegionChain {
|
||||
fn region(&self) -> crate::meta::region::future::ProvideRegion {
|
||||
ProvideRegion::region(&self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,11 +86,11 @@ pub mod region {
|
|||
pub mod credentials {
|
||||
use crate::environment::credentials::EnvironmentVariableCredentialsProvider;
|
||||
use crate::meta::credentials::{CredentialsProviderChain, LazyCachingCredentialsProvider};
|
||||
use crate::meta::region::ProvideRegion;
|
||||
use aws_types::credentials::{future, ProvideCredentials};
|
||||
use aws_types::os_shim_internal::{Env, Fs};
|
||||
use aws_types::region::Region;
|
||||
use smithy_async::rt::sleep::AsyncSleep;
|
||||
use smithy_client::erase::DynConnector;
|
||||
|
||||
use crate::provider_config::ProviderConfig;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[cfg(any(feature = "rustls", feature = "native-tls"))]
|
||||
|
@ -35,11 +98,7 @@ pub mod credentials {
|
|||
///
|
||||
/// The region from the default region provider will be used
|
||||
pub async fn default_provider() -> impl ProvideCredentials {
|
||||
use crate::meta::region::ProvideRegion;
|
||||
let region = super::region::default_provider().region().await;
|
||||
let mut builder = DefaultCredentialsChain::builder();
|
||||
builder.set_region(region);
|
||||
builder.build()
|
||||
DefaultCredentialsChain::builder().build().await
|
||||
}
|
||||
|
||||
/// Default AWS Credential Provider Chain
|
||||
|
@ -52,7 +111,7 @@ pub mod credentials {
|
|||
///
|
||||
/// More providers are a work in progress.
|
||||
///
|
||||
/// ## Example:
|
||||
/// # Examples
|
||||
/// Create a default chain with a custom region:
|
||||
/// ```rust
|
||||
/// use aws_types::region::Region;
|
||||
|
@ -67,6 +126,14 @@ pub mod credentials {
|
|||
/// use aws_config::default_provider::credentials::DefaultCredentialsChain;
|
||||
/// let credentials_provider = DefaultCredentialsChain::builder().build();
|
||||
/// ```
|
||||
///
|
||||
/// Create a default chain that uses a different profile:
|
||||
/// ```rust
|
||||
/// use aws_config::default_provider::credentials::DefaultCredentialsChain;
|
||||
/// let credentials_provider = DefaultCredentialsChain::builder()
|
||||
/// .profile_name("otherprofile")
|
||||
/// .build();
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultCredentialsChain(LazyCachingCredentialsProvider);
|
||||
|
||||
|
@ -92,14 +159,16 @@ pub mod credentials {
|
|||
profile_file_builder: crate::profile::credentials::Builder,
|
||||
web_identity_builder: crate::web_identity_token::Builder,
|
||||
credential_cache: crate::meta::credentials::lazy_caching::Builder,
|
||||
env: Option<Env>,
|
||||
region_override: Option<Box<dyn ProvideRegion>>,
|
||||
region_chain: crate::default_provider::region::Builder,
|
||||
conf: Option<ProviderConfig>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Sets the region used when making requests to AWS services
|
||||
///
|
||||
/// When unset, the default region resolver chain will be used.
|
||||
pub fn region(mut self, region: Region) -> Self {
|
||||
pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
|
||||
self.set_region(Some(region));
|
||||
self
|
||||
}
|
||||
|
@ -107,30 +176,8 @@ pub mod credentials {
|
|||
/// Sets the region used when making requests to AWS services
|
||||
///
|
||||
/// When unset, the default region resolver chain will be used.
|
||||
pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
|
||||
self.profile_file_builder.set_region(region.clone());
|
||||
self.web_identity_builder.set_region(region);
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the HTTPS connector used for this provider
|
||||
///
|
||||
/// If a connector other than Hyper is used or if the Tokio/Hyper features have been disabled
|
||||
/// this method MUST be used to specify a custom connector.
|
||||
pub fn connector(mut self, connector: DynConnector) -> Self {
|
||||
self.profile_file_builder
|
||||
.set_connector(Some(connector.clone()));
|
||||
self.web_identity_builder.set_connector(Some(connector));
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the sleep implementation used for this provider
|
||||
///
|
||||
/// By default, Tokio will be used to support async sleep during credentials for timeouts
|
||||
/// and reloading credentials. If the tokio default feature has been disabled, a custom
|
||||
/// sleep implementation must be provided.
|
||||
pub fn sleep(mut self, sleep: impl AsyncSleep + 'static) -> Self {
|
||||
self.credential_cache = self.credential_cache.sleep(sleep);
|
||||
pub fn set_region(&mut self, region: Option<impl ProvideRegion + 'static>) -> &mut Self {
|
||||
self.region_override = region.map(|provider| Box::new(provider) as _);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -158,24 +205,19 @@ pub mod credentials {
|
|||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Override the filesystem used for this provider
|
||||
/// Override the profile name used by this provider
|
||||
///
|
||||
/// This method exists to test credential providers
|
||||
pub fn fs(mut self, fs: Fs) -> Self {
|
||||
self.profile_file_builder.set_fs(fs.clone());
|
||||
self.web_identity_builder.set_fs(fs);
|
||||
/// When unset, the value of the `AWS_PROFILE` environment variable will be used.
|
||||
pub fn profile_name(mut self, name: &str) -> Self {
|
||||
self.profile_file_builder = self.profile_file_builder.profile_name(name);
|
||||
self.region_chain = self.region_chain.profile_name(name);
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Override the environment used for this provider
|
||||
///
|
||||
/// This method exists to test credential providers
|
||||
pub fn env(mut self, env: Env) -> Self {
|
||||
self.env = Some(env.clone());
|
||||
self.profile_file_builder.set_env(env.clone());
|
||||
self.web_identity_builder.set_env(env);
|
||||
/// Override the configuration used for this provider
|
||||
pub fn configure(mut self, config: ProviderConfig) -> Self {
|
||||
self.region_chain = self.region_chain.configure(&config);
|
||||
self.conf = Some(config);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -184,15 +226,21 @@ pub mod credentials {
|
|||
/// ## Panics
|
||||
/// This function will panic if no connector has been set and neither `rustls` and `native-tls`
|
||||
/// features have both been disabled.
|
||||
pub fn build(self) -> DefaultCredentialsChain {
|
||||
let profile_provider = self.profile_file_builder.build();
|
||||
let env_provider =
|
||||
EnvironmentVariableCredentialsProvider::new_with_env(self.env.unwrap_or_default());
|
||||
let web_identity_token_provider = self.web_identity_builder.build();
|
||||
pub async fn build(self) -> DefaultCredentialsChain {
|
||||
let region = match self.region_override {
|
||||
Some(provider) => provider.region().await,
|
||||
None => self.region_chain.build().region().await,
|
||||
};
|
||||
let conf = self.conf.unwrap_or_default().with_region(region);
|
||||
|
||||
let profile_provider = self.profile_file_builder.configure(&conf).build();
|
||||
let env_provider = EnvironmentVariableCredentialsProvider::new_with_env(conf.env());
|
||||
let web_identity_token_provider = self.web_identity_builder.configure(&conf).build();
|
||||
|
||||
let provider_chain = CredentialsProviderChain::first_try("Environment", env_provider)
|
||||
.or_else("Profile", profile_provider)
|
||||
.or_else("WebIdentityToken", web_identity_token_provider);
|
||||
let cached_provider = self.credential_cache.load(provider_chain);
|
||||
let cached_provider = self.credential_cache.configure(&conf).load(provider_chain);
|
||||
DefaultCredentialsChain(cached_provider.build())
|
||||
}
|
||||
}
|
||||
|
@ -210,21 +258,20 @@ pub mod credentials {
|
|||
stringify!($name)
|
||||
))
|
||||
.unwrap()
|
||||
.execute(|fs, env, conn| {
|
||||
.execute(|conf| async {
|
||||
crate::default_provider::credentials::Builder::default()
|
||||
.env(env)
|
||||
.fs(fs)
|
||||
.region(Region::from_static("us-east-1"))
|
||||
.connector(conn)
|
||||
.configure(conf)
|
||||
.build()
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use aws_sdk_sts::Region;
|
||||
|
||||
use crate::default_provider::credentials::DefaultCredentialsChain;
|
||||
use crate::test_case::TestEnvironment;
|
||||
use aws_types::credentials::ProvideCredentials;
|
||||
use tracing_test::traced_test;
|
||||
|
||||
make_test!(prefer_environment);
|
||||
|
@ -236,6 +283,25 @@ pub mod credentials {
|
|||
make_test!(web_identity_token_profile);
|
||||
make_test!(profile_overrides_web_identity);
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_name_override() {
|
||||
let (_, conf) =
|
||||
TestEnvironment::from_dir("./test-data/default-provider-chain/profile_static_keys")
|
||||
.unwrap()
|
||||
.provider_config()
|
||||
.await;
|
||||
let provider = DefaultCredentialsChain::builder()
|
||||
.profile_name("secondary")
|
||||
.configure(conf)
|
||||
.build()
|
||||
.await;
|
||||
let creds = provider
|
||||
.provide_credentials()
|
||||
.await
|
||||
.expect("creds should load");
|
||||
assert_eq!(creds.access_key_id(), "correct_key_secondary");
|
||||
}
|
||||
|
||||
/// Helper that uses `execute_and_update` instead of execute
|
||||
///
|
||||
/// If you run this, it will add another HTTP traffic log which re-records the request
|
||||
|
@ -247,13 +313,8 @@ pub mod credentials {
|
|||
"./test-data/default-provider-chain/web_identity_token_source_profile",
|
||||
))
|
||||
.unwrap()
|
||||
.execute_and_update(|fs, env, conn| {
|
||||
super::Builder::default()
|
||||
.env(env)
|
||||
.fs(fs)
|
||||
.region(Region::from_static("us-east-1"))
|
||||
.connector(conn)
|
||||
.build()
|
||||
.execute_and_update(|conf| async {
|
||||
super::Builder::default().configure(conf).build().await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#[cfg(feature = "default-provider")]
|
||||
pub mod default_provider;
|
||||
|
||||
#[cfg(feature = "environment")]
|
||||
/// Providers that load configuration from environment variables
|
||||
pub mod environment;
|
||||
|
||||
|
@ -61,6 +62,8 @@ mod test_case;
|
|||
#[cfg(feature = "web-identity-token")]
|
||||
pub mod web_identity_token;
|
||||
|
||||
pub mod provider_config;
|
||||
|
||||
/// Create an environment loader for AWS Configuration
|
||||
///
|
||||
/// ## Example
|
||||
|
@ -87,6 +90,7 @@ pub async fn load_from_env() -> aws_types::config::Config {
|
|||
/// Load default sources for all configuration with override support
|
||||
pub use loader::ConfigLoader;
|
||||
|
||||
#[cfg(feature = "default-provider")]
|
||||
mod loader {
|
||||
use crate::default_provider::{credentials, region};
|
||||
use crate::meta::region::ProvideRegion;
|
||||
|
@ -161,7 +165,7 @@ mod loader {
|
|||
} else {
|
||||
let mut builder = credentials::DefaultCredentialsChain::builder();
|
||||
builder.set_region(region.clone());
|
||||
SharedCredentialsProvider::new(builder.build())
|
||||
SharedCredentialsProvider::new(builder.build().await)
|
||||
};
|
||||
Config::builder()
|
||||
.region(region)
|
||||
|
@ -183,22 +187,24 @@ mod connector {
|
|||
|
||||
use smithy_client::erase::DynConnector;
|
||||
|
||||
pub fn must_have_connector() -> DynConnector {
|
||||
default_connector().expect("A connector was not available. Either set a custom connector or enable the `rustls` and `native-tls` crate features.")
|
||||
// unused when all crate features are disabled
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_connector(connector: Option<DynConnector>) -> DynConnector {
|
||||
connector.expect("A connector was not available. Either set a custom connector or enable the `rustls` and `native-tls` crate features.")
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
fn default_connector() -> Option<DynConnector> {
|
||||
pub fn default_connector() -> Option<DynConnector> {
|
||||
Some(DynConnector::new(smithy_client::conns::https()))
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "rustls"), feature = "native-tls"))]
|
||||
fn default_connector() -> Option<DynConnector> {
|
||||
pub fn default_connector() -> Option<DynConnector> {
|
||||
Some(DynConnector::new(smithy_client::conns::native_tls()))
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "rustls", feature = "native-tls")))]
|
||||
fn default_connector() -> Option<DynConnector> {
|
||||
pub fn default_connector() -> Option<DynConnector> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,8 +53,9 @@ impl CredentialsProviderChain {
|
|||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "default-provider")]
|
||||
/// Add a fallback to the default provider chain
|
||||
#[cfg(feature = "default-provider")]
|
||||
#[cfg(any(feature = "rustls", feature = "native-tls"))]
|
||||
pub async fn or_default_provider(self) -> Self {
|
||||
self.or_else(
|
||||
"DefaultProviderChain",
|
||||
|
@ -62,8 +63,9 @@ impl CredentialsProviderChain {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "default-provider")]
|
||||
/// Creates a credential provider chain that starts with the default provider
|
||||
#[cfg(feature = "default-provider")]
|
||||
#[cfg(any(feature = "rustls", feature = "native-tls"))]
|
||||
pub async fn default_provider() -> Self {
|
||||
Self::first_try(
|
||||
"DefaultProviderChain",
|
||||
|
|
|
@ -31,7 +31,7 @@ const DEFAULT_BUFFER_TIME: Duration = Duration::from_secs(10);
|
|||
#[derive(Debug)]
|
||||
pub struct LazyCachingCredentialsProvider {
|
||||
time: Box<dyn TimeSource>,
|
||||
sleeper: Box<dyn AsyncSleep>,
|
||||
sleeper: Arc<dyn AsyncSleep>,
|
||||
cache: Cache,
|
||||
loader: Arc<dyn ProvideCredentials>,
|
||||
load_timeout: Duration,
|
||||
|
@ -41,7 +41,7 @@ pub struct LazyCachingCredentialsProvider {
|
|||
impl LazyCachingCredentialsProvider {
|
||||
fn new(
|
||||
time: impl TimeSource,
|
||||
sleeper: Box<dyn AsyncSleep>,
|
||||
sleeper: Arc<dyn AsyncSleep>,
|
||||
loader: Arc<dyn ProvideCredentials>,
|
||||
load_timeout: Duration,
|
||||
default_credential_expiration: Duration,
|
||||
|
@ -121,6 +121,7 @@ mod builder {
|
|||
DEFAULT_LOAD_TIMEOUT,
|
||||
};
|
||||
use crate::meta::credentials::lazy_caching::time::SystemTimeSource;
|
||||
use crate::provider_config::ProviderConfig;
|
||||
|
||||
/// Builder for constructing a [`LazyCachingCredentialsProvider`].
|
||||
///
|
||||
|
@ -140,7 +141,7 @@ mod builder {
|
|||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
sleep: Option<Box<dyn AsyncSleep>>,
|
||||
sleep: Option<Arc<dyn AsyncSleep>>,
|
||||
load: Option<Arc<dyn ProvideCredentials>>,
|
||||
load_timeout: Option<Duration>,
|
||||
buffer_time: Option<Duration>,
|
||||
|
@ -153,6 +154,12 @@ mod builder {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
/// Override configuration for the [Builder]
|
||||
pub fn configure(mut self, config: &ProviderConfig) -> Self {
|
||||
self.sleep = config.sleep();
|
||||
self
|
||||
}
|
||||
|
||||
/// An implementation of [`ProvideCredentials`] that will be used to load
|
||||
/// the cached credentials once they're expired.
|
||||
pub fn load(mut self, loader: impl ProvideCredentials + 'static) -> Self {
|
||||
|
@ -165,7 +172,7 @@ mod builder {
|
|||
/// If using Tokio as the async runtime, this should be set to an instance of
|
||||
/// [`TokioSleep`](smithy_async::rt::sleep::TokioSleep).
|
||||
pub fn sleep(mut self, sleep: impl AsyncSleep + 'static) -> Self {
|
||||
self.sleep = Some(Box::new(sleep));
|
||||
self.sleep = Some(Arc::new(sleep));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -270,7 +277,7 @@ mod tests {
|
|||
let load_list = Arc::new(Mutex::new(load_list));
|
||||
LazyCachingCredentialsProvider::new(
|
||||
time,
|
||||
Box::new(TokioSleep::new()),
|
||||
Arc::new(TokioSleep::new()),
|
||||
Arc::new(provide_credentials_fn(move || {
|
||||
let list = load_list.clone();
|
||||
async move {
|
||||
|
@ -311,7 +318,7 @@ mod tests {
|
|||
}));
|
||||
let provider = LazyCachingCredentialsProvider::new(
|
||||
time,
|
||||
Box::new(TokioSleep::new()),
|
||||
Arc::new(TokioSleep::new()),
|
||||
loader,
|
||||
DEFAULT_LOAD_TIMEOUT,
|
||||
DEFAULT_CREDENTIAL_EXPIRATION,
|
||||
|
@ -422,7 +429,7 @@ mod tests {
|
|||
let time = TestTime::new(epoch_secs(100));
|
||||
let provider = LazyCachingCredentialsProvider::new(
|
||||
time,
|
||||
Box::new(TokioSleep::new()),
|
||||
Arc::new(TokioSleep::new()),
|
||||
Arc::new(provide_credentials_fn(|| async {
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
Ok(credentials(1000))
|
||||
|
|
|
@ -32,11 +32,12 @@ use aws_types::os_shim_internal::{Env, Fs};
|
|||
use aws_types::region::Region;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::connector::must_have_connector;
|
||||
use crate::connector::expect_connector;
|
||||
use crate::meta::region::ProvideRegion;
|
||||
use crate::profile::credentials::exec::named::NamedProviderFactory;
|
||||
use crate::profile::credentials::exec::{ClientConfiguration, ProviderChain};
|
||||
use crate::profile::parser::ProfileParseError;
|
||||
use crate::provider_config::ProviderConfig;
|
||||
use smithy_client::erase::DynConnector;
|
||||
|
||||
mod exec;
|
||||
|
@ -103,9 +104,11 @@ impl ProvideCredentials for ProfileFileCredentialsProvider {
|
|||
/// future::ProvideCredentials::new(self.load_credentials())
|
||||
/// }
|
||||
/// }
|
||||
/// # if cfg!(any(feature = "rustls", feature = "native-tls")) {
|
||||
/// let provider = ProfileFileCredentialsProvider::builder()
|
||||
/// .with_custom_provider("Custom", MyCustomProvider)
|
||||
/// .build();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Assume role credentials from a source profile
|
||||
|
@ -128,6 +131,7 @@ pub struct ProfileFileCredentialsProvider {
|
|||
env: Env,
|
||||
region: Option<Region>,
|
||||
connector: DynConnector,
|
||||
profile_override: Option<String>,
|
||||
}
|
||||
|
||||
impl ProfileFileCredentialsProvider {
|
||||
|
@ -148,6 +152,7 @@ impl ProfileFileCredentialsProvider {
|
|||
&self.region,
|
||||
&self.connector,
|
||||
&self.factory,
|
||||
self.profile_override.as_deref(),
|
||||
)
|
||||
.await;
|
||||
let inner_provider = profile.map_err(|err| match err {
|
||||
|
@ -277,62 +282,29 @@ impl Error for ProfileFileError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Builder for [`ProfileFileCredentialsProvider`](ProfileFileCredentialsProvider)
|
||||
/// Builder for [`ProfileFileCredentialsProvider`]
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
fs: Fs,
|
||||
env: Env,
|
||||
region: Option<Region>,
|
||||
connector: Option<DynConnector>,
|
||||
provider_config: Option<ProviderConfig>,
|
||||
profile_override: Option<String>,
|
||||
custom_providers: HashMap<Cow<'static, str>, Arc<dyn ProvideCredentials>>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
#[doc(hidden)]
|
||||
pub fn fs(mut self, fs: Fs) -> Self {
|
||||
self.fs = fs;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_fs(&mut self, fs: Fs) -> &mut Self {
|
||||
self.fs = fs;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn env(mut self, env: Env) -> Self {
|
||||
self.env = env;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_env(&mut self, env: Env) -> &mut Self {
|
||||
self.env = env;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the HTTPS connector used for requests to AWS
|
||||
pub fn connector(mut self, connector: DynConnector) -> Self {
|
||||
self.connector = Some(connector);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the HTTPS connector used for requests to AWS
|
||||
pub fn set_connector(&mut self, connector: Option<DynConnector>) -> &mut Self {
|
||||
self.connector = connector;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the region used for requests to AWS
|
||||
pub fn region(mut self, region: Region) -> Self {
|
||||
self.region = Some(region);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the region used for requests to AWS
|
||||
pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
|
||||
self.region = region;
|
||||
/// Override the configuration for the [`ProfileFileCredentialsProvider`]
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # async fn test() {
|
||||
/// use aws_config::profile::ProfileFileCredentialsProvider;
|
||||
/// use aws_config::provider_config::ProviderConfig;
|
||||
/// let provider = ProfileFileCredentialsProvider::builder()
|
||||
/// .configure(&ProviderConfig::with_default_region().await)
|
||||
/// .build();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
|
||||
self.provider_config = Some(provider_config.clone());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -356,9 +328,12 @@ impl Builder {
|
|||
/// future::ProvideCredentials::new(self.load_credentials())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # if cfg!(any(feature = "rustls", feature = "native-tls")) {
|
||||
/// let provider = ProfileFileCredentialsProvider::builder()
|
||||
/// .with_custom_provider("Custom", MyCustomProvider)
|
||||
/// .build();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn with_custom_provider(
|
||||
mut self,
|
||||
|
@ -370,35 +345,41 @@ impl Builder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Builds a [`ProfileFileCredentialsProvider`](ProfileFileCredentialsProvider)
|
||||
/// Override the profile name used by the [`ProfileFileCredentialsProvider`]
|
||||
pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
|
||||
self.profile_override = Some(profile_name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds a [`ProfileFileCredentialsProvider`]
|
||||
pub fn build(self) -> ProfileFileCredentialsProvider {
|
||||
let build_span = tracing::info_span!("build_profile_provider");
|
||||
let _enter = build_span.enter();
|
||||
let env = self.env.clone();
|
||||
let fs = self.fs;
|
||||
let conf = self.provider_config.unwrap_or_default();
|
||||
let mut named_providers = self.custom_providers.clone();
|
||||
named_providers
|
||||
.entry("Environment".into())
|
||||
.or_insert_with(|| {
|
||||
Arc::new(crate::environment::credentials::EnvironmentVariableCredentialsProvider::new_with_env(
|
||||
env.clone(),
|
||||
conf.env(),
|
||||
))
|
||||
});
|
||||
// TODO: ECS, IMDS, and other named providers
|
||||
let factory = exec::named::NamedProviderFactory::new(named_providers);
|
||||
let connector = self.connector.clone().unwrap_or_else(must_have_connector);
|
||||
let connector = expect_connector(conf.connector().cloned());
|
||||
let core_client = aws_hyper::Client::new(connector.clone());
|
||||
|
||||
ProfileFileCredentialsProvider {
|
||||
factory,
|
||||
client_config: ClientConfiguration {
|
||||
core_client,
|
||||
region: self.region.clone(),
|
||||
region: conf.region(),
|
||||
},
|
||||
fs,
|
||||
env,
|
||||
region: self.region.clone(),
|
||||
fs: conf.fs(),
|
||||
env: conf.env(),
|
||||
region: conf.region(),
|
||||
connector,
|
||||
profile_override: self.profile_override,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -409,12 +390,13 @@ async fn build_provider_chain(
|
|||
region: &dyn ProvideRegion,
|
||||
connector: &DynConnector,
|
||||
factory: &NamedProviderFactory,
|
||||
profile_override: Option<&str>,
|
||||
) -> Result<ProviderChain, ProfileFileError> {
|
||||
let profile_set = super::parser::load(&fs, &env).await.map_err(|err| {
|
||||
tracing::warn!(err = %err, "failed to parse profile");
|
||||
ProfileFileError::CouldNotParseProfile(err)
|
||||
})?;
|
||||
let repr = repr::resolve_chain(&profile_set)?;
|
||||
let repr = repr::resolve_chain(&profile_set, profile_override)?;
|
||||
tracing::info!(chain = ?repr, "constructed abstract provider from config file");
|
||||
exec::ProviderChain::from_repr(fs.clone(), connector, region.region().await, repr, &factory)
|
||||
}
|
||||
|
@ -425,7 +407,6 @@ mod test {
|
|||
|
||||
use crate::profile::credentials::Builder;
|
||||
use crate::test_case::TestEnvironment;
|
||||
use aws_types::region::Region;
|
||||
|
||||
macro_rules! make_test {
|
||||
($name: ident) => {
|
||||
|
@ -437,14 +418,7 @@ mod test {
|
|||
stringify!($name)
|
||||
))
|
||||
.unwrap()
|
||||
.execute(|fs, env, conn| {
|
||||
Builder::default()
|
||||
.env(env)
|
||||
.fs(fs)
|
||||
.region(Region::from_static("us-east-1"))
|
||||
.connector(conn)
|
||||
.build()
|
||||
})
|
||||
.execute(|conf| async move { Builder::default().configure(&conf).build() })
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
@ -454,19 +428,5 @@ mod test {
|
|||
make_test!(empty_config);
|
||||
make_test!(retry_on_error);
|
||||
make_test!(invalid_config);
|
||||
|
||||
#[tokio::test]
|
||||
async fn region_override() {
|
||||
TestEnvironment::from_dir("./test-data/profile-provider/region_override")
|
||||
.unwrap()
|
||||
.execute(|fs, env, conn| {
|
||||
Builder::default()
|
||||
.env(env)
|
||||
.fs(fs)
|
||||
.region(Region::from_static("us-east-2"))
|
||||
.connector(conn)
|
||||
.build()
|
||||
})
|
||||
.await
|
||||
}
|
||||
make_test!(region_override);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use aws_types::region::Region;
|
|||
use super::repr::{self, BaseProvider};
|
||||
|
||||
use crate::profile::credentials::ProfileFileError;
|
||||
use crate::provider_config::ProviderConfig;
|
||||
use crate::sts;
|
||||
use crate::web_identity_token::{StaticConfiguration, WebIdentityTokenCredentialsProvider};
|
||||
use aws_types::credentials::{self, CredentialsError, ProvideCredentials};
|
||||
|
@ -109,6 +110,10 @@ impl ProviderChain {
|
|||
web_identity_token_file,
|
||||
session_name,
|
||||
} => {
|
||||
let conf = ProviderConfig::empty()
|
||||
.with_connector(connector.clone())
|
||||
.with_fs(fs)
|
||||
.with_region(region);
|
||||
let provider = WebIdentityTokenCredentialsProvider::builder()
|
||||
.static_configuration(StaticConfiguration {
|
||||
web_identity_token_file: web_identity_token_file.into(),
|
||||
|
@ -117,9 +122,7 @@ impl ProviderChain {
|
|||
|| sts::util::default_session_name("web-identity-token-profile"),
|
||||
),
|
||||
})
|
||||
.fs(fs)
|
||||
.connector(connector.clone())
|
||||
.region(region)
|
||||
.configure(&conf)
|
||||
.build();
|
||||
Arc::new(provider)
|
||||
}
|
||||
|
@ -169,21 +172,16 @@ mod test {
|
|||
use crate::profile::credentials::exec::named::NamedProviderFactory;
|
||||
use crate::profile::credentials::exec::ProviderChain;
|
||||
use crate::profile::credentials::repr::{BaseProvider, ProfileChain};
|
||||
use crate::test_case::no_traffic_connector;
|
||||
use aws_sdk_sts::Region;
|
||||
use smithy_client::dvr;
|
||||
use smithy_client::erase::DynConnector;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn stub_connector() -> DynConnector {
|
||||
DynConnector::new(dvr::ReplayingConnection::new(vec![]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_on_unknown_provider() {
|
||||
let factory = NamedProviderFactory::new(HashMap::new());
|
||||
let chain = ProviderChain::from_repr(
|
||||
Default::default(),
|
||||
&stub_connector(),
|
||||
&no_traffic_connector(),
|
||||
Some(Region::new("us-east-1")),
|
||||
ProfileChain {
|
||||
base: BaseProvider::NamedSource("floozle"),
|
||||
|
|
|
@ -101,11 +101,15 @@ pub struct RoleArn<'a> {
|
|||
}
|
||||
|
||||
/// Resolve a ProfileChain from a ProfileSet or return an error
|
||||
pub fn resolve_chain(profile_set: &ProfileSet) -> Result<ProfileChain, ProfileFileError> {
|
||||
pub fn resolve_chain<'a>(
|
||||
profile_set: &'a ProfileSet,
|
||||
profile_override: Option<&str>,
|
||||
) -> Result<ProfileChain<'a>, ProfileFileError> {
|
||||
if profile_set.is_empty() {
|
||||
return Err(ProfileFileError::NoProfilesDefined);
|
||||
}
|
||||
let mut source_profile_name = profile_set.selected_profile();
|
||||
let mut source_profile_name =
|
||||
profile_override.unwrap_or_else(|| profile_set.selected_profile());
|
||||
let mut visited_profiles = vec![];
|
||||
let mut chain = vec![];
|
||||
let base = loop {
|
||||
|
@ -323,7 +327,7 @@ mod tests {
|
|||
|
||||
fn check(test_case: TestCase) {
|
||||
let source = ProfileSet::new(test_case.input.profile, test_case.input.selected_profile);
|
||||
let actual = resolve_chain(&source);
|
||||
let actual = resolve_chain(&source, None);
|
||||
let expected = test_case.output;
|
||||
match (expected, actual) {
|
||||
(TestOutput::Error(s), Err(e)) => assert!(
|
||||
|
|
|
@ -12,4 +12,7 @@ mod parser;
|
|||
pub use parser::{load, Profile, ProfileSet, Property};
|
||||
|
||||
pub mod credentials;
|
||||
pub mod region;
|
||||
|
||||
pub use credentials::ProfileFileCredentialsProvider;
|
||||
pub use region::ProfileFileRegionProvider;
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0.
|
||||
*/
|
||||
|
||||
//! Load a region from an AWS profile
|
||||
|
||||
use crate::meta::region::{future, ProvideRegion};
|
||||
use crate::provider_config::ProviderConfig;
|
||||
use aws_types::os_shim_internal::{Env, Fs};
|
||||
use aws_types::region::Region;
|
||||
|
||||
/// Load a region from a profile file
|
||||
///
|
||||
/// This provider will attempt to load AWS shared configuration, then read the `region` property
|
||||
/// from the active profile.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```ini
|
||||
/// # `~/.aws/config
|
||||
/// [default]
|
||||
/// region = us-west-2
|
||||
/// ```
|
||||
///
|
||||
/// This provider is part of the [default provider chain](crate::default_provider::region).
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ProfileFileRegionProvider {
|
||||
fs: Fs,
|
||||
env: Env,
|
||||
profile_override: Option<String>,
|
||||
}
|
||||
|
||||
/// Builder for [ProfileFileRegionProvider]
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
config: Option<ProviderConfig>,
|
||||
profile_override: Option<String>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Override the configuration for this provider
|
||||
pub fn configure(mut self, config: &ProviderConfig) -> Self {
|
||||
self.config = Some(config.clone());
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the profile name used by the [ProfileFileRegionProvider]
|
||||
pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
|
||||
self.profile_override = Some(profile_name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a [ProfileFileRegionProvider] from this builder
|
||||
pub fn build(self) -> ProfileFileRegionProvider {
|
||||
let conf = self.config.unwrap_or_default();
|
||||
ProfileFileRegionProvider {
|
||||
env: conf.env(),
|
||||
fs: conf.fs(),
|
||||
profile_override: self.profile_override,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfileFileRegionProvider {
|
||||
/// Create a new [ProfileFileRegionProvider]
|
||||
///
|
||||
/// To override the selected profile, set the `AWS_PROFILE` environment variable or use the [Builder].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
fs: Fs::real(),
|
||||
env: Env::real(),
|
||||
profile_override: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// [Builder] to construct a [ProfileFileRegionProvider]
|
||||
pub fn builder() -> Builder {
|
||||
Builder::default()
|
||||
}
|
||||
|
||||
async fn region(&self) -> Option<Region> {
|
||||
let profile = super::parser::load(&self.fs, &self.env)
|
||||
.await
|
||||
.map_err(|err| tracing::warn!(err = %err, "failed to parse profile"))
|
||||
.ok()?;
|
||||
let selected_profile = self
|
||||
.profile_override
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| profile.selected_profile());
|
||||
let selected_profile = profile.get_profile(selected_profile)?;
|
||||
selected_profile
|
||||
.get("region")
|
||||
.map(|region| Region::new(region.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ProvideRegion for ProfileFileRegionProvider {
|
||||
fn region(&self) -> future::ProvideRegion {
|
||||
future::ProvideRegion::new(self.region())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::profile::ProfileFileRegionProvider;
|
||||
use crate::provider_config::ProviderConfig;
|
||||
use crate::test_case::no_traffic_connector;
|
||||
use aws_sdk_sts::Region;
|
||||
use aws_types::os_shim_internal::{Env, Fs};
|
||||
use futures_util::FutureExt;
|
||||
use tracing_test::traced_test;
|
||||
|
||||
fn provider_config(dir_name: &str) -> ProviderConfig {
|
||||
let fs = Fs::from_test_dir(format!("test-data/profile-provider/{}/fs", dir_name), "/");
|
||||
let env = Env::from_slice(&[("HOME", "/home")]);
|
||||
ProviderConfig::empty()
|
||||
.with_fs(fs)
|
||||
.with_env(env)
|
||||
.with_connector(no_traffic_connector())
|
||||
}
|
||||
|
||||
#[traced_test]
|
||||
#[test]
|
||||
fn load_region() {
|
||||
let provider = ProfileFileRegionProvider::builder()
|
||||
.configure(&provider_config("region_override"))
|
||||
.build();
|
||||
assert_eq!(
|
||||
provider.region().now_or_never().unwrap(),
|
||||
Some(Region::from_static("us-east-1"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_region_env_profile_override() {
|
||||
let conf = provider_config("region_override").with_env(Env::from_slice(&[
|
||||
("HOME", "/home"),
|
||||
("AWS_PROFILE", "base"),
|
||||
]));
|
||||
let provider = ProfileFileRegionProvider::builder()
|
||||
.configure(&conf)
|
||||
.build();
|
||||
assert_eq!(
|
||||
provider.region().now_or_never().unwrap(),
|
||||
Some(Region::from_static("us-east-1"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_region_nonexistent_profile() {
|
||||
let conf = provider_config("region_override").with_env(Env::from_slice(&[
|
||||
("HOME", "/home"),
|
||||
("AWS_PROFILE", "doesnotexist"),
|
||||
]));
|
||||
let provider = ProfileFileRegionProvider::builder()
|
||||
.configure(&conf)
|
||||
.build();
|
||||
assert_eq!(provider.region().now_or_never().unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_region_explicit_override() {
|
||||
let conf = provider_config("region_override");
|
||||
let provider = ProfileFileRegionProvider::builder()
|
||||
.configure(&conf)
|
||||
.profile_name("base")
|
||||
.build();
|
||||
assert_eq!(
|
||||
provider.region().now_or_never().unwrap(),
|
||||
Some(Region::from_static("us-east-1"))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0.
|
||||
*/
|
||||
|
||||
//! Configuration Options for Credential Providers
|
||||
|
||||
use crate::connector::default_connector;
|
||||
use crate::default_provider::region::DefaultRegionChain;
|
||||
use aws_types::os_shim_internal::{Env, Fs};
|
||||
use aws_types::region::Region;
|
||||
use smithy_async::rt::sleep::{default_async_sleep, AsyncSleep};
|
||||
use smithy_client::erase::DynConnector;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Configuration options for Credential Providers
|
||||
///
|
||||
/// Most credential providers builders offer a `configure` method which applies general provider configuration
|
||||
/// options.
|
||||
///
|
||||
/// To use a region from the default region provider chain use [`ProviderConfig::with_default_region`].
|
||||
/// Otherwise, use [`ProviderConfig::without_region`]. Note that some credentials providers require a region
|
||||
/// to be explicitly set.
|
||||
#[derive(Clone)]
|
||||
pub struct ProviderConfig {
|
||||
env: Env,
|
||||
fs: Fs,
|
||||
connector: Option<DynConnector>,
|
||||
sleep: Option<Arc<dyn AsyncSleep>>,
|
||||
region: Option<Region>,
|
||||
}
|
||||
|
||||
impl Default for ProviderConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
env: Env::default(),
|
||||
fs: Fs::default(),
|
||||
connector: default_connector(),
|
||||
sleep: default_async_sleep(),
|
||||
region: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProviderConfig {
|
||||
/// Create a default provider config with the region unset.
|
||||
///
|
||||
/// Using this option means that you may need to set a region manually.
|
||||
///
|
||||
/// This constructor will use a default value for the HTTPS connector and Sleep implementation
|
||||
/// when they are enabled as crate features which is usually the correct option. To construct
|
||||
/// a `ProviderConfig` without these fields set, use [`ProviderConfig::empty`].
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use aws_config::provider_config::ProviderConfig;
|
||||
/// use aws_sdk_sts::Region;
|
||||
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
|
||||
/// let conf = ProviderConfig::without_region().with_region(Some(Region::new("us-east-1")));
|
||||
///
|
||||
/// # if cfg!(any(feature = "rustls", feature = "native-tls")) {
|
||||
/// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn without_region() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Constructs a ProviderConfig with no fields set
|
||||
pub fn empty() -> Self {
|
||||
ProviderConfig {
|
||||
env: Env::default(),
|
||||
fs: Fs::default(),
|
||||
connector: None,
|
||||
sleep: None,
|
||||
region: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a default provider config with the region region automatically loaded from the default chain.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # async fn test() {
|
||||
/// use aws_config::provider_config::ProviderConfig;
|
||||
/// use aws_sdk_sts::Region;
|
||||
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
|
||||
/// let conf = ProviderConfig::with_default_region().await;
|
||||
/// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "default-provider")]
|
||||
pub async fn with_default_region() -> Self {
|
||||
Self::without_region().load_default_region().await
|
||||
}
|
||||
|
||||
// When all crate features are disabled, these accessors are unused
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn env(&self) -> Env {
|
||||
self.env.clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn fs(&self) -> Fs {
|
||||
self.fs.clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn connector(&self) -> Option<&DynConnector> {
|
||||
self.connector.as_ref()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn sleep(&self) -> Option<Arc<dyn AsyncSleep>> {
|
||||
self.sleep.clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn region(&self) -> Option<Region> {
|
||||
self.region.clone()
|
||||
}
|
||||
|
||||
/// Override the region for the configuration
|
||||
pub fn with_region(mut self, region: Option<Region>) -> Self {
|
||||
self.region = region;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "default-provider")]
|
||||
/// Use the [default region chain](crate::default_provider::region) to set the
|
||||
/// region for this configuration
|
||||
///
|
||||
/// Note: the `env` and `fs` already set on this provider will be used when loading the default region.
|
||||
pub async fn load_default_region(self) -> Self {
|
||||
let provider_chain = DefaultRegionChain::builder().configure(&self).build();
|
||||
self.with_region(provider_chain.region().await)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn with_fs(self, fs: Fs) -> Self {
|
||||
ProviderConfig { fs, ..self }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn with_env(self, env: Env) -> Self {
|
||||
ProviderConfig { env, ..self }
|
||||
}
|
||||
|
||||
/// Override the HTTPS connector for this configuration
|
||||
///
|
||||
/// ## Note: Stability
|
||||
/// This method is expected to change to support HTTP configuration
|
||||
pub fn with_connector(self, connector: DynConnector) -> Self {
|
||||
ProviderConfig {
|
||||
connector: Some(connector),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Override the sleep implementation for this configuration
|
||||
pub fn with_sleep(self, sleep: impl AsyncSleep + 'static) -> Self {
|
||||
ProviderConfig {
|
||||
sleep: Some(Arc::new(sleep)),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,13 +6,17 @@
|
|||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::UNIX_EPOCH;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
use crate::provider_config::ProviderConfig;
|
||||
use aws_types::credentials::{self, ProvideCredentials};
|
||||
use aws_types::os_shim_internal::{Env, Fs};
|
||||
use serde::Deserialize;
|
||||
use smithy_async::rt::sleep::{AsyncSleep, Sleep};
|
||||
|
||||
use smithy_client::dvr::{NetworkTraffic, RecordingConnection, ReplayingConnection};
|
||||
use smithy_client::erase::DynConnector;
|
||||
use std::future::Future;
|
||||
|
||||
/// Test case credentials
|
||||
///
|
||||
|
@ -58,6 +62,19 @@ pub struct TestEnvironment {
|
|||
base_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Connector which expects no traffic
|
||||
pub fn no_traffic_connector() -> DynConnector {
|
||||
DynConnector::new(ReplayingConnection::new(vec![]))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InstantSleep;
|
||||
impl AsyncSleep for InstantSleep {
|
||||
fn sleep(&self, _duration: Duration) -> Sleep {
|
||||
Sleep::new(std::future::ready(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum TestResult {
|
||||
Ok(Credentials),
|
||||
|
@ -97,23 +114,36 @@ impl TestEnvironment {
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn provider_config(
|
||||
&self,
|
||||
) -> (RecordingConnection<ReplayingConnection>, ProviderConfig) {
|
||||
let connector = RecordingConnection::new(ReplayingConnection::new(
|
||||
self.network_traffic.events().clone(),
|
||||
));
|
||||
(
|
||||
connector.clone(),
|
||||
ProviderConfig::empty()
|
||||
.with_fs(self.fs.clone())
|
||||
.with_env(self.env.clone())
|
||||
.with_connector(DynConnector::new(connector.clone()))
|
||||
.with_sleep(InstantSleep)
|
||||
.load_default_region()
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Execute the test suite & record a new traffic log
|
||||
///
|
||||
/// A connector will be created with the factory, then request traffic will be recorded.
|
||||
/// Response are generated from the existing http-traffic.json.
|
||||
pub async fn execute_and_update<P>(&self, make_provider: impl Fn(Fs, Env, DynConnector) -> P)
|
||||
pub async fn execute_and_update<F, P>(&self, make_provider: impl Fn(ProviderConfig) -> F)
|
||||
where
|
||||
F: Future<Output = P>,
|
||||
P: ProvideCredentials,
|
||||
{
|
||||
let connector = RecordingConnection::new(ReplayingConnection::new(
|
||||
self.network_traffic.events().clone(),
|
||||
));
|
||||
let provider = make_provider(
|
||||
self.fs.clone(),
|
||||
self.env.clone(),
|
||||
DynConnector::new(connector.clone()),
|
||||
);
|
||||
let (connector, config) = self.provider_config().await;
|
||||
let provider = make_provider(config).await;
|
||||
let result = provider.provide_credentials().await;
|
||||
std::fs::write(
|
||||
self.base_dir.join("http-traffic-recorded.json"),
|
||||
|
@ -128,16 +158,19 @@ impl TestEnvironment {
|
|||
}
|
||||
|
||||
/// Execute a test case. Failures lead to panics.
|
||||
pub async fn execute<P>(&self, make_provider: impl Fn(Fs, Env, DynConnector) -> P)
|
||||
pub async fn execute<F, P>(&self, make_provider: impl Fn(ProviderConfig) -> F)
|
||||
where
|
||||
F: Future<Output = P>,
|
||||
P: ProvideCredentials,
|
||||
{
|
||||
let connector = ReplayingConnection::new(self.network_traffic.events().clone());
|
||||
let provider = make_provider(
|
||||
self.fs.clone(),
|
||||
self.env.clone(),
|
||||
DynConnector::new(connector.clone()),
|
||||
);
|
||||
let conf = ProviderConfig::empty()
|
||||
.with_fs(self.fs.clone())
|
||||
.with_env(self.env.clone())
|
||||
.with_connector(DynConnector::new(connector.clone()))
|
||||
.load_default_region()
|
||||
.await;
|
||||
let provider = make_provider(conf).await;
|
||||
let result = provider.provide_credentials().await;
|
||||
self.log_info();
|
||||
self.check_results(&result);
|
||||
|
|
|
@ -33,16 +33,30 @@
|
|||
//! role_arn = arn:aws:iam::123456789012:role/s3-reader
|
||||
//! web_identity_token_file = /token.jwt
|
||||
//! ```
|
||||
|
||||
//!
|
||||
//! # Example
|
||||
//! Web Identity Token providers are part of the [default chain](crate::default_provider::credentials),
|
||||
//! however, you can construct one explicitly if you don't want to use the default provider chain:
|
||||
//!
|
||||
/// ```rust
|
||||
/// # async fn test() {
|
||||
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
|
||||
/// use aws_config::provider_config::ProviderConfig;
|
||||
/// let provider = WebIdentityTokenCredentialsProvider::builder()
|
||||
/// .configure(&ProviderConfig::with_default_region().await)
|
||||
/// .build();
|
||||
/// # }
|
||||
/// ```
|
||||
use aws_sdk_sts::Region;
|
||||
use aws_types::os_shim_internal::{Env, Fs};
|
||||
use smithy_client::erase::DynConnector;
|
||||
|
||||
use crate::connector::must_have_connector;
|
||||
use crate::connector::expect_connector;
|
||||
use crate::provider_config::ProviderConfig;
|
||||
use crate::sts;
|
||||
use aws_types::credentials::{self, future, CredentialsError, ProvideCredentials};
|
||||
use std::borrow::Cow;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::Instrument;
|
||||
|
||||
const ENV_VAR_TOKEN_FILE: &str = "AWS_WEB_IDENTITY_TOKEN_FILE";
|
||||
const ENV_VAR_ROLE_ARN: &str = "AWS_ROLE_ARN";
|
||||
|
@ -132,6 +146,10 @@ impl WebIdentityTokenCredentialsProvider {
|
|||
&conf.role_arn,
|
||||
&conf.session_name,
|
||||
)
|
||||
.instrument(tracing::info_span!(
|
||||
"load_credentials",
|
||||
provider = "WebIdentityToken"
|
||||
))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -140,87 +158,52 @@ impl WebIdentityTokenCredentialsProvider {
|
|||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
source: Option<Source>,
|
||||
fs: Fs,
|
||||
connector: Option<DynConnector>,
|
||||
region: Option<Region>,
|
||||
config: Option<ProviderConfig>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
#[doc(hidden)]
|
||||
/// Set the Fs used for this provider
|
||||
pub fn fs(mut self, fs: Fs) -> Self {
|
||||
self.fs = fs;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Set the Fs used for this provider
|
||||
pub fn set_fs(&mut self, fs: Fs) -> &mut Self {
|
||||
self.fs = fs;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Set the process environment used for this provider
|
||||
pub fn env(mut self, env: Env) -> Self {
|
||||
self.source = Some(Source::Env(env));
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Set the process environment used for this provider
|
||||
pub fn set_env(&mut self, env: Env) -> &mut Self {
|
||||
self.source = Some(Source::Env(env));
|
||||
/// Configure generic options of the [WebIdentityTokenCredentialsProvider]
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # async fn test() {
|
||||
/// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
|
||||
/// use aws_config::provider_config::ProviderConfig;
|
||||
/// let provider = WebIdentityTokenCredentialsProvider::builder()
|
||||
/// .configure(&ProviderConfig::with_default_region().await)
|
||||
/// .build();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
|
||||
self.config = Some(provider_config.clone());
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure this builder to use [`StaticConfiguration`](StaticConfiguration)
|
||||
///
|
||||
/// WebIdentityToken providers load credentials from the file system. They may either determine
|
||||
/// the path from environment variables (default), or via a statically configured path.
|
||||
/// WebIdentityToken providers load credentials from the file system. The file system path used
|
||||
/// may either determine be loaded from environment variables (default), or via a statically
|
||||
/// configured path.
|
||||
pub fn static_configuration(mut self, config: StaticConfiguration) -> Self {
|
||||
self.source = Some(Source::Static(config));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the HTTPS connector used for this provider
|
||||
pub fn connector(mut self, connector: DynConnector) -> Self {
|
||||
self.connector = Some(connector);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the HTTPS connector used for this provider
|
||||
pub fn set_connector(&mut self, connector: Option<DynConnector>) -> &mut Self {
|
||||
self.connector = connector;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the region used for this provider
|
||||
pub fn region(mut self, region: Option<Region>) -> Self {
|
||||
self.region = region;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the region used for this provider
|
||||
pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
|
||||
self.region = region;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a [`WebIdentityTokenCredentialsProvider`]
|
||||
///
|
||||
/// ## Panics
|
||||
/// If no connector has been enabled via crate features and no connector has been provided via the
|
||||
/// builder, this function will panic.
|
||||
pub fn build(self) -> WebIdentityTokenCredentialsProvider {
|
||||
let connector = self.connector.unwrap_or_else(must_have_connector);
|
||||
let conf = self.config.unwrap_or_default();
|
||||
let connector = expect_connector(conf.connector().cloned());
|
||||
let client = aws_hyper::Client::new(connector);
|
||||
let source = self.source.unwrap_or_else(|| Source::Env(Env::default()));
|
||||
let source = self.source.unwrap_or_else(|| Source::Env(conf.env()));
|
||||
WebIdentityTokenCredentialsProvider {
|
||||
source,
|
||||
fs: self.fs,
|
||||
fs: conf.fs(),
|
||||
client,
|
||||
region: self.region,
|
||||
region: conf.region(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,17 +251,20 @@ mod test {
|
|||
use aws_sdk_sts::Region;
|
||||
use aws_types::os_shim_internal::{Env, Fs};
|
||||
|
||||
use crate::provider_config::ProviderConfig;
|
||||
use crate::test_case::no_traffic_connector;
|
||||
use aws_types::credentials::CredentialsError;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn unloaded_provider() {
|
||||
// empty environment
|
||||
let env = Env::from_slice(&[]);
|
||||
let provider = Builder::default()
|
||||
.region(Some(Region::new("us-east-1")))
|
||||
.env(env)
|
||||
.build();
|
||||
let conf = ProviderConfig::empty()
|
||||
.with_env(Env::from_slice(&[]))
|
||||
.with_connector(no_traffic_connector())
|
||||
.with_region(Some(Region::from_static("us-east-1")));
|
||||
|
||||
let provider = Builder::default().configure(&conf).build();
|
||||
let err = provider
|
||||
.credentials()
|
||||
.await
|
||||
|
@ -292,9 +278,14 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn missing_env_var() {
|
||||
let env = Env::from_slice(&[(ENV_VAR_TOKEN_FILE, "/token.jwt")]);
|
||||
let region = Some(Region::new("us-east-1"));
|
||||
let provider = Builder::default()
|
||||
.region(Some(Region::new("us-east-1")))
|
||||
.env(env)
|
||||
.configure(
|
||||
&ProviderConfig::empty()
|
||||
.with_region(region)
|
||||
.with_env(env)
|
||||
.with_connector(no_traffic_connector()),
|
||||
)
|
||||
.build();
|
||||
let err = provider
|
||||
.credentials()
|
||||
|
@ -320,9 +311,13 @@ mod test {
|
|||
]);
|
||||
let fs = Fs::from_map(HashMap::new());
|
||||
let provider = Builder::default()
|
||||
.region(Some(Region::new("us-east-1")))
|
||||
.fs(fs)
|
||||
.env(env)
|
||||
.configure(
|
||||
&ProviderConfig::empty()
|
||||
.with_connector(no_traffic_connector())
|
||||
.with_region(Some(Region::new("us-east-1")))
|
||||
.with_env(env)
|
||||
.with_fs(fs),
|
||||
)
|
||||
.build();
|
||||
let err = provider.credentials().await.expect_err("no JWT token");
|
||||
match err {
|
||||
|
|
|
@ -2,3 +2,8 @@
|
|||
region = us-east-1
|
||||
aws_access_key_id = correct_key
|
||||
aws_secret_access_key = correct_secret
|
||||
|
||||
[profile secondary]
|
||||
region = us-east-2
|
||||
aws_access_key_id = correct_key_secondary
|
||||
aws_secret_access_key = correct_secret_secondary
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
"AWS_WEB_IDENTITY_TOKEN_FILE": "/token.jwt",
|
||||
"AWS_ROLE_ARN": "arn:aws:iam::123456789012:role/test-role",
|
||||
"AWS_ROLE_SESSION_NAME": "test-session",
|
||||
"AWS_REGION": "us-east-1"
|
||||
"AWS_REGION": "eu-west-1"
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"action": {
|
||||
"Request": {
|
||||
"request": {
|
||||
"uri": "https://sts.us-east-1.amazonaws.com/",
|
||||
"uri": "https://sts.eu-west-1.amazonaws.com/",
|
||||
"headers": {
|
||||
"content-type": [
|
||||
"application/x-www-form-urlencoded"
|
||||
|
@ -22,7 +22,7 @@
|
|||
"aws-sdk-rust/0.1.0 os/macos lang/rust/1.55.0-nightly"
|
||||
],
|
||||
"host": [
|
||||
"sts.us-east-1.amazonaws.com"
|
||||
"sts.eu-west-1.amazonaws.com"
|
||||
]
|
||||
},
|
||||
"method": "POST"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"AWS_REGION": "us-east-1",
|
||||
"AWS_REGION": "us-east-2",
|
||||
"HOME": "/home"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[default]
|
||||
region = us-east-2
|
||||
# this region is overriden by the environment variable
|
||||
region = us-east-1
|
||||
role_arn = arn:aws:iam::123456789:role/integration-test
|
||||
source_profile = base
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -29,7 +30,7 @@ where
|
|||
|
||||
/// Returns a default sleep implementation based on the features enabled, or `None` if
|
||||
/// there isn't one available from this crate.
|
||||
pub fn default_async_sleep() -> Option<Box<dyn AsyncSleep>> {
|
||||
pub fn default_async_sleep() -> Option<Arc<dyn AsyncSleep>> {
|
||||
sleep_tokio()
|
||||
}
|
||||
|
||||
|
@ -38,7 +39,7 @@ pub fn default_async_sleep() -> Option<Box<dyn AsyncSleep>> {
|
|||
pub struct Sleep(Pin<Box<dyn Future<Output = ()> + Send + 'static>>);
|
||||
|
||||
impl Sleep {
|
||||
fn new(future: impl Future<Output = ()> + Send + 'static) -> Sleep {
|
||||
pub fn new(future: impl Future<Output = ()> + Send + 'static) -> Sleep {
|
||||
Sleep(Box::pin(future))
|
||||
}
|
||||
}
|
||||
|
@ -72,11 +73,11 @@ impl AsyncSleep for TokioSleep {
|
|||
}
|
||||
|
||||
#[cfg(feature = "rt-tokio")]
|
||||
fn sleep_tokio() -> Option<Box<dyn AsyncSleep>> {
|
||||
Some(Box::new(TokioSleep::new()))
|
||||
fn sleep_tokio() -> Option<Arc<dyn AsyncSleep>> {
|
||||
Some(Arc::new(TokioSleep::new()))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "rt-tokio"))]
|
||||
fn sleep_tokio() -> Option<Box<dyn AsyncSleep>> {
|
||||
fn sleep_tokio() -> Option<Arc<dyn AsyncSleep>> {
|
||||
None
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue