From 4dde06329992b418153e2b479bccb48e94ddb8ba Mon Sep 17 00:00:00 2001 From: Robert Helewka Date: Wed, 17 Jun 2026 06:58:36 -0400 Subject: [PATCH] fix(web): trust XFF for real client IP and correct port to 23081 - Configure nginx `set_real_ip_from` for RFC1918 ranges and enable `real_ip_recursive` so allowlists evaluate the true client IP instead of Docker's NAT gateway, preventing public exposure of `/metrics` and `/nginx_status` - Update published port from 23181 to 23081 in docker-compose --- docker-compose.yaml | 4 ++-- nginx/mnemosyne.conf | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 00b5f93..5995249 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -377,7 +377,7 @@ services: retries: 3 start_period: 60s - # ── Web: nginx reverse proxy, public port 23181 ──────────────────────────── + # ── Web: nginx reverse proxy, public port 23081 ──────────────────────────── # No Django env — nginx only knows how to route. Public listener is # templated into the conf file by Ansible if the port ever needs to change. web: @@ -390,7 +390,7 @@ services: mcp: condition: service_healthy ports: - - "23181:80" + - "23081:80" volumes: - ./nginx/mnemosyne.conf:/etc/nginx/conf.d/default.conf:ro - static:/var/www/static:ro diff --git a/nginx/mnemosyne.conf b/nginx/mnemosyne.conf index 40bd624..e17bc51 100644 --- a/nginx/mnemosyne.conf +++ b/nginx/mnemosyne.conf @@ -32,6 +32,19 @@ # resolution at startup and returns 502 after `docker compose restart app`. resolver 127.0.0.11 valid=10s; +# Recover the real client IP from X-Forwarded-For (set by HAProxy on Titania) +# before evaluating the RFC1918 allowlists below. nginx runs as a sidecar with +# a published port, so every proxied request arrives via Docker's NAT gateway +# (an RFC1918 address) — without this, the allowlists match that gateway and +# pass ALL external traffic, exposing /metrics and /nginx_status publicly. +# HAProxy's own health checks (e.g. to /healthz) carry no XFF and keep their +# real 10.10.x.x source, so they stay allowed. +set_real_ip_from 10.0.0.0/8; +set_real_ip_from 172.16.0.0/12; +set_real_ip_from 192.168.0.0/16; +real_ip_header X-Forwarded-For; +real_ip_recursive on; + # Preserve X-Forwarded-Proto from the upstream reverse proxy (HAProxy TLS # termination on Titania); fall back to $scheme only if there's no upstream # header. Inside the compose network $scheme is always `http` because HAProxy