diff --git a/README.md b/README.md index ac27ec4..bdc97f4 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,15 @@ Configured via YAML file (`--config`). Key settings: added to the always-enforced built-in ranges (loopback, private, link-local, CGNAT, benchmark, NAT64, and the like); an invalid CIDR aborts startup +- `trusted_proxies` — list of CIDR ranges of the reverse proxies in front + of pixa. `X-Forwarded-For` is believed only when the direct peer falls + inside one of these ranges; the logged and login-recorded client + address is then the rightmost forwarded entry that is not itself a + trusted proxy. Otherwise the direct peer address is used and the header + is ignored, so a client connecting directly cannot spoof its address. + Omitted or empty trusts no one; an invalid CIDR aborts startup. Set + this to your proxy's address range when deploying behind a reverse + proxy - `upstream_fetch_timeout` — timeout for origin requests - `upstream_max_response_size` — max origin response size - `downstream_timeout` — client response timeout diff --git a/TODO.md b/TODO.md index c345355..fa231ad 100644 --- a/TODO.md +++ b/TODO.md @@ -30,6 +30,17 @@ exhaustion # Completed Steps +- 2026-09-21 trusted-proxy client IP resolution (closes #94): a + `trusted_proxies` config key taking a list of CIDRs, parsed by the same + `net/netip` list parser as `blocked_networks` (an invalid entry aborts + startup naming the key and value; omitted or empty trusts no one); a new + `internal/clientip` package resolves the client address by honoring + `X-Forwarded-For` only when the direct peer is a trusted proxy, walking + the chain right-to-left to the rightmost non-proxy entry, so a client + connecting directly cannot spoof its address; the resolved address is + stored in the request context by a new middleware and used by the + request-logging middleware and the login-attempt logs in place of the + raw peer address; documented in `README.md` and `config.example.yml`. - 2026-09-21 blocked networks configuration extending SSRF protection: a `blocked_networks` config key taking a list of CIDRs (parsed with `net/netip`, an invalid entry aborts startup naming the key and value), diff --git a/config.example.yml b/config.example.yml index e3fbd4a..fd41c86 100644 --- a/config.example.yml +++ b/config.example.yml @@ -31,6 +31,18 @@ allowlist_hosts: # - 100.64.0.0/10 # - 2001:db8::/32 +# CIDR ranges of the reverse proxies in front of pixa. X-Forwarded-For +# is believed only when the direct peer is inside one of these ranges; +# the client address in the access log and login records is then the +# rightmost forwarded entry that is not itself a trusted proxy. A client +# connecting directly (peer outside these ranges) cannot spoof its +# address: the header is ignored and the peer address is used. Omitted or +# empty trusts no one; an invalid CIDR aborts startup. Set this when +# deploying behind a proxy. +# trusted_proxies: +# - 10.0.0.0/8 +# - 2001:db8::/32 + # Allow HTTP upstream (only for testing, always use HTTPS in production) allow_http: false