use std::sync::Arc;
use anyhow::{anyhow, Result};
use camino::Utf8PathBuf;
use tracing::info;
use crate::{
RunContext,
certificates::HostCertificate,
};
pub struct CertStore {
_context: Arc<RunContext>,
by_host: papaya::HashMap<String, Arc<HostCertificate>>,
by_file: papaya::HashMap<Utf8PathBuf, Arc<HostCertificate>>,
}
impl CertStore {
pub fn new(certs: Vec<Arc<HostCertificate>>, _context: Arc<RunContext>) -> Result<Self> {
info!("Loading host certificates");
let by_host: papaya::HashMap<String, Arc<HostCertificate>> = certs.iter()
.flat_map(|cert|
cert.hostnames.iter()
.map(|hostname| (
hostname.clone(),
cert.clone()))
)
.collect();
let by_file = certs.iter()
.flat_map(|cert| {
vec!((cert.keyfile.clone(), cert.clone()),
(cert.certfile.clone(), cert.clone()))
})
.collect();
info!("Loaded {} certificates", certs.len());
let certstore = Self {
_context,
by_host,
by_file,
};
Ok(certstore)
}
pub fn by_host(&self, host: &String) -> Option<Arc<HostCertificate>> {
let pmap = self.by_host.pin();
pmap.get(host)
.map(Arc::clone)
}
pub fn by_wildcard(&self, host: &str) -> Option<Arc<HostCertificate>> {
host.split_once('.')
.and_then(|(_host, domain)| {
let wildcard = format!("*.{domain}");
self.by_host(&wildcard)
})
}
pub fn by_file(&self, file: &Utf8PathBuf) -> Option<Arc<HostCertificate>> {
let pmap = self.by_file.pin();
pmap.get(file)
.cloned()
}
pub fn upsert(&self, newcert: Arc<HostCertificate>) -> Result<()> {
for hostname in newcert.hostnames.iter() {
let host = hostname.clone();
info!("Updating/inserting certificate for {host}");
self.by_host.pin().update_or_insert(host, |_old| newcert.clone(), newcert.clone());
}
let keyfile = newcert.keyfile.clone();
let certfile = newcert.certfile.clone();
let by_file = self.by_file.pin();
by_file.update_or_insert(keyfile, |_old| newcert.clone(), newcert.clone());
by_file.update_or_insert(certfile, |_old| newcert.clone(), newcert.clone());
Ok(())
}
pub fn update(&self, newcert: Arc<HostCertificate>) -> Result<()> {
for hostname in newcert.hostnames.iter() {
info!("Updating certificate for {hostname}");
self.by_host.pin().update(hostname.clone(), |_old| newcert.clone())
.ok_or(anyhow!("Matching host for {} not found in cert store", hostname))?;
}
let keyfile = newcert.keyfile.clone();
let certfile = newcert.certfile.clone();
let by_file = self.by_file.pin();
by_file.update(keyfile, |_old| newcert.clone())
.ok_or(anyhow!("File {} not found in cert store", newcert.keyfile))?;
by_file.update(certfile, |_old| newcert.clone())
.ok_or(anyhow!("File {} not found in cert store", newcert.certfile))?;
Ok(())
}
pub fn watchlist(&self) -> Vec<Utf8PathBuf> {
let by_host = self.by_host.pin();
by_host.values()
.filter_map(|h| if h.watch {
Some(vec![h.keyfile.clone(), h.certfile.clone()])
} else {
None
})
.flatten()
.collect()
}
}