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
8pub 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
45pub trait Anonymous {
47 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#[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 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 pub fn aws_access_key_id(&self) -> &str {
103 &self.key
104 }
105
106 pub fn aws_secret_access_key(&self) -> &str {
108 &self.secret
109 }
110
111 pub fn expires_at(&self) -> &Option<DateTime<Utc>> {
113 &self.expires_at
114 }
115
116 pub fn token(&self) -> &Option<String> {
118 &self.token
119 }
120
121 fn credentials_are_expired(&self) -> bool {
123 match self.expires_at {
124 Some(ref e) =>
125 {
128 *e < Utc::now() + ChronoDuration::seconds(20)
129 }
130 None => false,
131 }
132 }
133
134 pub fn claims(&self) -> &BTreeMap<String, String> {
136 &self.claims
137 }
138
139 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#[derive(Clone, Debug, PartialEq)]
162pub struct CredentialsError {
163 pub message: String,
165}
166
167impl CredentialsError {
168 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#[async_trait]
227pub trait ProvideAwsCredentials {
228 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#[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 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 pub fn get_ref(&self) -> &P {
262 &self.credentials_provider
263 }
264
265 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 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#[derive(Clone)]
315pub struct DefaultCredentialsProvider(AutoRefreshingProvider<ChainProvider>);
316
317impl DefaultCredentialsProvider {
318 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#[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 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 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 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
440fn 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
455fn 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}