Unset TRUSTED_PROXIES trusts the RFC 1918 ranges (closes #119)
check / check (push) Waiting to run

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. README.md, configs/nginx.conf and the comment in
script/frontend-viewport-test say the new default.

Model: opus-5-5
This commit was merged in pull request #121.
This commit is contained in:
2026-10-14 13:01:34 +02:00
parent 73b39e485b
commit cbcde098dc
5 changed files with 36 additions and 25 deletions
+18 -16
View File
@@ -412,9 +412,10 @@ address nginx passes on and no other. The image:
- Listens on port 8080 by default (override with `PORT` env var) - Listens on port 8080 by default (override with `PORT` env var)
- Takes the client address from `X-Forwarded-For` only on requests from the - 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
`bin/entrypoint.sh` checks each entry with `netwatch-server check-cidr`, which `10.0.0.0/8`, `172.16.0.0/12` or `192.168.0.0/16`. `bin/entrypoint.sh` checks
parses it as the backend parses its own `TRUSTED_PROXIES` each entry with `netwatch-server check-cidr`, which parses it as the backend
parses its own `TRUSTED_PROXIES`
- Sends access logs to stdout - Sends access logs to stdout
- Caches static assets with immutable headers - Caches static assets with immutable headers
- Sends the security headers `REPO_POLICIES.md` requires on every response, as - Sends the security headers `REPO_POLICIES.md` requires on every response, as
@@ -433,9 +434,9 @@ What the [upaas](https://git.eeqj.de/sneak/upaas) app for netwatch needs:
- **Port:** container port `8080`. - **Port:** container port `8080`.
- **Volume:** container path `/data`; the reports are kept in `/data/reports`. - **Volume:** container path `/data`; the reports are kept in `/data/reports`.
- **Environment variables:** none is required. An empty one counts as unset, and - **Environment variables:** none is required. An empty one counts as unset,
one set to a value netwatch cannot use stops the container at start, with the except `TRUSTED_PROXIES`, and one set to a value netwatch cannot use stops the
reason in its log. container at start, with the reason in its log.
- `PORT`, default `8080`: the container port, from 1 to 65535. `8081` cannot - `PORT`, default `8080`: the container port, from 1 to 65535. `8081` cannot
be used: the backend listens on it inside the container be used: the backend listens on it inside the container
- `REPORTS_PER_MINUTE`, default `60`: reports each client address may send a - `REPORTS_PER_MINUTE`, default `60`: reports each client address may send a
@@ -449,16 +450,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 `/`. 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 The container also stops if the path goes through a symbolic link that
leads out of `/data` or is written as a full path leads out of `/data` or is written as a full path
- `TRUSTED_PROXIES`, default empty: set it to the address the reverse proxy - `TRUSTED_PROXIES`, default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`: the
in front of the container connects from, as an IP address or CIDR; several addresses the reverse proxy in front of the container connects from, as IP
are separated by commas. nginx takes the client address from addresses or CIDRs separated by commas. nginx takes the client address
`X-Forwarded-For` only on a request from one of them, and the rate limit from `X-Forwarded-For` only on a request from one of them, and the rate
counts that address. Unset, `X-Forwarded-For` is ignored and every client limit counts that address. A list replaces the default. Set and empty,
behind the proxy shares the proxy's one allowance of `REPORTS_PER_MINUTE`. `X-Forwarded-For` is ignored and every client behind the proxy shares the
Name only addresses nothing but the proxy connects from: any client that proxy's one allowance of `REPORTS_PER_MINUTE`. Any client that connects
connects from one can write its own `X-Forwarded-For`, and through a port from one of these addresses can write its own `X-Forwarded-For`, and
Docker publishes, every client may connect from the Docker network's through a port Docker publishes, every client may connect from the Docker
gateway, such as `172.17.0.1`. 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, - `METRICS_USERNAME` and `METRICS_PASSWORD`, default empty: with both set,
the backend records Prometheus metrics of its requests and serves them at the backend records Prometheus metrics of its requests and serves them at
`/metrics` on the container port, to requests with this user name and `/metrics` on the container port, to requests with this user name and
+6
View File
@@ -21,6 +21,12 @@ Run `make frontend-viewport-test` in CI as its own step; it is not part of
# Completed Steps # 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-14: `POST /api/v1/reports` takes only a report sent with - 2026-10-14: `POST /api/v1/reports` takes only a report sent with
`Content-Type: application/json`, parameters such as `charset` allowed, and `Content-Type: application/json`, parameters such as `charset` allowed, and
refuses any other with 415 and the usual error body, storing nothing refuses any other with 415 and the usual error body, storing nothing
+6 -4
View File
@@ -34,9 +34,11 @@ fi
# TRUSTED_PROXIES names the reverse proxies in front of the container, # TRUSTED_PROXIES names the reverse proxies in front of the container,
# as IP addresses or CIDRs separated by commas. nginx takes the client # 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 # address from X-Forwarded-For only on a request from one of them.
# unset or empty, it trusts no one. configs/nginx.conf includes the file # Unset, they are the three private ranges of RFC 1918; set and empty,
# written here, one set_real_ip_from line per entry. # nginx trusts no one, which is why the default below follows - and not
# :-. configs/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, # 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 # 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 # 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 # below, naming the entry as written. set -f keeps a * in an entry from
# becoming a list of file names. # 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 set -f
for proxy in $(printf '%s' "$TRUSTED_PROXIES" | tr ',' ' '); do for proxy in $(printf '%s' "$TRUSTED_PROXIES" | tr ',' ' '); do
case "$proxy" in case "$proxy" in
+4 -3
View File
@@ -18,9 +18,10 @@ server {
# The client address comes from X-Forwarded-For only on a request # The client address comes from X-Forwarded-For only on a request
# from the reverse proxies in TRUSTED_PROXIES: bin/entrypoint.sh # from the reverse proxies in TRUSTED_PROXIES: bin/entrypoint.sh
# writes one set_real_ip_from line for each into this file, and # writes one set_real_ip_from line for each into this file. Unset,
# leaves it empty when TRUSTED_PROXIES is unset, so that by default # TRUSTED_PROXIES is the three private ranges of RFC 1918; set and
# the client address is the one each request comes from. # empty, the file is empty and the client address is the one each
# request comes from.
include /etc/nginx/trusted-proxies.conf; include /etc/nginx/trusted-proxies.conf;
real_ip_header X-Forwarded-For; real_ip_header X-Forwarded-For;
real_ip_recursive on; real_ip_recursive on;
+2 -2
View File
@@ -64,8 +64,8 @@ main() {
# configs/nginx.conf is a template: the image renders it over its own # configs/nginx.conf is a template: the image renders it over its own
# default.conf, with the same port and limit bin/entrypoint.sh uses. # default.conf, with the same port and limit bin/entrypoint.sh uses.
# The empty file it includes trusts no proxy, as bin/entrypoint.sh # The empty file it includes trusts no proxy, as bin/entrypoint.sh
# writes it when TRUSTED_PROXIES is unset. It also includes the # writes it when TRUSTED_PROXIES is set and empty. It also includes
# security headers, so the page runs under the shipped policy. # the security headers, so the page runs under the shipped policy.
docker run -d --rm --name "$SERVER" \ docker run -d --rm --name "$SERVER" \
--network "$NETWORK" --network-alias netwatch \ --network "$NETWORK" --network-alias netwatch \
-e PORT=8080 -e NGINX_ENVSUBST_FILTER='^PORT$' \ -e PORT=8080 -e NGINX_ENVSUBST_FILTER='^PORT$' \