From 022cafbe23cb21b3b1e25a31d1cc3889bcb1f5a8 Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 28 Jun 2026 16:13:12 +0200 Subject: [PATCH] docs: move proxy client-IP guidance to the deployment walkthrough Reverse-proxy client-IP resolution (trust_forwarded_headers / trusted_proxy_cidrs) was documented under HTTPS-SETUP because proxies are commonly used for TLS termination, but it's really a deployment concern. Relocate it to CLOUD-DEPLOY-WALKTHROUGH as a "Client IP behind a proxy or load balancer" section with a behavior table (no-proxy default, trusted-proxy XFF resolution, and the untrusted-peer spoofing gate). HTTPS-SETUP keeps the TLS-termination example and now cross-links to it; the deployment section links back for the cert details. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../docs/guides/CLOUD-DEPLOY-WALKTHROUGH.md | 34 +++++++++++++++++++ docs/content/docs/guides/HTTPS-SETUP.md | 19 +++-------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/docs/content/docs/guides/CLOUD-DEPLOY-WALKTHROUGH.md b/docs/content/docs/guides/CLOUD-DEPLOY-WALKTHROUGH.md index 613fa8b..e9d7c04 100644 --- a/docs/content/docs/guides/CLOUD-DEPLOY-WALKTHROUGH.md +++ b/docs/content/docs/guides/CLOUD-DEPLOY-WALKTHROUGH.md @@ -46,6 +46,40 @@ Minimum mitigations before going live: --- +## Client IP behind a proxy or load balancer + +Behind a reverse proxy or load balancer, the connection AfterTouch sees comes +from the proxy, not from the speaker. A few handlers act on the source IP (for +example the Spotify priming triggered by `/marge/streaming/support/power_on`, +and the device IP AfterTouch records), so in a proxied setup you usually want +it to recover the real speaker IP from the `X-Forwarded-For` header. + +Enable it in `data/settings.json`: + +- Set `"trust_forwarded_headers": true`. +- Set `"trusted_proxy_cidrs"` to your proxy's own IP range(s), for example + `["10.0.0.0/8"]`. It defaults to loopback (`127.0.0.0/8`, `::1/128`), which + already covers a proxy running on the same host. + +Make sure the proxy sets the header (nginx: +`proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`). Only +`X-Forwarded-For` is consulted (not `X-Real-IP` or `True-Client-IP`). + +| Deployment | `trust_forwarded_headers` | Client IP AfterTouch uses | +|---------------------------------------------------|---------------------------|----------------------------------------------------------------------------------------| +| Direct LAN / on-device (no proxy) | `false` (default) | the connecting socket's IP; `X-Forwarded-For` is ignored | +| Behind a proxy listed in `trusted_proxy_cidrs` | `true` | the rightmost `X-Forwarded-For` entry outside `trusted_proxy_cidrs` (the real speaker) | +| A request that did not arrive via a trusted proxy | `true` | the socket IP; its `X-Forwarded-For` is ignored (spoofing protection) | + +> **Do not enable `trust_forwarded_headers` on a flat LAN with no proxy.** A +> malicious speaker could then send `X-Forwarded-For` itself and spoof its +> source IP. A missing or unparseable header always falls back to the socket IP. + +For terminating TLS at the proxy (serving the certificate on `:443`), see the +[reverse proxy section of the HTTPS guide](HTTPS-SETUP.md#reverse-proxy-optional). + +--- + ## Step 1 — Deploy AfterTouch on your server ### Docker / Docker Compose (any VPS) diff --git a/docs/content/docs/guides/HTTPS-SETUP.md b/docs/content/docs/guides/HTTPS-SETUP.md index 6aeeeee..977ca20 100644 --- a/docs/content/docs/guides/HTTPS-SETUP.md +++ b/docs/content/docs/guides/HTTPS-SETUP.md @@ -125,20 +125,11 @@ server { } ``` -> **Tell the service to resolve the client IP from `X-Forwarded-For`.** When -> deploying behind a reverse proxy, set `"trust_forwarded_headers": true` in -> `data/settings.json`. With that flag on, the service reads the real client -> IP from `X-Forwarded-For` (chi walks the chain right-to-left, skipping your -> trusted-proxy IPs), so handlers that act on the source IP (e.g. the Spotify -> priming from `/marge/streaming/support/power_on`) see the speaker's real -> address instead of the proxy's. -> -> By default only `127.0.0.0/8` and `::1/128` are trusted proxy ranges. If -> your reverse proxy lives on a different host, list its CIDR(s) in -> `"trusted_proxy_cidrs"` (the proxy's own IP ranges, e.g. -> `["10.0.0.0/8"]`). Do **not** enable `trust_forwarded_headers` on a flat -> LAN with no proxy: a malicious speaker on the LAN could send -> `X-Forwarded-For` itself and spoof its source IP. +> **Client IP behind a proxy.** A reverse proxy changes the source IP the +> service sees, which matters for the handlers that act on it. Configuring +> AfterTouch to recover the real speaker IP from `X-Forwarded-For` +> (`trust_forwarded_headers` / `trusted_proxy_cidrs`) is covered under +> [Client IP behind a proxy or load balancer](CLOUD-DEPLOY-WALKTHROUGH.md#client-ip-behind-a-proxy-or-load-balancer). ---