aboutsummaryrefslogblamecommitdiff
path: root/src/cert_store.rs
blob: 65293952b3826ab570fa08f1c196459f030d02af (plain) (tree)






























































































































































                                                                                                             
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)
	}
}