#![cfg_attr(feature = "nightly-testing", feature(plugin))]
#![cfg_attr(feature = "nightly-testing", plugin(clippy))]
#![cfg_attr(not(feature = "unstable"), deny(warnings))]
extern crate chrono;
extern crate hyper;
extern crate regex;
extern crate serde_json;
pub use environment::EnvironmentProvider;
pub use container::ContainerProvider;
pub use instance_metadata::InstanceMetadataProvider;
pub use profile::ProfileProvider;
mod container;
mod environment;
mod instance_metadata;
mod profile;
use std::fmt;
use std::error::Error;
use std::io::Error as IoError;
use std::sync::Mutex;
use std::cell::RefCell;
use chrono::{Duration, UTC, DateTime, ParseError};
use serde_json::Value;
#[derive(Clone, Debug)]
pub struct AwsCredentials {
key: String,
secret: String,
token: Option<String>,
expires_at: DateTime<UTC>
}
impl AwsCredentials {
pub fn new<K, S>(key:K, secret:S, token:Option<String>, expires_at:DateTime<UTC>)
-> AwsCredentials where K:Into<String>, S:Into<String> {
AwsCredentials {
key: key.into(),
secret: secret.into(),
token: token,
expires_at: expires_at,
}
}
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) -> &DateTime<UTC> {
&self.expires_at
}
pub fn token(&self) -> &Option<String> {
&self.token
}
fn credentials_are_expired(&self) -> bool {
self.expires_at < UTC::now() + Duration::seconds(20)
}
}
#[derive(Debug, PartialEq)]
pub struct CredentialsError {
pub message: String
}
impl CredentialsError {
fn new(message: &str) -> CredentialsError {
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())
}
}
pub trait ProvideAwsCredentials {
fn credentials(&self) -> Result<AwsCredentials, CredentialsError>;
}
#[derive(Debug)]
pub struct BaseAutoRefreshingProvider<P, T> {
credentials_provider: P,
cached_credentials: T
}
pub type AutoRefreshingProviderSync<P> = BaseAutoRefreshingProvider<P, Mutex<AwsCredentials>>;
impl <P: ProvideAwsCredentials> AutoRefreshingProviderSync<P> {
pub fn with_mutex(provider: P) -> Result<AutoRefreshingProviderSync<P>, CredentialsError> {
let creds = try!(provider.credentials());
Ok(BaseAutoRefreshingProvider {
credentials_provider: provider,
cached_credentials: Mutex::new(creds)
})
}
}
impl <P: ProvideAwsCredentials> ProvideAwsCredentials for BaseAutoRefreshingProvider<P, Mutex<AwsCredentials>> {
fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
let mut creds = self.cached_credentials.lock().expect("Failed to lock the cached credentials Mutex");
if creds.credentials_are_expired() {
*creds = try!(self.credentials_provider.credentials());
}
Ok(creds.clone())
}
}
pub type AutoRefreshingProvider<P> = BaseAutoRefreshingProvider<P, RefCell<AwsCredentials>>;
impl <P: ProvideAwsCredentials> AutoRefreshingProvider<P> {
pub fn with_refcell(provider: P) -> Result<AutoRefreshingProvider<P>, CredentialsError> {
let creds = try!(provider.credentials());
Ok(BaseAutoRefreshingProvider {
credentials_provider: provider,
cached_credentials: RefCell::new(creds)
})
}
}
impl <P: ProvideAwsCredentials> ProvideAwsCredentials for BaseAutoRefreshingProvider<P, RefCell<AwsCredentials>> {
fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
let mut creds = self.cached_credentials.borrow_mut();
if creds.credentials_are_expired() {
*creds = try!(self.credentials_provider.credentials());
}
Ok(creds.clone())
}
}
pub type DefaultCredentialsProvider = AutoRefreshingProvider<ChainProvider>;
impl DefaultCredentialsProvider {
pub fn new() -> Result<DefaultCredentialsProvider, CredentialsError> {
Ok(try!(AutoRefreshingProvider::with_refcell(ChainProvider::new())))
}
}
pub type DefaultCredentialsProviderSync = AutoRefreshingProviderSync<ChainProvider>;
impl DefaultCredentialsProviderSync {
pub fn new() -> Result<DefaultCredentialsProviderSync, CredentialsError> {
Ok(try!(AutoRefreshingProviderSync::with_mutex(ChainProvider::new())))
}
}
#[derive(Debug, Default, Clone)]
pub struct ChainProvider {
profile_provider: Option<ProfileProvider>,
}
impl ProvideAwsCredentials for ChainProvider {
fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
EnvironmentProvider.credentials()
.or_else(|_| {
match self.profile_provider {
Some(ref provider) => provider.credentials(),
None => Err(CredentialsError::new(""))
}
})
.or_else(|_| ContainerProvider.credentials())
.or_else(|_| InstanceMetadataProvider.credentials())
.or_else(|_| Err(CredentialsError::new("Couldn't find AWS credentials in environment, credentials file, or IAM role.")))
}
}
impl ChainProvider {
pub fn new() -> ChainProvider {
ChainProvider {
profile_provider: ProfileProvider::new().ok(),
}
}
pub fn with_profile_provider(profile_provider: ProfileProvider)
-> ChainProvider {
ChainProvider {
profile_provider: Some(profile_provider),
}
}
}
fn in_ten_minutes() -> DateTime<UTC> {
UTC::now() + Duration::seconds(600)
}
fn extract_string_value_from_json(json_object: &Value, key: &str) -> Result<String, CredentialsError> {
match json_object.find(key) {
Some(v) => Ok(v.as_str().expect(&format!("{} value was not a string", key)).to_owned()),
None => Err(CredentialsError::new(&format!("Couldn't find {} in response.", key))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn credential_chain_explicit_profile_provider() {
let profile_provider = ProfileProvider::with_configuration(
"tests/sample-data/multiple_profile_credentials",
"foo",
);
let chain = ChainProvider::with_profile_provider(profile_provider);
let credentials = chain.credentials().expect(
"Failed to get credentials from default provider chain with manual profile",
);
assert_eq!(credentials.aws_access_key_id(), "foo_access_key");
assert_eq!(credentials.aws_secret_access_key(), "foo_secret_key");
}
}