use std::{path::PathBuf, sync::Arc};
use n0_error::{AnyError, StdResultExt};
use n0_future::{
task::{self, AbortOnDropHandle},
time::{self, Duration},
};
use reloadable_state::Reloadable;
use rustls::{
crypto::CryptoProvider,
server::{ClientHello, ResolvesServerCert},
sign::CertifiedKey,
};
use rustls_cert_file_reader::{FileReader, Format};
use rustls_cert_reloadable_resolver::{CertifiedKeyLoader, key_provider::Dyn};
use tokio_util::sync::CancellationToken;
pub const DEFAULT_CERT_RELOAD_INTERVAL: Duration = Duration::from_secs(60 * 60 * 24);
pub async fn reloading_resolver(
crypto_provider: &CryptoProvider,
cert_path: PathBuf,
key_path: PathBuf,
interval: std::time::Duration,
) -> Result<Arc<dyn ResolvesServerCert>, AnyError> {
let key_reader = FileReader::new(key_path, Format::PEM);
let certs_reader = FileReader::new(cert_path, Format::PEM);
let loader = CertifiedKeyLoader {
key_provider: Dyn(crypto_provider.key_provider),
key_reader,
certs_reader,
};
let resolver = ReloadingResolver::init(loader, interval)
.await
.std_context("cert loading")?;
Ok(Arc::new(resolver))
}
#[derive(Debug)]
struct ReloadingResolver<Loader: Send + 'static> {
reloadable: Arc<Reloadable<CertifiedKey, Loader>>,
_handle: AbortOnDropHandle<()>,
}
impl<Loader> ReloadingResolver<Loader>
where
Loader: Send + reloadable_state::core::Loader<Value = CertifiedKey> + 'static,
{
async fn init(loader: Loader, interval: Duration) -> Result<Self, Loader::Error> {
let (reloadable, _) = Reloadable::init_load(loader).await?;
let reloadable = Arc::new(reloadable);
let cancel_token = CancellationToken::new();
let _reloadable = reloadable.clone();
let _cancel_token = cancel_token.clone();
let _handle = task::spawn(async move {
let mut interval = time::interval(interval);
loop {
tokio::select! {
_ = interval.tick() => {
let _ = _reloadable.reload().await;
tracing::info!("Reloaded the certificate");
},
_ = _cancel_token.cancelled() => {
tracing::trace!("shutting down");
break;
}
}
}
});
let _handle = AbortOnDropHandle::new(_handle);
Ok(Self {
reloadable,
_handle,
})
}
}
impl<Loader> ResolvesServerCert for ReloadingResolver<Loader>
where
Loader: reloadable_state::core::Loader<Value = CertifiedKey>,
Loader: Send,
Loader: std::fmt::Debug,
{
fn resolve(&self, _client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
Some(self.reloadable.get())
}
}
impl<Loader: Send> std::ops::Deref for ReloadingResolver<Loader> {
type Target = Reloadable<CertifiedKey, Loader>;
fn deref(&self) -> &Self::Target {
&self.reloadable
}
}