В отличие от виртуальных машин, контейнеры разделяют ядро хостовой ОС. Это фундаментальная особенность модели: компрометация одного контейнера потенциально означает компрометацию соседних, а в худшем случае — побег из изоляции и захват всего узла. С учётом того, что в современных кластерах Kubernetes одновременно работают сотни контейнеров от десятков команд, цена одной уязвимости становится критически высокой.
Защита контейнеров — задача многоуровневая. Атаковать можно образ, реестр, среду выполнения, оркестратор, сеть, приложение и, наконец, само ядро хоста. На каждый из этих уровней существует свой класс инструментов: сканеры образов, runtime-мониторинг, admission-контроллеры, сетевые политики, sandbox-рантаймы, средства управления секретами.
Меня же интересует самый нижний и одновременно самый чувствительный уровень — уровень ядра Linux и средств мандатного контроля доступа (MAC, Mandatory Access Control). Именно здесь решается вопрос, что произойдёт, если все вышестоящие защитные механизмы окажутся обойдены: насколько хорошо контейнер изолирован от хоста и от соседей с точки зрения самой ОС.
1.1. Почему namespaces и cgroups недостаточно
Контейнеры изолируют с помощью механизмов Linux — это так называемые namespaces (они отделяют процессы, файловые системы, сеть и т. д.) и cgroups (они ограничивают ресурсы вроде CPU и памяти).
Но у такой изоляции есть нюанс: она работает по принципу «всё разрешено, если явно не скрыто». То есть namespaces просто «прячут» часть системы от контейнера, но не ставят жёстких запретов.
Из‑за этого могут возникнуть проблемы. Например, если внутри контейнера какой‑то процесс получит доступ к файлу или сетевому соединению (сокету) на хосте — через «утекший» файловый дескриптор, symbolic link или баг в программе, которая запускает контейнеры, — то namespaces не помогут. С точки зрения системы процесс по‑прежнему остаётся «в своём контейнере», и изоляция не срабатывает, что ведет к компрометации системы.
MAC-системы добавляют второй, независимый от дискреционных прав (DAC/UID) уровень проверки: даже процесс, запущенный от root внутри контейнера, при попытке выполнить операцию, не разрешённую политикой, получит ошибку. Это и есть тот самый последний рубеж, о котором идёт речь.
В мире Linux исторически сложились два основных средства MAC, и оба применяются для защиты контейнеров — AppArmor и SELinux.
1.2. AppArmor
AppArmor по принципу работы — path-based: для каждого процесса описывается профиль, явно перечисляющий допустимые пути файловой системы, capabilities и сетевые операции. Всё, что не разрешено, — запрещено. Профили могут работать в режиме enforce (блокировка) или complain (только логирование, полезно для отладки перед включением enforce).
1.3. AppArmor в Kubernetes
Начиная с Kubernetes 1.31 поддержка AppArmor получила статус stable
Пример
spec: securityContext: appArmorProfile: type: Localhost localhostProfile: k8s-nginx
Поле type принимает три значения :
RuntimeDefault(профиль контейнерного рантайма, напримерdocker-default/cri-containerd.apparmor.d),Localhost(кастомный профиль, предзагруженный на узле)Unconfined(без ограничений). Профиль можно задать как на уровне Pod'а, так и на уровне конкретного контейнера — при конфликте более высокий приоритет имеет профиль контейнера.
Важный момент: kubelet отклонит Pod, если указанный профиль не загружен в ядро узла, на который он запускается. При этом сам scheduler ничего не знает о том, какие профили загружены на каких нодах — это приходится решать вручную через node labels и nodeSelector, либо через Kubernetes Security Profiles Operator, который автоматизирует распространение и загрузку профилей по кластеру.
Пример профиля:
#include <tunables/global>
profile k8s-nginx flags=(attach_disconnected) { #include <abstractions/base>
network inet tcp file
#запрещаем запись в исполняемые пути
deny /bin/** w, deny /sbin/** w, deny /usr/bin/** w,}
1.4. Плюсы AppArmor
Низкий порог входа. Синтаксис профиля читается почти как список правил
allow/denyпо путям — не требуется разбираться в теории меток и типов.Уже включён по умолчанию. Docker и containerd применяют встроенный
docker-default/cri-containerd.apparmor.dпрофиль автоматически, без дополнительной настройки — базовая защита есть «из коробки».Легко дебажить. Режим
complainпозволяет собрать полный список нужных разрешений перед включением enforce, а нарушения подробно логируются вdmesg/journalctl.GA-статус и поддержка API начиная с Kubernetes 1.31.
1.5. Минусы AppArmor
Path-based модель по своей природе уязвима к обходам через файловую систему. Если у процесса есть способ добраться до объекта по нестандартному пути (symlink, hardlink, bind-mount,
/proc/self/fd/*), профиль, написанный для одного пути, может не сработать для другого пути к тому же объекту.Нет встроенного механизма распространения профилей в самом Kubernetes — обновление и раскатка
Localhost-профилей по всем узлам кластера — задача, которую нужно решать отдельно (DaemonSet, конфиг-менеджмент, Security Profiles Operator).Профиль должен существовать заранее на каждом узле, куда потенциально может быть заехать Pod — иначе Pod просто не запустится.
Поддерживается не во всех дистрибутивах (только для Ubuntu/Debian-семейства), в RHEL/CentOS/Fedora по умолчанию используется SELinux.
1.6. SELinux: метки вместо путей
SELinux, в отличие от AppArmor, работает не с путями, а с метками: каждому файлу, процессу, сокету присваивается контекст безопасности, и политика описывает, какие типы могут взаимодействовать. Это так называемое Type Enforcement. Дополнительно поддерживаются многоуровневая (MLS) и многокатегорийная (MCS, Multi-Category Security) модели — что делает SELinux существенно мощнее AppArmor.
1.7. SELinux в Kubernetes
В контексте контейнеров особенно ценно то, что каждому контейнеру можно присвоить уникальную метку: даже после побега из одного контейнера атакующий не получит доступ к файлам другого — метки не совпадут. Именно на этом строится модель MCS в container-рантаймах (Podman, CRI-O, Docker с --selinux-enabled): каждый контейнер по умолчанию получает случайную MCS-категорию вида s0:c1,c2, а вся файловая система контейнера помечается тем же лейблом
В манифесте Pod'а метка задаётся через seLinuxOptions:
spec: securityContext: seLinuxOptions: type: my_container_t
На практике чаще всего достаточно задать только поле level — именно оно отвечает за MCS-метку, применяемую и к контейнерам, и к их volume'ам:
spec: securityContext: seLinuxOptions: level: "s0:c123,c456"
Официальная документация Kubernetes прямо указывает, что том поддерживающий SELinux-разметку, будет принудительно использовать указанную метку, и если два Pod'а получат одинаковый уровень MCS, они смогут читать данные друг друга через общий volume — поэтому для изоляции между Pod'ами каждому Pod'у нужно назначать уникальную метку. Поле требует, чтобы модуль SELinux был загружен на хосте — на узлах Linux без SELinux эта настройка просто не работает.
Пример модуля политики:
policy_module(my_container, 1.0)
require { type container_t; type http_port_t;}
#свой домен для контейнераtype my_container_t;
#разрешаем слушать только HTTP-портallow my_container_t http_port_t:tcp_socket name_bind;
1.8. Плюсы SELinux
Очень гибкие настройки: типы, роли, MLS/MCS-уровни — можно строить многослойные модели допуска, а не только «путь разрешён/запрещён».
Автоматическая межконтейнерная изоляция через MCS без ручного написания политики — рантайм сам генерирует уникальную категорию для каждого контейнера.
Устойчивость к обходам через файловую систему. Метка присваивается инодам (inode) , а не путям — symlink или bind-mount на защищённый объект не меняет его метку, поэтому многие классы path-based обходов на SELinux просто не работают.
Включён и enforcing по умолчанию в RHEL, CentOS Stream, Fedora, а также в производных платформах — OpenShift требует SELinux в режиме enforcing как обязательное условие.
1.9. Минусы SELinux
Сложно. Написание собственной политики требует понимания Type Enforcement, доменов и типов — заметно сложнее, чем path-based правило в AppArmor.
Relabeling (получение новой метки)— источник накладных расходов и проблем совместимости, особенно на сетевых томах (NFS и подобные), где relabeling больших объёмов данных при каждом монтировании может быть медленным или вовсе не поддерживается.
Усложненный траблшутинг — сообщения
AVC denialв аудит-логе требуют отдельного навыка чтения (хотяaudit2allowиausearchсильно облегчают жизнь).Не является родным механизмом для Debian/Ubuntu-семейства, где исторически используется AppArmor.
1.10. Сравнение
Критерий | AppArmor | SELinux |
|---|---|---|
Модель | Path-based | Label-based (Type Enforcement, MLS/MCS) |
Порог входа | Низкий | Высокий |
Устойчивость к обходам через файловую систему (symlink, bind-mount) | Слабее | Сильнее |
Изоляция между Pod'ами "из коробки" | Нет, нужно писать вручную | Да, через автоматические MCS-метки |
Конфигурация в Kubernetes |
|
|
Типичная ОС по умолчанию | Ubuntu, Debian, openSUSE | RHEL, CentOS Stream, Fedora, OpenShift |
Накладные расходы | Минимальные | Возможен relabeling volume'ов |
1.11. Реальные кейсы: когда это спасало (и когда нет)
А теперь — конкретные случаи, где MAC-механизмы напрямую влияли на исход реальных уязвимостей в контейнерной экосистеме.
1.11.1. CVE-2025-31133: AppArmor блокирует, SELinux — нет
Суть атаки: уязвимость возникала из-за некорректной обработки механизма maskedPaths в runc. Атакующий мог заменить внутри контейнера файл /dev/null на символическую ссылку, указывающую на чувствительный файл в хост-системе (например, в /proc). В результате runc монтировал этот хост-файл внутрь контейнера, и злоумышленник получал возможность записывать в него — например, в /proc/sys/kernel/core_pattern или /proc/sysrq-trigger, что вело к эскалации привилегий или отказу в обслуживании.
Почему AppArmor сработал? Профили AppArmor в распространённых рантаймах (например, в Docker) по умолчанию явно запрещают или ограничивают доступ к таким «опасным» точкам монтирования и к манипуляциям с /dev/null. Когда атака пыталась подменить путь, профиль AppArmor блокировал операцию на этапе проверки пути к файлу.
Почему SELinux не сработал? Здесь ключевая деталь: SELinux работает с метками (labels) объектов. В этой конкретной атаке проблема была в логике самого рантайма — в том, как runc обрабатывал символические ссылки при монтировании. SELinux проверяет метки после того, как рантайм уже выполнил монтирование. Поскольку атака эксплуатировала уязвимость в логике монтирования runc, а не попытку доступа процесса к объекту с несовместимыми метками, механизм SELinux в этой цепочке не сработал как барьер.
1.11.2. CVE-2025-52565: SELinux блокирует, AppArmor — нет
Суть атаки: здесь проблема была в операции bind-mount при настройке консоли в контейнере. Runc выполнял привязку /dev/pts/$n к /dev/console. Из-за недостаточной проверки атакующий мог подменить /dev/pts/$n символической ссылкой. Ключевой момент: эта операция bind-mount выполнялась до того, как runc применял защиты maskedPaths и readonlyPaths. В результате атакующий получал права на запись к обычно защищённым файлам в /proc (тем же /proc/sysrq-trigger или /proc/sys/kernel/core_pattern).
Почему SELinux сработал? В стандартных политиках SELinux (например, containers-selinux) заложено, что при таком bind-mountе метка монтирования не переприсваивается (не происходит relabeling). Из-за этого процесс в контейнере по умолчанию не получает прав на запись в смонтированный таким образом файл в /proc. SELinux заблокировал попытку записи на уровне меток.
Почему AppArmor не сработал? По умолчанию профили AppArmor в распространённых рантаймах разрешают доступ к /dev/console. Поскольку атака эксплуатировала момент до применения локальных ограничений runc, а не прямой доступ процесса к файлу, стандартный профиль AppArmor не смог перехватить и заблокировать эту операцию. Чтобы закрыть эту брешь, пришлось бы вручную создавать кастомный профиль, явно запрещающий доступ к /dev/console, — но это могло сломать работу обычных контейнеров.
1.11.3. CVE-2025-52881: SELinux помог, а AppArmor — нет
Речь снова о цепочке уязвимостей в рантайме runc (CVE-2025-52881 в связке с CVE-2025-31133 и CVE-2025-52565). Суть в том, что атакующий использовал символические ссылки, чтобы перенаправить операции записи в /proc внутри контейнера. Цель — возможность записать в чувствительные файлы хоста (например, /proc/sys/kernel/core_pattern или /proc/sysrq-trigger).
Почему SELinux частично сработал? В некоторых конфигурациях политики SELinux для контейнерных сред (например, container_selinux) изначально ограничивают доступ процессов контейнера к определённым файлам в /proc. Если политика была настроена строго, она могла заблокировать такую перенаправленную запись — то есть SELinux выступил дополнительным барьером.
Почему AppArmor не сработал? Проблема в механике атаки. Атакующий мог перенаправить саму операцию записи LSM-меток (которые используются и AppArmor, и SELinux) так, что проверка на уровне меток становилась неэффективной. Например, он мог сделать так, чтобы запись, предназначенная для применения LSM-метки (чтобы контейнер работал в ограниченном профиле), попала в файл, который для LSM-проверки был «пустым» (вроде /proc/self/sched). В результате ядро применяло запись, но LSM-политика к процессу не применялась — процесс запускался фактически без ограничений. То есть AppArmor в этой конкретной схеме обхода не сработал как последняя линия защиты.
Эти примеры показывают: ни одна система защиты не является универсальной панацеей. Эффективность сильно зависит от конкретной конфигурации, политик и того, как именно устроена уязвимость. Часто лучший подход — использовать оба механизма вместе.
1.12. Рекомендации от NSA/CISA для Kubernetes
В августе 2021 года (с обновлением в 2022-м) NSA и CISA выпустили совместное руководство по hardening Kubernetes, в котором прямо рекомендуется использовать AppArmor, SELinux как обязательные механизмы безопасности для контейнеров (NSA/CISA Kubernetes Hardening Guidance).
Эти рекомендации впоследствии легли в основу автоматизированных проверок CIS Kubernetes Benchmark — в частности, проверки 4.1.8 («Ensure the SELinux context of the container is set») и 4.1.9 («Ensure AppArmor is configured to restrict container's access to resources»), которые сегодня проверяются инструментами типа kube-bench, Trivy Operator и облачными вроде Policy Controller-бандлами Google Cloud.
1.13. Итог
AppArmor и SELinux — это два проверенных временем механизма, лежащих в основе защиты контейнеров на уровне ядра, и оба продолжают доказывать свою состоятельность на реальных CVE. AppArmor выигрывает простотой и порогом входа, SELinux — гибкостью и устойчивостью к обходам через файловую систему, а признание в лице NSA/CISA и CIS Benchmark говорит о том, что оба инструмента являются "must have" для production-кластеров.
Но оба разрабатывались за рубежом и ориентированы на западные стандарты сертификации. Для государственного сектора, оборонной промышленности и объектов критической информационной инфраструктуры (КИИ) РФ этого недостаточно — требуется решение, прошедшее сертификацию ФСТЭК и реализующее мандатное управление доступом по отечественной модели. Один из примеров такого подхода — модель Parsec в Astra Linux Special Edition, которая в тч используется для защиты в платформе контейнеризации Боцман SE (ФСТЭК версия). Разбор того, как эта модель реализуется в контейнерных сценариях и насколько она сопоставима по возможностям с AppArmor и SELinux — тема для отдельной статьи.
Автор: Нияз Козлов, менеджер продукта платформы «Боцман»

