034 Публикация стенда как https://rxipam.rxmsk.ru через общий Caddy хоста (Caddyfile вне репозитория): UI и /api/v1 доступны из интернета, /docs, /redoc, /openapi.json — только из частных сетей (RFC 1918/4193, loopback). Caddy проксирует на опубликованный порт приложения — общая Docker-сеть отклонена из-за коллизии имён app/db с Nextcloud. TRUSTED_PROXIES=172.16.0.0/12: реальный IP клиента из X-Forwarded-For. Интеграционный тест журнала больше не проверяет подделку XFF с хоста (хост за Caddy доверенный), логику покрывает unit-тест. 035 Дата и IP последнего входа в разделе «Пользователи»: users.last_login_at / last_login_ip (миграция 0014 с заполнением из журнала), запись при успешном входе, UserOut, колонка в UI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.1 KiB
Итог: публикация через Caddy — rxipam.rxmsk.ru (изменение 034)
План: PLAN.md. Стенд ipam_control_006 опубликован как https://rxipam.rxmsk.ru через общий Caddy хоста (/opt/lvraid/apps/caddy).
Из интернета доступны UI и /api/v1; Swagger и OpenAPI — только из частных сетей.
Отклонение от плана: без общей Docker-сети
План предлагал подключить приложение к сети Caddy app_internal (docker-compose.caddy.yml, алиас ipam-app).
При применении обнаружилась коллизия имён. Compose регистрирует имя сервиса как DNS-алиас в каждой сети контейнера,
а в app_internal уже есть db и app стека Nextcloud:
- приложение по имени
dbушло в базу Nextcloud и не стартовало; - Caddy адресует Nextcloud как
app:9000— второйappв той же сети ломал бы Nextcloud.
Подключение откатили примерно через минуту: приложение всё это время перезапускалось, в логе Nextcloud ошибок нет, rxcloud отвечает.
Итоговая схема: Caddy проксирует на опубликованный порт приложения через шлюз своей сети — reverse_proxy 172.19.0.1:8088.
Сети не объединяются, docker-compose.yml не менялся, docker-compose.caddy.yml не создавался.
Что сделано
| Где | Изменение |
|---|---|
/opt/lvraid/apps/caddy/Caddyfile (вне репозитория) |
Блок rxipam.rxmsk.ru: access-лог rxipam.log (JSON, INFO, ротация); @docs_external (/docs, /docs/*, /redoc, /redoc/*, /openapi.json и not remote_ip private_ranges) → 404; reverse_proxy 172.19.0.1:8088; HSTS, -Server. Бэкап: Caddyfile.bak-2026-09-27. Правка на месте, inode сохранён — контейнер видит новый файл; применено caddy reload |
.env стенда (вне репозитория) |
TRUSTED_PROXIES=172.16.0.0/12: запросы Caddy приходят в приложение с шлюза Docker-моста (172.31.0.1) через NAT хоста. Диапазон вместо /32 — подсеть проекта может смениться при пересоздании сети |
.env.example |
Закомментированный пример TRUSTED_PROXIES для публикации через Caddy на том же хосте |
README.md |
«Публикация и эксплуатация» — фактическая схема вместо примера; строка 034 в истории |
tests/test_journal.py |
Из интеграционного test_client_ip_and_request_meta_recorded убрана проверка «подделка XFF игнорируется»: с TRUSTED_PROXIES=172.16.0.0/12 запросы тестов с хоста (через 172.31.0.1) доверенные. Логику покрывает unit-тест test_resolve_client_ip_trusts_forwarded_header_only_from_proxies |
Проверки
caddy validate— конфигурация корректна;caddy reloadбез рестарта. Соседние сайты после reload: vault, artstore, aycv — 200, rxcloud — 302 (редирект на вход).- Сертификат Let's Encrypt для
rxipam.rxmsk.ruвыпущен (DNS-01, Cloudflare). - Из частной сети через Caddy:
/200,/api/v1/auth/meбез токена 401,/docsи/openapi.json200. Заголовки HSTS и CSP присутствуют. - Matcher Swagger проверен на временном Caddy того же образа (тот же
path, без условия по IP):/docs,//docs,/%64ocs,/docs/oauth2-redirect,/openapi.json,/%6fpenapi.json,/redoc,/%72edoc→ 404;/200, API 401. Проверка важна: приложение само отдаёт Swagger на URL-кодированные пути (/%64ocs→ 200), и закрывает их именно Caddy. private_rangesраскрывается в192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8, 127.0.0.1/8, fd00::/8, ::1.- Реальный IP клиента: запрос через Caddy с LAN-адреса хоста и подложным
X-Forwarded-For: 8.8.8.8→ в журнале192.168.5.9. Подложный заголовок отброшен Caddy, реальный адрес получен из XFF. pytest -q— 15 passed (после правки теста выше).- Пробные неудачные входы (
rv-probe-034,rv-probe-034b) остались в журнале как событияsession.failed.
Не проверено (требует пользователя)
- DNS: CNAME
rxipam→mkt.rxmsk.ru(proxy Cloudflare выключен) ещё не создан — имя не резолвится. - Доступ из интернета (мобильный интернет или внешний хост):
/200 и вход в UI;/docs,/openapi.json→ 404; вrxipam.logу этих запросов публичныйremote_ip; в журнале приложения — публичныйclient_ipсобытия входа.
Рекомендации
- Доверие
172.16.0.0/12означает, что процессы и контейнеры на самом хосте (приходят через шлюз Docker-моста) могут подставить свой IP вX-Forwarded-For. LAN- и интернет-клиенты этого не могут: у LAN-клиентов, идущих напрямую на:8088, в журнале реальные адреса. Исключить хост из доверенных можно только прямой сетью Caddy ↔ приложение без NAT — это требует переименовать сервисыapp/dbстенда (коллизия выше), отдельная задача. - Split DNS на MikroTik (
rxipam.rxmsk.ru→192.168.5.9в LAN): иначе LAN-клиенты через hairpin видны как192.168.5.253и делят лимит «20 неудач на IP». - План 031, часть 1 (
DOCS_ENABLED=falseпо умолчанию) — вторая линия защиты Swagger, отдельной задачей. 172.19.0.1— шлюз внешней сетиapp_internal. При её пересоздании с другой подсетью обновитьreverse_proxyв Caddyfile.
Откат
- Caddy: содержимое
Caddyfile.bak-2026-09-27записать поверхCaddyfileна месте, затемdocker exec caddy caddy reload --config /etc/caddy/Caddyfile. - Стенд: убрать
TRUSTED_PROXIESиз.env,docker compose -p ipam_control_006 up -d.