Skip to main content

rusoto_credential/
lib.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/rusoto/rusoto/master/assets/logo-square.png"
3)]
4#![cfg_attr(feature = "nightly-testing", feature(plugin))]
5#![cfg_attr(not(feature = "unstable"), deny(warnings))]
6#![deny(missing_docs)]
7
8//! Types for loading and managing AWS access credentials for API requests.
9
10pub use crate::container::ContainerProvider;
11pub use crate::environment::EnvironmentProvider;
12pub use crate::instance_metadata::InstanceMetadataProvider;
13pub use crate::profile::ProfileProvider;
14pub use crate::secrets::Secret;
15pub use crate::static_provider::StaticProvider;
16pub use crate::variable::Variable;
17
18pub mod claims;
19mod container;
20mod environment;
21mod instance_metadata;
22mod profile;
23mod request;
24mod secrets;
25mod static_provider;
26#[cfg(test)]
27pub(crate) mod test_utils;
28mod variable;
29
30use async_trait::async_trait;
31use std::collections::BTreeMap;
32use std::env::{var as env_var, VarError};
33use std::error::Error;
34use std::fmt;
35use std::io::Error as IoError;
36use std::string::FromUtf8Error;
37use std::sync::Arc;
38use std::time::Duration;
39
40use chrono::{DateTime, Duration as ChronoDuration, ParseError, Utc};
41use hyper::Error as HyperError;
42use serde::Deserialize;
43use tokio::sync::Mutex;
44
45/// Representation of anonymity
46pub trait Anonymous {
47    /// Return true if a type is anonymous, false otherwise
48    fn is_anonymous(&self) -> bool;
49}
50
51impl Anonymous for AwsCredentials {
52    fn is_anonymous(&self) -> bool {
53        self.aws_access_key_id().is_empty() && self.aws_secret_access_key().is_empty()
54    }
55}
56
57/// AWS API access credentials, including access key, secret key, token (for IAM profiles),
58/// expiration timestamp, and claims from federated login.
59///
60/// # Anonymous example
61///
62/// Some AWS services, like [s3](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) do
63/// not require authenticated credentials. For these cases you can use `AwsCredentials::default`
64/// with `StaticProvider`.
65#[derive(Clone, Deserialize, Default)]
66pub struct AwsCredentials {
67    #[serde(rename = "AccessKeyId")]
68    key: String,
69    #[serde(rename = "SecretAccessKey")]
70    secret: String,
71    #[serde(rename = "SessionToken", alias = "Token")]
72    token: Option<String>,
73    #[serde(rename = "Expiration")]
74    expires_at: Option<DateTime<Utc>>,
75    #[serde(skip)]
76    claims: BTreeMap<String, String>,
77}
78
79impl AwsCredentials {
80    /// Create a new `AwsCredentials` from a key ID, secret key, optional access token, and expiry
81    /// time.
82    pub fn new<K, S>(
83        key: K,
84        secret: S,
85        token: Option<String>,
86        expires_at: Option<DateTime<Utc>>,
87    ) -> AwsCredentials
88    where
89        K: Into<String>,
90        S: Into<String>,
91    {
92        AwsCredentials {
93            key: key.into(),
94            secret: secret.into(),
95            token,
96            expires_at,
97            claims: BTreeMap::new(),
98        }
99    }
100
101    /// Get a reference to the access key ID.
102    pub fn aws_access_key_id(&self) -> &str {
103        &self.key
104    }
105
106    /// Get a reference to the secret access key.
107    pub fn aws_secret_access_key(&self) -> &str {
108        &self.secret
109    }
110
111    /// Get a reference to the expiry time.
112    pub fn expires_at(&self) -> &Option<DateTime<Utc>> {
113        &self.expires_at
114    }
115
116    /// Get a reference to the access token.
117    pub fn token(&self) -> &Option<String> {
118        &self.token
119    }
120
121    /// Determine whether or not the credentials are expired.
122    fn credentials_are_expired(&self) -> bool {
123        match self.expires_at {
124            Some(ref e) =>
125            // This is a rough hack to hopefully avoid someone requesting creds then sitting on them
126            // before issuing the request:
127            {
128                *e < Utc::now() + ChronoDuration::seconds(20)
129            }
130            None => false,
131        }
132    }
133
134    /// Get the token claims
135    pub fn claims(&self) -> &BTreeMap<String, String> {
136        &self.claims
137    }
138
139    /// Get the mutable token claims
140    pub fn claims_mut(&mut self) -> &mut BTreeMap<String, String> {
141        &mut self.claims
142    }
143}
144
145impl fmt::Debug for AwsCredentials {
146    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147        f.debug_struct("AwsCredentials")
148            .field("key", &self.key)
149            .field("secret", &"**********")
150            .field("token", &self.token.as_ref().map(|_| "**********"))
151            .field("expires_at", &self.expires_at)
152            .field("claims", &self.claims)
153            .finish()
154    }
155}
156
157/// Represents an Error that has occured during the fetching Credentials Phase.
158///
159/// This generally is an error message from one of our underlying libraries, however
160/// we wrap it up with this type so we can export one single error type.
161#[derive(Clone, Debug, PartialEq)]
162pub struct CredentialsError {
163    /// The underlying error message for the credentials error.
164    pub message: String,
165}
166
167impl CredentialsError {
168    /// Creates a new Credentials Error.
169    ///
170    /// * `message` - The Error message for this CredentialsError.
171    pub fn new<S>(message: S) -> CredentialsError
172    where
173        S: ToString,
174    {
175        CredentialsError {
176            message: message.to_string(),
177        }
178    }
179}
180
181impl fmt::Display for CredentialsError {
182    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183        write!(f, "{}", self.message)
184    }
185}
186
187impl Error for CredentialsError {}
188
189impl From<ParseError> for CredentialsError {
190    fn from(err: ParseError) -> CredentialsError {
191        CredentialsError::new(err)
192    }
193}
194
195impl From<IoError> for CredentialsError {
196    fn from(err: IoError) -> CredentialsError {
197        CredentialsError::new(err)
198    }
199}
200
201impl From<HyperError> for CredentialsError {
202    fn from(err: HyperError) -> CredentialsError {
203        CredentialsError::new(format!("Couldn't connect to credentials provider: {}", err))
204    }
205}
206
207impl From<serde_json::Error> for CredentialsError {
208    fn from(err: serde_json::Error) -> CredentialsError {
209        CredentialsError::new(err)
210    }
211}
212
213impl From<VarError> for CredentialsError {
214    fn from(err: VarError) -> CredentialsError {
215        CredentialsError::new(err)
216    }
217}
218
219impl From<FromUtf8Error> for CredentialsError {
220    fn from(err: FromUtf8Error) -> CredentialsError {
221        CredentialsError::new(err)
222    }
223}
224
225/// A trait for types that produce `AwsCredentials`.
226#[async_trait]
227pub trait ProvideAwsCredentials {
228    /// Produce a new `AwsCredentials` future.
229    async fn credentials(&self) -> Result<AwsCredentials, CredentialsError>;
230}
231
232#[async_trait]
233impl<P: ProvideAwsCredentials + Send + Sync> ProvideAwsCredentials for Arc<P> {
234    async fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
235        P::credentials(self).await
236    }
237}
238
239/// Wrapper for `ProvideAwsCredentials` that caches the credentials returned by the
240/// wrapped provider.  Each time the credentials are accessed, they are checked to see if
241/// they have expired, in which case they are retrieved from the wrapped provider again.
242///
243/// In order to access the wrapped provider, for instance to set a timeout, the `get_ref`
244/// and `get_mut` methods can be used.
245#[derive(Debug, Clone)]
246pub struct AutoRefreshingProvider<P: ProvideAwsCredentials + 'static> {
247    credentials_provider: P,
248    current_credentials: Arc<Mutex<Option<Result<AwsCredentials, CredentialsError>>>>,
249}
250
251impl<P: ProvideAwsCredentials + 'static> AutoRefreshingProvider<P> {
252    /// Create a new `AutoRefreshingProvider` around the provided base provider.
253    pub fn new(provider: P) -> Result<AutoRefreshingProvider<P>, CredentialsError> {
254        Ok(AutoRefreshingProvider {
255            credentials_provider: provider,
256            current_credentials: Arc::new(Mutex::new(None)),
257        })
258    }
259
260    /// Get a shared reference to the wrapped provider.
261    pub fn get_ref(&self) -> &P {
262        &self.credentials_provider
263    }
264
265    /// Get a mutable reference to the wrapped provider.
266    ///
267    /// This can be used to call `set_timeout` on the wrapped
268    /// provider.
269    pub fn get_mut(&mut self) -> &mut P {
270        &mut self.credentials_provider
271    }
272}
273
274#[async_trait]
275impl<P: ProvideAwsCredentials + Send + Sync + 'static> ProvideAwsCredentials
276    for AutoRefreshingProvider<P>
277{
278    async fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
279        loop {
280            let mut guard = self.current_credentials.lock().await;
281            match guard.as_ref() {
282                // no result from the future yet, let's keep using it
283                None => {
284                    let res = self.credentials_provider.credentials().await;
285                    *guard = Some(res);
286                }
287                Some(Err(e)) => return Err(e.clone()),
288                Some(Ok(creds)) => {
289                    if creds.credentials_are_expired() {
290                        *guard = None;
291                    } else {
292                        return Ok(creds.clone());
293                    };
294                }
295            }
296        }
297    }
298}
299
300/// Wraps a `ChainProvider` in an `AutoRefreshingProvider`.
301///
302/// The underlying `ChainProvider` checks multiple sources for credentials, and the `AutoRefreshingProvider`
303/// refreshes the credentials automatically when they expire.
304///
305/// # Warning
306///
307/// This provider allows the [`credential_process`][credential_process] option in the AWS config
308/// file (`~/.aws/config`), a method of sourcing credentials from an external process. This can
309/// potentially be dangerous, so proceed with caution. Other credential providers should be
310/// preferred if at all possible. If using this option, you should make sure that the config file
311/// is as locked down as possible using security best practices for your operating system.
312///
313/// [credential_process]: https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
314#[derive(Clone)]
315pub struct DefaultCredentialsProvider(AutoRefreshingProvider<ChainProvider>);
316
317impl DefaultCredentialsProvider {
318    /// Creates a new thread-safe `DefaultCredentialsProvider`.
319    pub fn new() -> Result<DefaultCredentialsProvider, CredentialsError> {
320        let inner = AutoRefreshingProvider::new(ChainProvider::new())?;
321        Ok(DefaultCredentialsProvider(inner))
322    }
323}
324
325#[async_trait]
326impl ProvideAwsCredentials for DefaultCredentialsProvider {
327    async fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
328        self.0.credentials().await
329    }
330}
331
332/// Provides AWS credentials from multiple possible sources using a priority order.
333///
334/// The following sources are checked in order for credentials when calling `credentials`:
335///
336/// 1. Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
337/// 2. `credential_process` command in the AWS config file, usually located at `~/.aws/config`.
338/// 3. AWS credentials file. Usually located at `~/.aws/credentials`.
339/// 4. IAM instance profile. Will only work if running on an EC2 instance with an instance profile/role.
340///
341/// If the sources are exhausted without finding credentials, an error is returned.
342///
343/// The provider has a default timeout of 30 seconds. While it should work well for most setups,
344/// you can change the timeout using the `set_timeout` method.
345///
346/// # Example
347///
348/// ```rust
349/// use std::time::Duration;
350///
351/// use rusoto_credential::ChainProvider;
352///
353/// let mut provider = ChainProvider::new();
354/// // you can overwrite the default timeout like this:
355/// provider.set_timeout(Duration::from_secs(60));
356/// ```
357///
358/// # Warning
359///
360/// This provider allows the [`credential_process`][credential_process] option in the AWS config
361/// file (`~/.aws/config`), a method of sourcing credentials from an external process. This can
362/// potentially be dangerous, so proceed with caution. Other credential providers should be
363/// preferred if at all possible. If using this option, you should make sure that the config file
364/// is as locked down as possible using security best practices for your operating system.
365///
366/// [credential_process]: https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
367#[derive(Debug, Clone)]
368pub struct ChainProvider {
369    environment_provider: EnvironmentProvider,
370    instance_metadata_provider: InstanceMetadataProvider,
371    container_provider: ContainerProvider,
372    profile_provider: Option<ProfileProvider>,
373}
374
375impl ChainProvider {
376    /// Set the timeout on the provider to the specified duration.
377    pub fn set_timeout(&mut self, duration: Duration) {
378        self.instance_metadata_provider.set_timeout(duration);
379        self.container_provider.set_timeout(duration);
380    }
381}
382
383async fn chain_provider_credentials(
384    provider: ChainProvider,
385) -> Result<AwsCredentials, CredentialsError> {
386    if let Ok(creds) = provider.environment_provider.credentials().await {
387        return Ok(creds);
388    }
389    if let Some(ref profile_provider) = provider.profile_provider {
390        if let Ok(creds) = profile_provider.credentials().await {
391            return Ok(creds);
392        }
393    }
394    if let Ok(creds) = provider.container_provider.credentials().await {
395        return Ok(creds);
396    }
397    if let Ok(creds) = provider.instance_metadata_provider.credentials().await {
398        return Ok(creds);
399    }
400    Err(CredentialsError::new(
401        "Couldn't find AWS credentials in environment, credentials file, or IAM role.",
402    ))
403}
404
405#[async_trait]
406impl ProvideAwsCredentials for ChainProvider {
407    async fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
408        chain_provider_credentials(self.clone()).await
409    }
410}
411
412impl ChainProvider {
413    /// Create a new `ChainProvider` using a `ProfileProvider` with the default settings.
414    pub fn new() -> ChainProvider {
415        ChainProvider {
416            environment_provider: EnvironmentProvider::default(),
417            profile_provider: ProfileProvider::new().ok(),
418            instance_metadata_provider: InstanceMetadataProvider::new(),
419            container_provider: ContainerProvider::new(),
420        }
421    }
422
423    /// Create a new `ChainProvider` using the provided `ProfileProvider`.
424    pub fn with_profile_provider(profile_provider: ProfileProvider) -> ChainProvider {
425        ChainProvider {
426            environment_provider: EnvironmentProvider::default(),
427            profile_provider: Some(profile_provider),
428            instance_metadata_provider: InstanceMetadataProvider::new(),
429            container_provider: ContainerProvider::new(),
430        }
431    }
432}
433
434impl Default for ChainProvider {
435    fn default() -> Self {
436        Self::new()
437    }
438}
439
440/// This is a helper function as Option<T>::filter is not yet stable (see issue #45860).
441/// <https://github.com/rust-lang/rfcs/issues/2036> also affects the implementation of this.
442fn non_empty_env_var(name: &str) -> Option<String> {
443    match env_var(name) {
444        Ok(value) => {
445            if value.is_empty() {
446                None
447            } else {
448                Some(value)
449            }
450        }
451        Err(_) => None,
452    }
453}
454
455/// Parses the response from an AWS Metadata Service, either from an IAM Role, or a Container.
456fn parse_credentials_from_aws_service(response: &str) -> Result<AwsCredentials, CredentialsError> {
457    Ok(serde_json::from_str::<AwsCredentials>(response)?)
458}
459
460#[cfg(test)]
461mod tests {
462    use std::fs::{self, File};
463    use std::io::Read;
464    use std::path::Path;
465
466    use crate::test_utils::{is_secret_hidden_behind_asterisks, lock_env, SECRET};
467    use quickcheck::quickcheck;
468
469    use super::*;
470
471    #[test]
472    fn default_empty_credentials_are_considered_anonymous() {
473        assert!(AwsCredentials::default().is_anonymous())
474    }
475
476    #[test]
477    fn credentials_with_values_are_not_considered_anonymous() {
478        assert!(!AwsCredentials::new("foo", "bar", None, None).is_anonymous())
479    }
480
481    #[test]
482    fn providers_are_send_and_sync() {
483        fn is_send_and_sync<T: Send + Sync>() {}
484
485        is_send_and_sync::<ChainProvider>();
486        is_send_and_sync::<AutoRefreshingProvider<ChainProvider>>();
487        is_send_and_sync::<DefaultCredentialsProvider>();
488    }
489
490    #[tokio::test]
491    async fn profile_provider_finds_right_credentials_in_file() {
492        let _guard = lock_env();
493        let profile_provider = ProfileProvider::with_configuration(
494            "tests/sample-data/multiple_profile_credentials",
495            "foo",
496        );
497
498        let credentials = profile_provider.credentials().await.expect(
499            "Failed to get credentials from profile provider using tests/sample-data/multiple_profile_credentials",
500        );
501
502        assert_eq!(credentials.aws_access_key_id(), "foo_access_key");
503        assert_eq!(credentials.aws_secret_access_key(), "foo_secret_key");
504    }
505
506    #[test]
507    fn parse_iam_task_credentials_sample_response() {
508        fn read_file_to_string(file_path: &Path) -> String {
509            match fs::metadata(file_path) {
510                Ok(metadata) => {
511                    if !metadata.is_file() {
512                        panic!("Couldn't open file");
513                    }
514                }
515                Err(_) => panic!("Couldn't stat file"),
516            };
517
518            let mut file = File::open(file_path).unwrap();
519            let mut result = String::new();
520            file.read_to_string(&mut result).ok();
521
522            result
523        }
524
525        let response = read_file_to_string(Path::new(
526            "tests/sample-data/iam_task_credentials_sample_response",
527        ));
528
529        let credentials = parse_credentials_from_aws_service(&response);
530
531        assert!(credentials.is_ok());
532        let credentials = credentials.unwrap();
533
534        assert_eq!(credentials.aws_access_key_id(), "AKIAIOSFODNN7EXAMPLE");
535        assert_eq!(
536            credentials.aws_secret_access_key(),
537            "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
538        );
539        assert!(credentials.token().is_some());
540
541        assert_eq!(
542            credentials.expires_at().expect(""),
543            DateTime::parse_from_rfc3339("2016-11-18T01:50:39Z").expect("")
544        );
545    }
546
547    #[cfg(test)]
548    quickcheck! {
549        fn test_aws_credentials_secrets_not_in_debug(
550            key: String,
551            valid_for: Option<i64>,
552            token: Option<()>
553        ) -> bool {
554            let creds = AwsCredentials::new(
555                key,
556                SECRET.to_owned(),
557                token.map(|_| test_utils::SECRET.to_owned()),
558                valid_for.map(|v| Utc::now() + ChronoDuration::seconds(v)),
559            );
560            is_secret_hidden_behind_asterisks(&creds)
561        }
562    }
563}