#![doc(
html_logo_url = "https://raw.githubusercontent.com/rusoto/rusoto/master/assets/logo-square.png"
)]
#![cfg_attr(feature = "nightly-testing", feature(plugin))]
#![cfg_attr(feature = "nightly-testing", plugin(clippy))]
#![cfg_attr(not(feature = "unstable"), deny(warnings))]
#![deny(missing_docs)]
extern crate chrono;
extern crate dirs;
#[macro_use]
extern crate futures;
extern crate hyper;
extern crate regex;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate shlex;
extern crate tokio_process;
extern crate tokio_timer;
pub use crate::container::{ContainerProvider, ContainerProviderFuture};
pub use crate::environment::{EnvironmentProvider, EnvironmentProviderFuture};
pub use crate::instance_metadata::{InstanceMetadataProvider, InstanceMetadataProviderFuture};
pub use crate::profile::{ProfileProvider, ProfileProviderFuture};
pub use crate::static_provider::StaticProvider;
pub mod claims;
mod container;
mod environment;
mod instance_metadata;
mod profile;
mod request;
mod static_provider;
pub(crate) mod test_utils;
use std::collections::BTreeMap;
use std::env::var as env_var;
use std::error::Error;
use std::fmt;
use std::io::Error as IoError;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use chrono::{DateTime, Duration as ChronoDuration, ParseError, Utc};
use futures::future::{err, Either, Shared, SharedItem};
use futures::{Async, Future, Poll};
use hyper::Error as HyperError;
#[derive(Clone, Deserialize)]
pub struct AwsCredentials {
#[serde(rename = "AccessKeyId")]
key: String,
#[serde(rename = "SecretAccessKey")]
secret: String,
#[serde(rename = "Token")]
token: Option<String>,
#[serde(rename = "Expiration")]
expires_at: Option<DateTime<Utc>>,
#[serde(skip)]
claims: BTreeMap<String, String>,
}
impl AwsCredentials {
pub fn new<K, S>(
key: K,
secret: S,
token: Option<String>,
expires_at: Option<DateTime<Utc>>,
) -> AwsCredentials
where
K: Into<String>,
S: Into<String>,
{
AwsCredentials {
key: key.into(),
secret: secret.into(),
token,
expires_at,
claims: BTreeMap::new(),
}
}
pub fn aws_access_key_id(&self) -> &str {
&self.key
}
pub fn aws_secret_access_key(&self) -> &str {
&self.secret
}
pub fn expires_at(&self) -> &Option<DateTime<Utc>> {
&self.expires_at
}
pub fn token(&self) -> &Option<String> {
&self.token
}
fn credentials_are_expired(&self) -> bool {
match self.expires_at {
Some(ref e) =>
{
*e < Utc::now() + ChronoDuration::seconds(20)
}
None => false,
}
}
pub fn claims(&self) -> &BTreeMap<String, String> {
&self.claims
}
pub fn claims_mut(&mut self) -> &mut BTreeMap<String, String> {
&mut self.claims
}
}
impl fmt::Debug for AwsCredentials {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("AwsCredentials")
.field("key", &self.key)
.field("secret", &"**********")
.field("token", &self.token.as_ref().map(|_| "**********"))
.field("expires_at", &self.expires_at)
.field("claims", &self.claims)
.finish()
}
}
#[derive(Debug, PartialEq)]
pub struct CredentialsError {
pub message: String,
}
impl CredentialsError {
pub fn new<S>(message: S) -> CredentialsError
where
S: ToString,
{
CredentialsError {
message: message.to_string(),
}
}
}
impl fmt::Display for CredentialsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl Error for CredentialsError {
fn description(&self) -> &str {
&self.message
}
}
impl From<ParseError> for CredentialsError {
fn from(err: ParseError) -> CredentialsError {
CredentialsError::new(err.description())
}
}
impl From<IoError> for CredentialsError {
fn from(err: IoError) -> CredentialsError {
CredentialsError::new(err.description())
}
}
impl From<HyperError> for CredentialsError {
fn from(err: HyperError) -> CredentialsError {
CredentialsError::new(format!("Couldn't connect to credentials provider: {}", err))
}
}
impl From<serde_json::Error> for CredentialsError {
fn from(err: serde_json::Error) -> CredentialsError {
CredentialsError::new(err.description())
}
}
pub trait ProvideAwsCredentials {
type Future: Future<Item = AwsCredentials, Error = CredentialsError> + 'static;
fn credentials(&self) -> Self::Future;
}
impl<P: ProvideAwsCredentials> ProvideAwsCredentials for Rc<P> {
type Future = P::Future;
fn credentials(&self) -> Self::Future {
P::credentials(&*self)
}
}
impl<P: ProvideAwsCredentials> ProvideAwsCredentials for Arc<P> {
type Future = P::Future;
fn credentials(&self) -> Self::Future {
P::credentials(&*self)
}
}
#[derive(Debug)]
pub struct AutoRefreshingProvider<P: ProvideAwsCredentials + 'static> {
credentials_provider: P,
shared_future: Mutex<Shared<P::Future>>,
}
impl<P: ProvideAwsCredentials + 'static> AutoRefreshingProvider<P> {
pub fn new(provider: P) -> Result<AutoRefreshingProvider<P>, CredentialsError> {
let future = provider.credentials();
Ok(AutoRefreshingProvider {
credentials_provider: provider,
shared_future: Mutex::new(future.shared()),
})
}
pub fn get_ref(&self) -> &P {
&self.credentials_provider
}
pub fn get_mut(&mut self) -> &mut P {
&mut self.credentials_provider
}
}
enum AutoRefreshingFutureInner<P: ProvideAwsCredentials + 'static> {
Cached(SharedItem<AwsCredentials>),
NotCached(Shared<P::Future>),
}
impl<P: ProvideAwsCredentials + 'static> AutoRefreshingFutureInner<P> {
fn from_shared_future(future: &mut Shared<P::Future>, provider: &P) -> Self {
match future.peek() {
None => AutoRefreshingFutureInner::NotCached(future.clone()),
Some(Ok(ref creds)) if !creds.credentials_are_expired() => {
AutoRefreshingFutureInner::Cached(creds.clone())
}
Some(_) => {
*future = provider.credentials().shared();
AutoRefreshingFutureInner::NotCached(future.clone())
}
}
}
}
impl<P: ProvideAwsCredentials + 'static> Clone for AutoRefreshingFutureInner<P> {
fn clone(&self) -> Self {
match *self {
AutoRefreshingFutureInner::Cached(ref shared_item) => {
AutoRefreshingFutureInner::Cached(shared_item.clone())
}
AutoRefreshingFutureInner::NotCached(ref shared_future) => {
AutoRefreshingFutureInner::NotCached(shared_future.clone())
}
}
}
}
pub struct AutoRefreshingProviderFuture<P: ProvideAwsCredentials + 'static> {
inner: AutoRefreshingFutureInner<P>,
}
impl<P: ProvideAwsCredentials + 'static> Future for AutoRefreshingProviderFuture<P> {
type Item = AwsCredentials;
type Error = CredentialsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.inner {
AutoRefreshingFutureInner::Cached(ref creds) => Ok(Async::Ready(creds.deref().clone())),
AutoRefreshingFutureInner::NotCached(ref mut future) => match future.poll() {
Err(err) => Err(CredentialsError {
message: err.message.to_owned(),
}),
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(item)) => Ok(Async::Ready(item.deref().clone())),
},
}
}
}
impl<P: ProvideAwsCredentials + 'static> ProvideAwsCredentials for AutoRefreshingProvider<P> {
type Future = AutoRefreshingProviderFuture<P>;
fn credentials(&self) -> Self::Future {
let mut shared_future = self
.shared_future
.lock()
.expect("Failed to lock the cached credentials Mutex");
AutoRefreshingProviderFuture {
inner: AutoRefreshingFutureInner::from_shared_future(
&mut shared_future,
&self.credentials_provider,
),
}
}
}
pub struct DefaultCredentialsProvider(AutoRefreshingProvider<ChainProvider>);
impl DefaultCredentialsProvider {
pub fn new() -> Result<DefaultCredentialsProvider, CredentialsError> {
let inner = AutoRefreshingProvider::new(ChainProvider::new())?;
Ok(DefaultCredentialsProvider(inner))
}
}
impl ProvideAwsCredentials for DefaultCredentialsProvider {
type Future = DefaultCredentialsProviderFuture;
fn credentials(&self) -> Self::Future {
let inner = self.0.credentials();
DefaultCredentialsProviderFuture(inner)
}
}
pub struct DefaultCredentialsProviderFuture(AutoRefreshingProviderFuture<ChainProvider>);
impl Future for DefaultCredentialsProviderFuture {
type Item = AwsCredentials;
type Error = CredentialsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.0.poll()
}
}
#[derive(Debug, Clone)]
pub struct ChainProvider {
environment_provider: EnvironmentProvider,
instance_metadata_provider: InstanceMetadataProvider,
container_provider: ContainerProvider,
profile_provider: Option<ProfileProvider>,
}
impl ChainProvider {
pub fn set_timeout(&mut self, duration: Duration) {
self.instance_metadata_provider.set_timeout(duration);
self.container_provider.set_timeout(duration);
}
}
pub struct ChainProviderFuture {
inner: Box<dyn Future<Item = AwsCredentials, Error = CredentialsError> + Send>,
}
impl Future for ChainProviderFuture {
type Item = AwsCredentials;
type Error = CredentialsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.inner.poll()
}
}
impl ProvideAwsCredentials for ChainProvider {
type Future = ChainProviderFuture;
fn credentials(&self) -> Self::Future {
let profile_provider = self.profile_provider.clone();
let instance_metadata_provider = self.instance_metadata_provider.clone();
let container_provider = self.container_provider.clone();
let future = self
.environment_provider
.credentials()
.or_else(move |_| match profile_provider {
Some(ref provider) => Either::A(provider.credentials()),
None => Either::B(err(CredentialsError::new(""))),
})
.or_else(move |_| container_provider.credentials())
.or_else(move |_| instance_metadata_provider.credentials())
.or_else(|_| {
Err(CredentialsError::new(
"Couldn't find AWS credentials in environment, credentials file, or IAM role.",
))
});
ChainProviderFuture {
inner: Box::new(future),
}
}
}
impl ChainProvider {
pub fn new() -> ChainProvider {
ChainProvider {
environment_provider: EnvironmentProvider::default(),
profile_provider: ProfileProvider::new().ok(),
instance_metadata_provider: InstanceMetadataProvider::new(),
container_provider: ContainerProvider::new(),
}
}
pub fn with_profile_provider(profile_provider: ProfileProvider) -> ChainProvider {
ChainProvider {
environment_provider: EnvironmentProvider::default(),
profile_provider: Some(profile_provider),
instance_metadata_provider: InstanceMetadataProvider::new(),
container_provider: ContainerProvider::new(),
}
}
}
impl Default for ChainProvider {
fn default() -> Self {
Self::new()
}
}
fn non_empty_env_var(name: &str) -> Option<String> {
match env_var(name) {
Ok(value) => {
if value.is_empty() {
None
} else {
Some(value)
}
}
Err(_) => None,
}
}
fn parse_credentials_from_aws_service(response: &str) -> Result<AwsCredentials, CredentialsError> {
Ok(serde_json::from_str::<AwsCredentials>(response)?)
}
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
#[cfg(test)]
mod tests {
use std::fs::{self, File};
use std::io::Read;
use std::path::Path;
use crate::test_utils::{is_secret_hidden_behind_asterisks, lock, ENV_MUTEX, SECRET};
use futures::Future;
use super::*;
#[test]
fn providers_are_send_and_sync() {
fn is_send_and_sync<T: Send + Sync>() {}
is_send_and_sync::<ChainProvider>();
is_send_and_sync::<AutoRefreshingProvider<ChainProvider>>();
is_send_and_sync::<DefaultCredentialsProvider>();
}
#[test]
fn provider_futures_are_send() {
fn is_send<T: Send>() {}
is_send::<ChainProviderFuture>();
is_send::<AutoRefreshingProviderFuture<ChainProvider>>();
}
#[test]
fn profile_provider_finds_right_credentials_in_file() {
let _guard = lock(&ENV_MUTEX);
let profile_provider = ProfileProvider::with_configuration(
"tests/sample-data/multiple_profile_credentials",
"foo",
);
let credentials = profile_provider.credentials().wait().expect(
"Failed to get credentials from profile provider using tests/sample-data/multiple_profile_credentials",
);
assert_eq!(credentials.aws_access_key_id(), "foo_access_key");
assert_eq!(credentials.aws_secret_access_key(), "foo_secret_key");
}
#[test]
fn parse_iam_task_credentials_sample_response() {
fn read_file_to_string(file_path: &Path) -> String {
match fs::metadata(file_path) {
Ok(metadata) => {
if !metadata.is_file() {
panic!("Couldn't open file");
}
}
Err(_) => panic!("Couldn't stat file"),
};
let mut file = File::open(file_path).unwrap();
let mut result = String::new();
file.read_to_string(&mut result).ok();
result
}
let response = read_file_to_string(Path::new(
"tests/sample-data/iam_task_credentials_sample_response",
));
let credentials = parse_credentials_from_aws_service(&response);
assert!(credentials.is_ok());
let credentials = credentials.unwrap();
assert_eq!(credentials.aws_access_key_id(), "AKIAIOSFODNN7EXAMPLE");
assert_eq!(
credentials.aws_secret_access_key(),
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
);
assert_eq!(
credentials.expires_at().expect(""),
DateTime::parse_from_rfc3339("2016-11-18T01:50:39Z").expect("")
);
}
#[cfg(test)]
quickcheck! {
fn test_aws_credentials_secrets_not_in_debug(
key: String,
valid_for: Option<i64>,
token: Option<()>
) -> bool {
let creds = AwsCredentials::new(
key,
SECRET.to_owned(),
token.map(|_| test_utils::SECRET.to_owned()),
valid_for.map(|v| Utc::now() + ChronoDuration::seconds(v)),
);
is_secret_hidden_behind_asterisks(&creds)
}
}
}