use std::sync::Arc;
use std::time::Duration;
use futures::Future;
use openssl;
use vault_api;
use errors::*;
use secret::{Secret, SecretBuilder};
use Cache;
use VaultApi;
use MAX_LIFETIME;
pub static PKI_BACKEND_NAME: &str = "pki";
static ROLE_NAME: &str = "metaswitch";
pub struct X509Builder {
common_name: String,
replace_after: Duration,
lifetime: Duration,
}
impl X509Builder {
pub fn new(common_name: String, replace_after: Duration, lifetime: Duration) -> Self {
X509Builder {
common_name,
replace_after,
lifetime,
}
}
}
impl SecretBuilder<X509> for X509Builder {
fn build<T: Into<String>, V: VaultApi>(
self,
client: Arc<V>,
token: T,
) -> Box<Future<Item = X509, Error = Error> + Send> {
X509::try_new(
self.common_name,
self.replace_after,
self.lifetime,
&*client,
token.into(),
)
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct X509 {
pub common_name: String,
pub certificate: String,
pub issuing_ca: String,
pub ca_chain: Option<Vec<String>>,
pub private_key: String,
pub private_key_type: String,
pub serial_number: String,
pub replace_after: Duration,
pub lifetime: Duration,
}
impl X509 {
fn new_from_vault(
resp: vault_api::models::GenerateCertificateResponseData,
common_name: String,
replace_after: Duration,
lifetime: Duration,
) -> Self {
X509 {
common_name,
certificate: resp.certificate,
issuing_ca: resp.issuing_ca,
ca_chain: resp.ca_chain,
private_key: resp.private_key,
private_key_type: resp.private_key_type,
serial_number: resp.serial_number,
replace_after,
lifetime,
}
}
fn try_new<V: VaultApi>(
common_name: String,
replacement_period: Duration,
lifetime: Duration,
client: &V,
token: String,
) -> Box<Future<Item = Self, Error = Error> + Send> {
let cert_params = vault_api::models::GenerateCertificateParameters {
ttl: Some(lifetime.as_secs().to_string() + "s"),
..vault_api::models::GenerateCertificateParameters::new(common_name.clone())
};
client
.generate_cert(
token,
PKI_BACKEND_NAME.to_string(),
ROLE_NAME.to_string(),
cert_params,
&vault_api::Context::new(),
)
.then(move |result| match result {
Ok(vault_api::GenerateCertResponse::Success(
vault_api::models::GenerateCertificateResponse {
data: Some(certificate_response),
..
},
)) => {
ensure!(
lifetime.as_secs() < MAX_LIFETIME,
"Excessive ttl: {:?}",
lifetime
);
ensure!(
lifetime > replacement_period,
"Lifetime of {:?} will expire prior to replacement at {:?}",
lifetime,
replacement_period
);
Ok(X509::new_from_vault(
certificate_response,
common_name,
replacement_period,
lifetime,
))
}
Ok(resp) => bail!(
"Unexpected certificate generation response from Vault: {:?}",
resp
),
Err(err) => {
Err(err).chain_err(|| "Failed to replace certificate, request to Vault failed")
}
})
.log(|m| debug!("Created new certificate: {:?}", m))
}
}
impl Secret for X509 {
fn get_new<T: Into<String>, V: VaultApi>(
self,
client: &V,
token: T,
) -> Box<Future<Item = Self, Error = Error> + Send> {
X509::try_new(
self.common_name.clone(),
self.replace_after,
self.lifetime,
client,
token.into(),
).log(|m| debug!("Got new certificate: {:?}", m))
}
fn get_time_to_replace(&self) -> &Duration {
&self.replace_after
}
fn update_cache(&self, cache: &mut Cache) {
cache
.certificates
.insert(self.common_name.clone(), self.clone());
}
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct CaChain {
root: String,
rest: Vec<String>,
}
impl CaChain {
pub fn try_from_pem(pem: &str) -> Result<Self> {
let mut stack = openssl::x509::X509::stack_from_pem(pem.as_bytes())
.chain_err(|| "CA chain was not valid PEM")?;
let root_x509 = stack.pop().ok_or_else(|| "CA chain was empty")?;
fn x509_to_pem(x509: &openssl::x509::X509) -> Result<String> {
let pem_bytes = x509.to_pem()
.chain_err(|| "Failed to convert CA chain into bytes")?;
String::from_utf8(pem_bytes).chain_err(|| "CA chain was not valid UTF8")
}
let root_pem = x509_to_pem(&root_x509)?;
let rest_pem = stack
.iter()
.map(x509_to_pem)
.collect::<Result<Vec<String>>>()?;
Ok(CaChain {
root: root_pem,
rest: rest_pem,
})
}
pub fn leaf(self) -> String {
self.rest.into_iter().next().unwrap_or(self.root)
}
pub fn root(self) -> String {
self.root
}
}