fix: isolate local ports and proxy trust defaults
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
DATABASE_URL=postgresql+psycopg://rf4:rf4_local@localhost:5432/rf4_spotter
|
DATABASE_URL=postgresql+psycopg://rf4:rf4_local@localhost:5432/rf4_spotter
|
||||||
|
# Local Compose ports bind to loopback; set 0.0.0.0 only for an intentional LAN demo.
|
||||||
|
RF4_BIND_ADDRESS=127.0.0.1
|
||||||
APP_VERSION=0.1.0
|
APP_VERSION=0.1.0
|
||||||
APP_REVISION=dev
|
APP_REVISION=dev
|
||||||
PUBLIC_API_URL=http://localhost:8000
|
PUBLIC_API_URL=http://localhost:8000
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ PUBLIC_CACHE_SECONDS=20
|
|||||||
|
|
||||||
ADMIN_TOKEN=replace-with-at-least-32-random-characters
|
ADMIN_TOKEN=replace-with-at-least-32-random-characters
|
||||||
RATE_LIMIT_SECRET=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
|
||||||
ADMIN_BASIC_USER=rf4admin
|
ADMIN_BASIC_USER=rf4admin
|
||||||
# Generate with: docker run --rm caddy:2.10.2-alpine caddy hash-password --plaintext 'YOUR PASSWORD'
|
# 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'
|
ADMIN_BASIC_PASSWORD_HASH='replace-with-caddy-password-hash'
|
||||||
|
|||||||
@@ -132,7 +132,9 @@ services:
|
|||||||
IMPORT_INTERVAL_SECONDS: ${IMPORT_INTERVAL_SECONDS:-3600}
|
IMPORT_INTERVAL_SECONDS: ${IMPORT_INTERVAL_SECONDS:-3600}
|
||||||
PUBLIC_CACHE_SECONDS: ${PUBLIC_CACHE_SECONDS:-20}
|
PUBLIC_CACHE_SECONDS: ${PUBLIC_CACHE_SECONDS:-20}
|
||||||
RATE_LIMIT_SECRET: ${RATE_LIMIT_SECRET:?Set RATE_LIMIT_SECRET}
|
RATE_LIMIT_SECRET: ${RATE_LIMIT_SECRET:?Set RATE_LIMIT_SECRET}
|
||||||
TRUSTED_PROXY_CIDRS: '${TRUSTED_PROXY_CIDRS:-"127.0.0.1/32,::1/128"}'
|
# 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"}'
|
||||||
RETENTION_SUBMISSION_DAYS: ${RETENTION_SUBMISSION_DAYS:-1}
|
RETENTION_SUBMISSION_DAYS: ${RETENTION_SUBMISSION_DAYS:-1}
|
||||||
RETENTION_UNREVIEWED_DAYS: ${RETENTION_UNREVIEWED_DAYS:-30}
|
RETENTION_UNREVIEWED_DAYS: ${RETENTION_UNREVIEWED_DAYS:-30}
|
||||||
RETENTION_APPROVED_PERSONAL_DAYS: ${RETENTION_APPROVED_PERSONAL_DAYS:-180}
|
RETENTION_APPROVED_PERSONAL_DAYS: ${RETENTION_APPROVED_PERSONAL_DAYS:-180}
|
||||||
@@ -230,7 +232,7 @@ services:
|
|||||||
S3_SECRET_KEY: ${S3_SECRET_KEY:?Set S3_SECRET_KEY}
|
S3_SECRET_KEY: ${S3_SECRET_KEY:?Set S3_SECRET_KEY}
|
||||||
SEED_DEMO_DATA: "false"
|
SEED_DEMO_DATA: "false"
|
||||||
RATE_LIMIT_SECRET: ${RATE_LIMIT_SECRET:?Set RATE_LIMIT_SECRET}
|
RATE_LIMIT_SECRET: ${RATE_LIMIT_SECRET:?Set RATE_LIMIT_SECRET}
|
||||||
TRUSTED_PROXY_CIDRS: '${TRUSTED_PROXY_CIDRS:-"127.0.0.1/32,::1/128"}'
|
TRUSTED_PROXY_CIDRS: '${TRUSTED_PROXY_CIDRS:-"172.16.0.0/12,127.0.0.1/32,::1/128"}'
|
||||||
COMMUNITY_IMPORT_INTERVAL_SECONDS: ${COMMUNITY_IMPORT_INTERVAL_SECONDS:-1800}
|
COMMUNITY_IMPORT_INTERVAL_SECONDS: ${COMMUNITY_IMPORT_INTERVAL_SECONDS:-1800}
|
||||||
RF4MAP_POINT_URL: ${RF4MAP_POINT_URL:-https://rf4map.ru/points/275}
|
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}
|
RF4POSTS_SPOT_URL: ${RF4POSTS_SPOT_URL:-https://rf4-posts.com/ru/spots/d0c6d9c6-4ebf-49a7-98a8-9a562553a8ee}
|
||||||
|
|||||||
+4
-4
@@ -20,8 +20,8 @@ services:
|
|||||||
MINIO_ROOT_USER: ${S3_ACCESS_KEY:-rf4-local}
|
MINIO_ROOT_USER: ${S3_ACCESS_KEY:-rf4-local}
|
||||||
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:-rf4-local-secret}
|
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:-rf4-local-secret}
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000"
|
- "${RF4_BIND_ADDRESS:-127.0.0.1}:9000:9000"
|
||||||
- "9001:9001"
|
- "${RF4_BIND_ADDRESS:-127.0.0.1}:9001:9001"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-fsS", "http://localhost:9000/minio/health/live"]
|
test: ["CMD", "curl", "-fsS", "http://localhost:9000/minio/health/live"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
@@ -75,7 +75,7 @@ services:
|
|||||||
minio-init:
|
minio-init:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "${RF4_BIND_ADDRESS:-127.0.0.1}:8000:8000"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/ready')"]
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/ready')"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
@@ -104,7 +104,7 @@ services:
|
|||||||
api:
|
api:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "4321:4321"
|
- "${RF4_BIND_ADDRESS:-127.0.0.1}:4321:4321"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "node", "-e", "fetch('http://localhost:4321').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
|
test: ["CMD", "node", "-e", "fetch('http://localhost:4321').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ R-пункты уточняют критерии существующих B/G/U/
|
|||||||
- [x] **R08 · P1 · Достоверная аналитика снастей (G07).** Публичные пороги закреплены минимум на 3 наблюдениях и 2 игроках независимо от URL-параметров; окно и freshness используют время улова с fallback на время импорта, ответ ограничен `limit`, а canonical ID назначается только при полном однозначном покрытии компонентных наблюдений. `1/1` нельзя превратить в рекомендацию, unresolved не получает ложную привязку.
|
- [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] **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 координацию.
|
- [x] **R10 · P1 · Единый cooldown и стадии запроса.** Community CLI уже освобождал резерв до HTTP-ответа; media CLI теперь использует тот же контракт: DNS/connection/OSError освобождают только собственный резерв, HTTPError или ошибка валидированного тела сохраняют cooldown, batch освобождает окно только если ни один asset не получил ответ. Offline regression добавлен; scheduler сохраняет site-wide координацию.
|
||||||
- [ ] **R18 · P1 · Границы локального и production запуска.** Loopback для dev-портов с явным opt-in LAN; проверить доверенные proxy/client-IP и rate-limit на двух клиентах. Критерий: default dev не открыт в LAN, production различает клиентов. Реальные серверные gates — A07.
|
- [ ] **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.
|
||||||
- [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] **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] **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, повтор не требует заново заполнять весь улов.
|
- [x] **R15 · P2 · Восстановление пользовательских действий.** Report UI теперь различает idempotency-конфликт и обычную ошибку, восстанавливает draft и предлагает повтор с новым ключом; plan получил восьмисекундный undo после удаления с обработкой storage errors. Draft сохраняется при 422/429/timeout, повтор не требует заново заполнять весь улов.
|
||||||
|
|||||||
Reference in New Issue
Block a user