Files
ros_control/docs/changes/025-trusted-proxies-port/plan.md
T
ayurishchevandClaude Opus 5.5 5367cb5e9c Доверенные прокси (реальный IP клиента) и параметризация порта
Пункты 14 и 16 ревью 2026-09-28 17:35 (docs/changes/025):
- TRUSTED_PROXIES (CIDR, по умолчанию пусто): X-Forwarded-For учитывается
  только от доверенного peer, цепочка разбирается справа налево; иначе
  заголовок игнорируется — подделать IP нельзя. Реальный IP — в блокировке
  входа и событиях auth.*; неверный CIDR — отказ старта;
- docker-compose: "${APP_BIND:-0.0.0.0}:${APP_PORT:-8000}:8000"; стенд на
  8001 через APP_PORT в .env, override-файл больше не нужен.

Тесты: 36 из 36. Стенд: поддельный X-Forwarded-For проигнорирован,
заблокирован реальный адрес; боевые данные не изменены. Ручная проверка
UI пользователем на момент коммита не подтверждена.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:19:10 +03:00

6.4 KiB
Raw Blame History

План: 025 — доверенные прокси (реальный IP клиента) и параметризация порта

Context

Ревью docs/reviews/2026-09-28-1735-codebase-review.md:

  • п. 14 — блокировка входа по IP (021) и IP в событиях auth.* берут request.client.host. За reverse-proxy все клиенты видны с адреса прокси: одна блокировка на всех и журнал без реальных адресов. Разбора X-Forwarded-For и доверенных прокси нет.
  • п. 16 — docker-compose.yml публикует жёстко "8000:8000"; порт 8000 хоста занят посторонним процессом, стенд работает на 8001 через override-файл вне репозитория.

Решения пользователя:

  • п. 16 — переменные APP_PORT (порт на хосте, по умолчанию 8000) и APP_BIND (адрес публикации, по умолчанию 0.0.0.0), как в ipam_control. В .env стенда оркестратор добавил APP_PORT=8001 — стенд поднимается обычным docker compose up -d, override не нужен.
  • п. 14 — TRUSTED_PROXIES пустой по умолчанию и на стенде: X-Forwarded-For игнорируется, пока прокси явно не указаны.

Изменения

п. 16 — порт (docker-compose.yml, .env.example, README)

  • ports: - "${APP_BIND:-0.0.0.0}:${APP_PORT:-8000}:8000". Внутренний порт контейнера не меняется (8000).
  • .env.example: APP_PORT=8000, APP_BIND=0.0.0.0 с комментарием (127.0.0.1 — только за reverse-proxy на том же хосте).
  • Приложение эти переменные не читает (только compose); Settings — extra="ignore", конфликтов нет.

п. 14 — реальный IP клиента (app/config.py, app/security.py или новый app/client_ip.py, app/ui/routes.py)

  • Settings.trusted_proxies: str = "" — CIDR через запятую (10.0.0.0/8,172.16.0.0/12); одиночный адрес — как /32//128. Невалидный CIDR — проблема в insecure_settings (отказ старта, как у секретов): «TRUSTED_PROXIES: неверная сеть <значение>» (это не секрет — значение можно показать).
  • client_ip(request) -> str: peer = request.client.host (или "unknown").
    • Если peer не входит в доверенные сети (или список пуст) — вернуть peer, X-Forwarded-For игнорируется.
    • Иначе разобрать X-Forwarded-For (все заголовки, через запятую) справа налево: пропускать адреса из доверенных сетей; первый недоверенный — IP клиента. Невалидная запись в цепочке — остановиться и вернуть последний валидный разобранный адрес (или peer, если таких нет). Все адреса доверенные / заголовка нет — peer.
    • Сети разбираются один раз (кэш по строке настройки).
  • login (app/ui/routes.py): ip = client_ip(request) вместо request.client.host — ключ блокировки login:<ip> и data.ip в auth.*. Другие места с request.client — найти grep'ом и перевести на client_ip.
  • uvicorn --proxy-headers не включать (иначе uvicorn сам подменит request.client по своей логике --forwarded-allow-ips).

Тесты (минимально, tests/test_app.py)

  • client_ip: пустой TRUSTED_PROXIES + XFF → peer (подделка игнорируется); доверенный peer + XFF: 1.2.3.4, 10.0.0.2 при 10.0.0.0/8 → 1.2.3.4; недоверенный peer + XFF → peer; мусор в XFF → последний валидный / peer; IPv6.
  • Блокировка входа за прокси: TestClient(client=("10.0.0.5", …)), TRUSTED_PROXIES=10.0.0.0/8, 5 неверных с XFF: 1.1.1.1 → 429 для 1.1.1.1, с XFF: 2.2.2.2 вход проходит.
  • insecure_settings: невалидный TRUSTED_PROXIES → проблема.

Документация

README: «Конфигурация» (APP_PORT, APP_BIND, TRUSTED_PROXIES), «Быстрый старт» (адрес с APP_PORT), «Безопасность» (вход за прокси — заменить «не поддерживается» на описание TRUSTED_PROXIES и предупреждение: доверие к сети Docker-моста делает доверенными и процессы хоста), «Эксплуатация» (стенд на 8001 — через APP_PORT в .env, override не нужен), число тестов, строка 025 в истории. summary.md — оркестратор.

Исполнение

Исполнитель (Sonnet): код, compose, .env.example, тесты, README; пересборка стенда обычной командой docker compose up -d --build --force-recreate (без override — порт 8001 берётся из .env). Тесты не запускает, не коммитит, .env не читает.

Проверка

  • pytest — все зелёные.
  • docker compose config → опубликован 8001 (из .env); контейнер Up на 0.0.0.0:8001->8000; /login 200; новый код.
  • Подделка: с хоста curl -H "X-Forwarded-For: 9.9.9.9" — 5 неверных входов → 6-й 429 уже без заголовка (блокируется реальный адрес, XFF проигнорирован); событие auth.failed содержит реальный IP (адрес шлюза Docker), не 9.9.9.9. Блокировка снимается перезапуском контейнера.
  • Боевые данные — сверка по ID.
  • Ручная проверка — пользователь: UI на http://<хост>:8001.