48 KiB
arch-06. Стандарт безопасности размещения сервисов
Общие границы системы и прикладная безопасность — в
arch-01-system-architecture.md. Docker Compose, nginx и сети контейнеров — вarch-03-docker-compose-blueprint.md. Настройки и секреты — вarch-04-settings-and-content.md. Процесс разработки и Definition of Done — вarch-05-agent-development-process.md.
Назначение
Документ задаёт обязательный минимальный стандарт размещения сервисов HAN на виртуальных машинах, подготовки VM и production-деплоя.
Главная цель — ограничить последствия компрометации отдельного сервиса: захват процесса или контейнера не должен автоматически давать доступ к host OS, Docker daemon, соседним сервисам, чужим секретам или всей private network.
Стандарт применяется к:
- VM с публичной точкой входа;
- внутренним VM в private network;
- VM без постоянного доступа в интернет, включая SigNoz;
- пользователям
deploy,adminиtunnel; - systemd-юнитам, Docker Compose и deployment-артефактам.
Если конкретный сервис не может выполнить требование, отклонение должно быть явно описано в его спецификации: причина, риск, компенсирующая мера, владелец и срок пересмотра. Молчаливое ослабление требований запрещено.
Модель угроз и границы доверия
Базовое допущение: атакующий может добиться выполнения кода внутри одного прикладного контейнера.
После этого он не должен получить:
- доступ к Docker socket или Docker API;
- root на host OS;
- возможность менять compose-файлы, systemd-юниты, deployment-скрипты или
sudoers; - секреты сервисов, которые не нужны скомпрометированному процессу;
- произвольный доступ к PostgreSQL, S3 и другим VM;
- возможность публиковать новый host port или подключать host directories;
- постоянный канал управления через неограниченный исходящий трафик.
Изоляция строится несколькими независимыми слоями: IAM и секреты, Unix-права, systemd/sudo, настройки контейнера, Docker networks, host firewall и cloud security groups. Один слой не считается заменой остальных.
Классы VM
VM с постоянным egress
VM имеет только утверждённые исходящие направления, необходимые сервисам: Selectel Secrets Manager, S3, внешние API, package/image registry и DNS/NTP по принятой схеме.
Постоянный egress не означает unrestricted internet access. Направления и назначение фиксируются в deployment inventory; лишние правила удаляются.
Private/no-egress VM
В steady state VM:
- не принимает соединения из интернета;
- не имеет общего выхода в интернет;
- принимает только явно разрешённый трафик из private network;
- администрируется через утверждённую private точку входа: bastion/основную VM или VPN.
SigNoz относится к этому классу, если его UI, OTLP и SSH доступны только из private network.
Каноническая классификация ВМ2 Processing
ВМ2 с message-safety и bitrix-sync — самостоятельная service VM с минимальным public ingress и постоянным ограниченным egress:
- собственный public DNS/IP или dedicated LB направляет
80/443только на nginx ВМ2; - public
80обслуживает только ACME challenge/HTTPS redirect; - public
443разрешает только exact/bitrix/sync/webhook/contactи/bitrix/sync/webhook/alert; остальные paths закрыты; - private ingress
8443/tcpразрешён только от security group ВМ1 и утверждённого ops path для Message Safety/internal API; - ни один public запрос ВМ2 не проходит через nginx ВМ1;
- host KESL имеет egress только к утверждённым источникам обновления из operator KESL runbook; Message Safety и broker не получают общий internet egress;
bitrix-syncимеет HTTPS egress только к утверждённому порталу Bitrix24;- Safety worker имеет доступ только к managed PostgreSQL, S3-quarantine и доверенному DNS resolver;
- локальный OTEL Collector имеет private egress к SigNoz;
- registry, package repositories и OS updates открываются только в bootstrap/controlled maintenance window.
ВМ2 использует отдельный cloud IAM principal. han-secrets получает только секреты сервисов ВМ2 и материализует раздельные root-owned файлы на tmpfs; общий secret bundle с ВМ1 запрещён. На ВМ2 один root Compose project и отдельный root-owned systemd deployment unit.
Для схемы message_safety разделяются DB roles: API/worker runtime читает active/исторические config_versions, но не создаёт и не активирует их; migration/config-admin role используется только controlled job и имеет право version activation. Config не содержит secrets, endpoint topology или MOCK flags.
Public и private ingress ВМ2 завершаются разными server blocks одного nginx без общего fallback. Public block использует сертификат доверенного CA и до proxy ограничивает Contact/alert webhook version-controlled source IP CIDR allow-list. Штатный робот передаёт отдельный receiver token в query и application/x-www-form-urlencoded body; query/body исключаются из logs/traces, а upstream проверяет token и document/entity/domain/member fields. Server-to-server ingress 8443 использует внутренний CA и service token вторым слоем. mTLS не обязателен для MVP.
Lifecycle private/no-egress VM
Этот lifecycle применяется к SigNoz и иным полностью private VM. Для ВМ2 обязательный public webhook ingress 80/443 после bootstrap не удаляется; вместо этого проверяются exact route и source IP CIDR allow-list, а SSH и все прочие public ports закрываются. Новый IP не добавляется автоматически: всплеск восстановлений Contact инкрементальной reconciliation инициирует проверку rejected-IP telemetry и controlled review allow-list.
Для первичной раскатки применяется двухфазный процесс.
flowchart LR
Bootstrap[Bootstrap_phase]
Verify[Verify_services_and_private_links]
Lockdown[Steady_state_lockdown]
Bootstrap -->|"temporary public SSH and package egress"| Verify
Verify -->|"remove public ingress and egress"| Lockdown
Фаза bootstrap
На ограниченное время разрешаются:
- SSH из утверждённого trusted ops CIDR, а не из
0.0.0.0/0- управляется через группу безопасности облачного провайдера; - egress, необходимый для обновлений ОС, установки пакетов и получения pinned images/artifacts;
- доступ
deployиadminв пределах правил этого документа.
До перехода в lockdown необходимо:
- установить обновления и минимальный набор пакетов;
- установить и проверить host firewall и fail2ban;
- развернуть сервисы и секреты;
- проверить health/readiness;
- проверить требуемые private-соединения в обоих направлениях;
- подтвердить альтернативный private путь администрирования;
- сохранить rollback-инструкцию и inventory разрешённых соединений.
Фаза lockdown
После проверки:
- public IP удаляется, если он больше не нужен;
- публичный SSH и любой иной internet ingress удаляются из cloud security group;
- host firewall принимает административный и прикладной трафик только из утверждённых private CIDR/SG;
- общий internet egress закрывается на cloud и host-уровне;
- временные bootstrap credentials, правила, installer-файлы и package caches удаляются, если они больше не нужны;
- с внешней сети проверяется недоступность SSH и сервисных портов;
- с VM проверяется запрет неразрешённого egress;
- из private network повторно проверяются SSH и обязательные service flows.
Раскатка private/no-egress VM не завершена, пока lockdown и обе группы проверок не зафиксированы в deployment checklist.
Повторное открытие
Временное открытие ingress/egress после lockdown — break-glass операция:
- фиксируются причина, исполнитель, окно работ и необходимые destination/ports;
- правило ограничивается trusted CIDR и минимальным сроком;
- после работ правила удаляются;
- повторяются проверки lockdown;
- факт закрытия фиксируется в runbook/журнале изменений.
Постоянно оставлять bootstrap-доступ «для будущих обновлений» запрещено.
Пользователи host OS
deploy
Используется для штатного деплоя. Пользователь:
- не входит в группы
docker,rootи другие root-equivalent группы; - не имеет общего
sudo, shell root иsudoedit; - не меняет compose-файлы, systemd-юниты, deployment-скрипты и конфигурацию секретов;
- может записывать только в выделенный incoming/staging-каталог;
- может запускать только заранее утверждённые операции над конкретными systemd-юнитами;
- на ВМ2 может запускать пять exact-argument вариантов root-owned Message Safety mode helper;
- читает только логи своего стека, без доступа к секретам других сервисов.
Членство в группе docker считается эквивалентом root и запрещено.
admin
Break-glass пользователь для восстановления:
- не используется для штатного деплоя;
- имеет персональные SSH-ключи, а не общий ключ команды;
- доступен только из trusted ops network/VPN, а для private VM — только через private path после lockdown;
- расширенные sudo-права выдаются осознанно и аудируются;
- ключи хранятся отдельно от deploy credentials и регулярно пересматриваются.
Доступ через cloud console/recovery mode также считается break-glass и должен быть ограничен ролями облачного проекта.
tunnel
Отдельный пользователь основной/bastion VM для доступа к PostgreSQL и другим private endpoints:
- не имеет sudo;
- не входит в deployment-группы;
- не получает доступ к секретам приложения;
- разрешает только local TCP forwarding;
- имеет allow-list конкретных
host:portчерезPermitOpen; - использует login shell
/usr/sbin/nologin(после отдельной проверки, что forwarding-only соединение работает); - не разрешает agent forwarding, X11 forwarding и TTY;
- ключ ограничивается теми же возможностями в
authorized_keys.
Произвольный SOCKS proxy и forwarding на неутверждённые адреса запрещены. Настройка должна быть проверена отдельной SSH-сессией до отключения старого пути.
Root
PermitRootLogin no;- пароль root заблокирован (
passwd -l root) как дополнительная мера; - штатные операции выполняются через именных пользователей;
- прямой root допускается только механизмом recovery провайдера при инциденте.
SSH baseline
Минимальные настройки production VM:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowAgentForwarding no
X11Forwarding no
AllowUsers deploy admin tunnel
Дополнительно:
- SSH для Private/no-egress VM разрешается cloud SG и host firewall только из trusted ops CIDR/VPN/private network;
- ключи пользователей индивидуальны; общий приватный ключ запрещён;
- устаревшие алгоритмы и пустые пароли запрещены;
- fail2ban включается на VM, где SSH хотя бы временно доступен из интернета;
AllowTcpForwarding noзадаётся по умолчанию, а исключениеlocal— только вMatch User tunnel;- после изменения выполняется проверка конфигурации sshd и вход во второй независимой сессии;
- текущую рабочую сессию не закрывают до успешной проверки нового доступа.
AllowUsers должен содержать только реально созданные учётные записи. Неиспользуемая роль не создаётся «на будущее».
Права deploy и production-деплой
Управление только через systemd
deploy не запускает docker, docker compose или произвольные root-скрипты через sudo. Docker Compose запускается root-owned systemd-юнитом или root-owned deployment helper с фиксированным интерфейсом.
Обновление executable, config или helper, используемого уже активным
Type=oneshot unit с RemainAfterExit=yes, требует явного
systemctl restart <unit>. enable --now включает unit и стартует только
неактивный unit, но не применяет новый helper к уже active/exited unit.
Rollout проверяет фактический live state после restart: созданные firewall
rules, freshness materialized secrets, owner/mode файлов и exit status
последнего запуска. Один ActiveState=active не доказывает применение новой
версии.
Sudoers хранится только в /etc/sudoers.d/deploy и проверяется через visudo. /etc/sudoers напрямую не редактируется.
Разрешения перечисляют полные команды и конкретные unit names без wildcard. Принципиальный пример:
Cmnd_Alias HAN_STATUS = /usr/bin/systemctl --no-pager status han-stack.service
Cmnd_Alias HAN_DEPLOY = /usr/bin/systemctl start han-deploy.service, \
/usr/bin/systemctl restart han-stack.service
Cmnd_Alias HAN_LOGS = /usr/bin/journalctl --no-pager -u han-stack.service
Cmnd_Alias HAN_SAFETY_MODE = /usr/local/sbin/han-message-safety-mode standard, \
/usr/local/sbin/han-message-safety-mode mock --text-free true --file-free true, \
/usr/local/sbin/han-message-safety-mode mock --text-free true --file-free false, \
/usr/local/sbin/han-message-safety-mode mock --text-free false --file-free true, \
/usr/local/sbin/han-message-safety-mode mock --text-free false --file-free false
deploy ALL=(root) NOPASSWD: HAN_STATUS, HAN_DEPLOY, HAN_LOGS, HAN_SAFETY_MODE
Фактические пути сверяются через command -v; разрешается только необходимый набор. Нельзя разрешать:
systemctl *,journalctl *, wildcard в unit name;systemctl status/journalctlс интерактивным pager (он может дать shell escape под root);- shell, editor, package manager,
cp,mv,chmod,chown; - произвольный путь к compose-файлу или environment-файлу;
- команды с параметрами, позволяющими подменить unit, working directory, image или mount.
han-message-safety-mode — исключение с конечным exact-argument allow-list, а не произвольный root-script. Он принадлежит root:root, недоступен deploy на запись, не принимает paths/commands/env expansion, атомарно меняет только root:han-message-safety 0640 /etc/han-chat/message-safety-mode.env; dedicated host group имеет GID 10001, совпадающий с primary GID non-root контейнера. Helper валидирует конфигурацию и выполняет только фиксированную Message Safety API recreate/restart operation внутри root Compose project. MOCK не имеет автоматического срока действия; выключение — отдельная явная команда standard. Все вызовы и old/new mode аудируются.
Ownership deployment-файлов
Root-owned и недоступны deploy на запись:
/etc/systemd/system/han-*.service;- production compose-файлы;
- deploy/helper scripts;
/etc/han-chat/message-safety-mode.env;/etc/sudoers.d/deploy;- secret mappings и credentials;
- active release manifest.
deploy может загружать артефакты только в отдельный каталог, например /var/lib/han-deploy/incoming, без права менять его parent. Активация выполняется фиксированным root-owned процессом после проверок:
- артефакт относится к ожидаемому проекту и версии;
- digest/signature соответствует approved release;
- отсутствуют symlink/path traversal;
- compose config прошёл валидацию;
- image reference pinned по version/digest;
- миграции и rollout соответствуют release manifest;
- release manifest задаёт owner/group/mode для каждого класса файлов:
data/config
0644или строже, secrets отдельно, executable scripts, preflight, hooks и helpers —0755/0750по назначению; - перед активацией выполняется
test -xдля каждого executable из manifest.
Глобальный rsync --chmod=F644, рекурсивный chmod или иной blanket mode,
снимающий executable bit со scripts/hooks/preflight, запрещён. Права
восстанавливаются из version-controlled manifest или явными install -m
для каждого класса артефактов, а не после первой ошибки запуска.
Если такого валидатора пока нет, compose/unit changes выполняет admin, а deploy ограничивается запуском уже подготовленного релиза. Выдавать deploy запись в production compose — не допустимая замена автоматизации.
Shell-артефакты (*.sh, entrypoint, hooks и helpers) обязаны поставляться с
LF line endings. Репозиторий фиксирует это через .gitattributes, а release
preflight проверяет отсутствие CRLF до активации. Ошибка вида
cannot execute: required file not found при существующем executable-файле
считается признаком некорректного shebang/line endings, а не основанием менять
права или запускать файл через обходной интерпретатор.
Изменение прав deploy
Новая доработка не получает дополнительные права автоматически. В change request указываются:
- требуемая операция и конкретный systemd unit;
- почему существующего интерфейса недостаточно;
- полный executable path и фиксированные аргументы;
- какие root-owned файлы читает или меняет операция;
- возможность command/path/argument injection;
- тест негативных сценариев;
- способ отзыва права и rollback.
Изменение:
- проходит review владельца инфраструктуры/безопасности;
- вносится отдельным файлом в
/etc/sudoers.d; - проверяется
visudo; - сначала проверяется в production-like среде;
- отражается в этом документе и deployment runbook;
- после rollout подтверждается через
sudo -l, что лишних прав нет.
Wildcard, временный NOPASSWD: ALL и включение в docker group запрещены даже как «временное» решение.
Секреты
Общие требования
- секреты не коммитятся и не хранятся в обычном
.env; .envсодержит только несекретную конфигурацию и ссылки/имена secret files;- контейнер получает только необходимые ему секреты;
- общий файл со всеми секретами стека не монтируется во все контейнеры;
- секреты не передаются в command line, build args, image layers и логи;
- runtime secret files доступны только root и целевому process UID/GID;
- ротация не требует выдачи сервису доступа к чужим секретам;
- приложение не выступает сетевым прокси секретов для других VM.
Значения uid, gid и mode в Compose file secrets нельзя считать
security boundary: Docker Compose при bind-backed secret может их игнорировать.
Фактические owner/mode задаются host-side materializer'ом и проверяются через
stat, позитивный read-test от фактического container UID/GID и негативный
тест от постороннего UID. Проверяется также execute/traverse permission всех
parent directories. root:root 0600 нельзя bind-mount в процесс, работающий
не от root: для несекретного control file используется dedicated host group,
совпадающий с primary GID контейнера, и минимальный режим 0640; secrets
получают эквивалентный минимальный ACL/group contract. Предупреждение Compose
об игнорировании этих атрибутов не подавляется и не трактуется как
подтверждение прав.
Структурированные секреты валидируются до старта потребителя. Для PEM это
означает проверку парсинга certificate/private key, отсутствие повторного
base64 или литеральных \n, соответствие public key и запрет зашифрованного
private key, если сервис не поддерживает non-interactive passphrase.
VM с egress: han-secrets
На VM с утверждённым доступом к Selectel:
- один host-side
han-secretsзапускается через systemd до старта стека; - используется отдельный IAM principal на VM/контур;
- IAM разрешает чтение только секретов сервисов этой VM;
- контейнеры не получают cloud IAM credentials и сами не обращаются в Secrets Manager;
- materialized secrets размещаются в
/run/han-chat/secretsна tmpfs; - ошибка получения обязательного секрета блокирует rollout (fail closed);
- автоматический fallback с Selectel на локальный production-файл запрещён.
Использование единой реализации han-secrets на нескольких VM допустимо; общая IAM-учётная запись и общий набор секретов — нет.
Private/no-egress VM
Cloud sync не требуется. admin во время bootstrap:
- получает минимальный набор секретов по защищённому каналу;
- размещает их в root-owned каталоге вне репозитория;
- задаёт каталогам
0700, файлам0400или более узкие ACL для целевого UID; - по возможности передаёт их процессу как systemd credentials или read-only secret files;
- удаляет временную копию и историю команд;
- фиксирует fingerprint/version секрета без его значения.
Допускается han-secrets в локальном file-режиме как единый loader, но он не должен создавать egress или зависимость от основной app-VM. Для ротации используется повторная контролируемая provisioning-процедура.
DB roles и migration boundary
Runtime, migration и config-admin роли разделяются для каждого сервиса. Runtime-role не получает DDL, ownership схемы или право менять immutable configuration. Migration-role монтируется только в controlled job и не передаётся runtime-контейнерам.
Cross-schema migration не получает постоянный broad access. Если ей нужно однократно перенести legacy data:
- владелец исходной схемы или DB administrator выдаёт именованной
migration-role минимальные временные
USAGEна schema иSELECTна конкретную таблицу; - migration копирует данные и fail-closed проверяет полноту переноса;
- владелец/администратор отзывает временные права после успешного commit.
Migration-role не выполняет REVOKE, ALTER или DROP на объекте чужого
owner. Такие contract-операции принадлежат owner migration исходного сервиса
либо отдельной административной процедуре. Проверку существования объекта
нельзя реализовывать как «нет доступа — значит объекта нет»: permission error
должен блокировать rollout, иначе legacy data может быть молча пропущена.
Alembic graph обязан сохранять известные ранее выданные revision IDs, включая
no-op baseline revisions. Удаление revision из нового image при наличии её в
alembic_version запрещено; совместимость обеспечивается bridge/no-op
revision, а не ручным stamp или правкой production DB.
Права на каталоги
Базовая модель:
| Путь | Владелец / режим | Назначение |
|---|---|---|
/opt/han-chat/releases/<version> |
root:root; каталоги 0755, data/config 0644, executable по manifest 0755/0750 |
immutable release |
/opt/han-chat/current |
root:root |
active release link; меняет только deployment helper/admin |
/var/lib/han-deploy/incoming |
deploy:deploy, 0750 |
загрузка неактивированных артефактов |
/etc/han |
root:root, 0750 или строже |
конфигурация и secret mappings |
/run/han-chat/secrets |
root:root, 0700 |
runtime secrets на tmpfs |
/var/lib/han-chat/public-tls |
root:han-nginx-tls, 0750; key/cert 0640 |
минимальный TLS staging для non-root edge |
/var/lib/han-chat/acme |
root:root, 0755 |
ACME webroot без private key |
/var/lib/han-chat/<service> |
UID сервиса, минимальные права | service state |
/var/log/han-chat |
root/service group, без world-read | host-side логи при необходимости |
Требования:
- world-writable каталоги в deployment path запрещены;
- setuid/setgid binaries не добавляются без обоснования;
- сервис не получает write к каталогу с executable/config, если ему нужен только state;
- bind mounts задаются read-only, кроме явно выделенных state/upload paths;
- backup-файлы и дампы получают не менее строгие права, чем исходные данные;
- symlinks из writable каталога не используются привилегированным helper без безопасной проверки.
Hardening контейнеров
Для каждого production-контейнера обязательна оценка и, где применимо, конфигурация:
services:
service:
user: "10001:10001"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp:rw,noexec,nosuid,nodev
Базовые правила:
- процесс запускается непривилегированным UID/GID;
- root в контейнере допускается только с документированным обоснованием;
privileged: trueзапрещён;network_mode: host,pid: host,ipc: hostиuserns_mode: hostзапрещены;- Docker socket/API не монтируется;
- Linux capabilities удаляются все, затем точечно возвращаются необходимые;
- root filesystem read-only; writable paths — отдельные volume/tmpfs;
- mount host paths минимален и read-only;
- default seccomp сохраняется; AppArmor/аналог провайдера включается, если доступен;
- задаются CPU/memory/PID limits, restart policy и healthcheck;
- сервис подключается только к необходимым Docker networks;
- внешний
ports:разрешён только утверждённой edge-точке; - image использует pinned version/digest, проходит vulnerability scan и не содержит package managers/compilers без необходимости;
- секреты не копируются в image и не доступны healthcheck-команде.
Если контейнер не может работать с read_only, в спецификации перечисляются конкретные writable paths. Полное отключение read_only без анализа запрещено.
Проверка совместимости image с hardening
Non-root UID сам по себе недостаточен. Для каждого pinned digest до rollout
составляется inventory всех путей, куда пишет entrypoint и процесс:
runtime/socket, cache/temp, generated config, logs и persistent state. Каждый
путь получает отдельный volume/tmpfs с минимальным размером и явными
uid/gid/mode; writable root filesystem, запуск root или возврат capabilities
не используются как универсальный workaround.
Проверяется не только основной binary, но и image entrypoint. Если vendor image
имеет отдельный unprivileged entrypoint, при принудительном user используется
именно он. Root entrypoint, который делает mkdir/chown, несовместим с
non-root + read_only, даже если сам daemon способен работать без root.
Изменение image digest повторяет эти проверки: tag/version, entrypoint, writable-path inventory, healthcheck semantics и фактический UID/GID считаются частью security contract образа.
Ownership persistent named volumes
Новый Docker named volume нельзя считать writable для non-root process:
начальный owner часто root:root, даже если target path в image принадлежит
service UID. Persistent volume до запуска потребителя подготавливает
идемпотентный one-shot init service:
- использует уже утверждённый pinned image с необходимыми
sh/chown/chmod, а не непроверенный floating utility image; - запускается с
user: 0:0,read_only: true,cap_drop: [ALL]и возвращает толькоCHOWN/FOWNER, если они действительно нужны; - не имеет сети (
network_mode: none) и доступа к secrets; - меняет owner/mode только mount root конкретного volume, без recursive chown чужого state;
- завершается с кодом
0, а consumer зависит отcondition: service_completed_successfully; - безопасно повторяется после recreate и отдельно проверяется в preflight.
Запуск основного collector/service от root, chmod 0777 и ручная правка
/var/lib/docker/volumes на host запрещены как способы исправления ownership.
TLS для non-root edge
Root-only дерево ACME/Certbot не монтируется целиком в non-root nginx и не
делается world-readable. Host-side root hook атомарно копирует только
fullchain.pem и privkey.pem в выделенный staging-каталог с группой
han-nginx-tls (канонический GID 11001); nginx получает этот каталог
read-only. Renewal hook сначала обновляет staged files, затем выполняет полный
config test и только после успеха отправляет reload. Права и соответствие
certificate/key проверяются preflight. Успешный hook обязан завершаться с
кодом 0 и пустым stderr: benign output nginx -t и progress Compose
перехватываются/подавляются на success path, но полностью выдаются в stderr
при ненулевом exit code. Иначе Certbot/оркестратор может пометить успешный
renewal как hook error.
Host KESL и root-owned broker
KESL 12.4 standalone и custom integration broker работают на host VM2, вне Compose. Граница между non-root Message Safety worker и privileged host:
- root-owned broker слушает только Unix socket
/run/han-kesl/scan.sock; TCP listener и Docker socket запрещены; - socket доступен только выделенной группе worker; owner/group/mode проверяются после каждого restart/reboot;
- worker не получает
kesl-control, shell или произвольный host path; - broker принимает bounded request, создаёт контролируемый временный файл,
вызывает только
kesl-control --scan-file --action Informи гарантированно очищает временные данные; - только однозначный
cleanдопускает allow;infectedдаёт deny; timeout, stale database, неизвестный output/exit code и недоступность scanner дают retry/503; - KESL обновляет database ежечасно; update egress принадлежит host KESL и ограничен approved sources;
- policy допускает
max_signature_age_hoursне выше720, seed —240.
Формат kesl-control, socket permissions, cleanup и throughput этой custom
integration подтверждаются gates на target VM2 с фактическим KESL 12.4.
Ошибки read-only file system устраняются точечным writable mount. Запрещено
лечить их глобальным read_only: false, root, privileged или broad
capability.
Сетевые ограничения
Cloud и host
Используются одновременно:
- cloud security groups — граница между internet/VPC/managed services;
- host firewall (UFW/nftables/iptables) — защита VM;
DOCKER-USER— защита от обхода UFW опубликованными Docker ports;- Docker networks — разделение сервисов внутри VM.
Default policy для ingress — deny. Разрешение задаёт source, destination, protocol, port и назначение. Правила «вся private network на все порты» запрещены.
DOCKER-USER обрабатывает forwarded packet после Docker DNAT. Поэтому policy
для published host ports сопоставляет original destination через
-m conntrack --ctorigdstport <HOST_PORT> (или эквивалент nftables), а не
текущий --dport, который уже может быть container port. Для каждого
разрешённого host port обязательны:
- positive external/private probe и рост counter именно allow rule;
- negative probe неразрешённого source/port и рост deny counter;
- проверка
iptables -S/nft ruleset после setup, restart firewall unit, restart Docker и reboot; - запрет считать UFW INPUT counter доказательством фильтрации published Docker port: такой трафик может обходить INPUT.
Firewall setup обязан обновлять helper и явно перезапускать active oneshot unit; наличие нового текста helper без изменения live rules является неуспешным rollout.
Cloud SG для managed PostgreSQL разрешает TLS-подключения только от VM/SG сервисов, которым нужна соответствующая схема. PostgreSQL, Redis, OTLP receivers, admin UI и internal API не публикуются в интернет.
Egress
- сервис без внешней интеграции не подключается к сети
egress; - внешние destination/ports фиксируются в inventory;
- DNS/NTP и package/image registry учитываются отдельно;
- временный bootstrap egress удаляется при lockdown;
- отсутствие технической возможности фильтровать по FQDN компенсируется NAT/proxy/provider firewall и мониторингом исходящих соединений.
Fail2ban
Fail2ban обязателен для SSH, временно или постоянно доступного из интернета. Он дополняет allow-list trusted CIDR и key-only auth, а не заменяет их.
Для VM без публичного ingress в steady state fail2ban можно оставить включённым, но основная защита — отсутствие внешнего маршрута и закрытые SG/firewall.
Минимизация host OS
- используется поддерживаемый минимальный образ ОС;
- пакеты устанавливаются из доверенных репозиториев с проверкой подписи;
- компиляторы, отладчики, сетевые утилиты и installer dependencies не остаются без эксплуатационной необходимости;
- отключаются неиспользуемые daemon/socket units;
- автоматические security updates или утверждённое patch window обязательны;
- kernel и container runtime регулярно обновляются;
- удаление пакетов выполняется по утверждённому allow-list, а не слепым
autoremove; - старые images/releases удаляются только после сохранения необходимого rollback window;
- cleanup не удаляет active image, последний рабочий релиз, forensic data или backup.
Для no-egress VM обновление выполняется в контролируемое окно через временный ограниченный egress либо проверенные offline packages/images. После обновления повторяется lockdown.
Логи, аудит и инциденты
Аудируются:
- входы
deploy,admin,tunnel; - sudo-вызовы и systemd deployment actions;
- изменение SG/firewall/SSH/sudoers;
- получение и ротация секретов без записи значений;
- открытие и закрытие break-glass доступа;
- версия/digest развернутого релиза.
Секреты, токены, содержимое credentials и полные PII в логи не попадают.
При подтверждённой компрометации контейнера:
- изолировать VM/контейнер сетевыми средствами;
- не использовать скомпрометированную VM как доверенную точку восстановления;
- ротировать доступные контейнеру секреты и service tokens;
- проверить соседние сервисы по разрешённым network flows;
- сохранить необходимые snapshot/log evidence;
- пересоздать VM из доверенного образа вместо ручной «очистки», если затронут host;
- задокументировать причину выхода за границу изоляции, если он произошёл.
Definition of Done для новой VM или сервиса
- определён класс VM: egress или private/no-egress;
- составлена матрица ingress/egress;
- созданы отдельные OS users и IAM principal;
- root/password SSH отключены после проверки key access;
deployне состоит вdockerи имеет только конкретные systemd-команды;- production-файлы root-owned и недоступны
deployна запись; - release manifest сохраняет executable modes; preflight/hooks запускаются
напрямую без обхода через
bash; - каждый контейнер проверен по hardening baseline;
- для каждого image digest проверены entrypoint, UID/GID и полный inventory writable paths;
- bind files проходят positive read-test от container UID/GID и negative test от постороннего UID; named volumes подготовлены ограниченным init-job;
- секреты разделены по сервисам/VM и отсутствуют в обычном
.env; - bind-backed secrets и staged TLS проверены по фактическим owner/mode и содержимому, а не только по декларации Compose;
- runtime/migration/config-admin DB roles разделены, временные cross-schema grants выданы и отозваны владельцем;
- healthcheck проверяет процесс, реально присутствующий в контейнере, а KESL broker/database freshness контролируется отдельным host-сигналом;
- healthcheck-команда и все её binaries подтверждены внутри exact pinned
digest; отсутствие
curl/wgetне обнаруживается впервые в production; - внутренние ports недоступны извне;
- live
DOCKER-USERrules проверены по original host ports после DNAT и переживают Docker restart/reboot; - обновлённые oneshot helpers применены explicit restart, а renewal hooks имеют пустой stderr при успехе;
- backup/restore и rollback проверены в объёме релиза;
- для private/no-egress VM завершён и зафиксирован lockdown;
- проверена недоступность внешних портов и неразрешённого egress;
- отклонения имеют владельца, компенсирующую меру и срок пересмотра.