use std::error::Error;
use std::time::Duration;
use futures::{Async, Future, Poll};
use futures::future::{FutureResult, err};
use hyper::{Body, Request};
use {AwsCredentials, CredentialsError, ProvideAwsCredentials,
parse_credentials_from_aws_service, non_empty_env_var};
use request::{HttpClient, HttpClientFuture};
const AWS_CREDENTIALS_PROVIDER_IP: &str = "169.254.170.2";
const AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: &str = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";
const AWS_CONTAINER_CREDENTIALS_FULL_URI: &str = "AWS_CONTAINER_CREDENTIALS_FULL_URI";
const AWS_CONTAINER_AUTHORIZATION_TOKEN: &str = "AWS_CONTAINER_AUTHORIZATION_TOKEN";
#[derive(Clone, Debug)]
pub struct ContainerProvider {
client: HttpClient,
timeout: Duration
}
impl ContainerProvider {
pub fn new() -> Self {
ContainerProvider {
client: HttpClient::new(),
timeout: Duration::from_secs(30)
}
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
}
pub struct ContainerProviderFuture {
inner: ContainerProviderFutureInner
}
enum ContainerProviderFutureInner {
Result(FutureResult<String, CredentialsError>),
Future(HttpClientFuture)
}
impl Future for ContainerProviderFuture {
type Item = AwsCredentials;
type Error = CredentialsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let resp = match self.inner {
ContainerProviderFutureInner::Result(ref mut result) =>
try_ready!(result.poll()),
ContainerProviderFutureInner::Future(ref mut future) =>
try_ready!(future.poll())
};
let creds = parse_credentials_from_aws_service(&resp)?;
Ok(Async::Ready(creds))
}
}
impl ProvideAwsCredentials for ContainerProvider {
type Future = ContainerProviderFuture;
fn credentials(&self) -> Self::Future {
let inner = match credentials_from_container(&self.client, self.timeout) {
Ok(future) => ContainerProviderFutureInner::Future(future),
Err(e) => ContainerProviderFutureInner::Result(err(e))
};
ContainerProviderFuture { inner: inner }
}
}
fn credentials_from_container(client: &HttpClient, timeout: Duration) -> Result<HttpClientFuture, CredentialsError> {
Ok(client.request(request_from_env_vars()?, timeout))
}
fn request_from_env_vars() -> Result<Request<Body>, CredentialsError> {
let relative_uri = non_empty_env_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI).map(
|path| format!("http://{}{}", AWS_CREDENTIALS_PROVIDER_IP, path)
);
match relative_uri {
Some(ref uri) => new_request(uri, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI),
None => match non_empty_env_var(AWS_CONTAINER_CREDENTIALS_FULL_URI) {
Some(ref uri) => {
let mut request = new_request(uri, AWS_CONTAINER_CREDENTIALS_FULL_URI)?;
if let Some(token) = non_empty_env_var(AWS_CONTAINER_AUTHORIZATION_TOKEN) {
match token.parse() {
Ok(parsed_token) => {
request.headers_mut().insert("authorization", parsed_token);
}
Err(err) => {
return Err(CredentialsError::new(format!("failed to parse token: {}", err)));
}
}
}
Ok(request)
},
None => Err(CredentialsError::new(
format!("Neither environment variable '{}' nor '{}' is set", AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI),
))
}
}
}
fn new_request(uri: &str, env_var_name: &str) -> Result<Request<Body>, CredentialsError> {
Request::get(uri).body(Body::empty())
.map_err(|error|
CredentialsError::new(
format!("Error while parsing URI '{}' derived from environment variable '{}': {}",
uri, env_var_name, error.description())
)
)
}
#[cfg(test)]
mod tests {
use std::env;
use std::sync::{Mutex, MutexGuard};
use super::*;
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 request_from_relative_uri() {
let path = "/xxx";
let _guard = lock(&ENV_MUTEX);
env::set_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, path);
env::set_var(AWS_CONTAINER_CREDENTIALS_FULL_URI, "dummy");
env::set_var(AWS_CONTAINER_AUTHORIZATION_TOKEN, "dummy");
let result = request_from_env_vars();
env::remove_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
env::remove_var(AWS_CONTAINER_CREDENTIALS_FULL_URI);
env::remove_var(AWS_CONTAINER_AUTHORIZATION_TOKEN);
assert!(result.is_ok());
let request = result.ok().unwrap();
assert_eq!(request.uri().path(), path);
assert_eq!(request.headers().contains_key("authorization"), false);
}
#[test]
fn error_from_missing_env_vars() {
let _guard = lock(&ENV_MUTEX);
env::remove_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
env::remove_var(AWS_CONTAINER_CREDENTIALS_FULL_URI);
let result = request_from_env_vars();
assert!(result.is_err());
}
#[test]
fn error_from_empty_env_vars() {
let _guard = lock(&ENV_MUTEX);
env::set_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, "");
env::set_var(AWS_CONTAINER_CREDENTIALS_FULL_URI, "");
env::set_var(AWS_CONTAINER_AUTHORIZATION_TOKEN, "");
let result = request_from_env_vars();
env::remove_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
env::remove_var(AWS_CONTAINER_CREDENTIALS_FULL_URI);
env::remove_var(AWS_CONTAINER_AUTHORIZATION_TOKEN);
assert!(result.is_err());
}
#[test]
fn request_from_full_uri_with_token() {
let url = "http://localhost/xxx";
let _guard = lock(&ENV_MUTEX);
env::remove_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
env::set_var(AWS_CONTAINER_CREDENTIALS_FULL_URI, url);
env::set_var(AWS_CONTAINER_AUTHORIZATION_TOKEN, "dummy");
let result = request_from_env_vars();
env::remove_var(AWS_CONTAINER_CREDENTIALS_FULL_URI);
env::remove_var(AWS_CONTAINER_AUTHORIZATION_TOKEN);
assert!(result.is_ok());
let request = result.ok().unwrap();
assert_eq!(request.uri().to_string(), url);
assert_eq!(request.headers().contains_key("authorization"), true);
}
#[test]
fn request_from_full_uri_without_token() {
let url = "http://localhost/xxx";
let _guard = lock(&ENV_MUTEX);
env::remove_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
env::set_var(AWS_CONTAINER_CREDENTIALS_FULL_URI, url);
env::remove_var(AWS_CONTAINER_AUTHORIZATION_TOKEN);
let result = request_from_env_vars();
env::remove_var(AWS_CONTAINER_CREDENTIALS_FULL_URI);
assert!(result.is_ok());
let request = result.ok().unwrap();
assert_eq!(request.uri().to_string(), url);
assert_eq!(request.headers().contains_key("authorization"), false);
}
#[test]
fn request_from_full_uri_with_empty_token() {
let url = "http://localhost/xxx";
let _guard = lock(&ENV_MUTEX);
env::remove_var(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
env::set_var(AWS_CONTAINER_CREDENTIALS_FULL_URI, url);
env::set_var(AWS_CONTAINER_AUTHORIZATION_TOKEN, "");
let result = request_from_env_vars();
env::remove_var(AWS_CONTAINER_CREDENTIALS_FULL_URI);
env::remove_var(AWS_CONTAINER_AUTHORIZATION_TOKEN);
assert!(result.is_ok());
let request = result.ok().unwrap();
assert_eq!(request.uri().to_string(), url);
assert_eq!(request.headers().contains_key("authorization"), false);
}
}