Внесены правки в документацию

This commit is contained in:
mi
2026-08-14 11:51:21 +03:00
parent 9eb8b2bc6e
commit e06a77ee1d
9 changed files with 286 additions and 22 deletions
@@ -27,6 +27,15 @@
- **Nginx ВМ2** независимо терминирует public HTTPS на отдельном host и публикует только exact Contact/alert webhook `bitrix-sync`. Отдельный private listener `8443` по internal CA маршрутизирует allow-listed Message Safety/internal paths.
- Между public route ВМ1 и ВМ2 нет reverse-proxy chain или fallback. Каждый nginx имеет собственные DNS, сертификат, rate limits и release lifecycle.
После Safety cutover root Compose ВМ1 не содержит local `message-safety`,
его Redis DB2 или `bitrix-sync`: это компоненты ВМ2. `api-backend` использует
`MESSAGE_SAFETY_URL=https://<private-vm2-name>:8443` и отдельный read-only CA
bind из root-owned staging-каталога, доступного фактическому UID/GID
`api-backend`. Validator обязан принимать private HTTPS hostname/SAN и
отклонять cross-host Docker DNS либо plaintext production URL. Наличие legacy
local Safety одновременно с remote URL является cutover error, а не
автоматическим fallback.
### Структура Compose через `include`
Каждый сервис описывается в собственном `docker-compose.yml` и подключается в root-файл своей VM директивой `include`:
@@ -119,7 +128,13 @@ Root Compose ВМ2 включает собственный nginx с public/priva
logs и persistent state;
- отдельный volume/tmpfs для каждого writable path с явными
`uid/gid/mode`, размером и mount flags;
- healthcheck именно того процесса, который реально запущен в контейнере;
- bind file проверяется реальным read-test от container UID/GID и отказом
постороннему UID, а не только host `stat`;
- новый named volume для non-root consumer подготавливается idempotent
one-shot init service с минимальными capabilities; consumer использует
`depends_on: condition: service_completed_successfully`;
- healthcheck именно того процесса, который реально запущен в контейнере, и
только binaries, наличие которых доказано внутри exact pinned digest;
- restart semantics: успешный one-shot exit не должен превращаться в
бесконечный restart/download loop.
@@ -131,6 +146,8 @@ mount, а не `read_only: false`, root, `privileged` или broad capabilities.
Compose file secrets с bind-backed `file:` могут игнорировать декларативные
`uid/gid/mode`. Их фактические host permissions создаёт secret materializer;
rollout проверяет owner/mode из контейнера и с host, не полагаясь на YAML.
`root:root 0600` несовместим с non-root consumer; dedicated group/ACL должен
совпадать с фактическим container GID и не давать доступ постороннему UID.
При сборке images необхоидмо добавлять нормализацию CRLF→LF
(например, RUN sed -i 's/\r$//' <directory/service-name> \
@@ -172,6 +189,13 @@ docker compose exec api-backend ruff format .
- pre-start config test выполняет реальный image entrypoint. До запуска
upstream-контейнеров их host variables временно подменяются loopback IP
только в test container; production Compose сохраняет service DNS names;
- после readiness upstream выполняется повторный `nginx -t` с production
Docker DNS names и контролируемый reload/recreate nginx. Первый ordered
start и `depends_on: service_started` не гарантируют, что hostname уже
резолвится;
- при recreate/смене IP upstream действует та же post-ready reload policy либо
используется явно протестированный dynamic resolver; stale IP/DNS в
загруженной nginx config недопустим;
- **политика HTTP/HTTPS по доменам** (каноническое правило — [`arch-01-system-architecture.md`](arch-01-system-architecture.md), «Принципы безопасности»):
- **веб-домен** (frontend, SPA, статика): `listen 80` допускается **только** для безусловного редиректа `301`/`308` на HTTPS; обработка бизнес-логики по HTTP запрещена;
- **API-домен** (если выделен отдельный host, напр. `api.example.ru`): **не** слушает порт `80`; только `listen 443 ssl`; HTTP-запросы к API-домену недоступны;
@@ -235,7 +259,11 @@ Python FastAPI backend.
- runtime role читает immutable active `message_safety.config_versions`; создавать/активировать config может только отдельный migration/config-admin job;
- использует локальный Redis Safety только для hot cache/rate/wakeup; PostgreSQL владеет task queue/leases;
- запускает async workers для file scan из S3-quarantine;
- API container получает read-only root-owned `/etc/han-chat/message-safety-mode.env`; менять его и перезапускать stack может только fixed helper, разрешённый `deploy` через exact-argument sudoers;
- API container получает read-only
`/etc/han-chat/message-safety-mode.env` с host contract
`root:han-message-safety 0640` и GID `10001`; менять его и перезапускать
stack может только fixed helper, разрешённый `deploy` через exact-argument
sudoers;
- экспортирует traces/logs в `otel-collector`;
- таймауты: POST check 5 s, GET task 2 s, file scan 60 s (см. [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), переменные `MESSAGE_SAFETY_*`).
@@ -402,6 +430,13 @@ Generated config, PID/socket, cache и logs без retention размещают
ограниченных tmpfs. Один writable volume не объединяет config/executable со
state.
Local OTEL queue на каждой VM использует отдельный persistent named volume.
Перед collector запускается `otel-queue-init`: ограниченный one-shot service
выставляет mount root в `10001:10001 0700`, не имеет сети/secrets и завершается
до старта collector. Collector остаётся non-root; запуск collector от root,
`chmod 0777` и надежда на автоматическое наследование ownership из image
запрещены.
## Переменные окружения
Каждая VM имеет свой allow-listed non-secret env manifest. Секреты доставляются отдельными service files согласно arch-04/06; общий env/secret bundle двух VM запрещён.
@@ -478,6 +513,9 @@ state.
- document/entity/domain/member fields проверяются в `bitrix-sync`; local app/event handler для CRM sync не используется;
- кэширование отключено; source IP/body/method/rate limits применяются до private proxy; IP rejects экспортируются в telemetry без IP label;
- при sync disabled/cutover route закрыт либо возвращает retryable `503`, а не `202 ignored`;
- `BITRIX_SYNC_ENABLED=false` требует активный edge allow-list только с
`deny all;`; наличие `allow` при disabled и отсутствие reviewed `allow` при
enabled являются preflight error;
- `/internal/sync/v1/*` не публикуется наружу (только internal network + `BITRIX_SYNC_SERVICE_TOKEN`).
## Rate limits и защита от abuse
@@ -545,6 +583,14 @@ WAF не заменяет обязательные лимиты, валидац
Наружу через `nginx` публикуются только health endpoint, которые нужны Bitrix24 install/callback validation или внешнему мониторингу. Internal services (`message-safety`, internal `bitrix-sync`, Redis, otel) проверяются только из Docker/VPC-сети.
Healthcheck считается валидным только после запуска внутри exact production
image digest с теми же `user`, `read_only`, mounts и dropped capabilities.
Команда не может предполагать наличие `curl`, `wget`, shell или package,
которого нет в image/SBOM; `ExitCode 127` является дефектом release, а не
`unhealthy` приложения. Для minimal nginx предпочтителен гарантированно
доступный native binary/config test либо специально включённый и проверенный
client.
Healthcheck не копируется между разными commands одного image без проверки.
Если updater не запускает daemon, daemon-socket healthcheck для него
отключается. Freshness данных контролируется отдельной метрикой/проверкой
@@ -558,7 +604,7 @@ host-side secrets/TLS materialized, controlled migrations и seed заверше
ВМ2 запускается в порядке:
1. `redis-safety` и local `otel-collector`;
1. `redis-safety`, `otel-queue-init`, затем local `otel-collector`;
2. `freshclam`, затем `clamd` до состояния healthy;
3. Message Safety API/worker и `bitrix-sync`;
4. nginx — последним, после успешного config test;
@@ -566,7 +612,7 @@ host-side secrets/TLS materialized, controlled migrations и seed заверше
ВМ1 запускается в порядке:
1. Redis и `otel-collector`;
1. Redis, `otel-queue-init`, затем `otel-collector`;
2. Keycloak, `sms-service`/worker, `api-backend` и `bitrix-local-app` с их
readiness-зависимостями;
3. notification expire/cleanup workers после готовности `api-backend`;
@@ -574,7 +620,11 @@ host-side secrets/TLS materialized, controlled migrations и seed заверше
Порядок rollout SMS подробнее задаёт module-11/module-10. Зависимости запуска не образуют цикл: Keycloak стартует при недоступном `sms-service`; это блокирует только новые real-mode orders, а verify уже active challenges продолжается по snapshot.
`depends_on` не заменяет проверку готовности. Сервисы должны уметь ждать зависимости или корректно деградировать. `api-backend` может стать ready для read API до ВМ2, но send endpoint обязан fail-closed при недоступной требуемой capability Safety.
`depends_on` не заменяет проверку готовности. Для edge применяются три слоя:
ordered start, readiness dependencies и post-ready config test/reload с
production DNS names. Сервисы должны уметь ждать зависимости или корректно
деградировать. `api-backend` может стать ready для read API до ВМ2, но send
endpoint обязан fail-closed при недоступной требуемой capability Safety.
## Развёртывание на ВМ1 и ВМ2
@@ -584,20 +634,31 @@ host-side secrets/TLS materialized, controlled migrations и seed заверше
4. Public nginx ВМ2 публикует `80/443`; `80` используется только для ACME/redirect, `443` — только exact CRM webhook. Private `8443` разрешён только от SG ВМ1 и ops для Message Safety/internal access.
5. Deploy/cutover ВМ2 не требует изменения public routes ВМ1. Для `bitrix-sync` rollback закрывает webhook routes на nginx ВМ2 либо возвращает retryable `503`, останавливает claims и сохраняет durable tasks/mapping; возврат к фиктивному `202 ignored` запрещён.
Host firewall обеих VM учитывает post-DNAT semantics `DOCKER-USER`: policy
published nginx ports сопоставляет original host destination через conntrack,
как определено в arch-06. Проверка только UFW INPUT или текущего container
`--dport` не является достаточной.
Перед первым `up` и после смены любого image digest обязательны permission
gates:
1. проверить LF/shebang и root ownership release-артефактов;
2. сверить UID/GID контейнеров с owner/mode host bind mounts, secret files и
2. проверить manifest modes и executable bit scripts/preflight/hooks; blanket
`F644` для всего release запрещён;
3. сверить UID/GID контейнеров с owner/mode host bind mounts, secret files и
TLS staging;
3. проверить доступ целевого UID и отказ постороннему UID;
4. выполнить PEM parse/key-match и Compose render;
5. запустить image-native config test/entrypoint с production hardening и
4. проверить доступ целевого UID и отказ постороннему UID, включая traverse
parent directories;
5. выполнить/проверить one-shot ownership init для non-root named volumes;
6. выполнить PEM parse/key-match и Compose render;
7. запустить image-native config test/entrypoint/healthcheck с production hardening и
временными test-only upstream values;
6. выдать migration-role только необходимые временные cross-schema grants,
8. выдать migration-role только необходимые временные cross-schema grants,
выполнить Alembic и отозвать grants владельцем;
7. после старта проверить отсутствие permission/restart loops, реальные
healthchecks и freshness updater data.
9. после readiness upstream повторить edge config test/reload с production
Docker DNS names;
10. после старта проверить отсутствие permission/restart loops, реальные
healthchecks и freshness updater data.
Неуспех gate исправляется в ownership, mount/entrypoint contract или DB grants.
Временное ослабление `read_only`, запуск root, broad chmod, добавление в