2021-12-07 12:50:44 +00:00
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use chrono::Utc;
|
|
|
|
use log::*;
|
|
|
|
use tokio::sync::watch;
|
2021-12-07 14:20:45 +00:00
|
|
|
use tokio::task::block_in_place;
|
2021-12-07 12:50:44 +00:00
|
|
|
|
|
|
|
use acme_micro::create_p384_key;
|
|
|
|
use acme_micro::{Directory, DirectoryUrl};
|
2021-12-07 14:20:45 +00:00
|
|
|
use rustls::sign::CertifiedKey;
|
2021-12-07 12:50:44 +00:00
|
|
|
|
|
|
|
use crate::cert::{Cert, CertSer};
|
2021-12-07 16:05:25 +00:00
|
|
|
use crate::consul::*;
|
2021-12-07 12:50:44 +00:00
|
|
|
use crate::proxy_config::ProxyConfig;
|
|
|
|
|
|
|
|
pub struct CertStore {
|
|
|
|
consul: Consul,
|
|
|
|
certs: RwLock<HashMap<String, Arc<Cert>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CertStore {
|
|
|
|
pub fn new(consul: Consul) -> Arc<Self> {
|
|
|
|
Arc::new(Self {
|
|
|
|
consul,
|
|
|
|
certs: RwLock::new(HashMap::new()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn watch_proxy_config(
|
|
|
|
self: Arc<Self>,
|
|
|
|
mut rx_proxy_config: watch::Receiver<Arc<ProxyConfig>>,
|
|
|
|
) {
|
|
|
|
while rx_proxy_config.changed().await.is_ok() {
|
|
|
|
let mut domains: HashSet<String> = HashSet::new();
|
|
|
|
|
|
|
|
let proxy_config: Arc<ProxyConfig> = rx_proxy_config.borrow().clone();
|
|
|
|
for ent in proxy_config.entries.iter() {
|
|
|
|
domains.insert(ent.host.clone());
|
|
|
|
}
|
|
|
|
info!("Ensuring we have certs for domains: {:#?}", domains);
|
|
|
|
|
|
|
|
for dom in domains.iter() {
|
|
|
|
if let Err(e) = self.get_cert(dom).await {
|
|
|
|
warn!("Error get_cert {}: {}", dom, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_cert(self: &Arc<Self>, domain: &str) -> Result<Arc<Cert>> {
|
|
|
|
// First, try locally.
|
|
|
|
{
|
|
|
|
let certs = self.certs.read().unwrap();
|
|
|
|
if let Some(cert) = certs.get(domain) {
|
|
|
|
if !cert.is_old() {
|
|
|
|
return Ok(cert.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Second, try from Consul.
|
|
|
|
if let Some(consul_cert) = self
|
|
|
|
.consul
|
|
|
|
.kv_get_json::<CertSer>(&format!("certs/{}", domain))
|
|
|
|
.await?
|
|
|
|
{
|
|
|
|
if let Ok(cert) = Cert::new(consul_cert) {
|
|
|
|
let cert = Arc::new(cert);
|
|
|
|
if !cert.is_old() {
|
|
|
|
self.certs
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.insert(domain.to_string(), cert.clone());
|
|
|
|
return Ok(cert);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Third, ask from Let's Encrypt
|
|
|
|
self.renew_cert(domain).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn renew_cert(self: &Arc<Self>, domain: &str) -> Result<Arc<Cert>> {
|
|
|
|
info!("Renewing certificate for {}", domain);
|
|
|
|
|
2021-12-07 16:05:25 +00:00
|
|
|
// ---- Acquire lock ----
|
|
|
|
|
|
|
|
let lock_path = format!("renew_lock/{}", domain);
|
|
|
|
let lock_name = format!("tricot/renew:{}@{}", domain, self.consul.local_node.clone());
|
|
|
|
let session = self
|
|
|
|
.consul
|
|
|
|
.create_session(&ConsulSessionRequest {
|
|
|
|
name: lock_name.clone(),
|
|
|
|
node: Some(self.consul.local_node.clone()),
|
|
|
|
lock_delay: Some("30s".into()),
|
|
|
|
ttl: Some("1m".into()),
|
|
|
|
behavior: Some("delete".into()),
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
if !self
|
|
|
|
.consul
|
|
|
|
.acquire(&lock_path, lock_name.clone().into(), &session)
|
|
|
|
.await?
|
|
|
|
{
|
|
|
|
bail!("Lock is already taken, not renewing for now.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---- Do let's encrypt stuff ----
|
|
|
|
|
2021-12-07 12:50:44 +00:00
|
|
|
let dir = Directory::from_url(DirectoryUrl::LetsEncrypt)?;
|
|
|
|
let contact = vec!["mailto:alex@adnab.me".to_string()];
|
|
|
|
|
|
|
|
let acc =
|
|
|
|
if let Some(acc_privkey) = self.consul.kv_get("letsencrypt_account_key.pem").await? {
|
|
|
|
info!("Using existing Let's encrypt account");
|
|
|
|
dir.load_account(std::str::from_utf8(&acc_privkey)?, contact)?
|
|
|
|
} else {
|
|
|
|
info!("Creating new Let's encrypt account");
|
2021-12-07 14:20:45 +00:00
|
|
|
let acc = block_in_place(|| dir.register_account(contact.clone()))?;
|
2021-12-07 12:50:44 +00:00
|
|
|
self.consul
|
|
|
|
.kv_put(
|
|
|
|
"letsencrypt_account_key.pem",
|
|
|
|
acc.acme_private_key_pem()?.into_bytes().into(),
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
acc
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut ord_new = acc.new_order(domain, &[])?;
|
|
|
|
let ord_csr = loop {
|
|
|
|
if let Some(ord_csr) = ord_new.confirm_validations() {
|
|
|
|
break ord_csr;
|
|
|
|
}
|
|
|
|
|
|
|
|
let auths = ord_new.authorizations()?;
|
|
|
|
|
|
|
|
info!("Creating challenge and storing in Consul");
|
|
|
|
let chall = auths[0].http_challenge().unwrap();
|
|
|
|
let chall_key = format!("challenge/{}", chall.http_token());
|
|
|
|
self.consul
|
|
|
|
.kv_put(&chall_key, chall.http_proof()?.into())
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
info!("Validating challenge");
|
2021-12-07 14:20:45 +00:00
|
|
|
block_in_place(|| chall.validate(Duration::from_millis(5000)))?;
|
2021-12-07 12:50:44 +00:00
|
|
|
|
|
|
|
info!("Deleting challenge");
|
|
|
|
self.consul.kv_delete(&chall_key).await?;
|
|
|
|
|
2021-12-07 14:20:45 +00:00
|
|
|
block_in_place(|| ord_new.refresh())?;
|
2021-12-07 12:50:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let pkey_pri = create_p384_key()?;
|
2021-12-07 14:20:45 +00:00
|
|
|
let ord_cert =
|
|
|
|
block_in_place(|| ord_csr.finalize_pkey(pkey_pri, Duration::from_millis(5000)))?;
|
|
|
|
let cert = block_in_place(|| ord_cert.download_cert())?;
|
2021-12-07 12:50:44 +00:00
|
|
|
|
|
|
|
info!("Keys and certificate obtained");
|
|
|
|
let key_pem = cert.private_key().to_string();
|
|
|
|
let cert_pem = cert.certificate().to_string();
|
|
|
|
|
|
|
|
let certser = CertSer {
|
|
|
|
hostname: domain.to_string(),
|
|
|
|
date: Utc::today().naive_utc(),
|
|
|
|
valid_days: cert.valid_days_left()?,
|
|
|
|
key_pem,
|
|
|
|
cert_pem,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.consul
|
|
|
|
.kv_put_json(&format!("certs/{}", domain), &certser)
|
|
|
|
.await?;
|
2021-12-07 16:05:25 +00:00
|
|
|
self.consul.release(&lock_path, "".into(), &session).await?;
|
2021-12-07 12:50:44 +00:00
|
|
|
|
|
|
|
let cert = Arc::new(Cert::new(certser)?);
|
|
|
|
self.certs
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.insert(domain.to_string(), cert.clone());
|
|
|
|
|
|
|
|
info!("Cert successfully renewed: {}", domain);
|
|
|
|
Ok(cert)
|
|
|
|
}
|
|
|
|
}
|
2021-12-07 14:20:45 +00:00
|
|
|
|
|
|
|
pub struct StoreResolver(pub Arc<CertStore>);
|
|
|
|
|
|
|
|
impl rustls::server::ResolvesServerCert for StoreResolver {
|
|
|
|
fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
|
|
|
|
let domain = client_hello.server_name()?;
|
|
|
|
let cert = futures::executor::block_on(self.0.get_cert(domain)).ok()?;
|
|
|
|
Some(cert.certkey.clone())
|
|
|
|
}
|
|
|
|
}
|