aboutsummaryrefslogtreecommitdiff
path: root/src/cert_store.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cert_store.rs')
-rw-r--r--src/cert_store.rs159
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)
+ }
+}