Skip to main content

max / makenotwork

2.9 KB · 78 lines History Blame Raw
1 //! Whether this host has usable outbound TLS trust anchors.
2 //!
3 //! mt ships none of its own. `reqwest` verifies through
4 //! `rustls-platform-verifier`, which on Linux loads the host store with
5 //! `rustls_native_certs::load_native_certs`, and the AWS client used by
6 //! `s3-storage` calls the same crate directly. Neither offers bundled roots, so
7 //! a thin or unreadable store takes out all three outbound paths at once: the
8 //! OAuth token exchange that logs users in, link previews, and S3 media.
9 //!
10 //! Left alone, that failure is invisible until someone tries to log in, on a box
11 //! that boots fine and passes a database-only health check. Probing the same call
12 //! the verifier will make turns it into a boot-time log line and an
13 //! `/api/health` field instead. The precondition itself is documented in
14 //! `deploy/README.md`.
15
16 use std::sync::OnceLock;
17
18 static ANCHORS_OK: OnceLock<bool> = OnceLock::new();
19
20 /// Whether the host trust store yielded any usable anchors.
21 ///
22 /// Probed once per process and cached, so `main` can force the boot-time log and
23 /// the health handler can read the answer on every poll without reloading the
24 /// store. Called from the health handler, which is why it is not `main`-only.
25 pub fn anchors_ok() -> bool {
26 *ANCHORS_OK.get_or_init(probe)
27 }
28
29 /// Load the host store and judge it the way the verifier does.
30 ///
31 /// Emptiness is the only failure. A store that yields some anchors and some
32 /// parse errors is what the platform verifier itself accepts (it logs the
33 /// ignored certificates and builds anyway), so treating partial success as a
34 /// failure here would report a problem mt does not have.
35 fn probe() -> bool {
36 let result = rustls_native_certs::load_native_certs();
37
38 for error in &result.errors {
39 tracing::warn!("trust store read error: {error}");
40 }
41
42 if result.certs.is_empty() {
43 tracing::error!(
44 "no CA certificates loaded from the host trust store. Every outbound \
45 TLS request will fail, including the OAuth token exchange that logs \
46 users in. Install or repair ca-certificates on this host; see \
47 deploy/README.md."
48 );
49 false
50 } else {
51 tracing::info!(
52 anchors = result.certs.len(),
53 "loaded CA certificates from the host trust store"
54 );
55 true
56 }
57 }
58
59 #[cfg(test)]
60 mod tests {
61 use super::anchors_ok;
62
63 /// Any developer or CI machine has a trust store, so this asserts the probe
64 /// reads one rather than asserting a hardcoded answer. It would fail on a
65 /// host that cannot make an outbound HTTPS request at all, which is the
66 /// condition worth failing on.
67 #[test]
68 fn probe_finds_anchors_on_this_host() {
69 assert!(anchors_ok(), "no CA anchors loaded from the host store");
70 }
71
72 /// The answer is cached, so repeated reads must agree and must not reload.
73 #[test]
74 fn repeated_reads_agree() {
75 assert_eq!(anchors_ok(), anchors_ok());
76 }
77 }
78