Files
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

59 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План: 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`.