waf_tls_managed/lib.rs
1// SPDX-FileCopyrightText: 2026 0x00spor3
2// SPDX-License-Identifier: Apache-2.0
3
4//! Managed TLS for [Light WAF](https://github.com/0x00spor3/Light-WAF): an implementation of the
5//! core's `waf_proxy::tls::TlsCertSource` trait, injected with `Proxy::builder(&cfg).cert_source(..)`
6//! without forking the core (which itself only ships a static certificate from disk).
7//!
8//! - **ACME, hitless** (`resolver()`): server certificates via TLS-ALPN-01 ([`acme`]) or DNS-01 with
9//! Cloudflare/Route53 ([`acme_dns01`], [`dns`]), with background renewal that rotates the live
10//! certificate without rebuilding the acceptor. In cluster mode one node issues and renews and every
11//! node serves the same certificate from a shared store (`waf-state-redis`).
12//! - **mTLS** (`client_verifier()`): require and verify client certificates against a configured CA
13//! bundle ([`mtls`]).
14//!
15//! A source may carry either, both, or neither (with a file fallback for the server certificate when
16//! ACME is off but mTLS is on).
17
18pub mod acme;
19pub mod acme_dns01;
20pub mod dns;
21pub mod mtls;
22
23use std::path::Path;
24use std::sync::Arc;
25
26use tokio_rustls::rustls::server::danger::ClientCertVerifier;
27use tokio_rustls::rustls::server::ResolvesServerCert;
28use waf_proxy::tls::{FileCertSource, TlsCertSource, TlsError, TlsMaterial};
29
30pub use acme::{AcmeError, AcmeParams, ChallengeStrategy};
31pub use mtls::{verifier_from_ca_file, MtlsError};
32
33/// A `TlsCertSource` that provides managed server certs (ACME resolver) and/or mTLS client
34/// verification, composed from the pieces in [`acme`]/[`mtls`]. Build it with
35/// [`ManagedTlsCertSource::builder`].
36pub struct ManagedTlsCertSource {
37 resolver: Option<Arc<dyn ResolvesServerCert>>,
38 verifier: Option<Arc<dyn ClientCertVerifier>>,
39 /// Server-cert fallback for the mTLS-only case (ACME off): the core's resolver-less
40 /// path still calls `load()`, so we serve the configured file cert there.
41 file_fallback: Option<FileCertSource>,
42}
43
44impl ManagedTlsCertSource {
45 pub fn builder() -> ManagedTlsBuilder {
46 ManagedTlsBuilder::default()
47 }
48}
49
50impl TlsCertSource for ManagedTlsCertSource {
51 fn load(&self) -> Result<TlsMaterial, TlsError> {
52 // With a resolver set the core never calls this (it uses `resolver()`); it is only
53 // reached in the mTLS-only case, where we serve the configured file cert.
54 match &self.file_fallback {
55 Some(fs) => fs.load(),
56 None => Err(TlsError::NoCertificates(Path::new(
57 "<managed TLS: a server cert requires either ACME ([tls.acme]) or a file cert>",
58 )
59 .to_path_buf())),
60 }
61 }
62
63 fn resolver(&self) -> Option<Arc<dyn ResolvesServerCert>> {
64 self.resolver.clone()
65 }
66
67 fn client_verifier(&self) -> Option<Arc<dyn ClientCertVerifier>> {
68 self.verifier.clone()
69 }
70}
71
72/// Builder for [`ManagedTlsCertSource`].
73#[derive(Default)]
74pub struct ManagedTlsBuilder {
75 resolver: Option<Arc<dyn ResolvesServerCert>>,
76 verifier: Option<Arc<dyn ClientCertVerifier>>,
77 file_fallback: Option<FileCertSource>,
78}
79
80impl ManagedTlsBuilder {
81 /// ACME server-cert resolver (hitless rotation). See [`acme::start_resolver`].
82 pub fn resolver(mut self, resolver: Arc<dyn ResolvesServerCert>) -> Self {
83 self.resolver = Some(resolver);
84 self
85 }
86
87 /// mTLS client-cert verifier. See [`mtls::verifier_from_ca_file`].
88 pub fn client_verifier(mut self, verifier: Arc<dyn ClientCertVerifier>) -> Self {
89 self.verifier = Some(verifier);
90 self
91 }
92
93 /// Server-cert file fallback, used by `load()` when no ACME resolver is set (mTLS-only).
94 pub fn file_fallback(mut self, source: FileCertSource) -> Self {
95 self.file_fallback = Some(source);
96 self
97 }
98
99 pub fn build(self) -> ManagedTlsCertSource {
100 ManagedTlsCertSource {
101 resolver: self.resolver,
102 verifier: self.verifier,
103 file_fallback: self.file_fallback,
104 }
105 }
106}