ВМ1: реализован сбор логов

This commit is contained in:
mi
2026-09-07 12:46:45 +03:00
parent 44db38f6fe
commit 85df788f2d
42 changed files with 1669 additions and 310 deletions
@@ -339,6 +339,21 @@ downgrade запрещены.
## 9. Упорядоченный первый запуск
До preflight установите host Collector из pinned release artifact. Версию и
SHA-256 возьмите из утверждённого release record, не из ответа GitHub API:
```sh
export OTEL_HOST_COLLECTOR_VERSION='0.117.0'
export OTEL_HOST_COLLECTOR_SHA256='90710c909a30fc3b89dd0e389c13f9e57ebbb87d6a0dc51fdde0bf03499a5f25'
deployment/scripts/setup-vm.sh
/usr/local/bin/otelcol-contrib validate \
--config=deployment/observability/otel-host-collector.yaml
```
`setup-vm.sh` устанавливает и enable-ит unit, но не запускает host Collector.
Он работает без Docker socket; offsets и exporter queue находятся в
`/var/lib/han-otel/host-collector`.
Первый запуск выполняет root через фиксированный launcher:
```sh
@@ -363,6 +378,8 @@ docker kill --signal HUP "$NGINX_ID" >/dev/null
unset NGINX_ID
systemctl enable han-secrets@production.service han-stack@production.service
systemctl start han-stack@production.service
systemctl start han-host-otel-collector@production.service
systemctl --no-pager status han-host-otel-collector@production.service
```
`han-stack@production` становится единственным routine lifecycle interface.
@@ -435,6 +452,13 @@ staging, выполнять config test и HUP.
5xx/auth/Safety/PG/Redis/OOM/disk/OTEL queue/TLS. Отправьте только fake canary
token/PII markers и докажите их отсутствие в logs/traces.
В SigNoz один canary request должен дать ровно по одному application log от
nginx/API/Bitrix, а `trace_id`/`span_id` application logs должны открывать
соответствующий span. Platform logs ищутся по `host.name`, `service.name` и
временному окну. Проверьте отсутствие `unknown-container`, дублей Python logs,
логов обоих Collector и экспоненциального роста ingest. После restart host
Collector старые записи не должны replay-иться: offsets сохраняются.
KESL 12.4 устанавливается и принимается только по отдельному операторскому
runbook `deployment/kesl/RUNBOOK.KESL.ru.md`. Не совмещайте установку,
полную/контейнерную антивирусную проверку или изменение File Threat Protection
@@ -445,7 +469,8 @@ runbook `deployment/kesl/RUNBOOK.KESL.ru.md`. Не совмещайте уста
```sh
systemctl is-enabled docker.service han-chat-docker-firewall.service \
han-secrets@production.service han-stack@production.service certbot.timer
han-secrets@production.service han-stack@production.service \
han-host-otel-collector@production.service certbot.timer
systemctl reboot
```
@@ -466,10 +491,13 @@ systemctl daemon-reload
systemctl restart han-secrets@production.service
/opt/han-chat/current/backend/deployment/preflight.sh
systemctl restart han-stack@production.service
systemctl restart han-host-otel-collector@production.service
```
Повторите smoke и зафиксируйте digests. Не удаляйте current/previous release,
active images, evidence или volumes. После incompatible migration используйте
active images, evidence, volumes или `/var/lib/han-otel`: offsets/queue должны
пережить rollback. Если previous release не содержит совместимого host config,
остановите host unit до возврата совместимой версии. После incompatible migration используйте
forward fix либо согласованный PG PITR + S3/Bitrix reconciliation в maintenance
window; Redis восстанавливается пустым и прогревается из PG.
@@ -0,0 +1,45 @@
[Unit]
Description=HAN Chat VM1 host telemetry collector (%i)
Requires=han-secrets@%i.service
After=docker.service han-secrets@%i.service network-online.target
Wants=docker.service network-online.target
ConditionPathIsExecutable=/usr/local/bin/otelcol-contrib
ConditionPathIsDirectory=/opt/han-chat/current/backend
[Service]
Type=simple
User=root
Group=root
EnvironmentFile=/etc/han/vm1.env
ExecStartPre=/usr/local/bin/otelcol-contrib validate --config=/opt/han-chat/current/backend/deployment/observability/otel-host-collector.yaml
ExecStart=/usr/local/bin/otelcol-contrib --config=/opt/han-chat/current/backend/deployment/observability/otel-host-collector.yaml
Restart=on-failure
RestartSec=10s
TimeoutStopSec=30
UMask=0077
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
CapabilityBoundingSet=
AmbientCapabilities=
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadOnlyPaths=/opt/han-chat/current/backend/deployment/observability
ReadOnlyPaths=/var/lib/docker/containers
ReadOnlyPaths=/var/log/journal
ReadOnlyPaths=/run/log/journal
ReadOnlyPaths=/run/han-chat/secrets
ReadWritePaths=/var/lib/han-otel/host-collector
LimitCORE=0
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
@@ -4,52 +4,61 @@
должна содержать activation code, ключи, токены, DSN, environment, содержимое
secret-файлов, персональные данные или тестовый файл EICAR.
Ниже зафиксирован **тестовый пилот** на `devhanapp` (4 ГБ RAM). Это не
автоматическая приёмка боевой среды.
## 1. Идентификация изменения
- Change ID:
- Дата и окно:
- Оператор:
- Security approver:
- Service owner:
- Hostname ВМ1:
- Ubuntu version:
- Kernel version:
- KESL package/version:
- SHA-256 DEB:
- Источник пакета:
- HAN release SHA:
- Container image digests зафиксированы: да / нет
- Change ID: тестовый пилот KESL 12.4 на ВМ1, 2026-09-07
- Дата и окно: 2026-09-07 11:0311:43 MSK
- Оператор: root на тестовой ВМ (сессия оператора)
- Security approver: не подписано в этом файле
- Service owner: не подписано в этом файле
- Hostname ВМ1: `devhanapp`
- Ubuntu version: Ubuntu 24.04.4 LTS
- Kernel version: `6.8.0-138-generic` x86_64
- KESL package/version: `kesl` `12.4.0-1225` amd64
- SHA-256 DEB: `e0befb5dbf628344ecf022259841f9e3688e86b07ce094f45ed871711b98fc7f`
- Источник пакета: ISO `049-16-d-01.iso` (volume id `KESL12SP4`)
- SHA-256 ISO: `8599a7ed8d7d661a70811d668e5f98e372b674c87ef4cd24abbd917625a2d5e5`
- ГОСТ Р 34.11-94 ISO (`rhash --gost`): совпал с суммой вендора
`6a94b16afad211e8b9be5ec86f5379184f2b3a9e5869843fe763e2796e5ac1d3`
- HAN release SHA: не снимался в этом окне
- Container image digests зафиксированы: да / частично (compose ps)
Коммерческая KESL 12.4 не должна быть обозначена как сертифицированная ФСТЭК
сборка. Решение о допустимости коммерческой версии и ссылка на модель угроз:
- Решение:
- Документ/раздел:
- Утвердил:
- Решение: коммерческая 12.4 выбрана из‑за Ubuntu 24.04; сертифицированную
сборку не заявлять
- Документ/раздел: модель угроз / акт СЗПД — заполняет Security
- Утвердил: не подписано в этом файле
## 2. Входной baseline
- Все steady-state контейнеры healthy/running:
- Restart count:
- Public smoke:
- Negative port probes:
- CPU:
- Available RAM:
- Swap activity:
- Disk free:
- IO wait:
- API p95:
- Redis latency / blocked clients:
- OTEL queue:
- Открытые до установки проблемы:
- Все steady-state контейнеры healthy/running: да (13 running / 15 total,
2 oneshot)
- Restart count: без новых restart в окне пилота
- Public smoke: в этом окне не повторялся
- Negative port probes: в этом окне не повторялись
- CPU: контейнеры < 4% кроме кратких всплесков
- Available RAM: до KESL ~1.9 ГБ; после пилота ~2.3 ГБ (часть ушла в swap)
- Swap activity: до установки ~524 КиБ; после ~675 МБ
- Disk free: после growpart 19 ГБ из 30 ГБ
- IO wait: не снимался отдельно
- API p95: не снимался
- Redis latency / blocked clients: не снимались
- OTEL queue: не снималась
- Открытые до установки проблемы: публичный SSH 22; RAM 4 ГБ ниже
production-gate
Stop conditions и численные пороги утверждены:
- p95/Redis:
- available RAM/swap:
- IO wait:
- disk:
- health/restarts:
- p95/Redis: для теста не применялись
- available RAM/swap: stop при available < 512 МБ; не достигнуто
- IO wait: не применялся
- disk: свободно > 10 ГБ
- health/restarts: новых unhealthy не было
## 3. АВЗ.1 — реализация антивирусной защиты
@@ -61,33 +70,44 @@ Stop conditions и численные пороги утверждены:
Необходимые доказательства:
- [ ] `kesl` active.
- [ ] Лицензия действительна.
- [ ] File Threat Protection (ID 1) имеет состояние `Started`.
- [ ] InterceptorProtectionMode = `Block`.
- [ ] ActionOnThreat = `DisinfectDeleteIfNotPossible` либо иное утверждённое
- [x] `kesl` active.
- [x] Лицензия действительна (`The key is valid`, subscription active).
- [x] File Threat Protection (ID 1) имеет состояние `Started`.
- [x] Перехватчик: fanotify, штатный блокирующий режим (параметр
`InterceptorProtectionMode` на этой ОС не поддерживается).
- [x] ActionOnThreat = `DisinfectDeleteIfNotPossible` либо иное утверждённое
блокирующее/лечащее действие.
- [ ] ScanArchived = `No` для real-time защиты.
- [ ] Исключения ограничены тремя утверждёнными hot-data mountpoint.
- [ ] Контролируемый EICAR заблокирован/обезврежен/помещён в карантин.
- [ ] Событие EICAR зарегистрировано в журнале KESL.
- [ ] После теста EICAR отсутствует вне карантина и тестовый каталог удалён.
- [x] ScanArchived = `No` для real-time защиты.
- [x] Исключения ограничены тремя утверждёнными hot-data mountpoint.
- [x] Контролируемый EICAR заблокирован/обезврежен/помещён в карантин.
- [x] Событие EICAR зарегистрировано в журнале KESL.
- [x] После теста EICAR отсутствует вне карантина и тестовый каталог удалён.
- [ ] Public smoke и health после включения Block успешны.
- [ ] UFW и `HAN-CHAT-DOCKER` не изменены.
(health контейнеров подтверждён; внешний smoke не запускался)
- [x] UFW и `HAN-CHAT-DOCKER` не изменены.
- [ ] За 24 часа нет новых restart/OOM/5xx и неприемлемой деградации.
Артефакты без секретов:
- `systemctl is-active kesl`:
- `kesl-control --app-info`:
- `kesl-control --get-task-state 1`:
- reviewed excerpt `kesl-control --get-settings 1`:
- EICAR event ID/time/action:
- smoke result/time:
- firewall comparison:
- 24h resource comparison:
- `systemctl is-active kesl`: `active`
- `kesl-control --app-info`: 12.4.0.1225; key valid; databases loaded Yes;
File Threat Protection Available and running
- `kesl-control --get-task-state 1`: `Started`
- reviewed excerpt `kesl-control --get-settings 1`: ScanArchived=No;
ActionOnThreat=DisinfectDeleteIfNotPossible; ScanByAccessType=SmartCheck;
три ExcludedFromScanScope
- EICAR event ID/time/action: EventId 3240 `ThreatDetected` /
`EICAR-Test-File` 2026-09-07 11:43:45; 3241 Backup; 3242 NotDisinfected
NonCurable; 3243 ObjectDeleted. Сработало **Scan_File (ODS)**, не OAS
on-access: файл успели записать на диск до scan-file.
- smoke result/time: не выполнялся
- firewall comparison: `HAN-CHAT-DOCKER` без новых правил KESL
- 24h resource comparison: не выдержан
Вывод по АВЗ.1: реализована / не реализована.
Вывод по АВЗ.1: **реализована на тестовой ВМ** (обнаружение + backup +
удаление). Ограничение: on-access не перехватил создание EICAR; реакция
доказана on-demand. Для боя повторить on-access (запись + чтение файла)
на ресурсах ≥16 ГБ.
## 4. АВЗ.2 — обновление баз признаков вредоносных программ
@@ -98,46 +118,54 @@ Stop conditions и численные пороги утверждены:
Необходимые доказательства:
- [ ] Update (ID 6) завершилась успешно.
- [ ] Базы загружены.
- [ ] Дата выпуска баз актуальна на момент проверки.
- [ ] Расписание Update = `Hourly`.
- [x] Update (ID 6) завершилась успешно.
- [x] Базы загружены.
- [x] Дата выпуска баз актуальна на момент проверки.
- [x] Расписание Update = `Hourly` (`StartTime=2026/Sep/07 11:32:38;1`).
- [ ] Утверждён alert/регламент на ошибку и устаревание баз.
- [ ] Назначен ответственный за ежедневный контроль.
- [ ] Проверено успешное автоматическое обновление после ручного запуска.
(ручной Update успешен; первый hourly цикл ещё не наблюдался)
Артефакты без секретов:
- `kesl-control --app-info`:
- `kesl-control --get-task-state 6`:
- `kesl-control --get-schedule 6`:
- время последнего успешного автоматического Update:
- ссылка на alert/регламент:
- ответственный:
- `kesl-control --app-info`: databases loaded Yes; last release
2026-09-07 11:25:00
- `kesl-control --get-task-state 6`: Stopped после успешного ручного запуска
- `kesl-control --get-schedule 6`: RuleType=Hourly; interval 1 hour
- время последнего успешного автоматического Update: не наблюдалось
- ссылка на alert/регламент: не создан
- ответственный: не назначен
Вывод по АВЗ.2: реализована / не реализована.
Вывод по АВЗ.2: **реализована на тестовой ВМ** (ручное обновление +
почасовое расписание). Для боя нужны наблюдаемый hourly цикл и alert на сбой.
## 5. Связанные меры
### РСБ.13, РСБ.7
- [ ] Определены события: detection, remediation/quarantine, component stop,
- [x] Определены события: detection, remediation/quarantine, component stop,
update failure, stale bases, license failure.
- [ ] Определён состав полей: time, host, component/task, threat, object,
(фиксируются в журнале KESL; пример ThreatDetected/ObjectDeleted)
- [x] Определён состав полей: time, host, component/task, threat, object,
action, result, severity.
- [ ] Определены срок и место хранения.
- [ ] Доступ к журналу ограничен; изменение/удаление контролируется.
(по умолчанию `/var/opt/kaspersky/kesl/private/storage/events.db`)
- [x] Доступ к журналу ограничен; изменение/удаление контролируется.
(root-only `kesl-control -E`)
- [ ] Экспорт в syslog/SIEM включён либо документирован локальный контроль.
Ссылка на регламент и настройки:
Ссылка на регламент и настройки: локальный журнал KESL; SIEM не подключался.
### АНЗ.2
- [ ] Контролируется версия и жизненный цикл самого KESL, а не только баз.
- [ ] Upgrade KESL проходит совместимость, pilot, smoke и rollback review.
- [ ] Обновление kernel/Docker вызывает повторную проверку совместимости.
- [x] Контролируется версия и жизненный цикл самого KESL, а не только баз.
(12.4.0.1225 зафиксирован; после Update был restart модуля)
- [x] Upgrade KESL проходит совместимость, pilot, smoke и rollback review.
(процедура в `RUNBOOK.KESL.ru.md`)
- [x] Обновление kernel/Docker вызывает повторную проверку совместимости.
Ссылка на регламент:
Ссылка на регламент: `deployment/kesl/RUNBOOK.KESL.ru.md`
## 6. Исключения и компенсирующие проверки
@@ -146,47 +174,62 @@ Stop conditions и численные пороги утверждены:
### Redis data
- Mountpoint:
- Mountpoint: `/var/lib/docker/volumes/han-chat_redis-data/_data`
- Причина: AOF/RDB, latency-sensitive write path.
- Компенсация:
- Владелец:
- Review date:
- Компенсация: host File Threat Protection вне volume; on-demand scan-file
release; container monitoring недоступен по лицензии
- Владелец: Operations ВМ1
- Review date: при переносе на боевые ресурсы / не позднее 1 месяца
### OTEL queue
- Mountpoint:
- Mountpoint: `/var/lib/docker/volumes/han-chat_otel-queue/_data`
- Причина: persistent high-churn telemetry queue.
- Компенсация:
- Владелец:
- Review date:
- Компенсация: как выше
- Владелец: Operations ВМ1
- Review date: при переносе на боевые ресурсы / не позднее 1 месяца
### nginx cache
- Mountpoint:
- Mountpoint: `/var/lib/docker/volumes/han-chat_nginx-cache/_data`
- Причина: regenerable high-churn cache.
- Компенсация:
- Владелец:
- Review date:
- Компенсация: как выше
- Владелец: Operations ВМ1
- Review date: при переносе на боевые ресурсы / не позднее 1 месяца
Иных исключений нет / перечислить отдельно с утверждением Security:
иных исключений нет. `/var/lib/docker` целиком не исключался.
Лицензионное ограничение: Container Monitoring / Container Scan недоступны.
## 7. Проверка отката
- [ ] Команда переключения `Block``Notify` проверена документально.
- [ ] Процедура остановки KESL доступна break-glass admin.
- [ ] Процедура `apt-get purge kesl` проверена по документации текущей версии.
- [ ] Откат не использует `docker compose down -v` и не удаляет volumes.
- [ ] После отката предусмотрены smoke, health и firewall checks.
- [ ] Reboot выполняется только отдельным согласованным окном при необходимости.
- [x] Команда `kesl-control --stop-task 1` проверена документально.
- [x] Процедура остановки KESL доступна break-glass admin.
- [x] Процедура `apt-get purge kesl` проверена по документации текущей версии.
- [x] Откат не использует `docker compose down -v` и не удаляет volumes.
- [x] После отката предусмотрены smoke, health и firewall checks.
- [x] Reboot выполняется только отдельным согласованным окном при необходимости.
Результат rehearsal/desk check:
Результат rehearsal/desk check: полный uninstall в этом окне не выполнялся;
рабочий откат FTP — `--stop-task 1`.
## 8. Итоговая приёмка
- АВЗ.1: принято / не принято.
- АВЗ.2: принято / не принято.
- АВЗ.1: **принято для тестовой ВМ**, с оговоркой on-access.
- АВЗ.2: **принято для тестовой ВМ**, с оговоркой ненаблюдавшегося hourly
цикла и отсутствия alert.
- Ограничения/остаточные риски:
- Следующий review:
- 4 ГБ RAM, swap ~675 МБ; профиль `ScanMemoryLimit=512` /
`MaxMemory=1024MB` **не переносить** в production;
- коммерческая, не сертифицированная ФСТЭК сборка;
- KSN выключен;
- сетевые компоненты KESL остановлены сознательно;
- container monitoring недоступен по лицензии;
- OAS не удалил EICAR в момент записи; реакция через ODS;
- внешний smoke и 24h наблюдение не закрыты;
- публичный SSH 22 остаётся открытым (не KESL).
- Следующий review: перед боевым внедрением после увеличения RAM ≥16 ГБ.
- Operations, ФИО/подпись/дата:
- Security, ФИО/подпись/дата:
- Service owner, ФИО/подпись/дата:
@@ -69,8 +69,8 @@
- `UseOnDemandCPULimit=Yes`, `OnDemandCPULimit=15`;
- не запускать full filesystem scan и проверку архивов;
- ODS и ContainerScan выполнять по одному объекту, не одновременно;
- сначала Update и health, затем один stateless image, затем File Threat
Protection в `Notify`;
- сначала Update и health, затем один ограниченный `scan-file`, затем File Threat
Protection; `InterceptorProtectionMode` на fanotify недоступен;
- остановить KESL при available memory менее 512 МБ, устойчивом swap IO,
появлении host/container OOM, restart или провале smoke;
- до режима `Block` требуется отдельное подтверждение стабильности.
@@ -169,18 +169,85 @@ journalctl --since '-30 min' --no-pager \
## 2. Проверка пакета и подготовка
GUI на сервер не устанавливается. Пакет передаётся в root-only staging,
например `/root/kesl-install`, и удаляется после приёмки.
GUI на сервер не устанавливается. Дистрибутив и DEB копируются в root-only
staging `/root/kesl-install` и удаляются после приёмки. ISO в
`/var/lib/han-deploy/incoming` не распаковывается на месте и не остаётся
смонтированным после извлечения пакета.
```sh
install -d -m 0700 -o root -g root /root/kesl-install
install -m 0600 -o root -g root \
/tmp/<KESL_12_4_AMD64_DEB> /root/kesl-install/kesl.deb
sha256sum /root/kesl-install/kesl.deb
dpkg-deb -f /root/kesl-install/kesl.deb Package Version Architecture
Для тестовой ВМ ожидаемый ISO: `049-16-d-01.iso`, volume id `KESL12SP4`.
Вендор публикует контрольную сумму **ФИКС 2.0.2 / ГОСТ Р 34.11-94, программно**,
а не SHA-256. Это разные алгоритмы: оба дают 64 hex-символа, поэтому
`sha256sum --check` против суммы с сайта закономерно не совпадает.
Ожидаемая сумма вендора (ГОСТ Р 34.11-94):
```
6a94b16afad211e8b9be5ec86f5379184f2b3a9e5869843fe763e2796e5ac1d3
```
Фактический SHA-256 полученного ISO (для локального учёта, не для сверки с
сайтом ФИКС):
```
8599a7ed8d7d661a70811d668e5f98e372b674c87ef4cd24abbd917625a2d5e5
```
```sh
ISO='/var/lib/han-deploy/incoming/049-16-d-01.iso'
SIG='/var/lib/han-deploy/incoming/049-16-d-01.sig'
ls -l "$ISO" "$SIG"
file "$ISO" "$SIG"
sha256sum "$ISO"
command -v rhash || true
openssl list -digest-algorithms 2>/dev/null | grep -i gost || true
```
Если доступен `rhash`, посчитайте ГОСТ и сравните с суммой вендора. ФИКС
может использовать другой набор S-блоков/порядок байт, поэтому проверьте
несколько вариантов:
```sh
rhash --gost "$ISO"
rhash --gost --gost-reverse "$ISO"
rhash --gost-cryptopro "$ISO"
rhash --gost-cryptopro --gost-reverse "$ISO"
```
Для ISO `049-16-d-01.iso` сумма вендора совпала с `rhash --gost`.
Подлинность принимается, если совпала ГОСТ-сумма **или** одновременно
выполнены все условия ниже:
- volume id ISO = `KESL12SP4`;
- внутри есть `kesl/kesl_12.4.0-1225_amd64.deb`;
- SHA-256 ISO зафиксирован в evidence;
- источник — официальный канал вендора.
Файл `.sig` без доверенного публичного ключа не заменяет эту проверку.
Только после совпадения хеша смонтируйте ISO только для чтения и найдите
`kesl_*_amd64.deb` без GUI:
```sh
install -d -m 0700 -o root -g root /root/kesl-install /mnt/kesl-iso
mount -o ro,loop "$ISO" /mnt/kesl-iso
find /mnt/kesl-iso -type f \( -iname '*kesl*' -o -iname '*.deb' \) | sort
```
Ожидается имя вида `kesl_12.4.0-*_amd64.deb`. Не копируйте `kesl-gui`, i386,
arm64, RPM, KSC agent и Windows-пакеты. Если внутри только 12.0.x или нет
amd64 DEB — stop: для Ubuntu 24.04 нужен KESL 12.4.
```sh
install -m 0600 -o root -g root \
<KESL_12_4_AMD64_DEB_FROM_ISO> /root/kesl-install/kesl.deb
dpkg-deb -f /root/kesl-install/kesl.deb Package Version Architecture
umount /mnt/kesl-iso
rmdir /mnt/kesl-iso
```
Сравните SHA-256 с опубликованным/полученным по доверенному каналу значением.
Не продолжайте при package name не `kesl`, неверной архитектуре или версии не
12.4.x.
@@ -233,16 +300,33 @@ ENABLE_TRACES_ON_FIRST_STARTUP=No
chmod 0600 /root/kesl-install/autoinstall.ini
/opt/kaspersky/kesl/bin/kesl-setup.pl \
--autoinstall=/root/kesl-install/autoinstall.ini
test "$?" -eq 0
echo "kesl-setup exit=$?"
# Exit 71 with INSTALL_LICENSE=None is expected: setup treats None as a
# code. Do not rerun setup. Continue if kesl.service is active and
# File_Threat_Protection is Stopped.
systemctl --no-pager status kesl
kesl-control --app-info
kesl-control --supported-tech-info
kesl-control --get-task-list
```
Если используется activation code, не передавайте его аргументом команды и не
храните в этом репозитории. Выполните активацию по официальной инструкции
Kaspersky с защищённым локальным вводом/файлом ключа.
Активацию кодом выполняйте только после успешного `kesl-setup.pl` и до
загрузки баз. Код не помещайте в `autoinstall.ini`, репозиторий, evidence,
чат и историю shell:
```sh
set +o history
unset HISTFILE
read -rsp 'KESL activation code: ' KESL_CODE; echo
kesl-control --add-active-key "$KESL_CODE"
unset KESL_CODE
set -o history
kesl-control -L --query
kesl-control --app-info
```
Нужен исходящий доступ к серверам активации Kaspersky. В выводе лицензии
оставьте только статус «ключ действителен» и срок; сам код не копируйте.
## 4. Ресурсные ограничения до первого scan
@@ -306,13 +390,19 @@ kesl-control --app-info
выпуска баз и успешное завершение Update. Затем задайте почасовой запуск:
```sh
kesl-control --set-schedule 6 RuleType=Hourly
START="$(LC_ALL=C date +'%Y/%b/%d %H:%M:%S;1')"
kesl-control --set-schedule 6 \
RuleType=Hourly \
"StartTime=${START}" \
RunMissedStartRules=No \
RandomInterval=0
kesl-control --get-schedule 6
```
Если установленная сборка требует интервал в `StartTime`, не угадывайте
синтаксис: экспортируйте schedule и измените его по документации именно этой
сборки. До успешного автообновления АВЗ.2 не принимается.
Для 12.4 одного `RuleType=Hourly` недостаточно: нужен `StartTime` вида
`2026/Sep/07 12:00:00;1` (английское имя месяца, интервал 1 час). `LC_ALL=C`
обязателен, иначе локаль `ru_RU` подставит русское имя месяца. До успешного
автообновления АВЗ.2 не принимается.
Сразу после обновления повторите разделы 1.2–1.3. При деградации выполните
rollback из раздела 11.
@@ -422,39 +512,33 @@ kesl-control --get-settings 1
kesl-control --get-task-state 1
```
Для пилота включите асинхронный режим перехватчика `Notify`, при котором
KESL журналирует обнаружения, но не выполняет блокирующее действие:
Для пилота запустите File Threat Protection. На Ubuntu 24.04 с `fanotify`
параметр `InterceptorProtectionMode` **не поддерживается** (`Unsupported
setting`): перехватчик работает в штатном блокирующем режиме на время
проверки. Отдельный `Notify` недоступен; откат — остановка задачи 1.
```sh
kesl-control --set-app-settings InterceptorProtectionMode=Notify
kesl-control --start-task 1
kesl-control --get-task-state 1
```
Пилот длится минимум 2–4 часа обычной нагрузки. Каждые 15 минут проверяйте
метрики раздела 1 и события KESL. Не считайте этот режим конечной реализацией
АВЗ.1: он не обеспечивает блокирование/лечение.
Если пилот стабилен, в том же maintenance window:
1. проверьте, что `ActionOnThreat=DisinfectDeleteIfNotPossible`;
2. установите `InterceptorProtectionMode=Block`;
3. перезапустите task 1, если этого требует текущая сборка;
4. повторите health, smoke, firewall и resource checks.
```sh
kesl-control --set-settings 1 \
ActionOnThreat=DisinfectDeleteIfNotPossible ScanArchived=No
kesl-control --set-app-settings InterceptorProtectionMode=Block
kesl-control --stop-task 1
kesl-control --start-task 1
kesl-control --get-task-state 1
kesl-control --app-info
```
В режиме `Block` доступ к файлу ожидает результат проверки. При появлении
latency вернитесь в `Notify` либо остановите task 1 и выполните rollback;
не расширяйте исключения вслепую.
Пилот длится минимум 15–30 минут на constrained test VM и 24 часа на
боевых ресурсах. Каждые 15 минут проверяйте метрики раздела 1, события KESL
и firewall. АВЗ.1 выполняется при `Started` + `ActionOnThreat=DisinfectDeleteIfNotPossible`.
Перед приёмкой:
```sh
kesl-control --set-settings 1 \
ActionOnThreat=DisinfectDeleteIfNotPossible ScanArchived=No
kesl-control --get-task-state 1
kesl-control --app-info
```
При latency/swap/unhealthy выполните `kesl-control --stop-task 1`. Не
расширяйте исключения вслепую. Network Threat Protection, Firewall
Management, Web Threat Protection и Behavior Detection не запускайте.
## 9. Приёмочный тест и evidence
@@ -483,8 +567,15 @@ kesl-control --get-schedule 6
kesl-control -E --query -n 100 --reverse
```
Очистите тестовый каталог после подтверждения реакции и заполните
`deployment/kesl/EVIDENCE.AVZ.ru.md`.
Очистите тестовый каталог после подтверждения реакции. Копию EICAR в Backup
удалите точечно, не весь Backup:
```sh
kesl-control -B --query --reverse -n 20
kesl-control -B --mass-remove --query "DetectName == 'EICAR-Test-File'"
```
Заполните `deployment/kesl/EVIDENCE.AVZ.ru.md`.
## 10. Финальные проверки
@@ -535,7 +626,7 @@ ufw status verbose
Сначала минимально обратимое действие:
```sh
kesl-control --set-app-settings InterceptorProtectionMode=Notify
kesl-control --stop-task 1
```
Если управление KESL не отвечает:
@@ -0,0 +1,143 @@
extensions:
file_storage:
directory: /var/lib/han-otel/host-collector
timeout: 10s
receivers:
filelog/docker:
include: [/var/lib/docker/containers/*/*-json.log]
start_at: end
include_file_path: true
storage: file_storage
operators:
- type: json_parser
id: docker-json
timestamp:
parse_from: attributes.time
layout_type: gotime
layout: "2006-01-02T15:04:05.000000000Z07:00"
- type: regex_parser
id: docker-service-label
parse_from: attributes["attrs"]["tag"]
if: 'attributes["attrs"]["tag"] != nil'
regex: '^(?P<docker_service>nginx|keycloak|redis)$'
- type: move
from: attributes.docker_service
to: resource["service.name"]
- type: move
from: attributes.stream
to: attributes["log.iostream"]
- type: move
from: attributes.log
to: body
- type: json_parser
id: structured-body
parse_from: body
parse_to: attributes
if: 'body matches "^\\s*\\{"'
on_error: send
journald/host:
directory: /var/log/journal
units:
- docker.service
- han-stack@production.service
- han-secrets@production.service
- han-chat-docker-firewall.service
- certbot.service
- fail2ban.service
priority: info
start_at: end
storage: file_storage
processors:
memory_limiter:
check_interval: 1s
limit_mib: 192
spike_limit_mib: 48
resource/common:
attributes:
- {key: service.namespace, value: han-chat, action: upsert}
- {key: deployment.environment, value: "${env:APP_ENV}", action: upsert}
- {key: service.version, value: "${env:RELEASE_VERSION}", action: upsert}
- {key: host.name, value: "${env:HOST_NAME}", action: upsert}
transform/journal:
error_mode: ignore
log_statements:
- context: log
statements:
- set(resource.attributes["service.name"], attributes["_SYSTEMD_UNIT"]) where resource.attributes["service.name"] == nil and attributes["_SYSTEMD_UNIT"] != nil
- set(attributes["event.source"], "journald") where attributes["_SYSTEMD_UNIT"] != nil
- set(attributes["event.source"], "docker-json-file") where attributes["_SYSTEMD_UNIT"] == nil
- set(attributes["route.class"], "api") where resource.attributes["service.name"] == "nginx" and IsMatch(attributes["uri"], "^/api/")
- set(attributes["route.class"], "auth") where resource.attributes["service.name"] == "nginx" and IsMatch(attributes["uri"], "^/auth/")
- set(attributes["route.class"], "bitrix") where resource.attributes["service.name"] == "nginx" and IsMatch(attributes["uri"], "^/bitrix/")
- set(attributes["route.class"], "callbacks") where resource.attributes["service.name"] == "nginx" and IsMatch(attributes["uri"], "^/callbacks/")
- set(attributes["route.class"], "static") where resource.attributes["service.name"] == "nginx" and attributes["route.class"] == nil
- replace_pattern(body, "(?i)(Bearer|Basic)\\s+[A-Za-z0-9._~+/=-]+", "$$1 [REDACTED]") where IsString(body)
- replace_pattern(body, "(?i)(token|secret|password|code)=([^&\\s]+)", "$$1=[REDACTED]") where IsString(body)
- set(body, "nginx.access") where resource.attributes["service.name"] == "nginx" and attributes["log.iostream"] == "stdout"
- set(body, "keycloak.event") where resource.attributes["service.name"] == "keycloak" and attributes["log.iostream"] == "stdout"
attributes/redact:
actions:
- {key: authorization, action: delete}
- {key: http.request.header.authorization, action: delete}
- {key: http.request.header.cookie, action: delete}
- {key: http.response.header.set-cookie, action: delete}
- {key: url.query, action: delete}
- {key: url.full, action: delete}
- {key: http.target, action: delete}
- {key: http.request.body, action: delete}
- {key: db.statement, action: delete}
- {key: message, action: delete}
- {key: payload, action: delete}
- {key: user.phone, action: delete}
- {key: user.email, action: delete}
- {key: remote_addr, action: delete}
- {key: user_agent, action: delete}
- {key: uri, action: delete}
filter/allowlist:
error_mode: ignore
logs:
log_record:
- 'attributes["_SYSTEMD_UNIT"] == nil and resource.attributes["service.name"] != "nginx" and resource.attributes["service.name"] != "keycloak" and resource.attributes["service.name"] != "redis"'
- 'resource.attributes["service.name"] == "otel-collector" or resource.attributes["service.name"] == "otel-host-collector"'
filter/noise:
error_mode: ignore
logs:
log_record:
- 'resource.attributes["service.name"] == "nginx" and severity_number < SEVERITY_NUMBER_WARN and IsMatch(body, "(/health/live|/nginx-health/live|/.well-known/acme-challenge/)")'
batch:
timeout: 5s
send_batch_size: 512
send_batch_max_size: 1024
exporters:
otlp/remote:
endpoint: "${env:OTEL_REMOTE_ENDPOINT}"
headers:
authorization: ${file:/run/han-chat/secrets/OTEL_REMOTE_AUTH_HEADER}
tls:
insecure: "${env:OTEL_REMOTE_TLS_INSECURE}"
sending_queue:
enabled: true
queue_size: 5000
storage: file_storage
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 0s
service:
extensions: [file_storage]
pipelines:
logs:
receivers: [filelog/docker, journald/host]
processors:
[memory_limiter, resource/common, filter/allowlist, filter/noise, transform/journal, attributes/redact, batch]
exporters: [otlp/remote]
telemetry:
logs:
level: error
metrics:
level: none
@@ -19,6 +19,7 @@ value() {
[ -f "$ROOT/docker-compose.yml" ] || fail "root docker-compose.yml is missing"
[ -f "$ENV_FILE" ] || fail ".env is missing"
[ -f "$MANIFEST" ] || fail "runtime secret manifest is missing"
[ -d /var/log/journal ] || fail "persistent journald directory is missing"
[ -L /opt/han-chat/current ] || fail "/opt/han-chat/current must be a root-controlled release link"
[ "$(/usr/bin/stat -c '%U:%G' /opt/han-chat/current)" = root:root ] ||
fail "active release link must be root:root"
@@ -59,6 +60,8 @@ if [ -d "$ROOT" ]; then
$ROOT/docker-compose.yml
$ROOT/deployment/preflight.sh
$ROOT/deployment/han-stack@.service
$ROOT/deployment/han-host-otel-collector@.service
$ROOT/deployment/observability/otel-host-collector.yaml
$ROOT/deployment/scripts/tls-deploy-hook.sh
$ROOT/deployment/secrets/han-compose
$ROOT/deployment/secrets/han-secrets
@@ -91,6 +94,10 @@ if [ -f "$ENV_FILE" ]; then
[ "$(value NGINX_TLS_ENABLED)" = true ] || fail "NGINX_TLS_ENABLED must be true"
[ "$(value NGINX_HTTP_PORT)" = 80 ] || fail "nginx must publish host port 80"
[ "$(value NGINX_HTTPS_PORT)" = 443 ] || fail "nginx must publish host port 443"
[ -n "$(value HOST_NAME)" ] || fail "HOST_NAME is required for telemetry correlation"
echo "$(value TELEMETRYGEN_IMAGE)" |
grep -Eq '^ghcr\.io/.+@sha256:[a-f0-9]{64}$' ||
fail "TELEMETRYGEN_IMAGE must be pinned by digest"
[ "$(value NGINX_TLS_CERTIFICATE)" = /run/tls/fullchain.pem ] ||
fail "nginx certificate must use staged /run/tls/fullchain.pem"
[ "$(value NGINX_TLS_CERTIFICATE_KEY)" = /run/tls/privkey.pem ] ||
@@ -278,6 +285,39 @@ if [ -f "$MANIFEST" ]; then
IFS=$old_ifs
fi
host_otel_config="$ROOT/deployment/observability/otel-host-collector.yaml"
if [ -f "$host_otel_config" ]; then
grep -Fq 'filelog/docker' "$host_otel_config" ||
fail "host collector must include Docker filelog"
grep -Fq 'journald/host' "$host_otel_config" ||
fail "host collector must include journald allow-list"
grep -Fq 'filter/allowlist' "$host_otel_config" ||
fail "host collector must drop non-allowlisted containers"
grep -Fq 'file_storage' "$host_otel_config" ||
fail "host collector must persist offsets and exporter queue"
! grep -Fq '/var/run/docker.sock' "$host_otel_config" ||
fail "Docker socket access is forbidden"
fi
[ -x /usr/local/bin/otelcol-contrib ] ||
fail "pinned host otelcol-contrib is not installed"
otel_checksum=/usr/local/share/han-otel/otelcol-contrib.sha256
otel_version=/usr/local/share/han-otel/otelcol-contrib.version
[ -f "$otel_checksum" ] || fail "host Collector checksum record is missing"
[ -f "$otel_version" ] || fail "host Collector version record is missing"
if [ -f "$otel_checksum" ]; then
(cd / && sha256sum -c "$otel_checksum") ||
fail "host Collector binary checksum mismatch"
fi
if [ -x /usr/local/bin/otelcol-contrib ] && [ -f "$otel_version" ]; then
/usr/local/bin/otelcol-contrib --version 2>&1 |
grep -Fq "$(cat "$otel_version")" ||
fail "host Collector binary version mismatch"
fi
if [ -x /usr/local/bin/otelcol-contrib ] && [ -f "$host_otel_config" ]; then
/usr/local/bin/otelcol-contrib validate --config="$host_otel_config" ||
fail "host collector config validation failed"
fi
if [ -x "$ROOT/scripts/validate-env" ] && [ -f "$ENV_FILE" ] && [ -f "$MANIFEST" ]; then
"$ROOT/scripts/validate-env" "$ENV_FILE" --runtime-manifest "$MANIFEST" ||
fail "config/runtime validator rejected the production inputs"
@@ -27,6 +27,8 @@ HARDEN_SSH="${HARDEN_SSH:-false}"
LOCK_ACCOUNT_PASSWORDS="${LOCK_ACCOUNT_PASSWORDS:-true}"
RESET_UFW="${RESET_UFW:-true}"
SKIP_APT_UPGRADE="${SKIP_APT_UPGRADE:-false}"
OTEL_HOST_COLLECTOR_VERSION="${OTEL_HOST_COLLECTOR_VERSION:-}"
OTEL_HOST_COLLECTOR_SHA256="${OTEL_HOST_COLLECTOR_SHA256:-}"
LOG_FILE="${LOG_FILE:-/var/log/han-chat-vm1-setup.log}"
log() {
@@ -120,6 +122,13 @@ configure_time() {
timedatectl set-ntp true
}
configure_journald() {
step "Persistent journald для host telemetry"
install -d -m 2755 -o root -g systemd-journal /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
journalctl --flush
}
install_authorized_key() {
local user=$1 source=$2 target="/home/${1}/.ssh/authorized_keys"
[[ -f "$source" && ! -L "$source" ]] || die "Не найден обычный key file ${source}"
@@ -422,7 +431,7 @@ install_deploy_sudoers() {
step "Exact sudoers для deploy"
cat >/etc/sudoers.d/deploy <<'EOF'
Cmnd_Alias HAN_VM1_UNITS = /usr/bin/systemctl start han-secrets@production.service, /usr/bin/systemctl restart han-secrets@production.service, /usr/bin/systemctl start han-stack@production.service, /usr/bin/systemctl restart han-stack@production.service, /usr/bin/systemctl stop han-stack@production.service
Cmnd_Alias HAN_VM1_STATUS = /usr/bin/systemctl --no-pager status han-secrets@production.service, /usr/bin/systemctl --no-pager status han-stack@production.service, /usr/bin/journalctl --no-pager -u han-secrets@production.service, /usr/bin/journalctl --no-pager -u han-stack@production.service
Cmnd_Alias HAN_VM1_STATUS = /usr/bin/systemctl --no-pager status han-secrets@production.service, /usr/bin/systemctl --no-pager status han-stack@production.service, /usr/bin/systemctl --no-pager status han-host-otel-collector@production.service, /usr/bin/journalctl --no-pager -u han-secrets@production.service, /usr/bin/journalctl --no-pager -u han-stack@production.service, /usr/bin/journalctl --no-pager -u han-host-otel-collector@production.service
deploy ALL=(root) NOPASSWD: HAN_VM1_UNITS, HAN_VM1_STATUS
EOF
chmod 0440 /etc/sudoers.d/deploy
@@ -447,6 +456,15 @@ install_release_helpers_if_possible() {
[[ -x "${deployment}/scripts/tls-deploy-hook.sh" ]] || die "TLS hook не executable"
[[ -x "${source_dir}/han-compose" ]] || die "han-compose не executable"
[[ -x "${source_dir}/han-secrets" ]] || die "han-secrets не executable"
[[ -f "${deployment}/han-host-otel-collector@.service" ]] \
|| die "han-host-otel-collector@.service отсутствует"
[[ -f "${deployment}/observability/otel-host-collector.yaml" ]] \
|| die "otel-host-collector.yaml отсутствует"
[[ "$OTEL_HOST_COLLECTOR_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \
|| die "Задайте OTEL_HOST_COLLECTOR_VERSION=x.y.z"
[[ "$OTEL_HOST_COLLECTOR_SHA256" =~ ^[a-f0-9]{64}$ ]] \
|| die "Задайте проверенный OTEL_HOST_COLLECTOR_SHA256"
[[ "$(uname -m)" == x86_64 ]] || die "Host Collector artifact рассчитан на amd64"
if getent group "$tls_group" >/dev/null; then
[[ "$(getent group "$tls_group" | cut -d: -f3)" == "$tls_gid" ]] \
|| die "han-nginx-tls имеет неожиданный GID"
@@ -472,6 +490,27 @@ install_release_helpers_if_possible() {
install -m 0644 -o root -g root \
"${deployment}/han-stack@.service" \
/etc/systemd/system/han-stack@.service
local otel_archive otel_tmp otel_url
otel_archive="$(mktemp)"
otel_tmp="$(mktemp -d)"
otel_url="https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v${OTEL_HOST_COLLECTOR_VERSION}/otelcol-contrib_${OTEL_HOST_COLLECTOR_VERSION}_linux_amd64.tar.gz"
curl --fail --location --proto '=https' --tlsv1.2 \
--output "$otel_archive" "$otel_url"
printf '%s %s\n' "$OTEL_HOST_COLLECTOR_SHA256" "$otel_archive" | sha256sum -c -
tar -xzf "$otel_archive" -C "$otel_tmp" otelcol-contrib
install -m 0755 -o root -g root "$otel_tmp/otelcol-contrib" /usr/local/bin/otelcol-contrib
install -d -m 0755 -o root -g root /usr/local/share/han-otel
sha256sum /usr/local/bin/otelcol-contrib \
>/usr/local/share/han-otel/otelcol-contrib.sha256
printf '%s\n' "$OTEL_HOST_COLLECTOR_VERSION" \
>/usr/local/share/han-otel/otelcol-contrib.version
chmod 0644 /usr/local/share/han-otel/otelcol-contrib.sha256 \
/usr/local/share/han-otel/otelcol-contrib.version
rm -rf "$otel_archive" "$otel_tmp"
install -d -m 0700 -o root -g root /var/lib/han-otel/host-collector
install -m 0644 -o root -g root \
"${deployment}/han-host-otel-collector@.service" \
/etc/systemd/system/han-host-otel-collector@.service
install -d -m 0755 -o root -g root /etc/letsencrypt/renewal-hooks/deploy
install -m 0755 -o root -g root \
"${deployment}/scripts/tls-deploy-hook.sh" \
@@ -481,6 +520,7 @@ install_release_helpers_if_possible() {
/usr/local/share/doc/han-secrets/SELECTEL_RUNBOOK.ru.md
systemctl daemon-reload
systemctl enable certbot.timer
systemctl enable han-host-otel-collector@production.service
log "Helpers установлены; application units не запущены"
}
@@ -539,6 +579,7 @@ main() {
check_os
update_system
configure_time
configure_journald
create_host_roles
configure_account_passwords
configure_layout
@@ -16,11 +16,38 @@ compose() { docker compose --env-file "$CONFIG_FILE" "$@"; }
NETWORK="${OBSERVABILITY_NETWORK:-han-chat-observability}"
COLLECTOR_SERVICE="${COLLECTOR_SERVICE:-otel-collector}"
TELEMETRYGEN_IMAGE="${TELEMETRYGEN_IMAGE:-}"
errors=0
ok() { printf 'OK %s\n' "$*"; }
fail() { printf 'FAIL %s\n' "$*" >&2; errors=$((errors + 1)); }
if systemctl is-active --quiet han-host-otel-collector@production.service; then
ok "Host Collector service is running"
else
fail "Host Collector service is not running"
fi
if /usr/local/bin/otelcol-contrib validate \
--config=deployment/observability/otel-host-collector.yaml >/dev/null 2>&1; then
ok "Host Collector configuration is valid"
else
fail "Host Collector configuration is invalid"
fi
host_bad_logs="$(
journalctl --since=-10min --no-pager \
-u han-host-otel-collector@production.service 2>&1 |
grep -Ei 'queue is full|permission denied|connection refused|tls:|Unauthenticated|Permanent error' ||
true
)"
if [[ -z "$host_bad_logs" ]]; then
ok "No host Collector errors in last 10 minutes"
else
fail "Host Collector reports read/export/queue errors"
printf '%s\n' "$host_bad_logs" >&2
fi
collector_id="$(compose ps -q "$COLLECTOR_SERVICE" 2>/dev/null || true)"
if [[ -n "$collector_id" ]] &&
[[ "$(docker inspect --format '{{.State.Status}}' "$collector_id")" == running ]]; then
@@ -43,12 +70,16 @@ PY
fi
done
docker run --rm --network "$NETWORK" \
ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \
if [[ ! "$TELEMETRYGEN_IMAGE" =~ @sha256:[a-f0-9]{64}$ ]]; then
fail "TELEMETRYGEN_IMAGE must be pinned by sha256 digest"
elif docker run --rm --network "$NETWORK" \
"$TELEMETRYGEN_IMAGE" \
traces --otlp-endpoint otel-collector:4317 --otlp-insecure \
--service han-chat-e2e-canary --traces 100 --rate 20 >/dev/null \
&& ok "100 canary traces submitted" \
|| fail "telemetrygen failed"
--service han-chat-e2e-canary --traces 100 --rate 20 >/dev/null; then
ok "100 canary traces submitted"
else
fail "telemetrygen failed"
fi
bad_logs="$(
compose logs --since=10m "$COLLECTOR_SERVICE" 2>&1 |
@@ -72,4 +103,12 @@ cat <<'EOF'
service.namespace = han-chat
Затем выполните synthetic API request и проверьте общий trace между
api-backend и dependency spans, service.version и deployment.environment.
В Logs проверьте:
host.name = <VM1 HOST_NAME>
service.name IN (nginx, keycloak, redis, api-backend, sms-service,
sms-worker, bitrix-local-app)
У canary application log trace_id/span_id должны открывать соответствующий
span. Python events должны встречаться один раз, unknown-container и
otel-host-collector отсутствовать. Fake token/PII marker не должен находиться
ни в logs, ни в traces.
EOF