docs/gateway: name the loopback-only vhost class the TLS claims exclude

The TLS section read as universal — "the gateway always terminates TLS",
"listens on httpsPort on every vhost". Measured against the rendered
config of an all-local host: ten of eleven vhosts carry two listens and
addSSL/forceSSL, and `grafana-metrics` carries one plain-HTTP listen on
127.0.0.1 with a single `= /metrics` location proxying grafana's unix
socket for the local collector.

The exception is right; a doc with no room for it is not. Someone asking
"is anything here serving plain HTTP" was told no, when the useful answer
is "no, except an internal class that is loopback-bound and deliberately
exempt" — which tells them what to look for on the next host.

One sentence where the modes are introduced rather than a patch to each
sentence that inherits the assumption, plus "every vhost" -> "every
routable vhost" where the self-signed default repeats it.

Two neighbouring claims were checked at the same time and hold: /_matrix/
proxies to 127.0.0.1:8008, and .well-known/matrix/client returns the
homeserver base_url with no port suffix on 443.

Closes #4191
This commit is contained in:
atlas 2026-09-11 07:28:36 +02:00 committed by mara
commit 4846c3b953

View file

@ -169,6 +169,13 @@ The gateway always terminates TLS — self-signed is the implicit floor when
nothing else is configured, so there is no http-only mode. Three modes,
selected by which (if any) external TLS source is set:
⚠️ **One vhost class is exempt, so expect it during a TLS audit.** A vhost bound to loopback for a
local consumer carries a single plain-HTTP listen and no TLS. `grafana-metrics`
(`nix/host-modules/swarm-grafana.nix`) is the only one in the tree today: it listens on `127.0.0.1`
alone and serves one `= /metrics` location from grafana's unix socket, for the collector on this
host to scrape. Nothing off-host can reach it, so TLS there protects nothing. Every vhost with a
routable name is covered by the modes below.
| mode | config | cert source | `.well-known` scheme |
|---|---|---|---|
| self-signed (default) | neither `tls.certDir` nor `tls.acme` set | host hive-CA signs a gateway leaf (RSA-4096) | `https` |
@ -202,7 +209,7 @@ Mutual exclusion: `tls.certDir` set together with `tls.acme.enable = true` fails
### Self-signed TLS (default)
On by default, and listens on `httpsPort` (default 443) on every vhost beside the plain-http `port` (default 80).
On by default, and listens on `httpsPort` (default 443) on every routable vhost beside the plain-http `port` (default 80). (The loopback-only exception is named under *TLS modes* above.)
The issuer is a **host-held hive CA**, not a bare self-signed leaf. A host service (`hive-tls-ca.service`, from the `hive-tls` module) generates a long-lived CA (`services.hyperhive.deploy.hive-controller.tls.caValidityDays`, default ~20y) under `services.hyperhive.deploy.hive-controller.tls.stateDir` (default `/var/lib/hive-tls`), then signs a gateway **leaf** (`leafValidityDays`, default 30d) with it. `hive-gateway-self-signed-cert` then imports the leaf into nginx's state dir (`/var/lib/hive-gateway/tls/{cert,key}.pem`).