From ec65f104ee47418143c31192544360bd20e1c867 Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Sun, 13 Sep 2026 17:21:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=BA=D1=81=D0=B8-=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA=D0=B8:=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BE=D0=B2=D0=B5=D1=80=D1=8F=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B8=D0=B7=D0=B2=D0=BE=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D0=BC=D1=83=20X-Forwarded-For?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit entrypoint.sh: --forwarded-allow-ips сужен с "*" до loopback + приватных сетей compose (переопределяемо FORWARDED_ALLOW_IPS) — uvicorn сканирует XFF справа и берёт реальный адрес, подставленное клиентом левое значение игнорируется. Caddyfile: reverse_proxy перезаписывает X-Forwarded-For реальным пиром (header_up {remote_host}) вместо добавления. Итог — достоверный IP для throttle и аудита. Инфра-часть проверяется на test-клоне. #58 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6 --- backend/entrypoint.sh | 12 ++++++++++-- deploy/vps/Caddyfile | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 3425430..0407db9 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -9,8 +9,16 @@ python -m app.bootstrap echo "[entrypoint] Запуск сервера…" # --proxy-headers + доверие к X-Forwarded-* от реверс-прокси (Caddy на VPS): -# чтобы приложение знало, что снаружи запрос пришёл по HTTPS. +# чтобы приложение знало, что снаружи запрос пришёл по HTTPS и кто реальный клиент. +# +# forwarded-allow-ips НЕ "*" (#58): при "*" uvicorn брал ЛЕВОЕ значение X-Forwarded-For, +# и клиент мог подставить произвольный IP (снятие throttle, порча аудита). Доверяем только +# апстримам из приватной сети compose (туннель к Caddy ходит на app:8000) и loopback +# (healthcheck) — тогда uvicorn сканирует XFF справа и берёт первый недоверенный адрес, +# т.е. реальный, добавленный Caddy. Портов на хост нет, снаружи к :8000 никто не ходит. +# Переопределяемо через FORWARDED_ALLOW_IPS, если сеть отличается. # --timeout-graceful-shutdown: SSE-потоки /api/events сами не закрываются, и без лимита # остановка ждала бы их до SIGKILL по stop_grace_period (30 с) — без lifespan-shutdown. +FORWARDED_ALLOW_IPS="${FORWARDED_ALLOW_IPS:-127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}" exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 1 \ - --proxy-headers --forwarded-allow-ips="*" --timeout-graceful-shutdown 10 + --proxy-headers --forwarded-allow-ips="$FORWARDED_ALLOW_IPS" --timeout-graceful-shutdown 10 diff --git a/deploy/vps/Caddyfile b/deploy/vps/Caddyfile index 09cc82c..c4f4f7f 100644 --- a/deploy/vps/Caddyfile +++ b/deploy/vps/Caddyfile @@ -5,8 +5,13 @@ # forbidden-stars.ru → 127.0.0.1:9001 ← ПК (по требованию) DEV/TEST # # Caddy сам терминирует TLS (он и есть edge: видит реального клиента), а вниз к -# приложению добавляет X-Forwarded-Proto=https / X-Forwarded-For / Host — +# приложению передаёт X-Forwarded-Proto=https / X-Forwarded-For / Host — # приложение это учитывает (uvicorn --proxy-headers). Положи файл в /etc/caddy/Caddyfile. +# +# X-Forwarded-For ПЕРЕЗАПИСЫВАЕМ реальным пиром (header_up ... {remote_host}), а не +# добавляем: иначе клиент мог бы подставить своё левое значение и подменить IP для +# throttle и аудита (#58). Вместе с сужением forwarded-allow-ips в entrypoint.sh это +# делает клиентский IP достоверным. # Сертификаты — см. deploy/vps/README.md (fullchain = leaf + промежуточные одним файлом). # # SSE (/api/events): отдельный handle БЕЗ encode и с flush_interval -1 — иначе сжатие/ @@ -65,11 +70,14 @@ forbiddenstars.ru { handle @sse { reverse_proxy 127.0.0.1:9000 { flush_interval -1 + header_up X-Forwarded-For {remote_host} } } handle { encode zstd gzip - reverse_proxy 127.0.0.1:9000 + reverse_proxy 127.0.0.1:9000 { + header_up X-Forwarded-For {remote_host} + } } import edge } @@ -80,11 +88,14 @@ forbidden-stars.ru { handle @sse { reverse_proxy 127.0.0.1:9001 { flush_interval -1 + header_up X-Forwarded-For {remote_host} } } handle { encode zstd gzip - reverse_proxy 127.0.0.1:9001 + reverse_proxy 127.0.0.1:9001 { + header_up X-Forwarded-For {remote_host} + } } import edge }