diff --git a/docs/swarm.md b/docs/swarm.md index e9c8587c..4e0d6fb8 100644 --- a/docs/swarm.md +++ b/docs/swarm.md @@ -53,6 +53,46 @@ optional: for peers whose self-signed TLS cert doesn't chain to a CA your host trusts. +`certFingerprint` scopes **only** to hive-c0re's own peer HTTPS checks +(the P33RS dashboard links and agent peer discovery below). It is +**not** consulted by matrix federation — tuwunel validates a peer's +federation certificate against the system CA bundle independently (see +*Matrix federation* below), so pinning a fingerprint here does nothing +for a self-signed matrix gateway cert. + +### Fingerprint format + +The value is the string `sha256:` followed by exactly 64 hexadecimal +digits — the SHA-256 digest of the peer's DER-encoded TLS leaf +certificate. The hex is case-insensitive (upper or lower both parse), +carries no colon separators between bytes, and any value not matching +this shape is ignored with a warning rather than weakening trust. + +``` +sha256:b1946ac92492d2347c6235b4d2611184a3f5b6cae6c19d6e3c2f0a8e7d4c9f12 +``` + +Generate it from the peer's certificate with openssl. The +`-fingerprint -sha256` output is uppercase and colon-separated, so +strip the colons, lowercase, and prepend the `sha256:` prefix: + +```sh +# from a PEM/CRT file +openssl x509 -in peer.crt -noout -fingerprint -sha256 \ + | sed 's/^.*=//; s/://g' | tr 'A-Z' 'a-z' | sed 's/^/sha256:/' + +# straight from the live endpoint (port 443) +echo | openssl s_client -connect peer.example.com:443 -servername peer.example.com 2>/dev/null \ + | openssl x509 -noout -fingerprint -sha256 \ + | sed 's/^.*=//; s/://g' | tr 'A-Z' 'a-z' | sed 's/^/sha256:/' +``` + +Pin the leaf certificate, not an intermediate or the CA — the +digest must match the exact cert the peer serves on its HTTPS +endpoint. When the peer rotates its cert, update the pin to the new +fingerprint (or switch the peer to a CA-trusted cert and drop the +field). + The nix module serialises the attrset to a `HYPERHIVE_PEERS` JSON array (`[{ domain, cert_fingerprint }]`) injected into the c0re environment and forwarded to agent containers. @@ -72,10 +112,15 @@ environment and forwarded to agent containers. qualified names (`agent@domain`). 3. **Matrix federation** — when `matrix.enable` is on, tuwunel - federates with the peer's matrix server at - `matrix.{peer-domain}:8448`. No extra config needed; federation - works as soon as the domains are reachable and TLS validates. - See `docs/matrix.md` for federation firewall requirements. + federates with the peer's matrix server (discovered via the peer's + `.well-known/matrix/server` delegation, which the gateway serves). + Federation validates the peer's TLS certificate against the + **system CA bundle** — independently of `certFingerprint`, which it + never consults. A self-signed gateway certificate therefore won't + federate even with a fingerprint pinned above: the peers need + CA-issued certs (ACME) or a shared private CA trusted on both + gateway hosts. See `docs/matrix.md` for federation firewall + TLS + requirements. ## Bilateral setup diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 9ae2899e..d66bc99b 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -120,11 +120,19 @@ in certFingerprint = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; - example = "sha256:abc123..."; + example = "sha256:b1946ac92492d2347c6235b4d2611184a3f5b6cae6c19d6e3c2f0a8e7d4c9f12"; description = '' Expected TLS certificate fingerprint for this peer's HTTPS endpoint. Null = trust the system CA bundle (for Let's Encrypt peers). Set to pin a self-signed cert. + + Format: the literal `sha256:` followed by exactly 64 + hex digits (case-insensitive, no colon separators) — the + SHA-256 digest of the peer's DER-encoded leaf certificate. + Generate with `openssl x509 -noout -fingerprint -sha256`, + then strip the colons and prepend `sha256:`. A malformed + value is ignored with a warning rather than weakening + trust. See docs/swarm.md for the full recipe. ''; }; @@ -173,7 +181,7 @@ in default = { }; example = { "lab.example.com" = { - certFingerprint = "sha256:abc123"; + certFingerprint = "sha256:b1946ac92492d2347c6235b4d2611184a3f5b6cae6c19d6e3c2f0a8e7d4c9f12"; }; "edge.corp" = { }; };