From 5535c4951a832d65755afa53822a36e96681320f Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Tue, 7 Dec 2021 13:50:44 +0100 Subject: Retrieve let's encrypt certificates --- src/cert_store.rs | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/cert_store.rs (limited to 'src/cert_store.rs') diff --git a/src/cert_store.rs b/src/cert_store.rs new file mode 100644 index 0000000..6529395 --- /dev/null +++ b/src/cert_store.rs @@ -0,0 +1,159 @@ +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; + +use acme_micro::create_p384_key; +use acme_micro::{Directory, DirectoryUrl}; + +use crate::cert::{Cert, CertSer}; +use crate::consul::Consul; +use crate::proxy_config::ProxyConfig; + +pub struct CertStore { + consul: Consul, + certs: RwLock>>, +} + +impl CertStore { + pub fn new(consul: Consul) -> Arc { + Arc::new(Self { + consul, + certs: RwLock::new(HashMap::new()), + }) + } + + pub async fn watch_proxy_config( + self: Arc, + mut rx_proxy_config: watch::Receiver>, + ) { + while rx_proxy_config.changed().await.is_ok() { + let mut domains: HashSet = HashSet::new(); + + let proxy_config: Arc = 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, domain: &str) -> Result> { + // 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::(&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, domain: &str) -> Result> { + info!("Renewing certificate for {}", domain); + + 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"); + let acc = dir.register_account(contact.clone())?; + 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"); + chall.validate(Duration::from_millis(5000))?; + + info!("Deleting challenge"); + self.consul.kv_delete(&chall_key).await?; + + ord_new.refresh()?; + }; + + let pkey_pri = create_p384_key()?; + let ord_cert = ord_csr.finalize_pkey(pkey_pri, Duration::from_millis(5000))?; + let cert = ord_cert.download_cert()?; + + 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?; + + let cert = Arc::new(Cert::new(certser)?); + self.certs + .write() + .unwrap() + .insert(domain.to_string(), cert.clone()); + + info!("Cert successfully renewed: {}", domain); + Ok(cert) + } +} -- cgit v1.2.3