#![cfg_attr(feature = "cargo-clippy", warn(missing_docs_in_private_items))]
#![warn(missing_docs)]
extern crate atomicwrites;
#[macro_use]
extern crate error_chain;
extern crate futures;
#[macro_use]
extern crate log;
extern crate openssl;
extern crate pinboard;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tokio_core;
extern crate tokio_timer;
extern crate url;
extern crate vault_api;
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, Instant};
use futures::{future, Future};
use pinboard::Pinboard;
use tokio_core::reactor::Remote;
use url::Url;
use vault_api::Api as VaultApi;
use cache::Cache;
pub use errors::*;
use registry::Registry;
pub use secret::pki::{CaChain, X509, X509Builder};
pub use secret::token::Token;
mod cache;
mod errors;
mod registry;
mod secret;
pub static MAX_LIFETIME: u64 = 60 * 60 * 24 * 365;
static CA_CERTIFICATE_CACHE_TTL: u64 = 60 * 10;
pub struct Client<V: VaultApi> {
x509_registry: Arc<Registry<V, X509>>,
ca_certificate_chain_cache: Arc<Pinboard<(CaChain, Instant)>>,
certificate_lifetime: Duration,
certificate_replacement: Duration,
}
impl<V: 'static + VaultApi + Send + Sync> Client<V> {
fn try_new_with_client<T: Into<Token>>(
initial_token: T,
cache_path: &Path,
remote: Remote,
client: Arc<V>,
certificate_replacement: Duration,
certificate_lifetime: Duration,
) -> Result<Self> {
let token =
initial_token
.into()
.keep_updated(client.clone(), &remote, cache_path.to_path_buf());
let x509_registry =
Arc::new(Registry::new(client, remote, cache_path.to_path_buf(), token).load_cache()?);
let ca_certificate_chain_cache = load_ca_certificate_chain(cache_path)?;
Ok(Client {
x509_registry,
ca_certificate_chain_cache,
certificate_lifetime,
certificate_replacement,
})
}
pub fn get_certificate<S: Into<String>>(
&self,
common_name: S,
) -> Box<Future<Item = X509, Error = Error> + Send> {
let common_name = common_name.into();
future::result(self.x509_registry
.get(&common_name))
.and_then({
let x509_registry = self.x509_registry.clone();
let certificate_lifetime = self.certificate_lifetime;
let certificate_replacement = self.certificate_replacement;
move |secret| {
secret
.map(|s| -> Box<Future<Item = X509, Error = Error> + Send> {
Box::new(future::result(Ok(s)))
})
.unwrap_or_else(|| {
x509_registry.register(
common_name.to_string(),
X509Builder::new(
common_name,
certificate_replacement,
certificate_lifetime,
),
)
})
}
})
.log(|m| debug!("Got X.509 certificate: {:?}", m))
}
pub fn get_root_certificate(&self) -> Box<Future<Item = String, Error = Error> + Send> {
Box::new(self.get_ca_certificate_chain().map(CaChain::root))
}
pub fn get_ca_certificate(&self) -> Box<Future<Item = String, Error = Error> + Send> {
Box::new(self.get_ca_certificate_chain().map(CaChain::leaf))
}
pub fn get_ca_certificate_chain(&self) -> Box<Future<Item = CaChain, Error = Error> + Send> {
let ca_certificate_chain_cache = self.ca_certificate_chain_cache.clone();
let cached_certificate_chain = match ca_certificate_chain_cache.read() {
Some((ref chain, t)) if t.elapsed() < Duration::from_secs(CA_CERTIFICATE_CACHE_TTL) => {
return Box::new(future::ok(chain.clone()));
}
Some((chain, _)) => Some(chain),
None => None,
};
self.x509_registry
.get_ca_certificate_chain()
.then(move |ca| match ca {
Ok(chain) => {
ca_certificate_chain_cache.set((chain.clone(), Instant::now()));
Ok(chain)
}
e @ Err(_) => if let Some(chain) = cached_certificate_chain {
warn!("Using cached CA certificate chain: {:?}", e);
Ok(chain)
} else {
error!("Failed to get CA certificate chain and none are cached");
e
},
})
.log(|m| debug!("Got CA certificate: {:?}", m))
}
}
fn load_ca_certificate_chain(cache_path: &Path) -> Result<Arc<Pinboard<(CaChain, Instant)>>> {
Ok(Arc::new(
Cache::load(cache_path)?
.ca_certificate_chain
.map(|c| Pinboard::new((c, Instant::now())))
.unwrap_or_default(),
))
}
pub type VaultClient = Client<vault_api::client::Client>;
impl VaultClient {
pub fn try_new<T: Into<Token>>(
vault_address: &Url,
vault_certificate: &Path,
initial_token: T,
new_cache_location: &Path,
remote: Remote,
certificate_replacement: Duration,
certificate_lifetime: Duration,
) -> Result<Self> {
let client = Arc::new(vault_api::client::Client::try_new_https(
vault_address.to_owned(),
vault_certificate,
).chain_err(|| "Failed to create Vault HTTPS client")?);
Client::try_new_with_client(
initial_token,
new_cache_location,
remote,
client,
certificate_replacement,
certificate_lifetime,
)
}
}