aws_config/environment/
credentials.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::env::VarError;
7
8use aws_credential_types::attributes::AccountId;
9use aws_credential_types::credential_feature::AwsCredentialFeature;
10use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials};
11use aws_credential_types::Credentials;
12use aws_types::os_shim_internal::Env;
13
14/// Load Credentials from Environment Variables
15///
16/// `EnvironmentVariableCredentialsProvider` uses the following variables:
17/// - `AWS_ACCESS_KEY_ID`
18/// - `AWS_SECRET_ACCESS_KEY` with fallback to `SECRET_ACCESS_KEY`
19/// - `AWS_SESSION_TOKEN` (optional)
20/// - `AWS_ACCOUNT_ID` (optional)
21#[derive(Debug, Clone)]
22pub struct EnvironmentVariableCredentialsProvider {
23    env: Env,
24}
25
26impl EnvironmentVariableCredentialsProvider {
27    fn credentials(&self) -> provider::Result {
28        let access_key = self
29            .env
30            .get("AWS_ACCESS_KEY_ID")
31            .and_then(err_if_blank)
32            .map_err(to_cred_error)?;
33        let secret_key = self
34            .env
35            .get("AWS_SECRET_ACCESS_KEY")
36            .and_then(err_if_blank)
37            .or_else(|_| self.env.get("SECRET_ACCESS_KEY"))
38            .and_then(err_if_blank)
39            .map_err(to_cred_error)?;
40        let session_token =
41            self.env
42                .get("AWS_SESSION_TOKEN")
43                .ok()
44                .and_then(|token| match token.trim() {
45                    "" => None,
46                    s => Some(s.to_string()),
47                });
48        let account_id =
49            self.env
50                .get("AWS_ACCOUNT_ID")
51                .ok()
52                .and_then(|account_id| match account_id.trim() {
53                    "" => None,
54                    s => Some(AccountId::from(s)),
55                });
56        let mut builder = Credentials::builder()
57            .access_key_id(access_key)
58            .secret_access_key(secret_key)
59            .provider_name(ENV_PROVIDER);
60        builder.set_session_token(session_token);
61        builder.set_account_id(account_id);
62        let mut creds = builder.build();
63        creds
64            .get_property_mut_or_default::<Vec<AwsCredentialFeature>>()
65            .push(AwsCredentialFeature::CredentialsEnvVars);
66        Ok(creds)
67    }
68}
69
70impl EnvironmentVariableCredentialsProvider {
71    /// Create a `EnvironmentVariableCredentialsProvider`
72    pub fn new() -> Self {
73        Self::new_with_env(Env::real())
74    }
75
76    /// Create a new `EnvironmentVariableCredentialsProvider` with `Env` overridden
77    ///
78    /// This function is intended for tests that mock out the process environment.
79    pub(crate) fn new_with_env(env: Env) -> Self {
80        Self { env }
81    }
82}
83
84impl Default for EnvironmentVariableCredentialsProvider {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90const ENV_PROVIDER: &str = "EnvironmentVariable";
91
92impl ProvideCredentials for EnvironmentVariableCredentialsProvider {
93    fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a>
94    where
95        Self: 'a,
96    {
97        future::ProvideCredentials::ready(self.credentials())
98    }
99}
100
101fn to_cred_error(err: VarError) -> CredentialsError {
102    match err {
103        VarError::NotPresent => CredentialsError::not_loaded("environment variable not set"),
104        e @ VarError::NotUnicode(_) => CredentialsError::unhandled(e),
105    }
106}
107
108fn err_if_blank(value: String) -> Result<String, VarError> {
109    if value.trim().is_empty() {
110        Err(VarError::NotPresent)
111    } else {
112        Ok(value)
113    }
114}
115
116#[cfg(test)]
117mod test {
118    use aws_credential_types::credential_feature::AwsCredentialFeature;
119    use aws_credential_types::provider::{error::CredentialsError, ProvideCredentials};
120    use aws_types::os_shim_internal::Env;
121    use futures_util::FutureExt;
122
123    use super::EnvironmentVariableCredentialsProvider;
124
125    fn make_provider(vars: &[(&str, &str)]) -> EnvironmentVariableCredentialsProvider {
126        EnvironmentVariableCredentialsProvider {
127            env: Env::from_slice(vars),
128        }
129    }
130
131    #[test]
132    fn valid_no_token() {
133        let provider = make_provider(&[
134            ("AWS_ACCESS_KEY_ID", "access"),
135            ("AWS_SECRET_ACCESS_KEY", "secret"),
136        ]);
137        let creds = provider
138            .provide_credentials()
139            .now_or_never()
140            .unwrap()
141            .expect("valid credentials");
142        assert_eq!(creds.session_token(), None);
143        assert_eq!(creds.access_key_id(), "access");
144        assert_eq!(creds.secret_access_key(), "secret");
145    }
146
147    #[test]
148    fn valid_with_token() {
149        let provider = make_provider(&[
150            ("AWS_ACCESS_KEY_ID", "access"),
151            ("AWS_SECRET_ACCESS_KEY", "secret"),
152            ("AWS_SESSION_TOKEN", "token"),
153        ]);
154
155        let creds = provider
156            .provide_credentials()
157            .now_or_never()
158            .unwrap()
159            .expect("valid credentials");
160        assert_eq!(creds.session_token().unwrap(), "token");
161        assert_eq!(creds.access_key_id(), "access");
162        assert_eq!(creds.secret_access_key(), "secret");
163    }
164
165    #[test]
166    fn empty_token_env_var() {
167        for token_value in &["", " "] {
168            let provider = make_provider(&[
169                ("AWS_ACCESS_KEY_ID", "access"),
170                ("AWS_SECRET_ACCESS_KEY", "secret"),
171                ("AWS_SESSION_TOKEN", token_value),
172            ]);
173
174            let creds = provider
175                .provide_credentials()
176                .now_or_never()
177                .unwrap()
178                .expect("valid credentials");
179            assert_eq!(creds.access_key_id(), "access");
180            assert_eq!(creds.secret_access_key(), "secret");
181            assert_eq!(creds.session_token(), None);
182        }
183    }
184
185    #[test]
186    fn secret_key_fallback() {
187        let provider = make_provider(&[
188            ("AWS_ACCESS_KEY_ID", "access"),
189            ("SECRET_ACCESS_KEY", "secret"),
190            ("AWS_SESSION_TOKEN", "token"),
191        ]);
192
193        let creds = provider
194            .provide_credentials()
195            .now_or_never()
196            .unwrap()
197            .expect("valid credentials");
198        assert_eq!(creds.session_token().unwrap(), "token");
199        assert_eq!(creds.access_key_id(), "access");
200        assert_eq!(creds.secret_access_key(), "secret");
201    }
202
203    #[test]
204    fn secret_key_fallback_empty() {
205        let provider = make_provider(&[
206            ("AWS_ACCESS_KEY_ID", "access"),
207            ("AWS_SECRET_ACCESS_KEY", " "),
208            ("SECRET_ACCESS_KEY", "secret"),
209            ("AWS_SESSION_TOKEN", "token"),
210        ]);
211
212        let creds = provider
213            .provide_credentials()
214            .now_or_never()
215            .unwrap()
216            .expect("valid credentials");
217        assert_eq!(creds.session_token().unwrap(), "token");
218        assert_eq!(creds.access_key_id(), "access");
219        assert_eq!(creds.secret_access_key(), "secret");
220    }
221
222    #[test]
223    fn missing() {
224        let provider = make_provider(&[]);
225        let err = provider
226            .provide_credentials()
227            .now_or_never()
228            .unwrap()
229            .expect_err("no credentials defined");
230        assert!(matches!(err, CredentialsError::CredentialsNotLoaded { .. }));
231    }
232
233    #[test]
234    fn empty_keys_env_vars() {
235        for [access_key_value, secret_key_value] in &[
236            &["", ""],
237            &[" ", ""],
238            &["access", ""],
239            &["", " "],
240            &[" ", " "],
241            &["access", " "],
242            &["", "secret"],
243            &[" ", "secret"],
244        ] {
245            let provider = make_provider(&[
246                ("AWS_ACCESS_KEY_ID", access_key_value),
247                ("AWS_SECRET_ACCESS_KEY", secret_key_value),
248            ]);
249
250            let err = provider
251                .provide_credentials()
252                .now_or_never()
253                .unwrap()
254                .expect_err("no credentials defined");
255            assert!(matches!(err, CredentialsError::CredentialsNotLoaded { .. }));
256        }
257    }
258
259    #[test]
260    fn credentials_feature() {
261        let provider = make_provider(&[
262            ("AWS_ACCESS_KEY_ID", "access"),
263            ("AWS_SECRET_ACCESS_KEY", "secret"),
264            ("SECRET_ACCESS_KEY", "secret"),
265            ("AWS_SESSION_TOKEN", "token"),
266        ]);
267
268        let creds = provider
269            .provide_credentials()
270            .now_or_never()
271            .unwrap()
272            .expect("valid credentials");
273        assert_eq!(
274            &vec![AwsCredentialFeature::CredentialsEnvVars],
275            creds.get_property::<Vec<AwsCredentialFeature>>().unwrap()
276        );
277    }
278
279    #[test]
280    fn real_environment() {
281        let provider = EnvironmentVariableCredentialsProvider::new();
282        // we don't know what's in the env, just make sure it doesn't crash.
283        let _fut = provider.provide_credentials();
284    }
285}