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

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
+7
View File
@@ -77,5 +77,12 @@
- Изменение MVP → arch-01 + arch-02 (+ arch-03/arch-04 при необходимости).
- Новый env или ключ `app_settings` → arch-04.
- Новая VM, изменение сетевой доступности, прав `deploy`, sudo/systemd, capabilities, volumes или способа доставки секретов → arch-06 (+ arch-03/arch-04 и deployment runbook).
- Изменение host bind owner/mode, named-volume ownership init, healthcheck
command/image digest, published Docker port/`DOCKER-USER`, release file
modes или renewal/systemd hook → arch-06 + arch-03 + профильный module +
runbook.
- Изменение service seed/schema/embedded artifact или feature flag, влияющего
на edge route/allow-list → arch-04 + профильный module + rollout/rollback
gates в module-10.
- Новый термин / enum → arch-00, затем поиск по arch-*.
- Закрытие пробела → убрать из «Открытые пробелы» и отразить решение в arch-*.
@@ -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, добавление в
+15 -1
View File
@@ -201,9 +201,17 @@ worker.lease_seconds=90
Runtime policy хранится в версионированной `message_safety.config_versions`, а не в `.env` и не в `han_app.app_settings`. Сюда входят task lease/deadline/attempts, internal rate/pending limits, retention/cache TTL, URL/DNS pipeline limits, ClamAV policy timeout/signature age и enabled file MIME/size policy. Для ClamAV schema допускает возраст сигнатур не более `720` часов (30 дней), seed `max_signature_age_hours` равен `240` часам (10 дней). Полный schema/seed/activation contract — module-05 §10.1 и §15.
Seed, JSON Schema и referenced artifacts входят в immutable Message Safety
image. Их изменение требует одновременно нового pinned image digest и новой
монотонной config version через config-admin job. Нельзя активировать config,
который старый runtime image не валидирует, оставлять старый image как
автоматический rollback после активации несовместимой schema или редактировать
active row на месте. Rollback выполняется новой config version, совместимой с
выбранным image; retired version повторно не активируется.
`han_app.app_settings:chat.attachments.*` остаётся бизнес-настройкой api-backend. Message Safety не получает cross-schema read к `han_app`; файл допускается только при пересечении business allow-list, active safety policy и immutable detector manifest. Active policy может сузить manifest, но не добавить parser и не увеличить hard limit.
В env Message Safety остаются только bootstrap/topology/capacity (`APP_ENV`, worker concurrency, DNS resolver, ClamAV/S3/OTLP endpoints); credentials доставляются secret files. Rules/detector versions вычисляются/проверяются по immutable artifacts. Emergency MOCK остаётся в отдельном root-owned mode file и намеренно не переносится в БД.
В env Message Safety остаются только bootstrap/topology/capacity (`APP_ENV`, worker concurrency, DNS resolver, ClamAV/S3/OTLP endpoints); credentials доставляются secret files. Rules/detector versions вычисляются/проверяются по immutable artifacts. Emergency MOCK остаётся в отдельном read-only bind file `root:han-message-safety 0640` с dedicated GID контейнера и намеренно не переносится в БД.
---
@@ -433,6 +441,12 @@ Production использует отдельные allow-listed env manifests В
При `false` контейнер остаётся live, `/health/ready` возвращает `503 sync_disabled`, worker/reconciliation не claim-ят работу. Public webhook не должен безусловно подтверждать событие как обработанное: до cutover endpoint закрывается на edge либо возвращает retryable `503`. Это не влияет на чат Open Lines.
Feature flag, edge allow-list и readiness образуют единый fail-closed
инвариант: при `false` активный nginx allow-list содержит только `deny all;`;
при `true` требуется хотя бы один reviewed source CIDR и готовый receiver.
Непустой allow-list при disabled, пустой allow-list при enabled или
расхождение route/readiness являются preflight error.
### `bitrix_sync.settings`
Versioned hot settings содержат batch size/wait, claim size, lease TTL, portal limiter refill/burst, max in-flight, retry base/max/horizon, Contact/alert reconciliation intervals, пороги всплеска Contact, восстановленных без webhook, и IDs/стадии/поля/SLA smart process. Новая версия активируется только после полной type/range/cross-field validation; невалидная версия не заменяет последнюю рабочую.
@@ -20,6 +20,13 @@
- Изменение deployment, VM topology, network exposure, volumes, Linux capabilities, OS/sudo-прав или способа доставки секретов требует impact analysis по arch-06.
- Агент не добавляет `deploy` в группу `docker` и не расширяет sudo wildcard-командами. Новое право оформляется как конкретная операция над конкретным systemd-unit с review и rollback.
- Production compose, systemd-units, deploy scripts и secret mappings остаются root-owned и недоступны `deploy` на запись.
- Release содержит version-controlled manifest owner/group/mode; blanket
`F644` sync запрещён, executable scripts/hooks/preflight сохраняют `+x`.
- Изменение image digest повторяет runtime-проверки exact image: UID/GID,
entrypoint, writable paths, healthcheck binaries и one-shot init jobs.
- Изменение bind permissions, feature flag/edge allow-list, published ports
или systemd oneshot helper требует positive/negative production-like test,
а не только review YAML/shell.
- Для private/no-egress VM допустим временный bootstrap с SSH из trusted ops CIDR и ограниченным egress для пакетов/образов.
- Раскатка private/no-egress VM считается незавершённой, пока не выполнен lockdown: public ingress/SSH и общий egress закрыты, private access проверен, а недоступность снаружи зафиксирована.
- Повторное открытие ingress/egress после lockdown — документированная break-glass операция с обязательным возвратом в lockdown, а не штатный способ деплоя.
@@ -101,6 +108,16 @@ Raw OTP запрещено хранить в открытом виде: это
- сервис запускается в root Docker Compose своей VM; CI отдельно валидирует оба projects и отсутствие cross-host `depends_on`/Docker DNS;
- remote Message Safety contract tests покрывают v2 `202 + Location + Retry-After`, sticky final result, terminal failed `503`, `409` invariant mapping и legacy v1 migration adapter;
- container проверен по arch-06: non-root, `no-new-privileges`, capabilities, read-only filesystem/writable paths, volumes, networks и resource limits;
- release manifest проверяет executable modes; bind files читаются реальным
container UID/GID, named volumes подготовлены ограниченным init-job;
- healthcheck выполнен внутри exact pinned digest, без несуществующих
вспомогательных binaries;
- published Docker ports проверены через live `DOCKER-USER` counters с учётом
DNAT; обновлённый active oneshot helper применён explicit restart;
- hooks/timers имеют корректный exit code и пустой stderr при успехе;
- feature flags, edge allow-lists и service readiness согласованы fail-closed;
- изменение embedded seed/schema выпущено как совместимая пара нового image
digest и новой монотонной config version с проверенным rollback;
- изменение прав `deploy`, systemd, network exposure, capabilities, volumes или secret delivery отражено в arch-06 и deployment runbook;
- для private/no-egress VM выполнен и зафиксирован bootstrap→lockdown checklist;
- worker, указанный в Compose/runbook, имеет реально зарегистрированный entrypoint в image; deployment не может заранее выдумывать имя команды;
@@ -217,6 +217,15 @@ AllowUsers deploy admin tunnel
`deploy` не запускает `docker`, `docker compose` или произвольные root-скрипты через sudo. Docker Compose запускается root-owned systemd-юнитом или root-owned deployment helper с фиксированным интерфейсом.
Обновление executable, config или helper, используемого уже активным
`Type=oneshot` unit с `RemainAfterExit=yes`, требует явного
`systemctl restart <unit>`. `enable --now` включает unit и стартует только
неактивный unit, но не применяет новый helper к уже active/exited unit.
Rollout проверяет фактический live state после restart: созданные firewall
rules, freshness materialized secrets, owner/mode файлов и exit status
последнего запуска. Один `ActiveState=active` не доказывает применение новой
версии.
Sudoers хранится только в `/etc/sudoers.d/deploy` и проверяется через `visudo`. `/etc/sudoers` напрямую не редактируется.
Разрешения перечисляют полные команды и конкретные unit names без wildcard. Принципиальный пример:
@@ -263,7 +272,16 @@ Root-owned и недоступны `deploy` на запись:
- отсутствуют symlink/path traversal;
- compose config прошёл валидацию;
- image reference pinned по version/digest;
- миграции и rollout соответствуют release manifest.
- миграции и rollout соответствуют release manifest;
- release manifest задаёт owner/group/mode для каждого класса файлов:
data/config `0644` или строже, secrets отдельно, executable scripts,
preflight, hooks и helpers — `0755`/`0750` по назначению;
- перед активацией выполняется `test -x` для каждого executable из manifest.
Глобальный `rsync --chmod=F644`, рекурсивный `chmod` или иной blanket mode,
снимающий executable bit со scripts/hooks/preflight, запрещён. Права
восстанавливаются из version-controlled manifest или явными `install -m`
для каждого класса артефактов, а не после первой ошибки запуска.
Если такого валидатора пока нет, compose/unit changes выполняет `admin`, а `deploy` ограничивается запуском уже подготовленного релиза. Выдавать `deploy` запись в production compose — не допустимая замена автоматизации.
@@ -313,9 +331,14 @@ Wildcard, временный `NOPASSWD: ALL` и включение в `docker` g
Значения `uid`, `gid` и `mode` в Compose file secrets нельзя считать
security boundary: Docker Compose при bind-backed secret может их игнорировать.
Фактические owner/mode задаются host-side materializer'ом и проверяются через
`stat` и негативный тест от постороннего UID. Предупреждение Compose об
игнорировании этих атрибутов не подавляется и не трактуется как подтверждение
прав.
`stat`, позитивный read-test от фактического container UID/GID и негативный
тест от постороннего UID. Проверяется также execute/traverse permission всех
parent directories. `root:root 0600` нельзя bind-mount в процесс, работающий
не от root: для несекретного control file используется dedicated host group,
совпадающий с primary GID контейнера, и минимальный режим `0640`; secrets
получают эквивалентный минимальный ACL/group contract. Предупреждение Compose
об игнорировании этих атрибутов не подавляется и не трактуется как
подтверждение прав.
Структурированные секреты валидируются до старта потребителя. Для PEM это
означает проверку парсинга certificate/private key, отсутствие повторного
@@ -382,7 +405,7 @@ revision, а не ручным `stamp` или правкой production DB.
| Путь | Владелец / режим | Назначение |
|---|---|---|
| `/opt/han-chat/releases/<version>` | `root:root`, `0755`/файлы `0644` | immutable release |
| `/opt/han-chat/releases/<version>` | `root:root`; каталоги `0755`, data/config `0644`, executable по manifest `0755/0750` | immutable release |
| `/opt/han-chat/current` | `root:root` | active release link; меняет только deployment helper/admin |
| `/var/lib/han-deploy/incoming` | `deploy:deploy`, `0750` | загрузка неактивированных артефактов |
| `/etc/han` | `root:root`, `0750` или строже | конфигурация и secret mappings |
@@ -455,6 +478,27 @@ non-root + `read_only`, даже если сам daemon способен раб
writable-path inventory, healthcheck semantics и фактический UID/GID считаются
частью security contract образа.
### Ownership persistent named volumes
Новый Docker named volume нельзя считать writable для non-root process:
начальный owner часто `root:root`, даже если target path в image принадлежит
service UID. Persistent volume до запуска потребителя подготавливает
идемпотентный one-shot init service:
- использует уже утверждённый pinned image с необходимыми `sh/chown/chmod`, а
не непроверенный floating utility image;
- запускается с `user: 0:0`, `read_only: true`, `cap_drop: [ALL]` и возвращает
только `CHOWN`/`FOWNER`, если они действительно нужны;
- не имеет сети (`network_mode: none`) и доступа к secrets;
- меняет owner/mode только mount root конкретного volume, без recursive chown
чужого state;
- завершается с кодом `0`, а consumer зависит от
`condition: service_completed_successfully`;
- безопасно повторяется после recreate и отдельно проверяется в preflight.
Запуск основного collector/service от root, `chmod 0777` и ручная правка
`/var/lib/docker/volumes` на host запрещены как способы исправления ownership.
### TLS для non-root edge
Root-only дерево ACME/Certbot не монтируется целиком в non-root nginx и не
@@ -463,7 +507,11 @@ Root-only дерево ACME/Certbot не монтируется целиком
`han-nginx-tls` (канонический GID `11001`); nginx получает этот каталог
read-only. Renewal hook сначала обновляет staged files, затем выполняет полный
config test и только после успеха отправляет reload. Права и соответствие
certificate/key проверяются preflight.
certificate/key проверяются preflight. Успешный hook обязан завершаться с
кодом `0` и пустым stderr: benign output `nginx -t` и progress Compose
перехватываются/подавляются на success path, но полностью выдаются в stderr
при ненулевом exit code. Иначе Certbot/оркестратор может пометить успешный
renewal как hook error.
### Daemon и updater как разные security-профили
@@ -500,6 +548,23 @@ capability.
Default policy для ingress — deny. Разрешение задаёт source, destination, protocol, port и назначение. Правила «вся private network на все порты» запрещены.
`DOCKER-USER` обрабатывает forwarded packet после Docker DNAT. Поэтому policy
для published host ports сопоставляет original destination через
`-m conntrack --ctorigdstport <HOST_PORT>` (или эквивалент nftables), а не
текущий `--dport`, который уже может быть container port. Для каждого
разрешённого host port обязательны:
- positive external/private probe и рост counter именно allow rule;
- negative probe неразрешённого source/port и рост deny counter;
- проверка `iptables -S`/nft ruleset после setup, restart firewall unit,
restart Docker и reboot;
- запрет считать UFW INPUT counter доказательством фильтрации published
Docker port: такой трафик может обходить INPUT.
Firewall setup обязан обновлять helper и явно перезапускать active oneshot
unit; наличие нового текста helper без изменения live rules является
неуспешным rollout.
Cloud SG для managed PostgreSQL разрешает TLS-подключения только от VM/SG сервисов, которым нужна соответствующая схема. PostgreSQL, Redis, OTLP receivers, admin UI и internal API не публикуются в интернет.
### Egress
@@ -561,9 +626,13 @@ Fail2ban обязателен для SSH, временно или постоян
- root/password SSH отключены после проверки key access;
- `deploy` не состоит в `docker` и имеет только конкретные systemd-команды;
- production-файлы root-owned и недоступны `deploy` на запись;
- release manifest сохраняет executable modes; preflight/hooks запускаются
напрямую без обхода через `bash`;
- каждый контейнер проверен по hardening baseline;
- для каждого image digest проверены entrypoint, UID/GID и полный inventory
writable paths;
- bind files проходят positive read-test от container UID/GID и negative test
от постороннего UID; named volumes подготовлены ограниченным init-job;
- секреты разделены по сервисам/VM и отсутствуют в обычном `.env`;
- bind-backed secrets и staged TLS проверены по фактическим owner/mode и
содержимому, а не только по декларации Compose;
@@ -571,7 +640,13 @@ Fail2ban обязателен для SSH, временно или постоян
grants выданы и отозваны владельцем;
- healthcheck проверяет процесс, реально присутствующий в контейнере, а
updater freshness контролируется отдельным сигналом;
- healthcheck-команда и все её binaries подтверждены внутри exact pinned
digest; отсутствие `curl`/`wget` не обнаруживается впервые в production;
- внутренние ports недоступны извне;
- live `DOCKER-USER` rules проверены по original host ports после DNAT и
переживают Docker restart/reboot;
- обновлённые oneshot helpers применены explicit restart, а renewal hooks
имеют пустой stderr при успехе;
- backup/restore и rollback проверены в объёме релиза;
- для private/no-egress VM завершён и зафиксирован lockdown;
- проверена недоступность внешних портов и неразрешённого egress;