1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Automatic ACME (Let's Encrypt) certificate provisioning + renewal
//! (issue #1608).
//!
//! This module lets an Autumn app obtain and auto-renew its own TLS
//! certificate over the ACME **HTTP-01** challenge — or, since #1620, over
//! **DNS-01**, which is what a *wildcard* certificate requires — without an
//! operator-provided certificate on disk or a reverse proxy. It builds directly on the #1603 TLS
//! listener: the issued certificate hot-swaps into the SAME
//! [`ReloadableCertResolver`](crate::tls::ReloadableCertResolver) the listener
//! already serves, so no second resolver or listener is introduced.
//!
//! The moving parts, each in its own submodule:
//!
//! - [`challenge`] — the `:80` listener that answers the ACME HTTP-01 challenge
//! (`/.well-known/acme-challenge/{token}`) and redirects every other request
//! to HTTPS.
//! - [`store`] — the [`AcmeStore`](store::AcmeStore) trait and its filesystem
//! implementation ([`FsAcmeStore`](store::FsAcmeStore)) that persists the ACME
//! account key and issued certificates (all `0600`).
//! - [`dns`] — the DNS-01 challenge path (issue #1620): the
//! [`DnsProvider`](dns::DnsProvider) seam and its Cloudflare / Route 53 /
//! exec-hook implementations, the credential surface, and the bounded wait for
//! `_acme-challenge` TXT propagation. Present exactly when
//! `[server.tls.acme.dns]` is configured.
//! - [`tenant_domains`] — per-tenant custom domains (issue #1635): the
//! one-certificate-per-hostname ACME issuer and the loop that verifies,
//! issues, renews and offboards every registered tenant domain. Present
//! exactly when `[server.tls.acme.custom_domains]` is enabled.
//! - [`renewal`] — issuance, the renew-before-expiry decision
//! ([`needs_renewal`](renewal::needs_renewal)), the background renewal loop,
//! the self-signed placeholder used to bind `:443` before the first issuance,
//! and the [`AcmeStatus`](renewal::AcmeStatus) /
//! [`AcmeHealthIndicator`](renewal::AcmeHealthIndicator) observability seam.
//!
//! # Deployment scope
//!
//! **HTTP-01 is single-host.** The renewal loop leader-elects correctly across a
//! fleet (so only one replica *orders* per certificate), but the HTTP-01 token
//! map ([`challenge::Http01Tokens`]) is per-process in-memory and the only
//! [`store::AcmeStore`] is the local-disk [`store::FsAcmeStore`]. Behind a load
//! balancer, the CA's `:80` validation request can therefore land on a replica
//! that never published the token (→ 404). Single-replica deployments are fully
//! correct; when ACME is configured alongside a distributed scheduler backend
//! (i.e. a multi-replica deployment) the app emits a startup `warn`.
//!
//! **DNS-01 does not have that limitation**: the challenge response lives in the
//! operator's zone rather than in one replica's memory, so the CA reaches it
//! wherever it validates from. The certificate store is still local disk, so a
//! follower adopts a renewed certificate only from a shared store — but nothing
//! about the *challenge* is replica-bound.
//!
//! The crypto backend is `ring` throughout (`instant-acme` and `rcgen` are both
//! pinned `default-features = false` + `ring`), the SAME backend the rest of
//! autumn-web uses — the workspace forbids a second crypto backend.
use crateAcmeDirectory;
/// Resolve an [`AcmeDirectory`] to its ACME directory URL.
///
/// Staging and production map to the Let's Encrypt endpoints; `Custom` returns
/// its configured URL verbatim (a private CA or a pebble test server).
/// A short, stable label for an [`AcmeDirectory`].
///
/// Used to key the ACME account file per directory so switching directories
/// (staging vs production) cleanly re-registers a fresh account rather than
/// reusing an incompatible one.
/// A short hex digest (first 8 bytes of SHA-256) of `input`, for
/// filesystem-safe identifiers.