diff options
author | Alex Auvolat <alex@adnab.me> | 2021-12-07 13:50:44 +0100 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2021-12-07 13:50:44 +0100 |
commit | 5535c4951a832d65755afa53822a36e96681320f (patch) | |
tree | f5b46340c16dbf97f69711f519824512b8d0db80 /src/cert_store.rs | |
parent | 61e6df6209b3c55e4c07c6baf2fabfba23a474f1 (diff) | |
download | tricot-5535c4951a832d65755afa53822a36e96681320f.tar.gz tricot-5535c4951a832d65755afa53822a36e96681320f.zip |
Retrieve let's encrypt certificates
Diffstat (limited to 'src/cert_store.rs')
-rw-r--r-- | src/cert_store.rs | 159 |
1 files changed, 159 insertions, 0 deletions
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<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); + + 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) + } +} |