Доверенные прокси (реальный 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>
This commit is contained in:
1 parent
6ff7f1b5f1
commit
5367cb5e9c
9 files changed
+211
-8
No files matched your search
@@ -0,0 +1,58 @@
|
||||
# План: 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`.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Итоги: 025 — доверенные прокси (реальный IP клиента) и параметризация порта
|
||||
|
||||
Источник — ревью `docs/reviews/2026-09-28-1735-codebase-review.md`, пункты 14 и 16.
|
||||
|
||||
## Сделано
|
||||
- **п. 16 — порт**: `docker-compose.yml` публикует `"${APP_BIND:-0.0.0.0}:${APP_PORT:-8000}:8000"` (как в ipam_control);
|
||||
`.env.example` — `APP_PORT`, `APP_BIND`. В `.env` стенда оркестратор добавил `APP_PORT=8001` (секреты не менялись) — стенд поднимается
|
||||
обычным `docker compose up -d`, override-файл вне репозитория больше не нужен.
|
||||
- **п. 14 — `TRUSTED_PROXIES`** (CIDR через запятую, по умолчанию пусто; на стенде пусто): `app/security.py::client_ip` — пустой список или peer
|
||||
не из доверенной сети → `X-Forwarded-For` игнорируется; иначе цепочка (все заголовки) справа налево, доверенные адреса пропускаются, первый
|
||||
недоверенный — IP клиента; невалидная запись обрывает разбор (последний валидный адрес прокси или peer — клиентский адрес не подставить);
|
||||
все доверенные / нет заголовка — peer. `app/config.py::trusted_networks` (кэш), невалидный CIDR — проблема в `insecure_settings` (отказ старта).
|
||||
`login` использует `client_ip`: ключ блокировки и `data.ip` в `auth.*`. uvicorn `--proxy-headers` не включён.
|
||||
- README: «Быстрый старт», «Конфигурация» (`APP_PORT`, `APP_BIND`, `TRUSTED_PROXIES`), «Безопасность» (разбор XFF, предупреждение о доверии
|
||||
к сети Docker-моста), «Эксплуатация» (стенд на 8001 через `.env`), число тестов, строка 025.
|
||||
|
||||
## Проверено
|
||||
- `pytest`: 36 из 36 (новые: алгоритм `client_ip` — пустой список, недоверенный peer, цепочка с прокси, все доверенные, мусор, нет заголовка, IPv6;
|
||||
блокировка входа за доверенным прокси; невалидный `TRUSTED_PROXIES`).
|
||||
- `docker compose config`: опубликован 8001 из `.env`; стенд без override — `0.0.0.0:8001->8000`, `/login` 200.
|
||||
- Подделка на стенде: 5 неверных входов с `X-Forwarded-For: 9.9.9.9` → 401; далее без заголовка и с `8.8.8.8` → 429 (заблокирован реальный адрес);
|
||||
события `auth.failed` ×4, `auth.locked` ×1 с IP `172.28.0.1` (шлюз Docker), не `9.9.9.9`. Блокировка снята перезапуском контейнера.
|
||||
- Боевые данные: группы и устройства совпадают по ID; добавлены 5 событий проверки (и 2 бэкапа, запущенные пользователем в UI в это время).
|
||||
|
||||
## Оговорки
|
||||
- С reverse-proxy реальный IP появится только после задания `TRUSTED_PROXIES`; `X-Forwarded-Proto` (для Secure-cookie) не разбирается — флаг задаётся `SESSION_COOKIE_SECURE`.
|
||||
- Ручная проверка UI пользователем на момент коммита не подтверждена.
|
||||
Reference in new issue
Block a user