From c4a573d91ddc55d8fa79f01592c12f0932080584 Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 28 Aug 2026 10:48:26 +0200 Subject: [PATCH] gateway: move verifiedProxyTo's 42-line rationale comment to docs/gateway.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment-block lint (added in 79dc8ca6) now trips on this block — genuinely pre-existing, unrelated to that change, just newly caught. Per the lint's own suggested remedy: relocated the full per-directive reasoning plus both footguns (session-cache keying, the Host-header clobber that can recurse a subrequest into itself) to a new "Dialing another vhost by name" section in docs/gateway.md, and left a short why + pointer comment in the source. No behavior change. --- docs/gateway.md | 44 ++++++++++++++++++ nix/host-modules/hive-gateway/vhost-lib.nix | 49 ++++----------------- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/docs/gateway.md b/docs/gateway.md index 91d9085f..e465cce7 100644 --- a/docs/gateway.md +++ b/docs/gateway.md @@ -599,3 +599,47 @@ Since the gateway always terminates TLS (see [TLS modes](#tls-modes) above), an enabled HSTS header is always served over https — there is no TLS-less mode that could violate it. +## Dialing another vhost by name (`verifiedProxyTo`) + +`vhost-lib.nix`'s `verifiedProxyTo` builds the `proxy_ssl_*` / +`proxy_set_header` block a module uses to dial another service on this +same gateway BY NAME over https, verified. One definition rather than a +copy per module: nginx verifies nothing by default +(`proxy_ssl_verify` is off), so a `proxy_pass https://…` without these +lines is encrypted and unauthenticated. That failure is invisible — it +works, and keeps working, against any certificate at all. + +Every line earns its place, each confirmed against a real nginx with +the opposite arm run as a control: + +- `verify` + `depth` — the chain is leaf -> intermediate -> root. +- `trusted_cert` — the bundle; nginx reads ALL certs in the file, which + the bundle's own doc warns is not true of every consumer. +- `ssl_name` — checks the HOSTNAME too. Without it a chain-only check + accepts any certificate this CA ever signed, and for an internal CA + that is every service on the hive. +- `server_name on` — sends SNI, or the far end cannot pick a cert. + +**⚠️ Session-cache footgun**: `proxy_ssl_session_reuse` is left at its +default (on), deliberately — this is used on per-request auth +subrequests, so the handshake it avoids is paid on every request. Worth +knowing when testing though: the session cache is keyed by upstream +address and NOT by trust config, so two locations pointing at one +upstream with different trust do not verify independently. + +**⚠️ Host-header clobber footgun**: `verifiedProxyTo` also pins `Host` +(and reinstates the rest of nginx's `recommendedProxySettings` header +set) to the target `name` rather than leaving it to be filled in later. +`name` here resolves back to THIS gateway — every consumer dials another +vhost on the same nginx, not a separate host — and nginx picks the vhost +to answer an HTTPS request from the `Host` header, not from the TLS SNI +that `proxy_ssl_name` sends. `recommendedProxySettings`'s own `Host +$host` (the CALLER's host, not the target) is textually appended by +nixpkgs AFTER a location's `extraConfig` — so it always wins over a +`proxy_set_header Host` written in the location body, and the subrequest +loops back into the ORIGINAL vhost instead of reaching the target, +recursing on its own `auth_request` until nginx's subrequest-depth limit +turns it into a plain 500. Every call site sets `recommendedProxySettings += false` on the location for exactly this reason — nixpkgs' version +would still clobber this one. + diff --git a/nix/host-modules/hive-gateway/vhost-lib.nix b/nix/host-modules/hive-gateway/vhost-lib.nix index c02f1745..77aaa6f6 100644 --- a/nix/host-modules/hive-gateway/vhost-lib.nix +++ b/nix/host-modules/hive-gateway/vhost-lib.nix @@ -87,47 +87,14 @@ in vhostTls; # Dial another service on this hive BY NAME over https, verified. - # - # One definition rather than a copy per module: nginx verifies - # nothing by default (`proxy_ssl_verify` is off), so a `proxy_pass - # https://…` without these lines is encrypted and unauthenticated. - # That failure is invisible — it works, and keeps working, against - # any certificate at all. - # - # Every line earns its place, each confirmed against a real nginx - # with the opposite arm run as a control: - # verify + depth — the chain is leaf -> intermediate -> root - # trusted_cert — the bundle; nginx reads ALL certs in the file, - # which the bundle's own doc warns is not true of - # every consumer - # ssl_name — checks the HOSTNAME too. Without it a chain-only - # check accepts any certificate this CA ever - # signed, and for an internal CA that is every - # service on the hive - # server_name on — sends SNI, or the far end cannot pick a cert - # - # ⚠️ `proxy_ssl_session_reuse` is left at its default (on) and that is - # deliberate: this is used on per-request auth subrequests, so the - # handshake it avoids is paid on every request. Worth knowing when - # testing though — the session cache is keyed by upstream address and - # NOT by trust config, so two locations pointing at one upstream with - # different trust do not verify independently. - # - # ⚠️ Also pins `Host` (and reinstates the rest of nginx's - # `recommendedProxySettings` header set) to `name` rather than leaving - # it to be filled in later. `name` here resolves back to THIS gateway - # — every consumer dials another vhost on the same nginx, not a - # separate host — and nginx picks the vhost to answer an HTTPS request - # from the `Host` header, not from the TLS SNI that `proxy_ssl_name` - # above sends. `recommendedProxySettings`'s own `Host $host` (the - # CALLER's host, not the target) is textually appended by nixpkgs - # AFTER a location's `extraConfig` — so it always wins over a - # `proxy_set_header Host` written in the location body, and the - # subrequest loops back into the ORIGINAL vhost instead of reaching - # the target, recursing on its own `auth_request` until nginx's - # subrequest-depth limit turns it into a plain 500. Every call site - # sets `recommendedProxySettings = false` on the location for exactly - # this reason — nixpkgs' version would still clobber this one. + # nginx verifies nothing by default (`proxy_ssl_verify` is off), so a + # `proxy_pass https://…` without this is encrypted but unauthenticated + # — invisibly, it works and keeps working against any certificate at + # all. Full reasoning for every directive here (plus two real + # footguns — session-cache keying, and a `Host`-header clobber that + # can recurse a subrequest into itself) is in docs/gateway.md's + # "Dialing another vhost by name" section — read it before touching + # this. verifiedProxyTo = name: '' proxy_ssl_verify on; proxy_ssl_verify_depth 3;