gateway: move verifiedProxyTo's 42-line rationale comment to docs/gateway.md
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.
This commit is contained in:
parent
8a0ecb307b
commit
c4a573d91d
2 changed files with 52 additions and 41 deletions
|
|
@ -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
|
above), an enabled HSTS header is always served over https — there is no
|
||||||
TLS-less mode that could violate it.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,47 +87,14 @@ in
|
||||||
vhostTls;
|
vhostTls;
|
||||||
|
|
||||||
# Dial another service on this hive BY NAME over https, verified.
|
# Dial another service on this hive BY NAME over https, verified.
|
||||||
#
|
# nginx verifies nothing by default (`proxy_ssl_verify` is off), so a
|
||||||
# One definition rather than a copy per module: nginx verifies
|
# `proxy_pass https://…` without this is encrypted but unauthenticated
|
||||||
# nothing by default (`proxy_ssl_verify` is off), so a `proxy_pass
|
# — invisibly, it works and keeps working against any certificate at
|
||||||
# https://…` without these lines is encrypted and unauthenticated.
|
# all. Full reasoning for every directive here (plus two real
|
||||||
# That failure is invisible — it works, and keeps working, against
|
# footguns — session-cache keying, and a `Host`-header clobber that
|
||||||
# any certificate at all.
|
# can recurse a subrequest into itself) is in docs/gateway.md's
|
||||||
#
|
# "Dialing another vhost by name" section — read it before touching
|
||||||
# Every line earns its place, each confirmed against a real nginx
|
# this.
|
||||||
# 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.
|
|
||||||
verifiedProxyTo = name: ''
|
verifiedProxyTo = name: ''
|
||||||
proxy_ssl_verify on;
|
proxy_ssl_verify on;
|
||||||
proxy_ssl_verify_depth 3;
|
proxy_ssl_verify_depth 3;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue