use crate::cert::{self, acme::{AcmeConfig, AcmeManager}};
use crate::cert::acme::ChallengeMap;
use crate::cert::tls::CertSource;
use crate::config::{
self, CertificateDef, ProxyConfig, TlsConfig, TlsListenerConfig,
};
use crate::auth;
use crate::metrics::Metrics;
use anyhow::Context;
use arc_swap::ArcSwap;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
pub(crate) type CertRegistry = HashMap<String, CertSource>;
pub(crate) fn build_authenticator(
backend: &Option<config::AuthBackend>,
) -> anyhow::Result<Arc<dyn auth::Authenticator>> {
match backend {
#[cfg(unix)]
Some(config::AuthBackend::Pam { service }) => {
tracing::info!(service, "auth: PAM");
Ok(Arc::new(auth::PamAuthenticator::new(service.clone())))
}
Some(config::AuthBackend::Ldap(cfg)) => {
tracing::info!(url = %cfg.url, "auth: LDAP");
Ok(Arc::new(auth::LdapAuthenticator::new(cfg.clone())))
}
Some(config::AuthBackend::File(cfg)) => {
tracing::info!(path = %cfg.path, "auth: file");
Ok(Arc::new(auth::FileAuthenticator::new(cfg)?))
}
Some(config::AuthBackend::Subrequest(cfg)) => {
tracing::info!(url = %cfg.url, "auth: subrequest");
Ok(Arc::new(auth::SubrequestAuthenticator::new(cfg)?))
}
None => Ok(Arc::new(auth::AnonymousAuthenticator)),
#[cfg(not(unix))]
Some(config::AuthBackend::Pam { .. }) => {
tracing::warn!(
"PAM auth configured but not supported on this \
platform; falling back to anonymous"
);
Ok(Arc::new(auth::AnonymousAuthenticator))
}
Some(config::AuthBackend::Jwt { .. }) => {
Ok(Arc::new(auth::AnonymousAuthenticator))
}
Some(config::AuthBackend::Oidc(cfg)) => {
tracing::info!(issuer = %cfg.issuer, "auth: OIDC");
Ok(Arc::new(auth::OidcAuthenticator))
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn build_cert_source(
tls_cfg: &TlsListenerConfig,
tls_defaults: &config::TlsOptions,
state_dir: Option<&PathBuf>,
challenges: &ChallengeMap,
cert_state: &cert::state::SharedCertState,
registry: &CertRegistry,
cert_key_mode: u32,
alpn: Option<&[String]>,
metrics: &Arc<Metrics>,
) -> anyhow::Result<(cert::tls::CertSource, Option<tokio::task::JoinHandle<()>>)> {
if let TlsConfig::Ref(name) = &tls_cfg.cert {
let src = registry
.get(name)
.cloned()
.with_context(|| format!("unknown certificate '{name}'"))?;
return Ok((src, None));
}
build_cert_source_from_source(
&tls_cfg.cert,
&tls_cfg.options,
tls_defaults,
state_dir,
challenges,
cert_state,
cert_key_mode,
alpn,
metrics,
)
.await
}
#[allow(clippy::too_many_arguments)]
async fn build_cert_source_from_source(
cert: &TlsConfig,
options: &config::TlsOptions,
tls_defaults: &config::TlsOptions,
state_dir: Option<&PathBuf>,
challenges: &ChallengeMap,
cert_state: &cert::state::SharedCertState,
cert_key_mode: u32,
alpn: Option<&[String]>,
metrics: &Arc<Metrics>,
) -> anyhow::Result<(cert::tls::CertSource, Option<tokio::task::JoinHandle<()>>)> {
match cert {
TlsConfig::Acme {
domains,
name,
email,
staging,
server,
retry_interval_secs,
challenge,
dns_provider,
} => {
let sd = state_dir
.expect("state_dir required for ACME (validated earlier)");
let resolved = options.resolve(tls_defaults);
let alpn_store = if *challenge
== crate::config::ChallengeKind::TlsAlpn01
{
Some(cert::acme_alpn::AlpnChallengeStore::new())
} else {
None
};
let acme_cfg = AcmeConfig {
domains: domains.clone(),
name: name.clone(),
email: email.clone(),
staging: *staging,
server: server.clone(),
state_dir: sd.clone(),
retry_interval: Duration::from_secs(*retry_interval_secs),
cert_key_mode,
challenge: *challenge,
dns_provider: dns_provider.clone(),
};
let mgr = Arc::new(
match alpn_store.clone() {
Some(s) => AcmeManager::new_with_alpn_store(
acme_cfg,
challenges.clone(),
resolved,
s,
),
None => AcmeManager::new(
acme_cfg,
challenges.clone(),
resolved,
),
}
.with_cert_state(cert_state.clone())
.with_metrics(metrics.clone()),
);
let (initial_acc, initial_pair, initial_failed) =
match mgr.ensure_valid_cert().await {
Ok(acc) => {
let mut pair = mgr
.load_cert_pair()
.context("loading initial ACME cert pair")?;
pair.alpn_store = alpn_store.clone();
(acc, pair, false)
}
Err(e) => {
tracing::warn!(
domains = ?domains,
retry_secs = retry_interval_secs,
"ACME initial acquisition failed: {e:#}; \
serving self-signed certificate while \
retrying"
);
let (acc, mut pair) = cert::tls::build_acceptor_with_pair_alpn(
&TlsListenerConfig {
cert: TlsConfig::SelfSigned,
options: options.clone(),
mtls: None,
ocsp: Default::default(),
},
tls_defaults,
alpn,
)
.context("building self-signed fallback")?;
pair.alpn_store = alpn_store.clone();
(acc, pair, true)
}
};
let acc = Arc::new(ArcSwap::new(Arc::new(initial_acc)));
let (cert_tx, cert_rx) =
tokio::sync::watch::channel(Arc::new(initial_pair));
let cert_tx = Arc::new(ArcSwap::new(Arc::new(cert_tx)));
let renewal_handle = crate::task::spawn_supervised(
"acme.renewal",
{
let mgr = mgr.clone();
let acc = acc.clone();
let cert_tx = cert_tx.clone();
async move {
let tx = (**cert_tx.load()).clone();
mgr.renewal_loop(acc, tx, initial_failed).await
}
},
);
Ok((
cert::tls::CertSource { tls: acc, cert_rx, cert_tx },
Some(renewal_handle),
))
}
TlsConfig::Files { .. } | TlsConfig::SelfSigned => {
let inline = TlsListenerConfig {
cert: cert.clone(),
options: options.clone(),
mtls: None,
ocsp: Default::default(),
};
let (initial, pair) = cert::tls::build_acceptor_with_pair_alpn(
&inline,
tls_defaults,
alpn,
)?;
let (cert_tx, cert_rx) =
tokio::sync::watch::channel(Arc::new(pair));
Ok((
cert::tls::CertSource {
tls: Arc::new(ArcSwap::new(Arc::new(initial))),
cert_rx,
cert_tx: Arc::new(ArcSwap::new(Arc::new(cert_tx))),
},
None,
))
}
TlsConfig::Ref(_) => {
unreachable!("Ref resolved by caller before this point")
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn build_cert_registry(
defs: &[CertificateDef],
tls_defaults: &config::TlsOptions,
state_dir: Option<&PathBuf>,
challenges: &ChallengeMap,
cert_state: &cert::state::SharedCertState,
cert_key_mode: u32,
existing: &CertRegistry,
existing_sources: &std::collections::HashMap<String, String>,
metrics: &Arc<Metrics>,
) -> anyhow::Result<(
CertRegistry,
// Per-cert ACME renewal handles, keyed by cert name. The
// SIGHUP reload path aborts these on cert removal so the
// task doesn't keep refreshing a cert nobody references.
HashMap<String, tokio::task::JoinHandle<()>>,
)> {
let mut registry = HashMap::new();
let mut handles: HashMap<String, tokio::task::JoinHandle<()>> =
HashMap::new();
for def in defs {
let source_fp = format!("{:?}", def.source);
if let Some(prev_fp) = existing_sources.get(&def.name)
&& *prev_fp == source_fp
&& let Some(prev_source) = existing.get(&def.name)
{
registry.insert(def.name.clone(), prev_source.clone());
continue;
}
let (cert_source, renewal_handle) = build_cert_source_from_source(
&def.source,
&Default::default(),
tls_defaults,
state_dir,
challenges,
cert_state,
cert_key_mode,
None,
metrics,
)
.await
.with_context(|| {
format!("building certificate '{}'", def.name)
})?;
registry.insert(def.name.clone(), cert_source);
if let Some(h) = renewal_handle {
handles.insert(def.name.clone(), h);
}
}
Ok((registry, handles))
}
pub(crate) fn build_upstream_tls(
proxy: &ProxyConfig,
) -> anyhow::Result<Option<Arc<rustls::ClientConfig>>> {
let utls = match &proxy.upstream_tls {
Some(u) => u,
None => return Ok(None),
};
let cfg = if utls.skip_verify {
rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipServerVerification))
.with_no_client_auth()
} else {
let mut roots = rustls::RootCertStore::empty();
roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_no_client_auth()
};
Ok(Some(Arc::new(cfg)))
}
#[derive(Debug)]
struct SkipServerVerification;
impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
rustls::crypto::aws_lc_rs::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}