Коротко. Контейнер работает на ядре хоста. По умолчанию Docker даёт пространства имён, урезанные capabilities, seccomp и профиль AppArmor, если модуль активен, но root в контейнере остаётся root на хосте. Без поломок включаются отказ от --privileged и сокета Docker, no-new-privileges, лимиты. На РЕД ОС SELinux для контейнеров требует selinux-enabled и меток томов, на Astra главный механизм МКЦ. User namespaces и песочницы только после тестового прогона, а для жёсткой границы лучше отдельные ВМ.

Служба ИБ спрашивает, чем контейнеры Directum RX изолированы от хоста и друг от друга. Ответ «это же контейнеры» не проходит: контейнер это обычный процесс на ядре хоста, и насколько он ограничен, зависит от нескольких независимых механизмов. Часть из них Docker включает сам, часть выключена по умолчанию, а одну неудачную настройку, вроде проброшенного внутрь сокета Docker, можно найти почти на любом сервере. Разбираем слои, как их проверить и что из этого реально работает на Astra Linux и РЕД ОС.

Симптом

  • Проверяющий просит описать меры изоляции контейнеров, а описать нечего, кроме «стоит Docker».
  • Сканер конфигурации (например, docker-bench-security) выдаёт длинный список замечаний.
  • После включения SELinux или ограничений в Docker сервисы падают с Permission denied на смонтированных каталогах или Operation not permitted при старте.
  • На сервере есть контейнеры с --privileged или с сокетом Docker внутри, и никто не помнит зачем.

Почему так

Контейнер не виртуальная машина. Все контейнеры работают на одном ядре с хостом, а отделяют их несколько слоёв, каждый со своей задачей.

Слой Что ограничивает В Docker по умолчанию
Пространства имён (namespaces) что процесс видит: процессы, сеть, файловую систему, имя хоста включены; ослабляются режимами --network host, --pid host
cgroups сколько процессора и памяти можно занять включены, лимиты не заданы
Capabilities какие привилегии root доступны процессу урезанный набор из примерно полутора десятков
seccomp какие системные вызовы ядра разрешены профиль по умолчанию запрещает несколько десятков опасных вызовов
AppArmor или SELinux к каким файлам и ресурсам есть доступ AppArmor: профиль docker-default, если модуль активен в ядре; SELinux: только при selinux-enabled
User namespaces кем процесс является на хосте выключены: root в контейнере это root на хосте
no-new-privileges можно ли повысить права через setuid-файлы выключено

Из таблицы следует, что при настройках по умолчанию процесс, работающий в контейнере от root, на хосте тоже root, и между ним и хостом остаются capabilities, seccomp и модуль безопасности ядра. Если контейнер запущен с --privileged, снимаются почти все слои сразу.

Диагностика

Что включено на хосте

cat /sys/kernel/security/lsm                       # какие модули безопасности ядра активны
docker info --format '{{json .SecurityOptions}}'   # что из них использует Docker

Во втором выводе ожидаются name=seccomp,profile=builtin и name=apparmor или name=selinux. Если там есть name=userns или name=rootless, включены пространства пользователей или Docker без root.

Как запущены контейнеры

docker ps -q | xargs docker inspect --format \
  '{{.Name}} user={{.Config.User}} priv={{.HostConfig.Privileged}} net={{.HostConfig.NetworkMode}} pid={{.HostConfig.PidMode}} capadd={{.HostConfig.CapAdd}} secopt={{.HostConfig.SecurityOpt}} apparmor={{.AppArmorProfile}}'

Что считать находкой:

  • priv=true: привилегированный контейнер. У прикладного сервиса такого быть не должно.
  • net=host или pid=host: контейнер видит сеть или процессы хоста.
  • secopt содержит seccomp=unconfined или apparmor=unconfined: слой выключен вручную.
  • capadd с SYS_ADMIN, NET_ADMIN, SYS_PTRACE: почти полный root.
  • Пустой user: процесс работает от root, если образ не меняет пользователя при старте.

Отдельно ищите сокет Docker, смонтированный в контейнер. Кто управляет сокетом, тот управляет хостом:

docker ps -q | xargs docker inspect --format '{{.Name}}{{range .Mounts}} {{.Source}}{{end}}' | grep docker.sock

Чаще всего сокет пробрасывают агентам мониторинга и сбора журналов, панелям управления контейнерами и сборщикам. Каждый такой контейнер в оценке рисков приравнивается к доступу root на хосте.

Как процесс выглядит снаружи

ps -eo user,pid,args | grep -E 'dotnet|java' | grep -v grep   # от кого работает процесс на хосте
grep Cap /proc/<pid>/status                                    # какие capabilities у процесса
capsh --decode=<значение CapEff>
ps -eZ | grep container_t                                      # на РЕД ОС: процессы с меткой контейнера SELinux

Комплексная проверка по рекомендациям CIS делается скриптом docker-bench-security. Его отчёт длинный, но удобен как чек-лист: каждый пункт проверяется и либо исправляется, либо записывается как принятый риск.

Что включается без поломок

Эти настройки почти никогда не мешают прикладным сервисам и включаются в первую очередь.

  1. Убрать --privileged, seccomp=unconfined, лишние cap_add и сокет Docker из прикладных контейнеров. Если без них что-то не работает, нужно найти, какое действие запрещено, и разрешить только его.
  2. no-new-privileges для всего демона. Процессы в контейнерах не смогут повысить права через setuid-файлы. Настройка в /etc/docker/daemon.json, пример ниже.
  3. Профиль seccomp по умолчанию не трогать. Он закрывает вызовы, которые прикладному сервису не нужны.
  4. Лимиты памяти и процессора. Это тоже изоляция: контейнер, съевший память, не уронит соседей. Для сервисов на .NET лимит ещё и меняет поведение сборщика мусора, об этом статья про память .NET.
{
  "no-new-privileges": true,
  "live-restore": true
}

live-restore добавлен для удобства: при перезапуске самого Docker контейнеры продолжают работать. После правки файла Docker перезапускается, и настройка действует на контейнеры, созданные после этого.

Astra Linux и РЕД ОС

Особенности установки RX на эти системы подробно разобраны в отдельной статье. Здесь только то, что касается изоляции.

РЕД ОС. SELinux обычно в режиме enforcing, но Docker сам по себе метки контейнерам не назначает: для этого нужен пакет container-selinux и "selinux-enabled": true в daemon.json. Без этой настройки контейнеры не получают собственных меток SELinux, и он их почти не ограничивает. После включения каталоги, которые монтируются в контейнеры, должны получить подходящую метку (:Z для каталога одного контейнера, :z для общего), иначе сервисы получат Permission denied на своих же данных. Включать это стоит на тестовом стенде с полным прогоном сценариев: если метки поставить неверно, ломается запись в смонтированные каталоги.

Astra Linux. Основной механизм здесь собственный мандатный контроль (МРД и МКЦ модуля PARSEC), и его режим определяет, что вообще можно делать с Docker и от какого уровня целостности работают процессы. Работает ли рядом AppArmor и применяет ли Docker профиль docker-default, покажут cat /sys/kernel/security/lsm и docker info. Отключать МКЦ ради контейнеров нельзя: для заказчика с требованием сертифицированной ОС это нарушение условий её применения.

На обеих системах работают seccomp, урезание capabilities, no-new-privileges и лимиты. Это базовый набор, который стоит иметь независимо от модуля безопасности.

Что требует проверки

  • User namespaces (userns-remap). Root в контейнере становится непривилегированным пользователем на хосте. Это сильная мера, но владельцы файлов в смонтированных каталогах меняются, а часть возможностей (сеть хоста, некоторые тома) с ней несовместима. Схема установки RX от вендора на неё не рассчитана, поэтому включать только после полного прогона на тестовом стенде и с готовностью к вопросам поддержки.
  • Docker без root (rootless). Те же оговорки, плюс ограничения на порты ниже 1024 и на сеть.
  • Файловая система только для чтения и запуск не от root. В Kubernetes это разобрано в статье про политики в кластере. В Docker то же самое делается параметрами read_only и user в описании сервиса, с теми же томами под временные файлы и журналы.

Песочницы: gVisor и Kata Containers

Песочницы добавляют границу, которой у обычного контейнера нет: отдельное ядро.

gVisor Kata Containers
Как устроено ядро в пространстве пользователя перехватывает системные вызовы каждый контейнер или под в лёгкой виртуальной машине
Что нужно среда выполнения runsc аппаратная виртуализация; внутри ВМ это вложенная виртуализация
Цена заметные накладные расходы на ввод-вывод и сеть, не все системные вызовы поддержаны память и время старта на каждую ВМ

Для сервисов RX песочницы почти никогда не оправданы. Серверы RX обычно сами виртуальные машины, и вложенную виртуализацию для Kata на них часто не включить. Совместимость gVisor с конкретным приложением проверяется только прогоном под нагрузкой, а вендор такую схему не описывает. Если нужна жёсткая граница, проще развести компоненты разного уровня доверия по разным виртуальным машинам.

Где песочница оправдана: там, где контейнер обрабатывает чужой непроверенный код или файлы. Например, сборочные агенты, которые собирают сторонние проекты, или отдельные сервисы разбора и конвертации внешних документов. Разбор сложных форматов файлов одна из самых частых точек атаки, и такие сервисы имеет смысл изолировать строже остальных.

Чего не делать

  • Не запускать контейнер с --privileged, «чтобы заработало». Это снимает почти все слои изоляции сразу.
  • Не отключать SELinux, МКЦ или seccomp ради одного сервиса. Сначала разобраться, какое именно действие запрещено: ausearch -m avc для SELinux, журнал ядра для seccomp и AppArmor.
  • Не пробрасывать сокет Docker в контейнеры без крайней нужды. Такой контейнер равен root на хосте.
  • Не включать userns-remap на продуктивном без тестового прогона.
  • Не внедрять gVisor или Kata для RX без нагрузочного теста и разговора с вендором.