max / makenotwork
| 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 OnceLock; |
| 17 | |
| 18 | static ANCHORS_OK: = 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 | |
| 26 | *ANCHORS_OK.get_or_init |
| 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 | |
| 36 | let result = load_native_certs; |
| 37 | |
| 38 | for error in &result.errors |
| 39 | warn!; |
| 40 | |
| 41 | |
| 42 | if result.certs.is_empty |
| 43 | 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 | info! |
| 52 | anchors = result.certs.len, |
| 53 | "loaded CA certificates from the host trust store" |
| 54 | ; |
| 55 | true |
| 56 | |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | use 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 | |
| 68 | |
| 69 | assert!; |
| 70 | |
| 71 | |
| 72 | /// The answer is cached, so repeated reads must agree and must not reload. |
| 73 | |
| 74 | |
| 75 | assert_eq!; |
| 76 | |
| 77 | |
| 78 |