use std::cmp;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use futures::Future;
use pinboard::NonEmptyPinboard;
use tokio_core::reactor::Remote;
use tokio_timer;
use vault_api;
use errors::*;
use secret::{keep_secret_up_to_date, Secret};
use VaultApi;
use MAX_LIFETIME;
#[derive(Debug, Clone)]
pub struct Token {
token: String,
next_renewal: Duration,
renew_period: Duration,
lifetime: Duration,
}
impl Token {
pub fn try_new<S: Into<String>>(
token: S,
lifetime: Duration,
renew_period: Duration,
) -> Result<Self> {
ensure!(
MAX_LIFETIME > lifetime.as_secs(),
"Lifetime ({:?}) exceeds maximum ({}s)",
lifetime,
MAX_LIFETIME
);
ensure!(
lifetime > renew_period,
"Lifetime of {:?} will expire prior to renewal at {:?}",
lifetime,
renew_period
);
Ok(Token {
token: token.into(),
next_renewal: Duration::new(0, 0),
renew_period,
lifetime,
})
}
pub fn get_token_str(&self) -> &String {
&self.token
}
pub fn keep_updated<V: 'static + VaultApi + Send + Sync>(
self,
client: Arc<V>,
remote: &Remote,
cache_path: PathBuf,
) -> Arc<NonEmptyPinboard<Self>> {
let token = Arc::new(NonEmptyPinboard::new(self));
let timer = tokio_timer::wheel()
.max_timeout(Duration::from_secs(MAX_LIFETIME))
.build();
remote.spawn({
let token = token.clone();
let remote = remote.clone();
move |_| {
keep_secret_up_to_date(&remote, token.clone(), client, timer, &cache_path, token)
}
});
token
}
}
impl Secret for Token {
fn get_new<T: Into<String>, V: VaultApi>(
self,
client: &V,
old_token: T,
) -> Box<Future<Item = Self, Error = Error> + Send> {
client
.renew_own_token(
old_token.into(),
vault_api::models::RenewSelfParameters { increment: None },
&vault_api::Context::new(),
)
.then(move |result| match result {
Ok(vault_api::RenewOwnTokenResponse::Success(
vault_api::models::AuthResponse {
auth:
Some(vault_api::models::AuthResponseAuth {
lease_duration: ttl,
..
}),
..
},
)) => {
let replace_after =
cmp::min(self.renew_period, Duration::from_secs((ttl as u64) / 3));
Ok(Token {
next_renewal: replace_after,
..self
})
}
Ok(resp) => bail!(
"Unexpected token replacement response from Vault: {:?}",
resp
),
Err(err) => {
Err(err).chain_err(|| "Failed to replace token, request to Vault failed")
}
})
.log(|m: &Token| debug!("Got new token: {:?}", m))
}
fn get_time_to_replace(&self) -> &Duration {
&self.next_renewal
}
}