use chrono::{DateTime, FixedOffset, Utc};
use futures::future::{result, FutureResult};
use futures::{Future, Poll};
use {non_empty_env_var, AwsCredentials, CredentialsError, ProvideAwsCredentials};
#[derive(Debug, Clone)]
pub struct EnvironmentProvider {
prefix: String,
}
impl Default for EnvironmentProvider {
fn default() -> Self {
EnvironmentProvider {
prefix: "AWS".to_owned(),
}
}
}
impl EnvironmentProvider {
pub fn with_prefix(prefix: &str) -> Self {
EnvironmentProvider {
prefix: prefix.to_owned(),
}
}
}
trait EnvironmentVariableProvider {
fn prefix(&self) -> &str;
fn access_key_id_var(&self) -> String {
format!("{}_ACCESS_KEY_ID", self.prefix())
}
fn secret_access_key_var(&self) -> String {
format!("{}_SECRET_ACCESS_KEY", self.prefix())
}
fn session_token_var(&self) -> String {
format!("{}_SESSION_TOKEN", self.prefix())
}
fn credential_expiration_var(&self) -> String {
format!("{}_CREDENTIAL_EXPIRATION", self.prefix())
}
}
impl EnvironmentVariableProvider for EnvironmentProvider {
fn prefix(&self) -> &str {
self.prefix.as_str()
}
}
pub struct EnvironmentProviderFuture {
inner: FutureResult<AwsCredentials, CredentialsError>,
}
impl Future for EnvironmentProviderFuture {
type Item = AwsCredentials;
type Error = CredentialsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.inner.poll()
}
}
impl ProvideAwsCredentials for EnvironmentProvider {
type Future = EnvironmentProviderFuture;
fn credentials(&self) -> Self::Future {
EnvironmentProviderFuture {
inner: result(credentials_from_environment(self)),
}
}
}
fn credentials_from_environment(
provider: &EnvironmentVariableProvider,
) -> Result<AwsCredentials, CredentialsError> {
let env_key = get_critical_variable(provider.access_key_id_var())?;
let env_secret = get_critical_variable(provider.secret_access_key_var())?;
let token = non_empty_env_var(&provider.session_token_var());
let var_name = provider.credential_expiration_var();
let expires_at = match non_empty_env_var(&var_name) {
Some(val) => Some(DateTime::<FixedOffset>::parse_from_rfc3339(&val)
.map(|dt| dt.with_timezone(&Utc))
.map_err(|e| {
CredentialsError::new(format!(
"Invalid {} in environment '{}': {}",
var_name, val, e
))
})?),
_ => None,
};
Ok(AwsCredentials::new(env_key, env_secret, token, expires_at))
}
fn get_critical_variable(var_name: String) -> Result<String, CredentialsError> {
non_empty_env_var(&var_name)
.ok_or_else(|| CredentialsError::new(format!("No (or empty) {} in environment", var_name)))
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
use std::env;
use std::sync::{Mutex, MutexGuard};
static AWS_ACCESS_KEY_ID: &str = "AWS_ACCESS_KEY_ID";
static AWS_SECRET_ACCESS_KEY: &str = "AWS_SECRET_ACCESS_KEY";
static AWS_SESSION_TOKEN: &str = "AWS_SESSION_TOKEN";
static AWS_CREDENTIAL_EXPIRATION: &str = "AWS_CREDENTIAL_EXPIRATION";
static E_NO_ACCESS_KEY_ID: &str = "No (or empty) AWS_ACCESS_KEY_ID in environment";
static E_NO_SECRET_ACCESS_KEY: &str = "No (or empty) AWS_SECRET_ACCESS_KEY in environment";
static E_INVALID_EXPIRATION: &str = "Invalid AWS_CREDENTIAL_EXPIRATION in environment";
lazy_static! {
static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
}
fn lock<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
match mutex.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
#[test]
fn get_temporary_credentials_from_env() {
let _guard = lock(&ENV_MUTEX);
env::set_var(AWS_ACCESS_KEY_ID, "id");
env::set_var(AWS_SECRET_ACCESS_KEY, "secret");
env::set_var(AWS_SESSION_TOKEN, "token");
let result = EnvironmentProvider::default().credentials().wait();
env::remove_var(AWS_ACCESS_KEY_ID);
env::remove_var(AWS_SECRET_ACCESS_KEY);
env::remove_var(AWS_SESSION_TOKEN);
assert!(result.is_ok());
let creds = result.ok().unwrap();
assert_eq!(creds.aws_access_key_id(), "id");
assert_eq!(creds.aws_secret_access_key(), "secret");
assert_eq!(creds.token(), &Some("token".to_string()));
}
#[test]
fn get_non_temporary_credentials_from_env() {
let _guard = lock(&ENV_MUTEX);
env::set_var(AWS_ACCESS_KEY_ID, "id");
env::set_var(AWS_SECRET_ACCESS_KEY, "secret");
env::remove_var(AWS_SESSION_TOKEN);
let result = EnvironmentProvider::default().credentials().wait();
env::remove_var(AWS_ACCESS_KEY_ID);
env::remove_var(AWS_SECRET_ACCESS_KEY);
assert!(result.is_ok());
let creds = result.ok().unwrap();
assert_eq!(creds.aws_access_key_id(), "id");
assert_eq!(creds.aws_secret_access_key(), "secret");
assert_eq!(creds.token(), &None);
}
#[test]
fn environment_provider_missing_key_id() {
let _guard = lock(&ENV_MUTEX);
env::remove_var(AWS_ACCESS_KEY_ID);
env::set_var(AWS_SECRET_ACCESS_KEY, "secret");
env::remove_var(AWS_SESSION_TOKEN);
let result = EnvironmentProvider::default().credentials().wait();
env::remove_var(AWS_SECRET_ACCESS_KEY);
assert!(result.is_err());
assert_eq!(
result.err(),
Some(CredentialsError::new(E_NO_ACCESS_KEY_ID))
);
}
#[test]
fn environment_provider_missing_secret() {
let _guard = lock(&ENV_MUTEX);
env::remove_var(AWS_SECRET_ACCESS_KEY);
env::set_var(AWS_ACCESS_KEY_ID, "id");
env::remove_var(AWS_SESSION_TOKEN);
let result = EnvironmentProvider::default().credentials().wait();
env::remove_var(AWS_ACCESS_KEY_ID);
assert!(result.is_err());
assert_eq!(
result.err(),
Some(CredentialsError::new(E_NO_SECRET_ACCESS_KEY))
);
}
#[test]
fn environment_provider_missing_credentials() {
let _guard = lock(&ENV_MUTEX);
env::remove_var(AWS_SECRET_ACCESS_KEY);
env::remove_var(AWS_ACCESS_KEY_ID);
env::remove_var(AWS_SESSION_TOKEN);
let result = EnvironmentProvider::default().credentials().wait();
assert!(result.is_err());
assert_eq!(
result.err(),
Some(CredentialsError::new(E_NO_ACCESS_KEY_ID))
);
}
#[test]
fn environment_provider_bad_expiration() {
let _guard = lock(&ENV_MUTEX);
env::set_var(AWS_ACCESS_KEY_ID, "id");
env::set_var(AWS_SECRET_ACCESS_KEY, "secret");
env::set_var(AWS_SESSION_TOKEN, "token");
env::set_var(AWS_CREDENTIAL_EXPIRATION, "lore ipsum");
let result = EnvironmentProvider::default().credentials().wait();
env::remove_var(AWS_ACCESS_KEY_ID);
env::remove_var(AWS_SECRET_ACCESS_KEY);
env::remove_var(AWS_SESSION_TOKEN);
env::remove_var(AWS_CREDENTIAL_EXPIRATION);
assert!(result.is_err());
assert!(match &result.err() {
&Some(CredentialsError { ref message }) => message.starts_with(E_INVALID_EXPIRATION),
_ => false,
});
}
#[test]
fn get_temporary_credentials_with_expiration_from_env() {
let _guard = lock(&ENV_MUTEX);
let now = Utc::now();
let now_str = now.to_rfc3339();
env::set_var(AWS_ACCESS_KEY_ID, "id");
env::set_var(AWS_SECRET_ACCESS_KEY, "secret");
env::set_var(AWS_SESSION_TOKEN, "token");
env::set_var(AWS_CREDENTIAL_EXPIRATION, now_str);
let result = EnvironmentProvider::default().credentials().wait();
env::remove_var(AWS_ACCESS_KEY_ID);
env::remove_var(AWS_SECRET_ACCESS_KEY);
env::remove_var(AWS_SESSION_TOKEN);
env::remove_var(AWS_CREDENTIAL_EXPIRATION);
assert!(result.is_ok());
let creds = result.ok().unwrap();
assert_eq!(creds.aws_access_key_id(), "id");
assert_eq!(creds.aws_secret_access_key(), "secret");
assert_eq!(creds.token(), &Some("token".to_string()));
assert_eq!(creds.expires_at(), &Some(now));
}
#[test]
fn regression_test_rfc_3339_compat() {
let _guard = lock(&ENV_MUTEX);
env::set_var(AWS_CREDENTIAL_EXPIRATION, "1996-12-19t16:39:57-08:00");
env::set_var(AWS_ACCESS_KEY_ID, "id");
env::set_var(AWS_SECRET_ACCESS_KEY, "secret");
let result = EnvironmentProvider::default().credentials().wait();
env::remove_var(AWS_CREDENTIAL_EXPIRATION);
env::remove_var(AWS_ACCESS_KEY_ID);
env::remove_var(AWS_SECRET_ACCESS_KEY);
assert_eq!(
result.unwrap().expires_at().unwrap().to_rfc3339(),
"1996-12-20T00:39:57+00:00"
);
}
#[test]
fn alternative_prefix() {
let _guard = lock(&ENV_MUTEX);
let now = Utc::now();
let now_str = now.to_rfc3339();
env::set_var("MYAPP_ACCESS_KEY_ID", "id");
env::set_var("MYAPP_SECRET_ACCESS_KEY", "secret");
env::set_var("MYAPP_SESSION_TOKEN", "token");
env::set_var("MYAPP_CREDENTIAL_EXPIRATION", now_str);
let result = EnvironmentProvider::with_prefix("MYAPP")
.credentials()
.wait();
env::remove_var("MYAPP_ACCESS_KEY_ID");
env::remove_var("MYAPP_SECRET_ACCESS_KEY");
env::remove_var("MYAPP_SESSION_TOKEN");
env::remove_var("MYAPP_CREDENTIAL_EXPIRATION");
assert!(result.is_ok());
let creds = result.ok().unwrap();
assert_eq!(creds.aws_access_key_id(), "id");
assert_eq!(creds.aws_secret_access_key(), "secret");
assert_eq!(creds.token(), &Some("token".to_string()));
assert_eq!(creds.expires_at(), &Some(now));
}
}