Внедрение KESL на ВМ2 + замена CLAMAV на KESL

This commit is contained in:
mi
2026-09-08 01:39:37 +03:00
parent 85df788f2d
commit fdfdeaffb4
43 changed files with 2210 additions and 329 deletions
@@ -31,7 +31,7 @@ Test-only правила по первому символу, случайные
| Safety Service Owner | lifecycle v2, API/data contracts, service-owned config, capacity и cutover sign-off |
| Rule Pack Owner | версия rules bundle, corpus, monitor rollout и release notes |
| Security Owner | approval `monitor → deny` и config changes, ослабляющих policy; threat model, egress/secrets и risk acceptance |
| Operations Owner | VM2, ClamAV signatures, alerts, rollback/reprovision и restore rehearsal |
| Operations Owner | VM2, host KESL/broker, антивирусные базы, alerts, rollback/reprovision и restore rehearsal |
Один человек может выполнять несколько ролей, но для каждого production release роли и approvals должны быть записаны в release checklist.
@@ -45,7 +45,7 @@ Test-only правила по первому символу, случайные
- валидацию file metadata и фактического формата;
- чтение файла из S3-quarantine по read-only credentials;
- вычисление authoritative SHA-256;
- антивирусную проверку файла через ClamAV;
- антивирусную проверку файла через root-owned fail-closed broker по Unix socket `/run/han-kesl/scan.sock`; broker вызывает host KESL 12.4 standalone;
- выбор sync/async режима;
- создание и исполнение async safety tasks;
- sticky final verdict, verdict cache и audit в схеме `message_safety`;
@@ -78,7 +78,7 @@ Test-only правила по первому символу, случайные
| `MOCK=true`, `TEXT_FREE=false`, `FILE_FREE=true` | forced `403 deny` | forced `200 allow` |
| `MOCK=true`, `TEXT_FREE=false`, `FILE_FREE=false` | forced `403 deny` | forced `403 deny` |
При `MOCK=true` не выполняются normalization/rules, URL extraction/DNS, S3 read/checksum/format/ClamAV, verdict caches, task creation и async worker. Service authentication, body-size/JSON/strict DTO validation, idempotency conflict protection, PostgreSQL audit и rate limits остаются обязательными controls.
При `MOCK=true` не выполняются normalization/rules, URL extraction/DNS, S3 read/checksum/format/KESL scan, verdict caches, task creation и async worker. Service authentication, body-size/JSON/strict DTO validation, idempotency conflict protection, PostgreSQL audit и rate limits остаются обязательными controls.
Forced allow использует `rule_id=safety.mock_forced_allow`; forced deny — `rule_id=safety.mock_forced_deny`, `reason_code=message_blocked`. Mock никогда не возвращает `202`. Internal response содержит `processing_mode=mock`; `api-backend` не раскрывает mode/rule клиенту. Mode фиксируется при первом принятии `message_id`: ранее созданный standard task/idempotency result не переклассифицируется и завершается в standard, а новый mock request не создаёт task. Это исключает смену verdict посередине обработки.
@@ -118,8 +118,8 @@ Rules не заменяют безопасный rendering. Frontend и Bitrix i
| Недопустимый размер/MIME | Сверка DTO с allow-list и лимитами | `403 deny` |
| Подмена MIME | Magic-byte/content sniffing, сверка declared MIME | `403 deny` |
| Подмена содержимого после complete | Version-specific read + ETag + полный SHA-256 | `403 deny` |
| Malware | ClamAV scan актуальными сигнатурами | `403 deny` |
| Архивная бомба/ресурсное истощение | Лимиты размера, stream scan, ClamAV limits/timeouts | deny при policy hit; error при сбое |
| Malware | Host KESL 12.4 scan через fail-closed broker с актуальной базой | `403 deny` |
| Архивная бомба/ресурсное истощение | Лимиты размера, bounded staging и KESL/broker limits/timeouts | deny при policy hit; error при сбое |
| Polyglot/неоднозначный формат | Строгий формат detector и deny при mismatch/ambiguity | `403 deny` |
| Повтор известного файла | Cache по SHA-256 + versions | Sticky cached verdict |
@@ -179,7 +179,7 @@ flowchart TD
task --> worker[File_worker]
worker --> objectRead[S3_stream_and_SHA256]
objectRead --> formatCheck[Format_validation]
formatCheck --> avScan[ClamAV_scan]
formatCheck --> avScan[KESL_broker_scan]
avScan --> finalVerdict[Persist_sticky_verdict]
finalVerdict --> taskGet[GET_task_200_or_403]
```
@@ -255,7 +255,7 @@ Rules поставляются как статический read-only bundle `a
| `file.encrypted_content` | file_content | deny | encrypted/password-protected container |
| `file.active_content` | file_content | deny | PDF JavaScript/OpenAction/Launch/XFA/embedded |
| `file.parser_limit` | file_content | deny | parser resource/decompression/object limit |
| `file.malware_detected` | file_content | deny | ClamAV FOUND |
| `file.malware_detected` | file_content | deny | KESL сообщает infected |
Все domain deny используют `reason_code=message_blocked`; детализация остаётся во внутреннем `rule_id`. DTO/schema/authorization errors не получают `rule_id`.
@@ -389,29 +389,28 @@ Worker:
5. определяет реальный формат по содержимому;
6. сверяет detector result с declared MIME;
7. запрещает encrypted/password-protected и неподдерживаемые containers;
8. передаёт поток в `clamd` через internal network;
8. передаёт bounded file broker-у через Unix socket `/run/han-kesl/scan.sock`; root-owned broker вызывает `kesl-control --scan-file --action Inform`;
9. сохраняет sticky final verdict и audit;
10. записывает cache только для terminal результата;
11. завершает lease.
Чистый файл получает allow только если успешно завершились **все** обязательные проверки. `ClamAV FOUND`, checksum mismatch, format mismatch, unsupported encrypted content или policy limit дают deny с отдельным `rule_id`.
Чистый файл получает allow только если успешно завершились **все** обязательные проверки и broker однозначно вернул `clean`. KESL `infected`, checksum mismatch, format mismatch, unsupported encrypted content или policy limit дают deny с отдельным `rule_id`.
ClamAV timeout, protocol error, недоступность S3/DB/Redis или потеря lease не являются deny. Task остаётся pending/retryable в пределах deadline; после исчерпания retry получает terminal infrastructure failure, который API отдаёт как `503`, а не `403`.
KESL/broker timeout, ошибка запуска или разбора результата `kesl-control`, stale database, недоступность S3/DB/Redis или потеря lease не являются ни allow, ни domain deny. Task остаётся pending/retryable в пределах deadline; после исчерпания retry получает terminal infrastructure failure, который API отдаёт как `503`, а не `403`.
### 7.3. AV runtime
### 7.3. KESL runtime и broker
MVP использует отдельный `clamd` sidecar/service в private Docker network:
MVP использует установленный на VM2 host KESL 12.4 standalone и отдельный custom integration broker:
- порт не публикуется наружу;
- сигнатуры обновляет `freshclam`;
- readiness требует daemon PING и допустимый возраст signatures;
- `max_signature_age_hours` допускается в диапазоне `1..720` часов
(не более 30 дней); seed policy использует `240` часов (10 дней);
- limits согласованы с максимальным размером файла;
- контейнер non-root, read-only root filesystem где возможно, отдельный writable volume только для signatures/runtime;
- worker не передаёт в clamd object key, имя пользователя или иные PII.
- KESL и broker не входят в Compose; `clamd`/`freshclam`, их volumes, healthchecks и egress из Compose удалены;
- root-owned broker слушает только Unix socket `/run/han-kesl/scan.sock`; ожидаемые права socket — `root:han-message-safety 0660`, socket монтируется в worker, TCP listener отсутствует;
- broker fail-closed: принимает только bounded scan request, не принимает произвольные command/arguments/path traversal и вызывает фиксированный `kesl-control --scan-file --action Inform`;
- `clean` разрешает продолжить allow-ветку, `infected` даёт domain deny; неизвестный формат/exit code, timeout, недоступность KESL и stale database дают retry, затем `503`;
- readiness проверяет broker, KESL version/database date и допустимый возраст базы; `max_signature_age_hours` допускается в диапазоне `1..720`, seed — `240`;
- KESL обновляет базы на host ежечасно по операторскому KESL runbook; egress к источникам обновления принадлежит host KESL, не Compose;
- worker не передаёт object key, имя пользователя или иные PII; временный файл и broker state очищаются по завершении.
Недоступность AV переводит capability `files` в `unavailable` и запрещает новые file allow, но оставляет core/text readiness доступной.
Broker — custom integration: точный формат и exit semantics `kesl-control`, безопасная передача файла, очистка и throughput обязаны пройти gates на target VM2 с фактическим KESL 12.4. Недоступность scanner переводит capability `files` в `unavailable` и запрещает новые file allow, но оставляет core/text readiness доступной.
### 7.4. Исполнимая матрица форматов
@@ -425,7 +424,7 @@ MVP использует отдельный `clamd` sidecar/service в private D
Parser выполняется с CPU/memory/wall-time limits в отдельном sandboxed subprocess. Detector ambiguity, resource limit и disagreement detector↔declared MIME дают соответствующий `403 rule_id`. Immutable `detector-manifest.json` содержит bundle version, package/native library versions, supported MIME и hard limits; `detector_version` вычисляется как hash manifest, а не задаётся env. Active config ссылается на доступные `rules_version`/`detector_version` и может только сузить MIME/limits. Изменение manifest или active file policy инвалидирует file verdict cache через versioned cache key.
`scanner_engine=clamav`. `signatures_version` формируется из ClamAV engine version и CVD/CLD metadata/hash после успешного `freshclam` activate/reload; значение входит в verdict/cache/audit и readiness.
`scanner_engine=kesl`. `signatures_version` — стабильный hash канонической строки из KESL version и database date, полученных после успешной проверки broker/KESL; значение входит в verdict/cache/audit и readiness.
## 8. Internal API
@@ -697,14 +696,14 @@ Public `422` содержит стандартный error envelope без inter
- strict DTO, body/text/URL/file limits и запрет unknown fields;
- read-only S3-quarantine access, без list/write/delete;
- **запрет HTTP fetch/render/redirect follow** к пользовательским URL; link pipeline использует только доверенный DNS;
- egress по назначению: worker → DNS/S3/PostgreSQL, app → local Redis/OTLP/clamd; внешнего reputation API нет;
- egress по назначению: worker → DNS/S3/PostgreSQL, app → local Redis/OTLP; KESL update egress принадлежит host и ограничен операторским KESL runbook; внешнего reputation API нет;
- parameterized SQL и least-privilege DB role только на schema `message_safety`;
- Redis Safety ACL только prefixes `han:safety:*`;
- dependency pinning, SBOM/image scanning;
- non-root, read-only root fs, tmpfs `/tmp`, dropped capabilities, no-new-privileges;
- OpenAPI UI выключен в production;
- безопасные generic errors без stack/internal addresses;
- правила и AV signatures обновляются только trusted deployment process.
- rules и KESL database обновляются только trusted deployment/operator process.
- MOCK не имеет runtime/public API; mode file root-owned, а `deploy` имеет sudo только на argument-validating helper и approved restart.
## 13. Observability и privacy
@@ -726,10 +725,10 @@ Metrics:
- `message_safety_mock_enabled` gauge и forced outcomes по `text|file`/`allow|deny`;
- rule hits по low-cardinality `rule_id`;
- task queue/age/attempts/lease conflicts/timeouts;
- scan latency/bytes buckets/AV outcome;
- scan latency/bytes buckets/KESL outcome;
- signatures age/version info;
- cache hit/miss;
- PostgreSQL/Redis/S3/ClamAV latency and errors;
- PostgreSQL/Redis/S3/KESL broker latency and errors;
- active/used `config_version`, activation result и config refresh age;
- auth rejects/rate limit/readiness.
@@ -748,7 +747,7 @@ Telemetry collector unavailable не влияет на safety verdict и readine
- Redis Safety status как degraded accelerator, не core gate;
- worker heartbeat/lease processing;
- S3-quarantine Head/Get read permission на безопасный canary object;
- ClamAV PING и допустимый возраст signatures;
- broker socket, KESL status и допустимый возраст database;
- trusted DNS resolver для `links`;
- OpenAPI/runtime parity проверяется на startup/CI, а не сетевым probe каждого ready request.
@@ -777,7 +776,7 @@ Telemetry collector unavailable не влияет на safety verdict и readine
}
```
Core `HTTP 503 status=not_ready` используется только для invalid config/rules/PostgreSQL. Потеря worker heartbeat делает `worker=unavailable` и `files=unavailable`, но сохраняет text/links; ClamAV/S3 down выключает только `files`; DNS down — только `links`; Redis down прогревается из PostgreSQL и не выключает core. Каждый POST повторно проверяет требуемую capability и остаётся источником correctness; api-backend может кэшировать health snapshot не дольше 5 с только для fast-fail. Через private nginx ВМ2 endpoint доступен как exact `GET /internal/safety/status`, который проксируется в `/health/ready`; прямой `/health/ready` остаётся локальным container health. Private alias ограничен SG/source allow-list, не требует service token и не раскрывает credentials/hostnames.
Core `HTTP 503 status=not_ready` используется только для invalid config/rules/PostgreSQL. Потеря worker heartbeat делает `worker=unavailable` и `files=unavailable`, но сохраняет text/links; KESL broker/S3 down или stale KESL database выключает только `files`; DNS down — только `links`; Redis down прогревается из PostgreSQL и не выключает core. Каждый POST повторно проверяет требуемую capability и остаётся источником correctness; api-backend может кэшировать health snapshot не дольше 5 с только для fast-fail. Через private nginx ВМ2 endpoint доступен как exact `GET /internal/safety/status`, который проксируется в `/health/ready`; прямой `/health/ready` остаётся локальным container health. Private alias ограничен SG/source allow-list, не требует service token и не раскрывает credentials/hostnames.
В MOCK health всегда явно возвращает `processing_mode=mock`, `mock_policy.text=allow|deny`, `mock_policy.file=allow|deny` и `status=degraded`, даже если forced responses доступны. Normal pipeline dependencies показываются как `bypassed`, не `ok`. Active alert не закрывается до возврата в `standard`.
@@ -793,7 +792,7 @@ Core `HTTP 503 status=not_ready` используется только для in
### 15.1. Seed active config в `message_safety`
```yaml
schema_version: 1
schema_version: 2
rules_bundle_ref: rules-2026-01-01
detector_manifest_ref: detector-2026-08-03
task:
@@ -820,7 +819,7 @@ link:
url_max_length: 2048
dns_lookup_timeout_sec: 1
pipeline_timeout_sec: 2
clamav:
kesl:
scan_timeout_sec: 45
max_signature_age_hours: 240
file_policy:
@@ -850,8 +849,7 @@ activation. Image-only rollback после активации несовмест
APP_ENV=production-like
MESSAGE_SAFETY_WORKER_CONCURRENCY=5
MESSAGE_SAFETY_DNS_RESOLVERS=<VPC-resolver-IP>
MESSAGE_SAFETY_CLAMAV_HOST=clamd
MESSAGE_SAFETY_CLAMAV_PORT=3310
MESSAGE_SAFETY_KESL_SOCKET=/run/han-kesl/scan.sock
SELECTEL_S3_ENDPOINT_URL=https://s3.storage.selcloud.ru
SELECTEL_S3_BUCKET_QUARANTINE=han-chat-quarantine
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
@@ -875,7 +873,7 @@ Availability SLO для MVP не утверждается. До cutover обяз
| File | sustained 2 checks/s, 5 worker slots | среднее processing ≤2.5 с, p95 final ≤60 с, p99/public wait ≤300 с |
| Pending/backpressure | до 100 active tasks | queue age p95 ≤5 с; при 100 новый file POST получает retryable `503` без task |
File corpus: 70% JPEG/PNG/WebP до 1 MiB, 20% PDF/HEIC до 2 MiB, 10% boundary samples до 5 MiB; включает clean, EICAR, malformed, encrypted и parser-limit cases. Если 5 slots не подтверждают 2 file/s и среднее ≤2.5 с, Safety Service Owner до cutover увеличивает slots/CPU/scan lanes и повторяет тест.
File corpus: 70% JPEG/PNG/WebP до 1 MiB, 20% PDF/HEIC до 2 MiB, 10% boundary samples до 5 MiB; включает clean, EICAR, malformed, encrypted и parser-limit cases. Если 5 slots не подтверждают 2 file/s и среднее ≤2.5 с, Safety Service Owner до cutover корректирует slots/CPU/broker concurrency в пределах подтверждённой KESL capacity и повторяет target-VM test.
Превышение text/file token bucket → `429 rate_limit_exceeded` + `Retry-After`. Pending считается authoritative запросом PostgreSQL; Redis используется как быстрый счётчик. При Redis outage rate limiter использует conservative in-process limits, а pending gate остаётся в PostgreSQL.
@@ -920,7 +918,7 @@ Runtime schema сравнивается с committed artifact contract test. Ter
- PostgreSQL claim/lease/fencing concurrency/recovery; Redis cache loss;
- pending=100 backpressure, token buckets и 5 worker slots;
- S3 versioned read, ETag/version changed/missing/timeout;
- ClamAV clean, EICAR, FOUND, timeout, daemon down, stale signatures;
- KESL broker clean, EICAR/infected, timeout, scanner down, stale database и неизвестный формат `kesl-control`;
- full SHA-256 and size mismatch;
- duplicate concurrent request and worker retry;
- dependency recovery without changed final verdict.
@@ -972,10 +970,10 @@ Runtime schema сравнивается с committed artifact contract test. Ter
### 18.2. Definition of Done
- production v2 `200/202/403` contract реализован без stub divergence;
- MOCK 2×2 text/file matrix даёт только sync `200/403`; тест доказывает отсутствие calls к rules/DNS/S3/ClamAV/cache/workers и сохранение auth/DTO/idempotency/audit/rate limits;
- MOCK 2×2 text/file matrix даёт только sync `200/403`; тест доказывает отсутствие calls к rules/DNS/S3/KESL broker/cache/workers и сохранение auth/DTO/idempotency/audit/rate limits;
- text-rules cache, text rules, link cache и URL pipeline покрывают threat model и false-positive corpus;
- fetch/render/redirect follow к пользовательским URL отсутствует по design и тестам;
- files проходят metadata, authoritative SHA-256, format detector и ClamAV;
- files проходят metadata, authoritative SHA-256, format detector и KESL broker;
- final task verdict sticky и durable;
- idempotency/concurrency/recovery доказаны тестами;
- PG schema `message_safety`, Redis Safety и S3 version-specific read работают по least privilege;
@@ -985,13 +983,13 @@ Runtime schema сравнивается с committed artifact contract test. Ter
- mock mode сохраняется internal, не раскрывается public; forced file allow получает `scan_status=bypassed`, не `clean`;
- public deny использует `safety.chat.blocked` и не раскрывает internal rule;
- blocked/failed content не попадает в Bitrix и не promote-ится;
- health/readiness и capability-specific POST отражают PG/Redis/S3/workers/ClamAV/DNS/rules;
- health/readiness и capability-specific POST отражают PG/Redis/S3/workers/KESL broker/database age/DNS/rules;
- performance acceptance §15.4 пройден на target sizing;
- immutable/versioned S3 negative tests и conditional promote пройдены;
- OpenAPI 3.1 и runtime parity зелёные;
- logs/metrics/traces не содержат содержимое сообщений, файлов и secrets;
- hardened containers запускаются без public port;
- runbook описывает signature update, stale signatures, AV outage, retry и rollback.
- runbook описывает hourly KESL update, stale database, broker/KESL outage, retry и rollback.
- `deploy` может выполнить все пять exact mode commands, но не читать/писать mode config и не получить Docker/general sudo; helper rollback, persistent MOCK alert и возврат в standard испытаны.
## 19. Размещение проекта и вынос на отдельную ВМ
@@ -1016,7 +1014,7 @@ Stub в `codebase/backend/message-safety/` не является целевой
### 19.2. Целевая топология
Message Safety и `bitrix-sync` выносятся **за пределы ВМ HAN_CHAT (ВМ1)** на самостоятельную **ВМ2 Processing** в той же private network/VPC. Цель — снять с ВМ1 ClamAV/file scan, DNS classification, S3 streaming, CRM sync и входящий CRM webhook. Message Safety остаётся private API; Bitrix24 обращается напрямую к отдельному public host nginx ВМ2.
Message Safety и `bitrix-sync` выносятся **за пределы ВМ HAN_CHAT (ВМ1)** на самостоятельную **ВМ2 Processing** в той же private network/VPC. Цель — снять с ВМ1 file scan, DNS classification, S3 streaming, CRM sync и входящий CRM webhook. Host KESL 12.4 standalone и broker работают на VM2 вне Compose. Message Safety остаётся private API; Bitrix24 обращается напрямую к отдельному public host nginx ВМ2.
```mermaid
flowchart LR
@@ -1030,7 +1028,7 @@ flowchart LR
gateway[VM2_nginx_public_private]
safety[message-safety_api_worker]
sync[bitrix-sync]
clamav[clamd]
kesl[KESL_12_4_and_broker]
redis2[Redis_Safety]
collector[otel-collector]
end
@@ -1046,13 +1044,13 @@ flowchart LR
safety --> redis2
safety --> managedPg
safety --> s3q
safety --> clamav
safety -->|"Unix socket"| kesl
sync --> managedPg
sync --> bitrix
collector --> signoz
```
На ВМ2 один root Compose включает собственный nginx с public webhook `443` и private `8443`, `message-safety` API/worker, `clamd`/`freshclam`, `bitrix-sync`, Redis Safety и local OTEL Collector. Managed PostgreSQL и S3 остаются вне VM. Внешнего URL reputation provider нет.
На ВМ2 один root Compose включает собственный nginx с public webhook `443` и private `8443`, `message-safety` API/worker, `bitrix-sync`, Redis Safety и local OTEL Collector. `clamd`/`freshclam` в Compose отсутствуют; host KESL 12.4 standalone и root-owned broker управляются отдельно. Managed PostgreSQL и S3 остаются вне VM. Внешнего URL reputation provider нет.
### 19.3. Исключение из docker compose HAN_CHAT
@@ -1079,7 +1077,7 @@ flowchart LR
| `arch-03` | один Compose ВМ1 | один root Compose на каждой VM |
| `arch-04` | local Docker URLs | remote HTTPS URL ВМ1; service-specific env/secrets ВМ2 |
| CI/CD | build из backend tree | отдельный pipeline artifact `codebase/message-safety` |
| Security groups | intra-docker network | internet→nginx ВМ2:80/443 с exact route policy; ВМ1→ВМ2:8443; ВМ2→PG/S3/DNS/Bitrix/SigNoz/signature CDN по назначению |
| Security groups | intra-docker network | internet→nginx ВМ2:80/443 с exact route policy; ВМ1→ВМ2:8443; containers→PG/S3/DNS/Bitrix/SigNoz, host KESL→approved update sources по назначению |
Схема **`han_app` не получает новых таблиц** для remote safety: orchestration и recovery по-прежнему владеет `api-backend`. Меняется только сетевой адрес internal API и deployment boundary.
@@ -1087,7 +1085,7 @@ flowchart LR
- отдельный public IP/host nginx ВМ2 допускает только ACME/redirect policy на `80` и два exact CRM webhook на `443`; public Safety, generic `/internal/*`, admin и health запрещены;
- inbound: private TCP 8443 только с SG ВМ1/ops; server-auth TLS internal CA + service token;
- outbound по container identity: worker→PostgreSQL/S3/DNS; `freshclam`→signature CDN; `bitrix-sync`→Bitrix24; collector→SigNoz;
- outbound по identity: worker→PostgreSQL/S3/DNS; `bitrix-sync`→Bitrix24; collector→SigNoz; host KESL→approved update sources по операторскому runbook;
- capability health Safety доступен api-backend и ops как exact `/internal/safety/status` на private `8443`; container `/health/live` и `/health/ready` наружу не публикуются;
- при недоступности ВМ2 `api-backend` возвращает `503 dependency_unavailable`, не отправляет сообщения в Bitrix.
@@ -1102,7 +1100,7 @@ flowchart LR
3. **Text/rules** — normalization, versioned bundle/schema, monitor corpus и hard active-content deny.
4. **URL policy** — parser/IDNA/IP/DNS, cache split, NXDOMAIN monitor и запрет fetch.
5. **File/S3** — immutable version precondition, authoritative checksum, detector manifest и hard matrix.
6. **ClamAV** — clamd streaming, freshclam activate/rollback/signature age и EICAR tests.
6. **KESL broker** — host KESL 12.4 standalone, root-owned fail-closed Unix-socket broker, hourly update/database age и EICAR tests; формат `kesl-control` и throughput проверяются на target VM.
7. **api-backend integration** — v2 adapter, Location polling, M8/company replica/mnemonic, conditional promote.
8. **VM2/observability/MOCK** — root Compose, internal nginx/TLS, egress, local collector, root-owned five-command mode helper, persistent alert и dashboards.
9. **Acceptance** — contract/security/failure/load tests §1518 и S3 negative gate.
@@ -8,7 +8,7 @@
Документ задаёт, **что агент ВМ2 реализует в Compose, коде, тестах и алертах этой машины**.
ВМ2 владеет nginx (public CRM webhook + private `8443`), `message-safety`, `bitrix-sync`, `clamd`/`freshclam`, Redis Safety и локальным Collector. Guest bootstrap, Keycloak, `api-backend` и `bitrix-local-app` живут на ВМ1. ВМ2 не использует Docker hostname collector ВМ1.
ВМ2 владеет nginx (public CRM webhook + private `8443`), `message-safety`, `bitrix-sync`, Redis Safety и локальным Collector в Compose, а также host KESL 12.4 standalone и root-owned fail-closed broker. Guest bootstrap, Keycloak, `api-backend` и `bitrix-local-app` живут на ВМ1. ВМ2 не использует Docker hostname collector ВМ1.
Агент ВМ2 не добавляет scrape, дашборды и алерты сервисов ВМ1.
@@ -22,7 +22,7 @@
| Redis Safety | `redis` |
| local Collector | `otel-collector` |
`clamd` / `freshclam` покрываются host/container metrics и сигналами Safety (signature age, scan lanes), отдельное `service.name` в реестр arch-07 не добавляется без явного решения.
Host KESL и broker покрываются host/systemd metrics и сигналами Safety (database age, broker availability/latency, scan concurrency); отдельное `service.name` в реестр arch-07 не добавляется без явного решения.
Различать экземпляр от ВМ1 через `host.name` / `service.instance.id`.
@@ -44,7 +44,7 @@ Scrape targets ВМ2 (кроме самого Collector): Redis Safety exporter,
- server spans API с route template; poll `202` не маскирует финальный verdict;
- worker spans claim/process/finalize и span links на origin `request_id` / `trace_id` caller;
- child spans: PostgreSQL Safety schema, Redis Safety, S3 quarantine, ClamAV/DNS classification без file content и raw URL;
- child spans: PostgreSQL Safety schema, Redis Safety, S3 quarantine, KESL broker/DNS classification без file content, socket payload/path и raw URL;
- `/health/live` исключить из traces; capability/readiness — metrics и sampled logs;
- stub mode (`400`, non-sticky) маркируется как `stub`; production SLO Safety на stub недостоверен.
@@ -67,7 +67,7 @@ Exporter и ACL — arch-07 §8.28.3. Клиентские pool/queue metrics
### 4.5. Host/Docker ВМ2
CPU, memory, disk, network, restarts/OOM, Docker daemon, clock sync — arch-07 §8.5. Отдельно контролировать `clamd` restarts, signature age и scan lane saturation.
CPU, memory, disk, network, restarts/OOM, Docker daemon, clock sync — arch-07 §8.5. Отдельно контролировать host KESL/broker unit state, права `/run/han-kesl/scan.sock`, database age, hourly update result и broker/KESL saturation.
## 5. Метрики бизнес-потоков ВМ2
@@ -105,7 +105,7 @@ UUID/user/session/dialog/task/message id не labels.
В SigNoz, с filter `host.name` / environment ВМ2:
1. **nginx ingress ВМ2**: RPS, 4xx/5xx, upstream latency/status, TLS, cache, **CRM webhook** (accept/reject by source IP, method, rate limit). Без guest API/WS — это ВМ1.
2. **message-safety**: capabilities, verdicts, `202` poll, PG queue age/leases/fencing, ClamAV/signature age, file/link cache и DNS dependency. Stub явно маркируется.
2. **message-safety**: capabilities, verdicts, `202` poll, PG queue age/leases/fencing, KESL broker/database age, file/link cache и DNS dependency. `scanner_engine=kesl`; `signatures_version` представлен как hash KESL version + database date. Stub явно маркируется.
3. **bitrix-sync**: mode/readiness, queue depth/oldest age, workflow/command transitions, CRM batch latency/subcommand outcome, limiter/throttle, retry/DLQ, webhook/reconciliation lag, mapping invariants и business-alert SLA. До cutover — `sync_disabled`.
4. **Redis Safety**: memory/evictions/AOF/latency/clients/keyspace.
@@ -127,7 +127,7 @@ UUID/user/session/dialog/task/message id не labels.
- Redis Safety unavailable, AOF error или sustained evictions;
- Collector ВМ2 exporter queue >80%, dropped/refused telemetry >0 sustained;
- TLS expiry public webhook host и private `8443` <14 дней warning, <7 дней page;
- disk/OOM/restart loop ВМ2; `clamd` restart loop / stale signatures по runbook module-05.
- disk/OOM/restart loop ВМ2; KESL/broker unit down, hourly update failure или stale database по runbook module-05.
### Ticket/warning
@@ -151,7 +151,7 @@ Optional profile `observability-local` на ВМ2 по умолчанию вык
### Высокая latency сообщения (hop ВМ2)
1. Найти task/span по `request_id` caller.
2. Разделить Safety API, worker lease, ClamAV/DNS, S3 quarantine, sync queue.
2. Разделить Safety API, worker lease, KESL broker/DNS, S3 quarantine, sync queue.
3. Проверить circuit, queue age, PG leases/fencing и Redis Safety.
4. Не повторять ambiguous scan/send без исходного idempotency key.
5. Следовать runbook [`module-05-message-safety.md`](module-05-message-safety.md) / [`module-07-bitrix-sync.md`](module-07-bitrix-sync.md).
@@ -171,7 +171,7 @@ Optional profile `observability-local` на ВМ2 по умолчанию вык
- Collector ВМ2 validate + up в root Compose; hostname collector ВМ1 не используется;
- инструментированы `message-safety` API/worker и `bitrix-sync`;
- nginx JSON parsing, webhook reject metric и private `8443` correlation проверены;
- Redis Safety, host/Collector, ClamAV signature age metrics доступны;
- Redis Safety, host/Collector, KESL broker/database age/hourly update metrics доступны;
- дашборды и alerts §6–§7 provisioned либо явно TBD до SigNoz packaging;
- stub/MOCK маркируются; production Safety SLO не объявляется на stub;
- до sync cutover dashboard `sync_disabled`;
@@ -8,7 +8,7 @@
## 1. Границы
ВМ2 владеет nginx public `80/443` (только exact CRM webhook) и private `8443` (Message Safety), `message-safety` API/worker, `clamd`/`freshclam`, `bitrix-sync`, Redis Safety, Collector. Guest API, Keycloak, SPA и SMS callback здесь не разворачиваются.
ВМ2 владеет nginx public `80/443` (только exact CRM webhook) и private `8443` (Message Safety), `message-safety` API/worker, `bitrix-sync`, Redis Safety и Collector в Compose. KESL 12.4 standalone и root-owned fail-closed broker работают на host VM2 вне Compose; broker слушает `/run/han-kesl/scan.sock`. Guest API, Keycloak, SPA и SMS callback здесь не разворачиваются.
`<BACKEND_ROOT>` / `<BACKEND_REPO_URL>` — репозиторий ВМ2. Public ACME host — `<PROCESSING_PUBLIC_HOST>`. Private DNS `processing.internal` не публикуется.
@@ -23,15 +23,15 @@
- 100 pending принимаются; 101-й file POST — retryable `503` без новой task;
- RPS overflow — `429 + Retry-After`;
- long Safety poll не блокирует WS/read API ВМ1;
- если gate не пройден — увеличить slots/CPU/clamd scan lanes; production traffic не открывать.
- если gate не пройден — корректировать slots/CPU/broker concurrency в пределах подтверждённой KESL capacity; production traffic не открывать.
Monthly availability SLO Safety в MVP не задаётся. Scale-out/ВМ3: sustained CPU/RAM >70%, queue age >30 с, провал performance gates, contention `bitrix-sync` или независимый release cadence. Workers масштабируются первыми по queue depth, `clamd` — scan lanes.
Monthly availability SLO Safety в MVP не задаётся. Scale-out/ВМ3: sustained CPU/RAM >70%, queue age >30 с, провал performance gates, contention `bitrix-sync` или независимый release cadence. Workers масштабируются первыми по queue depth; предел задаёт проверенная throughput host KESL/broker.
## 3. Hardening и egress
Arch-10 §5 / arch-06. Public Docker ports — `80,443`. Private `8443` не internet SG.
Default-deny egress. Bootstrap window для registry/OS, затем закрыть. Оставить: S3, approved Bitrix portal (`bitrix-sync`), signature CDN (`freshclam`), DNS/NTP, SigNoz `4317`, PG. Постоянный open egress запрещён.
Default-deny egress. Bootstrap window для registry/OS, затем закрыть. Оставить: S3, approved Bitrix portal (`bitrix-sync`), DNS/NTP, SigNoz `4317`, PG; для host KESL — только approved update sources по операторскому KESL runbook. Постоянный open egress запрещён.
Отдельный IAM principal Selectel: только VM2 secret names.
@@ -57,11 +57,11 @@ Checkout exact SHA. Структура: root Compose, `nginx`, Safety, `bitrix-s
## 6. Root Compose ВМ2
Сервисы: public/private nginx, `message-safety-api`, `message-safety-worker`, `clamd`, `freshclam`, `bitrix-sync`, Redis Safety, local `otel-collector`.
Сервисы Compose: public/private nginx, `message-safety-api`, `message-safety-worker`, `bitrix-sync`, Redis Safety, local `otel-collector`. `clamd`/`freshclam` удалены.
Networks: `public` (только nginx webhook/ACME), `backend`, `egress` (freshclam, bitrix-sync, Safety worker → S3/PG/DNS, collector → SigNoz), `observability`. Safety API/clamd/Redis без общего internet egress.
Networks: `public` (только nginx webhook/ACME), `backend`, `egress` (`bitrix-sync`, Safety worker → S3/PG/DNS, collector → SigNoz), `observability`. Safety API/Redis без общего internet egress. Worker получает только read/write bind Unix socket `/run/han-kesl/scan.sock`, а не доступ к host command execution.
Volumes: Redis Safety data (rebuildable), ClamAV signatures, ACME public, `otel-queue` + init. Frontend-static нет. Published: nginx 80/443; `8443` только SG ВМ1/ops.
Volumes: Redis Safety data (rebuildable), ACME public, `otel-queue` + init. KESL database/runtime не являются Compose volumes. Frontend-static нет. Published: nginx 80/443; `8443` только SG ВМ1/ops.
Compose gate — arch-10 применительно к этому Compose.
@@ -108,9 +108,11 @@ Rollback: закрыть webhook или `503`; `BITRIX_SYNC_ENABLED=false`; ВМ
## 10. Ordered startup ВМ2
1. Redis Safety, `otel-queue-init`, Collector;
2. `clamd`/`freshclam`, Safety API/worker, `bitrix-sync`;
3. nginx последним: оба TLS, exact webhook, capability health, signature age, negative ingress/egress.
1. Выполнить operator KESL runbook: проверить KESL 12.4 standalone, database date и ежечасное обновление.
2. `enable/start` broker socket/service; проверить status и owner/group/mode `/run/han-kesl/scan.sock`.
3. Redis Safety, `otel-queue-init`, Collector.
4. Safety API/worker и `bitrix-sync`; files остаётся fail-closed до успешной проверки broker.
5. nginx последним: оба TLS, exact webhook, capability health, KESL database age, negative ingress/egress.
До enablement `bitrix-sync``sync_disabled`. Safety v2 capability `text|links|files|worker`. Redis Safety не core gate readiness.
@@ -139,7 +141,7 @@ Cutover gates: private TLS chain/SAN; Safety v2 PG migration и lease/fencing sm
Rollback ВМ2 не требует изменения nginx ВМ1. Caller rollback — runbook ВМ1.
## 14. Emergency MOCK и Freshclam
## 14. Emergency MOCK и KESL
`deploy` без root login:
@@ -153,11 +155,11 @@ sudo /usr/local/sbin/han-message-safety-mode standard
Helper `root:root 0755`; sudoers только этот executable. Config `root:han-message-safety 0640`, GID `10001`. Нет auto-expiry; incident не закрывать без `standard` и canary. File в MOCK — `scan_status=bypassed`, не `clean`. `deploy` не пишет Compose/Docker.
`freshclam`: controlled egress только к signature CDN. Seed `max_signature_age_hours=240`, schema max `720`. Stale/failed update выключает только `files` и alert. Новая база — integrity/EICAR, atomic activate; regression — предыдущая валидная база.
KESL обновляет базы на host ежечасно по отдельному operator KESL runbook. Seed `max_signature_age_hours=240`, schema max `720`. Stale/failed update выключает только `files` и создаёт alert; scanner error не становится allow. Broker является custom integration: формат/exit semantics `kesl-control --scan-file --action Inform` и throughput подтверждаются target-VM gates.
## 15. Rollback, ops, incidents ВМ2
Redis Safety restore — empty/clean, lazy cache ([`module-04-redis-vm2.md`](module-04-redis-vm2.md)). Routine: signature age, Safety queue, sync DLQ/webhook lag, Collector, disk, egress still deny.
Redis Safety restore — empty/clean, lazy cache ([`module-04-redis-vm2.md`](module-04-redis-vm2.md)). Routine: KESL version/database date и hourly update, broker/socket status, Safety queue, sync DLQ/webhook lag, Collector, disk, egress still deny.
Incident: Safety timeout — checkpoint, не новый task id; Bitrix down — circuit/DLQ, webhook `503`; MOCK page — вернуть standard; cert processing host и private CA.
@@ -171,7 +173,7 @@ Incident: Safety timeout — checkpoint, не новый task id; Bitrix down
- private `8443` fail-closed до cutover, затем только источники ВМ1;
- webhook allow-list и preflight `BITRIX_SYNC_ENABLED` согласованы;
- capability/load gates module-05;
- MOCK helper и Freshclam rehearsal;
- MOCK helper и KESL/broker rehearsal;
- observability ВМ2 + redaction (нет query/body/file content);
- cutover §13 не объявлен выполненным только документацией.