aws_config/profile/
token.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Profile File Based Token Providers
7
8use crate::profile::cell::ErrorTakingOnceCell;
9#[allow(deprecated)]
10use crate::profile::profile_file::ProfileFiles;
11use crate::profile::ProfileSet;
12use crate::provider_config::ProviderConfig;
13use crate::sso::SsoTokenProvider;
14use aws_credential_types::provider::{
15    error::TokenError, future, token::ProvideToken, token::Result as TokenResult,
16};
17use aws_types::{region::Region, SdkConfig};
18
19async fn load_profile_set(provider_config: &ProviderConfig) -> Result<&ProfileSet, TokenError> {
20    provider_config
21        .try_profile()
22        .await
23        .map_err(|parse_err| TokenError::invalid_configuration(parse_err.clone()))
24}
25
26fn create_token_provider(
27    sdk_config: &SdkConfig,
28    provider_config: &ProviderConfig,
29    profile_set: &ProfileSet,
30) -> Result<SsoTokenProvider, TokenError> {
31    let repr = crate::profile::credentials::repr::resolve_chain(profile_set)
32        .map_err(TokenError::invalid_configuration)?;
33    match repr.base {
34        crate::profile::credentials::repr::BaseProvider::Sso {
35            sso_session_name,
36            sso_region,
37            sso_start_url,
38            ..
39        } => {
40            let mut builder = SsoTokenProvider::builder().configure(sdk_config);
41            builder.set_session_name(sso_session_name.map(|s| s.to_string()));
42            Ok(builder
43                .region(Region::new(sso_region.to_string()))
44                .start_url(sso_start_url)
45                .build_with(provider_config.env(), provider_config.fs()))
46        }
47        _ => Err(TokenError::not_loaded(
48            "no sso-session configured in profile file",
49        )),
50    }
51}
52
53/// AWS profile-based access token provider
54///
55/// This token provider loads SSO session config from `~/.aws/config`,
56/// and uses that config to resolve a cached SSO token from `~/.aws/sso/cache`.
57/// The AWS CLI can be used to establish the cached SSO token.
58///
59/// Generally, this provider is constructed via the default provider chain. However,
60/// it can also be manually constructed with the builder:
61/// ```rust,no_run
62/// use aws_config::profile::ProfileFileTokenProvider;
63/// let provider = ProfileFileTokenProvider::builder().build();
64/// ```
65/// _Note: this provider, when called, will load and parse the `~/.aws/config` file
66/// only once. Parsed file contents will be cached indefinitely._
67///
68/// This provider requires a profile with a `sso_session` configured. For example,
69/// ```ini
70/// [default]
71/// sso_session = example
72/// region = us-west-2
73///
74/// [sso-session example]
75/// sso_start_url = https://example.awsapps.com/start
76/// sso_region = us-west-2
77/// ```
78#[derive(Debug)]
79pub struct ProfileFileTokenProvider {
80    sdk_config: SdkConfig,
81    provider_config: ProviderConfig,
82    inner_provider: ErrorTakingOnceCell<SsoTokenProvider, TokenError>,
83}
84
85impl ProfileFileTokenProvider {
86    /// Builder for this token provider.
87    pub fn builder() -> Builder {
88        Builder::default()
89    }
90
91    async fn load_token(&self) -> TokenResult {
92        let inner_provider = self
93            .inner_provider
94            .get_or_init(
95                {
96                    let sdk_config = self.sdk_config.clone();
97                    let provider_config = self.provider_config.clone();
98                    move || async move {
99                        let profile_set = load_profile_set(&provider_config).await?;
100                        create_token_provider(&sdk_config, &provider_config, profile_set)
101                    }
102                },
103                TokenError::unhandled(
104                    "profile file token provider initialization error already taken",
105                ),
106            )
107            .await?;
108
109        inner_provider.provide_token().await
110    }
111}
112
113impl ProvideToken for ProfileFileTokenProvider {
114    fn provide_token<'a>(&'a self) -> future::ProvideToken<'a>
115    where
116        Self: 'a,
117    {
118        future::ProvideToken::new(self.load_token())
119    }
120}
121
122/// Builder for [`ProfileFileTokenProvider`].
123#[derive(Debug, Default)]
124pub struct Builder {
125    provider_config: Option<ProviderConfig>,
126    profile_override: Option<String>,
127    #[allow(deprecated)]
128    profile_files: Option<ProfileFiles>,
129}
130
131impl Builder {
132    /// Override the configuration for the [`ProfileFileTokenProvider`]
133    pub(crate) fn configure(mut self, provider_config: &ProviderConfig) -> Self {
134        self.provider_config = Some(provider_config.clone());
135        self
136    }
137
138    /// Override the profile name used by the [`ProfileFileTokenProvider`]
139    pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
140        self.profile_override = Some(profile_name.into());
141        self
142    }
143
144    /// Set the profile file that should be used by the [`ProfileFileTokenProvider`]
145    #[allow(deprecated)]
146    pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
147        self.profile_files = Some(profile_files);
148        self
149    }
150
151    /// Builds a [`ProfileFileTokenProvider`]
152    pub fn build(self) -> ProfileFileTokenProvider {
153        let build_span = tracing::debug_span!("build_profile_token_provider");
154        let _enter = build_span.enter();
155        let conf = self
156            .provider_config
157            .unwrap_or_default()
158            .with_profile_config(self.profile_files, self.profile_override);
159
160        ProfileFileTokenProvider {
161            sdk_config: conf.client_config(),
162            provider_config: conf,
163            inner_provider: ErrorTakingOnceCell::new(),
164        }
165    }
166}
167
168#[cfg(test)]
169mod test {
170    use aws_credential_types::provider::token::ProvideToken;
171
172    /// Test generation macro
173    ///
174    /// # Examples
175    /// **Run the test case in `test-data/default-token-provider-chain/test_name`
176    /// ```no_run
177    /// make_test!(test_name);
178    /// ```
179    ///
180    /// **Update (responses are replayed but new requests are recorded) the test case**:
181    /// ```no_run
182    /// make_test!(update: test_name)
183    /// ```
184    ///
185    /// **Run the test case against a real HTTPS connection:**
186    /// > Note: Be careful to remove sensitive information before committing. Always use a temporary
187    /// > AWS account when recording live traffic.
188    /// ```no_run
189    /// make_test!(live: test_name)
190    /// ```
191    macro_rules! make_test {
192        ($name:ident $(#[$m:meta])*) => {
193            make_test!($name, execute, $(#[$m])*);
194        };
195        (update: $name:ident) => {
196            make_test!($name, execute_and_update);
197        };
198        (live: $name:ident) => {
199            make_test!($name, execute_from_live_traffic);
200        };
201        ($name:ident, $func:ident, $(#[$m:meta])*) => {
202            make_test!($name, $func, std::convert::identity $(, #[$m])*);
203        };
204        ($name:ident, builder: $provider_config_builder:expr) => {
205            make_test!($name, execute, $provider_config_builder);
206        };
207        ($name:ident, $func:ident, $provider_config_builder:expr $(, #[$m:meta])*) => {
208            $(#[$m])*
209            #[tokio::test]
210            async fn $name() {
211                let _ = crate::test_case::TestEnvironment::from_dir(
212                    concat!(
213                        "./test-data/default-token-provider-chain/",
214                        stringify!($name)
215                    ),
216                    crate::test_case::test_token_provider(|config| {
217                        async move {
218                            crate::default_provider::token::Builder::default()
219                                .configure(config)
220                                .build()
221                                .await
222                                .provide_token()
223                                .await
224                        }
225                    }),
226                )
227                .await
228                .unwrap()
229                .map_provider_config($provider_config_builder)
230                .$func()
231                .await;
232            }
233        };
234    }
235
236    make_test!(profile_keys);
237    make_test!(profile_keys_case_insensitive);
238    make_test!(profile_name);
239}