From 14ccfec5e60740872da52cda6037b689bf0b6e58 Mon Sep 17 00:00:00 2001 From: IK Date: Tue, 22 Sep 2026 20:36:33 +0700 Subject: [PATCH] fix: narrow production proxy trust network --- .env.production.example | 5 +++-- compose.production.yaml | 11 +++++++---- docs/ROADMAP.md | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.env.production.example b/.env.production.example index ac367af..a6984ef 100644 --- a/.env.production.example +++ b/.env.production.example @@ -14,8 +14,9 @@ PUBLIC_CACHE_SECONDS=20 ADMIN_TOKEN=replace-with-at-least-32-random-characters RATE_LIMIT_SECRET=replace-with-at-least-32-random-characters -# Caddy is trusted only from the private Docker network; use the exact edge CIDR if customized. -TRUSTED_PROXY_CIDRS=172.16.0.0/12,127.0.0.1/32,::1/128 +# Keep these two values aligned if the Docker edge network is customized. +EDGE_NETWORK_SUBNET=172.30.0.0/24 +TRUSTED_PROXY_CIDRS=172.30.0.0/24,127.0.0.1/32,::1/128 ADMIN_BASIC_USER=rf4admin # Generate with: docker run --rm caddy:2.10.2-alpine caddy hash-password --plaintext 'YOUR PASSWORD' ADMIN_BASIC_PASSWORD_HASH='replace-with-caddy-password-hash' diff --git a/compose.production.yaml b/compose.production.yaml index f8de485..7bd3b52 100644 --- a/compose.production.yaml +++ b/compose.production.yaml @@ -132,9 +132,9 @@ services: IMPORT_INTERVAL_SECONDS: ${IMPORT_INTERVAL_SECONDS:-3600} PUBLIC_CACHE_SECONDS: ${PUBLIC_CACHE_SECONDS:-20} RATE_LIMIT_SECRET: ${RATE_LIMIT_SECRET:?Set RATE_LIMIT_SECRET} - # Caddy reaches API over the private Docker network, not loopback. - # Override with the exact edge-network CIDR when the deployment network is customized. - TRUSTED_PROXY_CIDRS: '${TRUSTED_PROXY_CIDRS:-"172.16.0.0/12,127.0.0.1/32,::1/128"}' + # Caddy reaches API over the dedicated private edge network, not loopback. + # Keep this CIDR aligned with EDGE_NETWORK_SUBNET when customizing the network. + TRUSTED_PROXY_CIDRS: '${TRUSTED_PROXY_CIDRS:-"172.30.0.0/24,127.0.0.1/32,::1/128"}' RETENTION_SUBMISSION_DAYS: ${RETENTION_SUBMISSION_DAYS:-1} RETENTION_UNREVIEWED_DAYS: ${RETENTION_UNREVIEWED_DAYS:-30} RETENTION_APPROVED_PERSONAL_DAYS: ${RETENTION_APPROVED_PERSONAL_DAYS:-180} @@ -232,7 +232,7 @@ services: S3_SECRET_KEY: ${S3_SECRET_KEY:?Set S3_SECRET_KEY} SEED_DEMO_DATA: "false" RATE_LIMIT_SECRET: ${RATE_LIMIT_SECRET:?Set RATE_LIMIT_SECRET} - TRUSTED_PROXY_CIDRS: '${TRUSTED_PROXY_CIDRS:-"172.16.0.0/12,127.0.0.1/32,::1/128"}' + TRUSTED_PROXY_CIDRS: '${TRUSTED_PROXY_CIDRS:-"172.30.0.0/24,127.0.0.1/32,::1/128"}' COMMUNITY_IMPORT_INTERVAL_SECONDS: ${COMMUNITY_IMPORT_INTERVAL_SECONDS:-1800} RF4MAP_POINT_URL: ${RF4MAP_POINT_URL:-https://rf4map.ru/points/275} RF4POSTS_SPOT_URL: ${RF4POSTS_SPOT_URL:-https://rf4-posts.com/ru/spots/d0c6d9c6-4ebf-49a7-98a8-9a562553a8ee} @@ -262,6 +262,9 @@ services: networks: edge: + ipam: + config: + - subnet: ${EDGE_NETWORK_SUBNET:-172.30.0.0/24} backend: internal: true diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 4d9f4d0..aaa02ef 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -33,7 +33,7 @@ R-пункты уточняют критерии существующих B/G/U/ - [x] **R08 · P1 · Достоверная аналитика снастей (G07).** Публичные пороги закреплены минимум на 3 наблюдениях и 2 игроках независимо от URL-параметров; окно и freshness используют время улова с fallback на время импорта, ответ ограничен `limit`, а canonical ID назначается только при полном однозначном покрытии компонентных наблюдений. `1/1` нельзя превратить в рекомендацию, unresolved не получает ложную привязку. - [x] **R09 · P1 · Полное удаление личных данных.** Retention и admin-delete загружают дочерние компоненты, очищают их `source_url/raw_payload` вместе с родительским report и сбрасывают upload-token; retention теперь обнаруживает старые reports даже при уже очищенных родительских полях, если provenance осталась в component. Повторная очистка остаётся безопасной; S3/БД failure drill — отдельный эксплуатационный gate. - [x] **R10 · P1 · Единый cooldown и стадии запроса.** Community CLI уже освобождал резерв до HTTP-ответа; media CLI теперь использует тот же контракт: DNS/connection/OSError освобождают только собственный резерв, HTTPError или ошибка валидированного тела сохраняют cooldown, batch освобождает окно только если ни один asset не получил ответ. Offline regression добавлен; scheduler сохраняет site-wide координацию. -- [ ] **R18 · P1 · Границы локального и production запуска.** Первый шаг выполнен: локальный Compose публикует API, web и MinIO только на loopback через `RF4_BIND_ADDRESS` с явным opt-in LAN; production по умолчанию доверяет приватной Docker-сети Caddy и позволяет сузить `TRUSTED_PROXY_CIDRS` до точного edge-CIDR. Rate-limit игнорирует XFF от недоверенного peer и различает двух клиентов через trusted proxy. Остались реальная proxy-цепочка на сервере, точный CIDR после развёртывания и A07 acceptance. +- [ ] **R18 · P1 · Границы локального и production запуска.** Первый шаг выполнен: локальный Compose публикует API, web и MinIO только на loopback через `RF4_BIND_ADDRESS` с явным opt-in LAN; production использует выделенную edge-сеть `172.30.0.0/24`, а `TRUSTED_PROXY_CIDRS` синхронизирован с её CIDR и может быть явно изменён вместе с `EDGE_NETWORK_SUBNET`. Rate-limit игнорирует XFF от недоверенного peer и различает двух клиентов через trusted proxy. Остались реальная proxy-цепочка на сервере и A07 acceptance. - [x] **R11 · P2 · Честное качество и происхождение media (B25).** API использует точный hostname allowlist для `rf4db`/`rf4map`/`rf4-stat`, неизвестные и malformed URL получают `unknown`, а не ложный `rf4-official`. UI различает низкое разрешение и наличие проверенной card-версии, не утверждая отсутствие альтернатив без данных manifest. Cache/revocation и production visual acceptance остаются отдельными gates. - [x] **R14 · P2 · Подтверждённый каталог и demo seed.** `SEED_DEMO_DATA=false` больше не создаёт редакционные описания тестовых точек; demo-уловы имеют отдельный `seed:` namespace и catalog audit считает их отдельно от подтверждённых reports. Loaded canonical counts и external staging остаются раздельными, media count не используется как catalog count. - [x] **R15 · P2 · Восстановление пользовательских действий.** Report UI теперь различает idempotency-конфликт и обычную ошибку, восстанавливает draft и предлагает повтор с новым ключом; plan получил восьмисекундный undo после удаления с обработкой storage errors. Draft сохраняется при 422/429/timeout, повтор не требует заново заполнять весь улов.