From 47b5ccd682de0d6133304d1763c246107169ec53 Mon Sep 17 00:00:00 2001 From: sneak Date: Wed, 14 Oct 2026 08:48:15 +0000 Subject: [PATCH] Unset TRUSTED_PROXIES trusts the RFC 1918 ranges (closes #119) With TRUSTED_PROXIES unset, bin/entrypoint.sh gives nginx one set_real_ip_from line for each of 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16, as sneak ruled on 2026-09-22 for every app. A list replaces them, and set and empty trusts no proxy; each entry is checked as before. The backend inside the container still trusts only 127.0.0.1/32. Both READMEs, nginx.conf and the comment in script/frontend-viewport-test say the new default. Model: opus-5-5 --- README.md | 30 ++++++++++++++++-------------- TODO.md | 6 ++++++ backend/README.md | 14 ++++++++------ bin/entrypoint.sh | 10 ++++++---- nginx.conf | 7 ++++--- script/frontend-viewport-test | 5 +++-- 6 files changed, 43 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d9497b4..8842f2a 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,8 @@ which listens only inside the container, on `127.0.0.1:8081`. The image: - Listens on port 8080 by default (override with `PORT` env var) - Takes the client address from `X-Forwarded-For` only on requests from the - reverse proxies named in `TRUSTED_PROXIES`, and by default from none + reverse proxies named in `TRUSTED_PROXIES`, by default from any address in + `10.0.0.0/8`, `172.16.0.0/12` or `192.168.0.0/16` - Sends access logs to stdout - Caches static assets with immutable headers - Sends the security headers `REPO_POLICIES.md` requires on every response, as @@ -291,9 +292,9 @@ What the [upaas](https://git.eeqj.de/sneak/upaas) app for netwatch needs: - **Port:** container port `8080`. - **Volume:** container path `/data`; the reports are kept in `/data/reports`. -- **Environment variables:** none is required. An empty one counts as unset, and - one set to a value netwatch cannot use stops the container at start, with the - reason in its log. +- **Environment variables:** none is required. An empty one counts as unset, + except `TRUSTED_PROXIES`, and one set to a value netwatch cannot use stops the + container at start, with the reason in its log. - `PORT`, default `8080`: the container port, from 1 to 65535. `8081` cannot be used: the backend listens on it inside the container - `REPORTS_PER_MINUTE`, default `60`: reports each client address may send a @@ -307,16 +308,17 @@ What the [upaas](https://git.eeqj.de/sneak/upaas) app for netwatch needs: in: `/data` or a path below it, with no `.` or `..` part and no extra `/`. The container also stops if the path goes through a symbolic link that leads out of `/data` or is written as a full path - - `TRUSTED_PROXIES`, default empty: set it to the address the reverse proxy - in front of the container connects from, as an IP address or CIDR; several - are separated by commas. nginx takes the client address from - `X-Forwarded-For` only on a request from one of them, and the rate limit - counts that address. Unset, `X-Forwarded-For` is ignored and every client - behind the proxy shares the proxy's one allowance of `REPORTS_PER_MINUTE`. - Name only addresses nothing but the proxy connects from: any client that - connects from one can write its own `X-Forwarded-For`, and through a port - Docker publishes, every client may connect from the Docker network's - gateway, such as `172.17.0.1`. + - `TRUSTED_PROXIES`, default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`: the + addresses the reverse proxy in front of the container connects from, as IP + addresses or CIDRs separated by commas. nginx takes the client address + from `X-Forwarded-For` only on a request from one of them, and the rate + limit counts that address. A list replaces the default. Set and empty, + `X-Forwarded-For` is ignored and every client behind the proxy shares the + proxy's one allowance of `REPORTS_PER_MINUTE`. Any client that connects + from one of these addresses can write its own `X-Forwarded-For`, and + through a port Docker publishes, every client may connect from the Docker + network's gateway, such as `172.17.0.1`, which the default includes; where + that matters, name only the addresses nothing but the proxy connects from. - `METRICS_USERNAME` and `METRICS_PASSWORD`, default empty: with both set, the backend records Prometheus metrics of its requests and serves them at `/metrics` on the container port, to requests with this user name and diff --git a/TODO.md b/TODO.md index 53f31f5..7cd11a8 100644 --- a/TODO.md +++ b/TODO.md @@ -22,6 +22,12 @@ Decide whether the repo moves to the layout `REPO_POLICIES.md` gives, with # Completed Steps +- 2026-10-14: with `TRUSTED_PROXIES` unset, the container's nginx trusts the + three private ranges of RFC 1918, `10.0.0.0/8`, `172.16.0.0/12` and + `192.168.0.0/16` ([#119](https://git.eeqj.de/sneak/netwatch/issues/119)), as + sneak ruled on 2026-09-22 for every app; it trusted no proxy before. A list + replaces them, and set and empty it trusts none. The backend inside the + container still trusts only `127.0.0.1/32` - 2026-10-07: the files shared from `sneak/prompts` are its copies at commit `dd4027b` ([#113](https://git.eeqj.de/sneak/netwatch/issues/113)), with this repository's own entries after the shared content in `.gitignore`, diff --git a/backend/README.md b/backend/README.md index c6844b8..831f1b0 100644 --- a/backend/README.md +++ b/backend/README.md @@ -121,12 +121,14 @@ replaces the security headers this server sets with those in the root The container's own `TRUSTED_PROXIES` goes to nginx instead: IP addresses or CIDRs, separated by commas, of the reverse proxies in front of the container. nginx takes the client address from `X-Forwarded-For` only on a request from one -of them. Unset or empty, nginx trusts no proxy, and the client address is the -one each request comes from, so every client behind a proxy shares one rate -limit. An entry that is not an IP address or CIDR, such as a hostname or -`1.2.3`, stops the container at start with an error naming `TRUSTED_PROXIES`: -the entrypoint checks each entry with `netwatch-server check-cidr`, which parses -it as this server parses its own `TRUSTED_PROXIES`. +of them. Unset, nginx trusts `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16`, +and a list replaces them. Set and empty, nginx trusts no proxy, and the client +address is the one each request comes from, so every client behind a proxy +shares one rate limit. An entry that is not an IP address or CIDR, such as a +hostname or `1.2.3`, stops the container at start with an error naming +`TRUSTED_PROXIES`: the entrypoint checks each entry with +`netwatch-server check-cidr`, which parses it as this server parses its own +`TRUSTED_PROXIES`. ### Report storage diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index ee53bfe..f249f93 100755 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -34,9 +34,11 @@ fi # TRUSTED_PROXIES names the reverse proxies in front of the container, # as IP addresses or CIDRs separated by commas. nginx takes the client -# address from X-Forwarded-For only on a request from one of them, so -# unset or empty, it trusts no one. nginx.conf includes the file written -# here, one set_real_ip_from line per entry. +# address from X-Forwarded-For only on a request from one of them. +# Unset, they are the three private ranges of RFC 1918; set and empty, +# nginx trusts no one, which is why the default below follows - and not +# :-. nginx.conf includes the file written here, one set_real_ip_from +# line per entry. # # nginx looks up an entry it cannot read as an address as a hostname, # and trusts what it finds (1.2.3 is found as 1.2.0.3). So each entry @@ -45,7 +47,7 @@ fi # TRUSTED_PROXIES. Its error, naming the CIDR, is dropped for the one # below, naming the entry as written. set -f keeps a * in an entry from # becoming a list of file names. -TRUSTED_PROXIES="${TRUSTED_PROXIES:-}" +TRUSTED_PROXIES="${TRUSTED_PROXIES-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}" set -f for proxy in $(printf '%s' "$TRUSTED_PROXIES" | tr ',' ' '); do case "$proxy" in diff --git a/nginx.conf b/nginx.conf index d8b0c67..bee6d5a 100644 --- a/nginx.conf +++ b/nginx.conf @@ -18,9 +18,10 @@ server { # The client address comes from X-Forwarded-For only on a request # from the reverse proxies in TRUSTED_PROXIES: bin/entrypoint.sh - # writes one set_real_ip_from line for each into this file, and - # leaves it empty when TRUSTED_PROXIES is unset, so that by default - # the client address is the one each request comes from. + # writes one set_real_ip_from line for each into this file. Unset, + # TRUSTED_PROXIES is the three private ranges of RFC 1918; set and + # empty, the file is empty and the client address is the one each + # request comes from. include /etc/nginx/trusted-proxies.conf; real_ip_header X-Forwarded-For; real_ip_recursive on; diff --git a/script/frontend-viewport-test b/script/frontend-viewport-test index d26e0fe..841de75 100755 --- a/script/frontend-viewport-test +++ b/script/frontend-viewport-test @@ -64,8 +64,9 @@ main() { # nginx.conf is a template: the image renders it over its own # default.conf, with the same port and limit bin/entrypoint.sh uses. # The empty file it includes trusts no proxy, as bin/entrypoint.sh - # writes it when TRUSTED_PROXIES is unset. nginx.conf also includes - # the security headers, so the page runs under the shipped policy. + # writes it when TRUSTED_PROXIES is set and empty. nginx.conf also + # includes the security headers, so the page runs under the shipped + # policy. docker run -d --rm --name "$SERVER" \ --network "$NETWORK" --network-alias netwatch \ -e PORT=8080 -e NGINX_ENVSUBST_FILTER='^PORT$' \