use std::ops::ControlFlow;
use std::path::PathBuf;
use std::sync::Arc;
use rustls_acme::AcmeConfig as RustlsAcmeConfig;
use rustls_acme::caches::DirCache;
use crate::RuntimeError;
use crate::config::AcmeBase;
use crate::runtime_state::LifecycleSignals;
pub use rustls_acme::AcmeState;
pub(crate) async fn acme_renewal_loop<S, T, E>(events: S, signals: LifecycleSignals)
where
S: futures_util::Stream<Item = Result<T, E>>,
T: std::fmt::Debug,
E: std::fmt::Display,
{
use futures_util::StreamExt;
let mut events = std::pin::pin!(events);
loop {
let event = match signals.guard(events.next()).await {
ControlFlow::Break(()) => return,
ControlFlow::Continue(event) => event,
};
match report_event(event) {
ControlFlow::Break(()) => return,
ControlFlow::Continue(()) => {}
}
}
}
fn report_event<T, E>(event: Option<Result<T, E>>) -> ControlFlow<()>
where
T: std::fmt::Debug,
E: std::fmt::Display,
{
match event {
None => {
tracing::warn!("acme: renewal stream ended; certificates will not be renewed");
ControlFlow::Break(())
}
Some(Ok(ok)) => {
tracing::info!(event = ?ok, "acme: renewal event");
ControlFlow::Continue(())
}
Some(Err(err)) => {
tracing::warn!(%err, "acme: renewal error");
ControlFlow::Continue(())
}
}
}
#[derive(Debug, Clone)]
pub struct AcmeConfig {
base: AcmeBase,
}
impl AcmeConfig {
pub fn new(tool_name: &str, domains: impl IntoIterator<Item = impl Into<Box<str>>>) -> Self {
Self {
base: AcmeBase::new(tool_name, domains),
}
}
pub fn email(mut self, email: impl Into<Box<str>>) -> Self {
self.base = self.base.email(email);
self
}
pub fn cache_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.base = self.base.cache_dir(path);
self
}
pub fn staging(mut self, staging: bool) -> Self {
self.base = self.base.staging(staging);
self
}
pub fn cache_path(&self) -> &std::path::Path {
self.base.cache_path()
}
pub fn build(
self,
) -> Result<
(
Arc<rustls::ServerConfig>,
rustls_acme::AcmeState<std::io::Error>,
),
RuntimeError,
> {
let AcmeBase {
domains,
email,
cache_dir,
staging,
} = self.base;
let mut acme_cfg = RustlsAcmeConfig::new(domains.iter())
.cache(DirCache::new(cache_dir))
.directory_lets_encrypt(!staging);
if let Some(email) = email {
acme_cfg = acme_cfg.contact_push(format!("mailto:{email}"));
}
let state = acme_cfg.state();
let resolver = state.resolver();
let mut server_config = rustls::ServerConfig::builder_with_provider(Arc::new(
rustls::crypto::aws_lc_rs::default_provider(),
))
.with_safe_default_protocol_versions()
.map_err(|e| {
RuntimeError::Tls(format!("failed to configure TLS protocol versions: {e}").into())
})?
.with_no_client_auth()
.with_cert_resolver(resolver);
server_config.alpn_protocols = vec![
rustls_acme::acme::ACME_TLS_ALPN_NAME.to_vec(),
b"h2".to_vec(),
b"http/1.1".to_vec(),
];
Ok((Arc::new(server_config), state))
}
}