From 9eb8b2bc6e099dade2162a23349c42233ae25c27 Mon Sep 17 00:00:00 2001 From: mi Date: Fri, 14 Aug 2026 10:39:47 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BC=D0=B5=D0=BD=D1=8F=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20clamav?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../arch-03-docker-compose-blueprint.md | 3 + architectory/arch-04-settings-and-content.md | 2 +- .../arch-06-service-hosting-security.md | 4 +- codebase/Signoz/README.md | 7 +- codebase/services/deployment/RUNBOOK.ru.md | 96 +++++++++++++++++++ .../app/artifacts/config.schema.json | 2 +- .../app/artifacts/seed-config.yaml | 2 +- .../tests/test_config_and_schema.py | 11 +++ modules/module-05-message-safety.md | 6 +- modules/module-10-deployment-runbook.md | 2 +- releases/#1.1 VM-service-deploy.md | 77 ++++++++++++++- 11 files changed, 196 insertions(+), 16 deletions(-) diff --git a/architectory/arch-03-docker-compose-blueprint.md b/architectory/arch-03-docker-compose-blueprint.md index 4910e80..377b48d 100644 --- a/architectory/arch-03-docker-compose-blueprint.md +++ b/architectory/arch-03-docker-compose-blueprint.md @@ -255,6 +255,9 @@ security-профили: - работоспособность updater подтверждается состоянием `Up`, отсутствием restart loop и отдельным контролем возраста/signature version, а не искусственным container healthcheck. +- Архитектурная верхняя граница допустимого возраста signatures — `720` часов + (30 дней); активный seed-порог `max_signature_age_hours` — `240` часов + (10 дней), то есть строже предельного значения. Смена digest ClamAV требует повторной проверки entrypoint, UID/GID, writable paths, `clamd` health и фактического обновления signatures. Нельзя менять diff --git a/architectory/arch-04-settings-and-content.md b/architectory/arch-04-settings-and-content.md index 66c827d..b0936b2 100644 --- a/architectory/arch-04-settings-and-content.md +++ b/architectory/arch-04-settings-and-content.md @@ -199,7 +199,7 @@ worker.lease_seconds=90 ## Service-owned настройки `message-safety` -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. Полный schema/seed/activation contract — module-05 §10.1 и §15. +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. `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. diff --git a/architectory/arch-06-service-hosting-security.md b/architectory/arch-06-service-hosting-security.md index 1b71853..a751809 100644 --- a/architectory/arch-06-service-hosting-security.md +++ b/architectory/arch-06-service-hosting-security.md @@ -479,7 +479,9 @@ certificate/key проверяются preflight. не превращала успешный one-shot exit в download loop/rate limit; - унаследованный healthcheck, проверяющий отсутствующий в updater-контейнере daemon, отключается; updater контролируется по `Up`, restart count, логам и - возрасту сигнатур. + возрасту сигнатур; +- security policy разрешает настроить порог возраста не выше `720` часов + (30 дней); production-like seed использует более строгие `240` часов. Ошибки `read-only file system` устраняются точечным writable mount. Запрещено лечить их глобальным `read_only: false`, root, `privileged` или broad diff --git a/codebase/Signoz/README.md b/codebase/Signoz/README.md index be0c6b9..0427dec 100644 --- a/codebase/Signoz/README.md +++ b/codebase/Signoz/README.md @@ -75,16 +75,15 @@ Host signoz-private IdentityFile C:\Users\MI\.ssh\hansel-private ProxyJump han-jump ``` +Затем ssh signoz-ui -N +(устаревшее: На рабочем компьютере откройте туннель до SigNoz через HAN_CHAT: ```powershell ssh -L 8080:127.0.0.1:8080 signoz-private -N ``` -Пока есть доступ снаружи, можно проще: -ssh -i C:\Users\MI\.ssh\hansel-private -L 8080:127.0.0.1:8080 root@135.106.166.7 -N - - +) Маршрут SSH: Windows → публичный адрес HAN_CHAT → `192.168.0.5:22`. Назначение `127.0.0.1:8080` в `-L` открывает конечная VM SigNoz, а не HAN_CHAT. diff --git a/codebase/services/deployment/RUNBOOK.ru.md b/codebase/services/deployment/RUNBOOK.ru.md index 8a21b77..9edb28a 100644 --- a/codebase/services/deployment/RUNBOOK.ru.md +++ b/codebase/services/deployment/RUNBOOK.ru.md @@ -504,6 +504,36 @@ deployment/preflight.sh новый монотонный номер и отдельные значения `--actor`/`--approved-by`; повторно активировать старую версию нельзя. Alembic downgrade запрещён. +При обновлении ClamAV policy образ Message Safety должен содержать согласованные +seed и schema: seed `max_signature_age_hours=240`, schema maximum `720` +(30 дней). После обновления immutable image digest создайте новую config +version; существующую active version не редактируйте и не активируйте повторно: + +```sh +NEXT_VERSION='<СЛЕДУЮЩИЙ_МОНОТОННЫЙ_НОМЕР>' +/usr/local/sbin/han-vm2-compose --profile ops pull \ + message-safety-migrate +/usr/local/sbin/han-vm2-compose --profile ops run --rm \ + --entrypoint message-safety-config message-safety-migrate \ + validate /app/app/artifacts/seed-config.yaml +/usr/local/sbin/han-vm2-compose --profile ops run --rm \ + --entrypoint message-safety-config message-safety-migrate \ + create /app/app/artifacts/seed-config.yaml \ + --version "$NEXT_VERSION" --actor '' +/usr/local/sbin/han-vm2-compose --profile ops run --rm \ + --entrypoint message-safety-config message-safety-migrate \ + activate --version "$NEXT_VERSION" --approved-by '' +/usr/local/sbin/han-vm2-compose up -d --no-deps --force-recreate \ + message-safety-api message-safety-worker +/usr/local/sbin/han-vm2-compose ps \ + message-safety-api message-safety-worker clamd freshclam +unset NEXT_VERSION +``` + +Старый image, schema которого ограничивает поле значением `168`, нельзя +оставлять после активации значения `240`: сначала обновите +`MESSAGE_SAFETY_IMAGE` на новый digest и проверьте `config --quiet`. + ### Gate 4 — конфигурация nginx до запуска После выпуска public TLS в `/etc/letsencrypt` и материализации internal TLS @@ -819,3 +849,69 @@ han-message-safety-mode mock --text-free false --file-free false задокументируйте минимальные writable пути для сигнатур/runtime и компенсируйте сетевыми и ресурсными лимитами. Egress к signature-CDN получает только `freshclam`; `clamd` — нет. + + +# Gate 9 завершает техническую приёмку VM2, но не означает production cutover сервисов. + +Дальнейший порядок: + +1. Проверить автозапуск: + +```sh +systemctl is-enabled \ + docker.service \ + han-chat-vm2-docker-firewall.service \ + han-secrets-vm2.service \ + han-processing.service \ + certbot.timer + +systemctl is-active \ + docker.service \ + han-chat-vm2-docker-firewall.service \ + han-processing.service \ + certbot.timer + +/usr/local/sbin/han-vm2-compose ps +``` + +2. Провести reboot-gate. Только после проверки отдельного входа `admin` и доступа к консоли Selectel: + +```sh +systemctl reboot +``` + +После переподключения повторить команды выше и кратко Gate 6–8: HTTPS, firewall, private Safety API. + +3. Зафиксировать итог релиза: + +```sh +/usr/local/sbin/han-vm2-compose config --images +/usr/local/sbin/han-vm2-compose ps +systemctl list-timers certbot.timer +journalctl --no-pager -u han-processing.service -u han-secrets-vm2.service +``` + +Сохранить версии образов, дату приёмки и результаты gates без значений секретов. + +4. Настроить эксплуатационный мониторинг: + +- container unhealthy/restart/OOM; +- срок TLS; +- возраст ClamAV signatures; +- OTEL queue/export errors; +- disk/RAM; +- активный MOCK mode; +- недоступность private Safety API. + +5. Далее — отдельный controlled cutover Message Safety на VM1: private URL, internal CA, service token, API integration, rollback rehearsal и функциональные проверки. + +6. `bitrix-sync` пока оставить: + +```dotenv +BITRIX_SYNC_ENABLED=false +BITRIX_SYNC_MODE=disabled +``` + +Public allow-list — только `deny all;`. Включать Bitrix можно лишь после выполнения gates `module-07`: поля портала, webhooks, migrations, grants, backfill/watermark и rollback rehearsal. + +Таким образом, ближайший шаг сейчас — reboot-gate и фиксация приёмки VM2. Затем переход к интеграции VM1, а не немедленное включение Bitrix. \ No newline at end of file diff --git a/codebase/services/message-safety/app/artifacts/config.schema.json b/codebase/services/message-safety/app/artifacts/config.schema.json index 8f5db07..10cedb2 100644 --- a/codebase/services/message-safety/app/artifacts/config.schema.json +++ b/codebase/services/message-safety/app/artifacts/config.schema.json @@ -50,7 +50,7 @@ }, "clamav": { "type": "object", "additionalProperties": false, "required": ["scan_timeout_sec", "max_signature_age_hours"], - "properties": {"scan_timeout_sec": {"type": "integer", "minimum": 1, "maximum": 120}, "max_signature_age_hours": {"type": "integer", "minimum": 1, "maximum": 168}} + "properties": {"scan_timeout_sec": {"type": "integer", "minimum": 1, "maximum": 120}, "max_signature_age_hours": {"type": "integer", "minimum": 1, "maximum": 720}} }, "file_policy": { "type": "object", "additionalProperties": false, "required": ["enabled_mime_types", "max_size_bytes"], diff --git a/codebase/services/message-safety/app/artifacts/seed-config.yaml b/codebase/services/message-safety/app/artifacts/seed-config.yaml index 68a43b2..670d4a5 100644 --- a/codebase/services/message-safety/app/artifacts/seed-config.yaml +++ b/codebase/services/message-safety/app/artifacts/seed-config.yaml @@ -23,7 +23,7 @@ link: pipeline_timeout_sec: 2 clamav: scan_timeout_sec: 45 - max_signature_age_hours: 24 + max_signature_age_hours: 240 file_policy: enabled_mime_types: [image/jpeg, image/png, image/webp, image/heic, image/heif, application/pdf] diff --git a/codebase/services/message-safety/tests/test_config_and_schema.py b/codebase/services/message-safety/tests/test_config_and_schema.py index e724bf7..37ae83f 100644 --- a/codebase/services/message-safety/tests/test_config_and_schema.py +++ b/codebase/services/message-safety/tests/test_config_and_schema.py @@ -7,6 +7,7 @@ from pathlib import Path import pytest import yaml +from jsonschema import ValidationError from app.config import validate_config from app.db import Base @@ -34,6 +35,16 @@ def test_config_cross_field_and_manifest_subset(artifacts: Path) -> None: validate_config(bad, artifacts) +def test_clamav_signature_age_policy_bounds(artifacts: Path) -> None: + document = seed(artifacts) + assert document["clamav"]["max_signature_age_hours"] == 240 + document["clamav"]["max_signature_age_hours"] = 720 + validate_config(document, artifacts) + document["clamav"]["max_signature_age_hours"] = 721 + with pytest.raises(ValidationError): + validate_config(document, artifacts) + + def test_normative_tables_are_in_service_schema() -> None: expected = { "safety_requests", diff --git a/modules/module-05-message-safety.md b/modules/module-05-message-safety.md index 1d59cba..1808869 100644 --- a/modules/module-05-message-safety.md +++ b/modules/module-05-message-safety.md @@ -405,6 +405,8 @@ MVP использует отдельный `clamd` sidecar/service в private D - порт не публикуется наружу; - сигнатуры обновляет `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. @@ -820,7 +822,7 @@ link: pipeline_timeout_sec: 2 clamav: scan_timeout_sec: 45 - max_signature_age_hours: 24 + max_signature_age_hours: 240 file_policy: enabled_mime_types: - image/jpeg @@ -832,7 +834,7 @@ file_policy: max_size_bytes: 5242880 ``` -JSON Schema задаёт типы/ranges и cross-field constraints: `heartbeat_sec < lease_sec < execution_deadline_sec`, scan/pipeline timeout не больше execution deadline, TTL/retention положительны. `enabled_mime_types` — непустое уникальное подмножество detector manifest; `max_size_bytes` и последующие format overrides не превышают hard limits manifest. Referenced rules/detector artifacts обязаны быть доступны и пройти hash/signature verification до activation. +JSON Schema задаёт типы/ranges и cross-field constraints: `heartbeat_sec < lease_sec < execution_deadline_sec`, scan/pipeline timeout не больше execution deadline, TTL/retention положительны, `max_signature_age_hours` не превышает `720` часов (30 дней). `enabled_mime_types` — непустое уникальное подмножество detector manifest; `max_size_bytes` и последующие format overrides не превышают hard limits manifest. Referenced rules/detector artifacts обязаны быть доступны и пройти hash/signature verification до activation. ### 15.2. Env и secrets Message Safety на ВМ2 diff --git a/modules/module-10-deployment-runbook.md b/modules/module-10-deployment-runbook.md index eba5033..cc85c0c 100644 --- a/modules/module-10-deployment-runbook.md +++ b/modules/module-10-deployment-runbook.md @@ -1318,7 +1318,7 @@ Root устанавливает helper и `/etc/sudoers.d/deploy-message-safety- Перед включением operator фиксирует incident/change ID и выбранные text/file policies; после команды проверяет `processing_mode=mock`, forced canaries, отсутствие `202`, metric/active alert и audit actor. Ранее принятые standard tasks сохраняют mode и завершаются без переклассификации; только новые requests используют MOCK. Автоматического timeout нет: mode действует без ограничения по времени до явного `standard`. Поэтому перед закрытием incident обязателен возврат в standard, проверка normal capabilities и text/link/EICAR canary. File, разрешённый в MOCK, маркируется `scan_status=bypassed`, а не `clean`. -`freshclam` имеет controlled egress только к утверждённому signature CDN. Max signature age 24 ч; stale/failed update выключает только `files` и поднимает alert. Новая база проходит integrity/load/EICAR canary и atomic activate/reload; при regression возвращается последняя валидная база. +`freshclam` имеет controlled egress только к утверждённому signature CDN. Seed `max_signature_age_hours` равен `240` ч (10 дней), а schema запрещает значения выше `720` ч (30 дней); stale/failed update выключает только `files` и поднимает alert. Новая база проходит integrity/load/EICAR canary и atomic activate/reload; при regression возвращается последняя валидная база. Initial ВМ2: 4 vCPU/8 GiB/80 GiB, resource/PID limits и backpressure. Workers масштабируются первыми по queue depth, `clamd` — scan lanes. ВМ3/scale-out инициируются при sustained CPU/RAM >70%, queue age >30 с, провале module-05 performance gates, contention `bitrix-sync` или независимом release cadence. diff --git a/releases/#1.1 VM-service-deploy.md b/releases/#1.1 VM-service-deploy.md index 03e0dac..2def5c6 100644 --- a/releases/#1.1 VM-service-deploy.md +++ b/releases/#1.1 VM-service-deploy.md @@ -47,11 +47,11 @@ docker push $REGISTRY/han-bitrix-sync:1.0.3 docker image inspect $REGISTRY/han-bitrix-sync:1.0.3 --format '{{index .RepoDigests 0}}' cd /mnt/c/Users/MI/Documents/Assistent/HAN_chat_specification/codebase/services/message-safety -docker build -t han-message-safety:1.0.1 . +docker build -t han-message-safety:1.0.2 . REGISTRY=cr.selcloud.ru/han-images # ваш registry -docker tag han-message-safety:1.0.1 $REGISTRY/han-message-safety:1.0.1 -docker push $REGISTRY/han-message-safety:1.0.1 -docker image inspect $REGISTRY/han-message-safety:1.0.1 --format '{{index .RepoDigests 0}}' +docker tag han-message-safety:1.0.2 $REGISTRY/han-message-safety:1.0.2 +docker push $REGISTRY/han-message-safety:1.0.2 +docker image inspect $REGISTRY/han-message-safety:1.0.2 --format '{{index .RepoDigests 0}}' @@ -890,4 +890,71 @@ docker run --rm --network han-processing_observability \ после успешной проверки нужно удалить docker rmi ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest -docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" \ No newline at end of file +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + +# Проверка финальная + +1. Проверить автозапуск: + +```sh +systemctl is-enabled \ + docker.service \ + han-chat-vm2-docker-firewall.service \ + han-secrets-vm2.service \ + han-processing.service \ + certbot.timer + +(все enabled) + +systemctl is-active \ + docker.service \ + han-chat-vm2-docker-firewall.service \ + han-processing.service \ + certbot.timer + +(все active) + +/usr/local/sbin/han-vm2-compose ps --format "table {{.Service}}\t{{.Status}}\t{{.Ports}}" +``` + +2. Провести reboot-gate. Только после проверки отдельного входа `admin` и доступа к консоли Selectel: + +```sh +systemctl reboot +``` + +После переподключения повторить команды выше и кратко Gate 6–8: HTTPS, firewall, private Safety API. + +3. Зафиксировать итог релиза: + +```sh +/usr/local/sbin/han-vm2-compose config --images +/usr/local/sbin/han-vm2-compose ps +systemctl list-timers certbot.timer +journalctl --no-pager -u han-processing.service -u han-secrets-vm2.service +``` + +Сохранить версии образов, дату приёмки и результаты gates без значений секретов. + +4. Настроить эксплуатационный мониторинг: + +- container unhealthy/restart/OOM; +- срок TLS; +- возраст ClamAV signatures; +- OTEL queue/export errors; +- disk/RAM; +- активный MOCK mode; +- недоступность private Safety API. + +5. Далее — отдельный controlled cutover Message Safety на VM1: private URL, internal CA, service token, API integration, rollback rehearsal и функциональные проверки. + +6. `bitrix-sync` пока оставить: + +```dotenv +BITRIX_SYNC_ENABLED=false +BITRIX_SYNC_MODE=disabled +``` + +Public allow-list — только `deny all;`. Включать Bitrix можно лишь после выполнения gates `module-07`: поля портала, webhooks, migrations, grants, backfill/watermark и rollback rehearsal. + +Таким образом, ближайший шаг сейчас — reboot-gate и фиксация приёмки VM2. Затем переход к интеграции VM1, а не немедленное включение Bitrix. \ No newline at end of file