Изоляция контейнеров Directum RX: seccomp, capabilities, AppArmor, SELinux, user namespaces и песочницы на Astra и РЕД ОС
Что на самом деле отделяет контейнер Directum RX от хоста и что можно усилить без поломок. Слои изоляции Docker и их значения по умолчанию, как проверить каждый одной командой, что включается на Astra Linux и РЕД ОС, когда оправданы gVisor и Kata Containers, и чего не делать, чтобы «заработало».
Коротко. Контейнер работает на ядре хоста. По умолчанию 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. Его отчёт длинный, но удобен как чек-лист: каждый пункт проверяется и либо исправляется, либо записывается как принятый риск.
Что включается без поломок
Эти настройки почти никогда не мешают прикладным сервисам и включаются в первую очередь.
- Убрать
--privileged,seccomp=unconfined, лишниеcap_addи сокет Docker из прикладных контейнеров. Если без них что-то не работает, нужно найти, какое действие запрещено, и разрешить только его. no-new-privilegesдля всего демона. Процессы в контейнерах не смогут повысить права через setuid-файлы. Настройка в/etc/docker/daemon.json, пример ниже.- Профиль seccomp по умолчанию не трогать. Он закрывает вызовы, которые прикладному сервису не нужны.
- Лимиты памяти и процессора. Это тоже изоляция: контейнер, съевший память, не уронит соседей. Для сервисов на .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 без нагрузочного теста и разговора с вендором.
Похожая картина у вас?
Разберём ваш контур за 30 минут бесплатного созвона: версия RX, состав сервисов, что болит сильнее всего.
Написать