Безопасность контейнеризации: углубленный курс по LXC, LXD и Incus

Профессиональный курс по проектированию защищенных сред на базе системных контейнеров. Охватывает архитектурные различия LXC/Incus, механизмы изоляции ядра Linux, детальный разбор актуальных CVE и методы эшелонированной защиты.

1. Архитектура LXC, LXD и Incus: эволюция от низкоуровневых инструментов к демонам-гипервизорам

Архитектура LXC, LXD и Incus: эволюция от низкоуровневых инструментов к демонам-гипервизорам

Когда ИБ-специалисты слышат слово «контейнер», первая ассоциация почти всегда связана с Docker или Kubernetes — эфемерными средами для запуска одного приложения (application containers). Однако в мире Linux существует параллельная вселенная системных контейнеров (system containers). В них запускается полноценный процесс инициализации (например, systemd), работают фоновные службы, демоны журналирования и планировщики задач. С точки зрения операционной системы и администратора, системный контейнер ведет себя как полноценная виртуальная машина, но без накладных расходов на эмуляцию оборудования и запуск отдельного ядра.

Понимание того, как именно конструируются эти среды, критически важно для проектирования защищенной инфраструктуры. В отличие от аппаратной виртуализации (KVM/QEMU), где границей безопасности выступает гипервизор, в контейнерах граница безопасности — это само ядро Linux хостовой системы. Эволюция инструментов управления этой границей прошла путь от набора разрозненных утилит до мощных централизованных демонов, что радикально изменило архитектуру и, как следствие, поверхность атаки.

LXC: Фундамент изоляции без центрального управления

Проект LXC (Linux Containers) появился задолго до Docker и стал первой попыткой объединить разрозненные механизмы ядра Linux в единый пользовательский интерфейс.

На фундаментальном уровне контейнеров в Linux не существует. Это абстракция, создаваемая комбинацией трех основных механизмов ядра:

  • Namespaces (пространства имен) — изолируют видимость системных ресурсов (PID, сети, точек монтирования, IPC, UTS, пользователей).
  • Cgroups (контрольные группы) — ограничивают и учитывают потребление физических ресурсов (CPU, память, I/O).
  • LSM (Linux Security Modules) и Seccomp — ограничивают права доступа процессов внутри контейнера к системным вызовам и объектам ядра, даже если процесс имеет права root в своем пространстве имен.
  • Архитектура классического LXC предельно минималистична и децентрализована. В ее основе лежит библиотека liblxc, написанная на C.

    !Сравнение архитектур LXC и Incus

    Когда администратор выполняет команду lxc-start -n mycontainer, происходит следующее: Утилита командной строки напрямую обращается к liblxc. Библиотека читает конфигурационный файл контейнера (обычно из /var/lib/lxc/mycontainer/config), парсит его и выполняет серию системных вызовов (преимущественно clone() с флагами CLONE_NEW* для создания новых пространств имен). После настройки cgroups и применения политик AppArmor/SELinux, процесс выполняет системный вызов execve() для запуска /sbin/init внутри изолированной среды.

    С точки зрения безопасности архитектура LXC имеет следующие особенности:

  • Отсутствие единой точки отказа. Нет постоянно работающего демона, который управляет всеми контейнерами. Каждый контейнер — это просто дерево процессов в ядре.
  • Локальное состояние. Состояние системы описывается текстовыми файлами. Нет базы данных, которую можно скомпрометировать.
  • Сложность управления доступом. Поскольку нет демона, нет и встроенной системы Role-Based Access Control (RBAC). Право на запуск контейнера определяется правами доступа к бинарным файлам LXC и файлам конфигурации на уровне файловой системы хоста (DAC).
  • Сложность масштабирования политик безопасности. Настроить сложную сеть (например, Open vSwitch) или управлять распределенными хранилищами (ZFS, Ceph) через чистый LXC требует написания множества shell-скриптов (hooks), которые выполняются с привилегиями root на хосте. Ошибка в таком скрипте ведет к немедленной компрометации хоста.
  • LXC предоставил отличный низкоуровневый API (liblxc), но для корпоративных инфраструктур требовался инструмент оркестрации, который взял бы на себя управление сетью, хранилищами и жизненным циклом сотен инстансов.

    LXD: Появление демона-гипервизора

    В 2015 году компания Canonical (разработчик Ubuntu) представила LXD. Несмотря на схожесть названий, LXD не заменял LXC, а надстраивался над ним. LXD — это демон, написанный на языке Go, который предоставляет REST API для управления системными контейнерами, используя liblxc под капотом.

    Переход от набора утилит к постоянно работающему демону — это тектонический сдвиг в архитектуре. LXD позиционировался как «гипервизор для контейнеров».

    В этой модели администратор больше не взаимодействует с конфигурационными файлами напрямую. Клиентская утилита lxc (не путать с утилитами проекта LXC, такими как lxc-start) отправляет HTTPS-запросы к демону LXD. Демон может слушать как локальный Unix-сокет (/var/lib/lxd/unix.socket), так и сетевой TCP-порт.

    Архитектурные изменения и их влияние на ИБ:

  • Централизация состояния. LXD хранит конфигурации, профили и состояние сети в локальной базе данных (изначально SQLite, позже кластерная dqlite). Это позволяет реализовать транзакционность изменений, но делает базу данных критичным активом.
  • REST API как новая поверхность атаки. Появление сетевого API означает, что управление контейнерами можно осуществлять удаленно. Аутентификация строится на клиентских TLS-сертификатах. Уязвимости в обработке API (например, SSRF или Path Traversal) становятся векторами атак на хост-систему.
  • Управление привилегиями через сокет. Локальный доступ к LXD осуществляется через Unix-сокет. Любой пользователь, состоящий в группе lxd, может отправлять команды демону. Поскольку демон работает от имени пользователя root и имеет полный доступ к ядру, доступ к сокету эквивалентен прямому доступу к root на хосте. (Этот критический вектор угроз будет детально разобран в следующей главе).
  • Абстракция ресурсов. LXD взял на себя управление ZFS-пулами, Btrfs-квотами и сетевыми мостами. С точки зрения безопасности это плюс: стандартизированный код на Go, управляющий ресурсами, менее подвержен уязвимостям инъекций, чем самописные bash-скрипты в hooks классического LXC.
  • LXD сделал системные контейнеры удобными для облачных провайдеров и энтерпрайза. Однако со временем политика Canonical в отношении проекта начала вызывать вопросы у сообщества.

    Incus: Форк и современный выбор сообщества

    В середине 2023 года произошли события, изменившие ландшафт системных контейнеров. Компания Canonical приняла решение вывести LXD из-под крыла независимой организации LinuxContainers (которая исторически развивала LXC, LXCFS и LXD) и перевести разработку полностью внутрь компании. Это сопровождалось требованием подписания Contributor License Agreement (CLA), что оттолкнуло многих независимых разработчиков.

    В ответ на это сообщество LinuxContainers во главе со Стефаном Грабером (Stéphane Graber), создателем и бывшим ведущим разработчиком LXD, объявило о создании форка под названием Incus.

    Incus — это не просто переименованный LXD. За первые месяцы независимой разработки из кодовой базы были удалены сотни тысяч строк устаревшего или специфичного для инфраструктуры Canonical кода.

    Ключевые архитектурные отличия Incus от последних версий LXD:

  • Отказ от Snap-зависимости. Canonical активно продвигала установку LXD через свой проприетарный формат пакетов Snap. Это создавало проблемы с изоляцией: Snap-пакет LXD запускался внутри собственного AppArmor-профиля и mount-namespace, что приводило к конфликтам при пробросе блочных устройств или работе с ZFS. Incus поставляется в виде классических .deb и .rpm пакетов, работая напрямую в хостовой системе, что делает аудит его взаимодействия с ядром прозрачным.
  • Удаление интеграции с Ubuntu Advantage. Код, отвечавший за проверку коммерческих лицензий Canonical и интеграцию с их биллингом, был полностью вырезан. Для ИБ-специалиста меньше кода в привилегированном демоне означает меньшую поверхность атаки.
  • Переход на стандартные протоколы аутентификации. Incus интегрировал поддержку OpenID Connect (OIDC) для аутентификации в REST API, заменив проприетарные механизмы макарунов (macaroons), использовавшиеся в LXD. Это позволяет бесшовно интегрировать управление контейнерами в корпоративные системы Identity Provider (Keycloak, Azure AD).
  • Удаление поддержки устаревших концепций. Incus отказался от поддержки старых версий хранилищ и сети, сосредоточившись на современных механизмах ядра (например, переход на абстракции OVN для сложных сетевых топологий).
  • Сегодня Incus является основным выбором для новых проектов, требующих безопасной контейнеризации. Он сохранил всю мощь клиент-серверной архитектуры LXD, но избавился от вендор-лока и избыточного кода. Выпуск Incus 6.0 LTS закрепил его статус как enterprise-ready решения.

    Анатомия запуска: от API к изолированному процессу

    Чтобы эффективно защищать инфраструктуру на базе Incus, необходимо понимать пошаговый процесс создания границы безопасности. Что именно происходит в системе, когда администратор отправляет команду на запуск?

    !Процесс запуска контейнера Incus

    Архитектурно этот процесс пересекает несколько слоев абстракции: от сетевого запроса до низкоуровневых структур ядра.

  • Прием и валидация запроса. Клиент отправляет POST-запрос на эндпоинт /1.0/instances/mycontainer/state с указанием действия start. Демон incusd (работающий с правами root) принимает запрос, проверяет TLS-сертификат клиента или токен OIDC, и сверяет права доступа (RBAC).
  • Подготовка ресурсов (Storage & Network). Демон обращается к драйверу хранилища (например, ZFS). Он создает клон файловой системы из образа (snapshot clone) и монтирует его во временную директорию на хосте. Параллельно демон создает виртуальные сетевые интерфейсы (veth-пары) и подключает один конец к виртуальному коммутатору хоста (мосту или OVN).
  • Вызов liblxc. Подготовив окружение, incusd использует Go-биндинги (go-lxc) для передачи конфигурации библиотеке liblxc. На этом этапе управление переходит от кода на Go к коду на C.
  • Форк и создание пространств имен (Namespaces). liblxc вызывает системный вызов ядра clone() с флагами изоляции. Например:
  • - CLONE_NEWPID создает изолированное дерево процессов. Процесс внутри контейнера получит PID 1. - CLONE_NEWNET создает пустой сетевой стек. liblxc переместит второй конец veth-интерфейса в это пространство. - CLONE_NEWNS создает новое пространство точек монтирования.
  • Изоляция файловой системы (pivot_root). Внутри нового пространства монтирования процесс выполняет системный вызов pivot_root. В отличие от старого chroot, который просто меняет видимый корневой каталог (и из которого можно сбежать), pivot_root физически отмонтирует корневую файловую систему хоста для данного пространства имен и заменяет ее на смонтированный ZFS-клон контейнера. Хостовая файловая система становится абсолютно недоступной.
  • Сброс привилегий (Capabilities и Seccomp). Перед запуском целевого процесса liblxc применяет профиль Seccomp, блокируя опасные системные вызовы (например, kexec_load, open_by_handle_at). Затем процесс сбрасывает часть Linux Capabilities (например, CAP_SYS_BOOT, CAP_MAC_ADMIN), чтобы даже root внутри контейнера не мог модифицировать ядро. Наконец, процесс переводится в профиль AppArmor, сгенерированный демоном Incus.
  • Запуск init-системы. Только после того как все слои защиты (Namespaces, Cgroups, Seccomp, AppArmor, Capabilities) применены, процесс вызывает execve() для запуска /sbin/init (обычно systemd) внутри контейнера.
  • Мониторинг. Процесс liblxc на хосте завершается, но оставляет легковесный фоновый процесс lxc-monitored, который следит за состоянием PID 1 внутри контейнера и сообщает демону incusd о его остановке или падении.
  • Понимание этой цепочки критично для расследования инцидентов. Если злоумышленник находит уязвимость нулевого дня в ядре, позволяющую обойти pivot_root или Seccomp, он прорывает границу на шагах 5-6. Если же злоумышленник получает доступ к REST API с правами администратора, он легитимно просит демона (шаг 1) создать контейнер без изоляции (отключив Seccomp и AppArmor через конфигурацию), что делает взлом ядра ненужным.

    Эволюция от LXC к Incus — это переход от ручного конструирования изоляции к автоматизированной фабрике контейнеров. Демон-гипервизор берет на себя рутину по генерации строгих профилей безопасности и управлению ресурсами, минимизируя риск человеческой ошибки. Однако платой за это удобство становится появление мощного привилегированного процесса, компрометация которого означает полный контроль над инфраструктурой.

    10. Проектирование защищенной инфраструктуры: разработка регламентов, стандартов и архитектурных политик

    Наличие надежных криптографических алгоритмов не делает систему безопасной, если ключи хранятся в открытом виде. Точно так же использование User Namespaces, Seccomp и cgroups v2 не защитит инфраструктуру, если параметры изоляции применяются хаотично, а доступ к API не регламентирован. В корпоративной среде безопасность контейнеризации переходит от настройки отдельных механизмов ядра к проектированию архитектуры, где ошибка разработчика или компрометация одного сервиса математически не может привести к отказу всей системы.

    Проектирование защищенной среды требует перехода к парадигме Policy-as-Code (политика как код), строгой сегментации сетей и внедрению регламентов, которые исключают человеческий фактор при развертывании системных контейнеров.

    Архитектурное разделение плоскостей управления и данных

    Фундаментом безопасной инфраструктуры на базе Incus является физическое или жесткое логическое разделение Management Plane (плоскости управления) и Data Plane (плоскости данных).

    Оставлять слушающий сокет демона Incus (порт 8443) в той же сети, где маршрутизируется трафик самих контейнеров — критическая архитектурная ошибка. В случае реализации SSRF-атаки внутри контейнера или обхода сетевых ограничений злоумышленник получит прямую видимость REST API гипервизора.

    !Эшелонированная архитектура кластера Incus

    Правильный архитектурный стандарт требует выделения изолированного Management VLAN. Узлы кластера Incus должны иметь минимум два физических или агрегированных сетевых интерфейса:

  • Интерфейс управления (Management Interface): Привязан к выделенному VLAN. На этом интерфейсе слушает демон Incus, здесь же происходит синхронизация состояния базы данных dqlite между узлами кластера и передача образов. Доступ к этой сети должен быть разрешен только с IP-адресов jump-серверов администраторов и раннеров CI/CD систем.
  • Интерфейс данных (Data Interface): Используется исключительно для бриджей (Linux Bridge, Open vSwitch) или интеграции с OVN (Open Virtual Network). Демон Incus не должен прослушивать этот интерфейс.
  • Для реализации изоляции между арендаторами (multi-tenancy) на уровне сети стандартом де-факто является использование OVN. В отличие от традиционных Linux-бриджей, OVN позволяет создавать распределенные логические маршрутизаторы и коммутаторы, инкапсулируя трафик контейнеров в туннели Geneve. Это означает, что даже если злоумышленник получит права root внутри контейнера и переведет сетевой интерфейс в неразборчивый режим (promiscuous mode), он не сможет перехватить трафик соседних контейнеров, так как на уровне хоста трафик криптографически или логически изолирован в рамках виртуальных сетей OVN.

    Стандартизация параметров изоляции через профили

    В Incus конфигурация контейнера формируется путем наложения профилей. Регламент безопасности должен запрещать создание контейнеров с ручным указанием флагов изоляции. Вместо этого разрабатывается иерархия неизменяемых профилей, которые хранятся в Git-репозитории и применяются через Terraform или официальный провайдер OpenTofu.

    Базовый профиль enterprise-secure-default, который должен неявно применяться ко всем новым контейнерам, обязан включать следующие директивы:

    Параметр lxc.mount.auto = proc:mixed sys:mixed cgroup:mixed играет критическую роль. Он указывает liblxc монтировать /proc и /sys таким образом, чтобы пути, потенциально позволяющие взаимодействовать с ядром хоста (например, /proc/sysrq-trigger или /sys/class/dmi), были смонтированы в режиме read-only или полностью скрыты (замонтированы пустыми tmpfs).

    Для сервисов, не требующих сохранения состояния на диске (stateless), архитектурная политика должна предписывать использование неизменяемых корневых файловых систем. В Incus это достигается параметром volatile.rootfs.last_state.ro = true (или настройкой ZFS/Btrfs тома в read-only). При этом для корректной работы init-системы контейнера (systemd) директории /run, /tmp и /var/log пробрасываются как эфемерные tmpfs. Это блокирует целый класс атак, связанных с закреплением вредоносного ПО (persistence) внутри системного контейнера.

    Регламент управления цепочкой поставок образов

    Атаки на цепочку поставок (Supply Chain Attacks) требуют строгих политик в отношении того, откуда гипервизор имеет право скачивать образы. По умолчанию Incus настроен на доверие публичным серверам образов (например, images.linuxcontainers.org).

    В защищенной инфраструктуре публичные remote-серверы должны быть удалены из конфигурации всех узлов кластера. Регламент предписывает создание внутреннего приватного сервера образов.

    Процесс выглядит следующим образом:

  • Выделенный CI/CD pipeline скачивает базовый образ с публичного сервера в изолированной песочнице.
  • Производится сканирование образа на уязвимости (CVE) и внедрение корпоративных сертификатов CA.
  • Образ подписывается криптографическим ключом организации.
  • Образ публикуется во внутреннем реестре Incus.
  • На узлах кластера устанавливается параметр images.remote_cache_expiry, который контролирует время жизни кэшированных образов. Для критичных сред он устанавливается в минимальное значение, чтобы принудительно запрашивать актуальные хэши с внутреннего сервера, предотвращая запуск контейнеров из устаревших образов с известными уязвимостями.

    Гранулярная авторизация и аудит API

    Переход от статических паролей к OpenFGA меняет подход к выдаче прав. Архитектурная политика должна запрещать использование локальных групп incus-admin для ежедневных операций. Все инженеры и CI/CD системы должны аутентифицироваться через OIDC-провайдер, после чего их права проверяются в OpenFGA.

    Регламент авторизации строится на принципе наименьших привилегий (PoLP). Вместо выдачи глобального доступа на чтение, формируются графы отношений. Например, CI/CD система проекта "Alpha" получает отношение can_manage только к объекту project:alpha.

    Более того, политики OpenFGA позволяют ограничивать конкретные действия. Важно разделить права на управление жизненным циклом (start/stop) и права на выполнение команд внутри контейнера (exec). Доступ к incus exec эквивалентен получению shell-доступа и должен быть запрещен для автоматизированных систем деплоя, оставаясь доступным только для инженеров третьей линии поддержки (L3) в рамках расследования инцидентов (через break-glass процедуры).

    Для аудита инфраструктуры необходимо настроить непрерывный экспорт логов демона. Внутренний логгер Incus фиксирует все API-запросы, включая информацию о том, какой OIDC-пользователь инициировал действие. Эти события должны маршрутизироваться в корпоративную SIEM-систему. Особое внимание в правилах корреляции SIEM следует уделять событиям:

  • container_created с флагом security.privileged=true (если ограничение не было заблокировано на уровне проекта).
  • container_exec — запуск интерактивных сессий.
  • Изменение конфигурации профилей по умолчанию.
  • Стратегия реагирования на инциденты (IR) и форензика

    Наличие регламента реагирования на компрометацию контейнера отличает зрелую инфраструктуру от уязвимой. Если система обнаружения вторжений (IDS) на уровне хоста (например, Falco или Tracee, анализирующие eBPF-события) фиксирует аномальную активность внутри контейнера — например, попытку чтения /etc/shadow или запуск майнера — стандартная реакция «убить процесс и удалить контейнер» является ошибочной. Она уничтожает цифровые улики.

    Правильный архитектурный регламент требует перевода контейнера в динамический карантин с сохранением состояния памяти для последующей форензики (расследования).

    !Процесс изоляции и форензики скомпрометированного контейнера

    Этот процесс опирается на интеграцию Incus с проектом CRIU (Checkpoint/Restore In Userspace). Процедура реагирования, которая должна быть автоматизирована через API, включает следующие шаги:

  • Сетевая изоляция (Network Quarantine): На лету к контейнеру применяется профиль quarantine-net, который удаляет текущие сетевые интерфейсы и подключает интерфейс к изолированному OVN-коммутатору без выхода в интернет. Это останавливает утечку данных (exfiltration) и обрывает связь с Command & Control (C2) серверами злоумышленника, но оставляет контейнер работать.
  • Заморозка (Freezing): Вызов команды incus pause <container_name>. Демон использует подсистему cgroups (freezer controller), чтобы мгновенно приостановить выполнение всех процессов в контейнере. Процессы остаются в оперативной памяти, но перестают получать кванты процессорного времени. Это предотвращает срабатывание логических бомб или механизмов самоуничтожения малвари, реагирующих на потерю сети.
  • Снятие дампа памяти (Stateful Snapshot): Выполняется команда incus snapshot create <container_name> <snap_name> --stateful. Incus вызывает CRIU, который аккуратно считывает страницы памяти приостановленных процессов, состояние регистров процессора и открытые файловые дескрипторы, сохраняя их на диск.
  • Анализ: Полученный дамп памяти и файловой системы передается команде ИБ для анализа с помощью инструментов вроде Volatility, что позволяет извлечь ключи шифрования или расшифрованные пейлоады, которые существовали только в RAM.
  • Управление жизненным циклом и патч-менеджмент

    Архитектурная политика должна строго регламентировать версии используемого ПО. В корпоративной среде недопустимо использование rolling-релизов Incus. Стандартом является использование LTS (Long Term Support) версий (например, Incus 6.0 LTS), которые получают обновления безопасности без ломающих изменений API или поведения демона.

    Обновление узлов кластера должно производиться по принципу Rolling Update. Архитектура кластеризации Incus позволяет без простоя сервисов переносить контейнеры с обновляемого узла на соседние. Для системных контейнеров, не поддерживающих живую миграцию (Live Migration) из-за привязки к специфичному оборудованию (например, проброшенным GPU), регламент должен предусматривать Maintenance Window.

    Важным аспектом патч-менеджмента является контроль ядра хостовой ОС. Поскольку системные контейнеры делят ядро с хостом, уязвимость в ядре (например, в подсистеме io_uring или eBPF) автоматически ставит под угрозу все контейнеры. Регламент должен требовать отключения неиспользуемых подсистем ядра на хосте через параметры загрузчика (GRUB) или sysctl. Если приложение в контейнере не требует асинхронного ввода-вывода нового поколения, системный вызов io_uring_setup должен быть заблокирован через Seccomp-профиль Incus, так как исторически он является частым источником уязвимостей локального повышения привилегий.

    Проектирование защищенной инфраструктуры — это процесс создания системы сдержек и противовесов. Изоляция UID защищает файлы хоста, cgroups предотвращают отказ в обслуживании, OVN изолирует сеть, а OpenFGA защищает плоскость управления. Только комплексное применение этих инструментов, закрепленное в виде автоматизированных политик и регламентов реагирования, позволяет использовать системные контейнеры в средах с высокими требованиями к информационной безопасности.

    2. Центральная модель угроз: анализ рисков доступа к сокету демона и привилегий на хосте

    Центральная модель угроз: анализ рисков доступа к сокету демона и привилегий на хосте

    Специалисту выдают доступ к серверу для управления контейнерами. В системе создана специальная группа, например, lxd или incus-admin, и учетная запись разработчика заботливо в нее добавлена. Администратор хоста уверен, что соблюдает принцип наименьших привилегий: разработчик не имеет прав sudo, не может устанавливать пакеты на хосте и ограничен рамками CLI-утилиты для работы с контейнерами. Однако через три минуты разработчик читает содержимое /etc/shadow хостовой машины, а еще через минуту — добавляет свой SSH-ключ в /root/.ssh/authorized_keys.

    Эта ситуация — не результат эксплуатации 0-day уязвимости в гипервизоре. Это штатное, задокументированное поведение системы, вытекающее из фундаментальной архитектуры демонов управления контейнерами. Понимание того, где проходит граница безопасности, а где начинается иллюзия изоляции, является отправной точкой для проектирования защищенной инфраструктуры на базе LXD и Incus.

    Анатомия привилегий: почему демон требует root-доступа

    Чтобы понять природу риска, необходимо спуститься на уровень взаимодействия демона с ядром Linux. В отличие от легковесных утилит или rootless-контейнеров (например, Podman в пользовательском режиме), демоны LXD и Incus спроектированы как системные гипервизоры. Их задача — создавать полноценные виртуальные среды, которые операционная система воспринимает как изолированные сущности с собственными сетевыми стеками, лимитами ресурсов и таблицами маршрутизации.

    Для выполнения этих задач процесс демона (будь то lxd или incus) должен работать в пространстве хоста с правами суперпользователя. Это не архитектурная недоработка, а жесткое требование ядра Linux для выполнения целого класса системных вызовов и операций:

  • Манипуляции с сетевым стеком. Создание виртуальных интерфейсов (veth-пар), добавление их в сетевые мосты хоста, настройка правил iptables или nftables для NAT и проброса портов требует наличия CAP_NET_ADMIN в первоначальном (хостовом) сетевом пространстве имен.
  • Управление ресурсами (cgroups). Демон должен взаимодействовать с иерархией cgroupfs (обычно смонтированной в /sys/fs/cgroup), создавая новые контрольные группы для ограничения CPU, памяти и дискового ввода-вывода. Запись в эти файлы на хосте требует root-прав.
  • Монтирование файловых систем. Инициализация корневой файловой системы контейнера (rootfs) включает в себя операции с блочными устройствами (ZFS, Btrfs, LVM) и использование системного вызова mount с флагами вроде MS_BIND или монтирование псевдофайловых систем (proc, sysfs).
  • Управление пространствами имен. Вызов unshare или clone с флагами CLONE_NEWPID, CLONE_NEWNS, CLONE_NEWNET для создания изолированной среды, а также последующая настройка маппинга идентификаторов (UID/GID) через /proc/[pid]/uid_map требует высочайших привилегий в системе.
  • Таким образом, демон обладает абсолютной властью над хостом. Любая команда, которую демон получает и одобряет к исполнению, выполняется от имени пользователя root в корневом пространстве имен хоста.

    Локальный UNIX-сокет как плоскость управления

    Взаимодействие администратора с демоном происходит не напрямую через вызовы библиотек, а через REST API. Если управление осуществляется локально (с того же хоста), этот API доступен через локальный UNIX-доменный сокет.

    Исторически в LXD этот сокет располагается по пути /var/lib/lxd/unix.socket (или в специфичных путях при использовании Snap-пакетов). В Incus стандартный путь — /var/lib/incus/unix.socket.

    !Архитектура взаимодействия через UNIX-сокет

    Ключевой момент безопасности кроется в правах доступа к этому файлу сокета. Ядро Linux позволяет управлять доступом к UNIX-сокетам с помощью стандартных POSIX-прав (owner, group, others). Обычно сокет имеет права 0660, где владельцем является root, а группой — lxd (в случае LXD) или incus-admin (в случае Incus).

    Когда пользователь вводит команду incus launch ubuntu:22.04 my-container, CLI-клиент формирует HTTP-запрос (например, POST /1.0/instances) и отправляет его в этот сокет. Демон, прослушивающий сокет, использует механизм SO_PEERCRED для запроса у ядра Linux информации о процессе, который установил соединение. Ядро надежно сообщает демону UID и GID клиента.

    Если GID клиента совпадает с группой администраторов сокета, демон считает запрос полностью авторизованным. В этой модели нет промежуточных проверок: если вы можете писать в сокет, демон выполнит любой валидный REST API запрос от вашего имени с правами хостового root.

    Вектор тривиальной эскалации: от сокета к хостовому root

    Опасность заключается в том, что API демона обладает достаточной гибкостью, чтобы легитимными средствами разрушить изоляцию. Разработчики LXC/Incus заложили в систему мощные инструменты для проброса устройств и директорий внутрь контейнеров, что необходимо для многих production-задач. Однако в руках пользователя, имеющего доступ к сокету, эти инструменты превращаются в механизм повышения привилегий (Privilege Escalation).

    Рассмотрим классический вектор атаки, состоящий из трех шагов.

    Шаг 1. Создание привилегированного контейнера. По умолчанию современные версии создают непривилегированные контейнеры (с изоляцией User Namespaces). Но API позволяет запросить отключение этой защиты: incus launch images:alpine/3.18 exploit -c security.privileged=true Демон, получив этот запрос от члена группы incus-admin, послушно создает контейнер, в котором пользователь root внутри контейнера имеет тот же UID (0), что и root на хосте.

    Шаг 2. Проброс корневой файловой системы хоста. В API существует тип устройства disk, который позволяет выполнить bind-mount любой директории хоста внутрь контейнера: incus config device add exploit hostfs disk source=/ path=/mnt/host recursive=true Поскольку команду выполняет демон (работающий от root на хосте), он без проблем читает хостовый корень / и монтирует его в директорию /mnt/host внутри файловой системы контейнера.

    Шаг 3. Исполнение команд. Атакующему остается лишь запустить оболочку внутри контейнера: incus exec exploit -- /bin/sh Оказавшись в контейнере, атакующий переходит в /mnt/host. Поскольку контейнер привилегированный (UID 0 внутри равен UID 0 снаружи), атакующий имеет полный доступ на чтение и запись к любым файлам хоста. Он может прочитать /mnt/host/etc/shadow, изменить /mnt/host/etc/sudoers или подменить бинарный файл /mnt/host/bin/bash на бэкдор.

    !Пошаговая эскалация привилегий через bind-mount

    Этот процесс не требует компиляции эксплоитов, обхода ASLR или поиска переполнений буфера. Это использование штатного API по прямому назначению.

    Границы ответственности и официальная модель угроз

    Чтобы грамотно выстраивать защиту, необходимо четко понимать официальную модель угроз (Threat Model), декларируемую разработчиками проектов Linux Containers. В документации четко проведена линия между тем, что считается уязвимостью (и получает идентификатор CVE), и тем, что является особенностью архитектуры (by design).

    > Доступ к группе администраторов демона (lxd или incus-admin) эквивалентен локальному доступу с правами root на хосте. Любая эскалация привилегий, совершенная пользователем из этой группы, не является уязвимостью безопасности. > > Официальная документация Incus по безопасности

    Исходя из этого постулата, модель угроз строится следующим образом:

  • Хост против Контейнера (Host vs Container). Если вредоносный процесс запускается внутри непривилегированного контейнера и находит способ вырваться на хост (Container Escape) — это критическая уязвимость. Разработчики оперативно выпускают патчи для таких случаев.
  • Контейнер против Контейнера (Container vs Container). Если процесс в одном контейнере может прочитать память или файловую систему другого контейнера (без явной настройки совместного доступа) — это уязвимость.
  • Непривилегированный пользователь хоста против Контейнера. Если пользователь хоста, не входящий в группу администраторов демона, может повлиять на работу контейнеров — это уязвимость.
  • Пользователь с доступом к API против Хоста. Если пользователь, авторизованный в API как администратор (через локальный сокет или удаленные TLS-сертификаты), захватывает хост — это не уязвимость.
  • Непонимание этого четвертого пункта регулярно приводит к ошибкам в корпоративных политиках безопасности. Администраторы выдают доступ к группе lxd разработчикам CI/CD пайплайнов, полагая, что изолируют их в песочнице, а де-факто выдают им ключи от всего сервера.

    Эволюция защиты: от монолитного доступа к гранулярному контролю

    Осознавая архитектурные риски локального сокета, разработчики внедряли механизмы для снижения вероятности случайной компрометации. В этом аспекте проект Incus, являясь современным форком, предлагает более совершенную модель разграничения прав по сравнению с классическим LXD.

    Разделение локальных групп

    В классическом LXD существовала одна основная группа — lxd. Любой ее член получал полный контроль. В Incus архитектура локального доступа была пересмотрена и разделена на несколько уровней:

  • Группа incus-admin: Полный аналог старой группы lxd. Предоставляет неограниченный административный доступ к API. Добавление сюда равносильно выдаче прав root.
  • Группа incus: Предназначена для обычных пользователей. По умолчанию члены этой группы могут управлять только теми проектами (Projects), к которым им явно предоставлен доступ, и их права внутри этих проектов ограничены.
  • Группа incus-restricted: Еще более жесткий режим, при котором пользователь не может использовать часть API, способную повлиять на хост (например, запрещен проброс произвольных путей с хоста, запрещено создание привилегированных контейнеров).
  • Проекты (Projects) как граница изоляции

    Для предотвращения выдачи полного доступа к сокету используется концепция Проектов. Проект в Incus — это логический срез ресурсов демона. Он имеет собственные профили, образы, сети и контейнеры.

    Вместо того чтобы давать разработчику неограниченный доступ к сокету (и, следовательно, ко всему хосту), администратор создает отдельный проект с включенной опцией restricted.

    Пример конфигурации безопасного проекта: incus project set dev-project restricted=true incus project set dev-project restricted.devices.disk.paths=/tmp,/var/log/app incus project set dev-project restricted.containers.privilege=unprivileged

    В таком проекте демон на уровне API отвергнет команду security.privileged=true или попытку примонтировать /etc с хоста, даже если клиент технически прошел аутентификацию. Демон проверит контекст проекта и заблокирует операцию до того, как она дойдет до системных вызовов ядра.

    Интеграция с OIDC и отказ от локальных групп

    Для крупных инфраструктур управление безопасностью через локальные UNIX-группы (/etc/group) становится неэффективным. Современный подход заключается в минимизации использования локального сокета.

    Вместо этого демон настраивается на прослушивание сетевого порта (с обязательным mTLS) и интеграцию с провайдерами OpenID Connect (OIDC). В этом сценарии локальный сокет unix.socket остается доступным только для пользователя root (для экстренного восстановления), а все разработчики и автоматизированные системы (CI/CD) ходят через сеть, получая токены с жестко заданными ролями (RBAC). Демон валидирует токен, извлекает из него роль пользователя и применяет ограничения API в соответствии с этой ролью.

    Резюме архитектурного парадокса

    Центральная модель угроз системных контейнеров строится на принятии факта: плоскость управления (Control Plane) не изолирована от хоста. Демон-гипервизор — это часть ядра инфраструктуры, работающая с абсолютными привилегиями.

    Локальный UNIX-сокет является прямым мостом к этим привилегиям. Защита архитектуры начинается не с настройки параметров внутри самих контейнеров, а с жесткого контроля доступа к этому сокету. Рассматривать членство в группе управления контейнерами как "безопасную альтернативу sudo" — критическая ошибка проектирования. Безопасность достигается только через использование встроенных механизмов ограничения API (Restricted Projects) и отказ от выдачи административных прав конечным пользователям.

    3. Фундаментальные механизмы изоляции: User Namespaces, сопоставление UID/GID и типы контейнеров

    Фундаментальные механизмы изоляции: User Namespaces, сопоставление UID/GID и типы контейнеров

    Если внутри стандартного контейнера выполнить команду whoami, система ответит: root. Если в этот же момент администратор хоста выполнит ps aux | grep init, он увидит, что процесс контейнера запущен от имени пользователя root. Но если проделать тот же эксперимент в Incus или LXD с настройками по умолчанию, администратор хоста увидит совершенно иную картину: процесс init принадлежит загадочному пользователю с UID 1000000.

    Это расхождение — не косметический трюк, а фундамент современной контейнерной безопасности. Разница между реальным root и виртуальным определяет, сможет ли злоумышленник, захвативший контейнер, уничтожить хост-систему.

    Привилегированные и непривилегированные контейнеры

    В архитектуре LXC, LXD и Incus контейнеры строго делятся на два типа, определяющих их отношение к ядру хоста.

    Привилегированные контейнеры (Privileged containers) — это контейнеры, в которых идентификаторы пользователей (UID) и групп (GID) внутри изолированной среды в точности совпадают с идентификаторами на хосте. Пользователь root () внутри такого контейнера является тем же самым пользователем root () для ядра хост-системы.

    Исторически это был единственный способ запуска контейнеров. С точки зрения безопасности привилегированный контейнер считается unsafe (небезопасным). Изоляция в нем строится исключительно на механизмах cgroups, AppArmor/SELinux, Seccomp и других пространствах имен (Mount, PID, Network). Если злоумышленник находит уязвимость в ядре (например, ошибку в обработчике файловой системы или сетевом стеке) или находит способ смонтировать корневую файловую систему хоста (bind-mount escape), он осуществляет атаку от имени реального суперпользователя. Ядро не видит причин отказывать процессу с в доступе к критическим ресурсам.

    Непривилегированные контейнеры (Unprivileged containers) — это контейнеры, использующие пространство имен пользователей (User Namespaces). В них применяется математический сдвиг идентификаторов. Пользователь root внутри контейнера имеет только в своей изолированной проекции. На уровне ядра хоста этот процесс выполняется от имени обычного, непривилегированного пользователя с высоким идентификатором (например, ).

    Incus и LXD по умолчанию создают только непривилегированные контейнеры. Создание привилегированного контейнера требует явного указания конфигурации security.privileged=true, что в современных регламентах Hardening допускается только в исключительных случаях (например, для запуска специфического оборудования, требующего прямого доступа к /dev/mem, что само по себе нарушает концепцию изоляции).

    Архитектура User Namespaces и механизм сопоставления

    Пространство имен пользователей (User Namespaces) — это механизм ядра Linux, позволяющий изолировать идентификаторы пользователей и групп, ключи, а также возможности (capabilities). Это единственное пространство имен, для создания которого (через системный вызов clone с флагом CLONE_NEWUSER) процессу не требуются права root на хосте.

    Когда процесс создает новое User Namespace, ядро устанавливает границу трансляции. Внутри этой границы процесс может обладать полным набором capabilities и иметь . Но ядро всегда знает, как этот внутренний идентификатор отображается на реальный идентификатор хоста.

    !Интерактивный калькулятор трансляции UID и проверки DAC

    Правила этой трансляции описываются в файлах /proc/PID/uid_map и /proc/PID/gid_map. Формат записи в этих файлах состоит из трех чисел:

  • — начальный идентификатор внутри контейнера.
  • — начальный идентификатор на хосте.
  • — длина диапазона (количество непрерывных идентификаторов).
  • Типичная запись для непривилегированного контейнера Incus выглядит так: 0 1000000 65536

    Эта строка приказывает ядру применять следующую логику:

  • Внутренний (root в контейнере) превращается в на хосте.
  • Внутренний (обычный пользователь ubuntu в контейнере) превращается в на хосте.
  • Диапазон заканчивается на внутреннем , который проецируется на .
  • Почему длина диапазона обычно равна ? Это требование стандарта POSIX, согласно которому система должна поддерживать 16-битные идентификаторы пользователей (от до ). Традиционный пользователь nobody имеет . Если выделить контейнеру диапазон меньше , многие системные службы внутри контейнера (например, systemd или nfs-kernel-server), пытающиеся сменить пользователя на nobody или systemd-network, завершатся с ошибкой, так как их целевой UID окажется за пределами разрешенного маппинга.

    Откуда берутся внешние идентификаторы?

    Ядро не позволяет произвольно назначать . Процесс, настраивающий маппинг (в нашем случае демон Incus), может использовать только те идентификаторы, которые ему делегированы системой.

    Делегирование настраивается в конфигурационных файлах /etc/subuid и /etc/subgid. Установка Incus добавляет туда записи вида: root:1000000:1000000000 incus:1000000:1000000000

    Это означает, что демону Incus (и пользователю root, от имени которого он работает) разрешено использовать миллиард идентификаторов, начиная с миллионного, для построения User Namespaces. Такой огромный пул нужен для реализации продвинутых техник изоляции, которые мы рассмотрим ниже.

    Ограничения DAC как главный рубеж обороны

    Discretionary Access Control (DAC) — это базовая система разграничения прав доступа в Linux, оперирующая владельцами файлов и правами на чтение, запись и исполнение (rwx).

    Сила непривилегированных контейнеров заключается в том, что трансляция User Namespaces происходит до того, как ядро проверяет права DAC.

    Рассмотрим классический вектор атаки: Container Escape через монтирование файловой системы хоста. Допустим, злоумышленнику удалось обмануть демона управления или использовать уязвимость ядра, чтобы смонтировать директорию /etc хоста внутрь контейнера по пути /mnt/host_etc.

    В привилегированном контейнере злоумышленник выполнит cat /mnt/host_etc/shadow. Ядро увидит, что процесс имеет , файл /etc/shadow принадлежит с правами 0600, и разрешит чтение. Хэши паролей хоста скомпрометированы.

    В непривилегированном контейнере злоумышленник также выполнит cat /mnt/host_etc/shadow. Однако ядро обработает запрос иначе:

  • Процесс внутри контейнера имеет .
  • Ядро транслирует его через uid_map и видит, что реальный идентификатор процесса — .
  • Ядро проверяет владельца файла /etc/shadow на хосте. Владелец — .
  • Ядро применяет правила DAC: может ли пользователь читать файл, принадлежащий пользователю с правами 0600?
  • Ответ отрицательный. Злоумышленник получает Permission denied.
  • Проблема неразмеченных идентификаторов (overflowuid)

    Что произойдет, если процесс внутри непривилегированного контейнера попытается прочитать файл, владелец которого вообще не попадает в диапазон трансляции? Например, файл на хосте принадлежит , а контейнеру выделен диапазон .

    В этом случае ядро использует механизм overflowuid (по умолчанию , что соответствует пользователю nobody). Для процесса внутри контейнера владелец такого файла будет отображаться как nobody. Любая попытка записи в такой файл будет отклонена DAC, так как процесс (даже будучи внутренним root) не имеет прав на модификацию файлов, принадлежащих системному overflowuid, если только права файла не установлены как 777 (что на хосте является грубой ошибкой администрирования).

    Эшелонированная изоляция: security.idmap.isolated

    По умолчанию Incus оптимизирует использование ресурсов и применяет один и тот же базовый маппинг (0 1000000 65536) для всех создаваемых непривилегированных контейнеров.

    С точки зрения защиты хоста это безопасно. Но с точки зрения защиты контейнеров друг от друга (Multi-tenancy) возникает критическая уязвимость. Если злоумышленник совершает побег из Контейнера А и получает доступ к файловой системе хоста (с правами ), он не сможет прочитать /etc/shadow хоста. Но он сможет прочитать файлы Контейнера Б, потому что файловая система Контейнера Б также принадлежит пользователю на хосте. DAC ядра Linux воспримет злоумышленника из Контейнера А как законного владельца файлов Контейнера Б.

    Для защиты от горизонтального перемещения между контейнерами применяется параметр security.idmap.isolated.

    Когда администратор устанавливает incus config set <container> security.idmap.isolated=true, демон Incus обращается к пулу идентификаторов, выделенному в /etc/subuid, и нарезает уникальный, непересекающийся диапазон для конкретного контейнера.

  • Контейнер А может получить маппинг: 0 1000000 65536.
  • Контейнер Б получит маппинг: 0 1065536 65536.
  • Контейнер В получит маппинг: 0 1131072 65536.
  • Теперь, даже если злоумышленник вырвется из Контейнера А, его процессы будут иметь . При попытке доступа к данным Контейнера Б, принадлежащим , ядро заблокирует операцию на уровне DAC. Именно для обеспечения такой изоляции сотням контейнеров демону Incus требуется пул из миллиарда идентификаторов.

    VFS idmapped mounts: разделение данных без изменения владельцев

    Строгая изоляция через смещение UID создает серьезную архитектурную проблему при необходимости совместного использования данных.

    Предположим, на хосте есть директория /data/web, принадлежащая пользователю (разработчик). Мы хотим пробросить эту директорию в непривилегированный контейнер (где работает Nginx) по пути /var/www/html.

    Если просто выполнить bind-mount, Nginx внутри контейнера увидит, что директория принадлежит overflowuid (поскольку хостовый не попадает в диапазон ), и не сможет получить к ней доступ.

    Исторически эту проблему решали рекурсивной сменой владельца (chown -R 1001000:1001000 /data/web). Но это ломало доступ для разработчика на хосте и занимало огромное время для больших директорий. Позже в Ubuntu появился патч ядра shiftfs, который на лету подменял UID, но он так и не был принят в основную ветку ядра Linux (mainline) из-за проблем с архитектурой.

    Современное и безопасное решение, которое использует Incus — это VFS idmapped mounts, добавленное в ядро Linux начиная с версии 5.12.

    !Архитектура VFS idmapped mounts

    VFS (Virtual File System) idmapped mounts позволяет создавать новые точки монтирования, в которых ядро динамически переводит идентификаторы владельцев файлов на уровне виртуальной файловой системы, не изменяя реальные метаданные на диске.

    В Incus это настраивается через конфигурацию дискового устройства с параметром shift=true: incus config device add webserver htdocs disk source=/data/web path=/var/www/html shift=true

    При такой конфигурации ядро Linux создает специальный слой монтирования. Когда процесс из контейнера (где Nginx работает от внутреннего ) запрашивает файл, запрос проходит через User Namespace, превращаясь в . Затем запрос попадает в слой VFS idmap, который знает, что для этой конкретной точки монтирования нужно транслировать обратно в хостовый .

    В результате:

  • Nginx в контейнере видит владельца файла как (внутренний).
  • Разработчик на хосте видит владельца файла как (хостовый).
  • Права DAC соблюдаются с обеих сторон.
  • На диске файлы остаются нетронутыми.
  • Эта технология окончательно закрыла брешь между удобством использования (которое раньше заставляло администраторов отказываться от User Namespaces и использовать привилегированные контейнеры) и строгими требованиями безопасности.

    Понимание того, как User Namespaces взаимодействуют с DAC и трансляцией идентификаторов, критически важно. Это не просто абстрактная теория: именно ошибки в реализации маппинга или некорректная обработка граничных условий в ядре становятся причиной большинства современных уязвимостей, позволяющих осуществить обход изоляции, которые мы будем детально разбирать в дальнейшем.

    4. Анализ критических уязвимостей: детальный разбор CVE и векторов обхода изоляции в рантайме

    Анализ критических уязвимостей: детальный разбор CVE и векторов обхода изоляции в рантайме

    Даже математически безупречно настроенный User Namespace не спасет инфраструктуру, если уязвимость кроется в самом механизме создания или управления контейнером. Исторически сложилось так, что большинство успешных побегов из контейнеров (Container Escapes) происходит не из-за ошибок в ядре Linux, а из-за логических уязвимостей в управляющих демонах — LXD или Incus. Когда процесс с правами root на хосте взаимодействует с недоверенными данными (образами, шаблонами или конфигурациями), малейшая ошибка в последовательности системных вызовов превращает надежную песочницу в открытую дверь.

    В классической архитектуре изоляции мы опираемся на трансляцию идентификаторов. Если внутри контейнера процесс работает от имени пользователя с , то на хосте ядро видит его как . Однако демоны управления, такие как incusd, работают вне этого пространства имен. Они обладают полными привилегиями и выполняют операции от имени контейнера. Именно этот мост между привилегированным хостом и непривилегированным гостем становится главной мишенью для атак.

    Утечка дескрипторов и состояние гонки: CVE-2026-34178

    Один из самых сложных для обнаружения классов уязвимостей в рантайме — это утечка файловых дескрипторов (File Descriptor Leak) в сочетании с состоянием гонки (Race Condition). CVE-2026-34178 демонстрирует, как злоумышленник, уже имеющий выполнение кода внутри непривилегированного контейнера, может перехватить дескриптор, принадлежащий хостовому процессу incusd, и использовать его для выхода за пределы изоляции.

    Механика работы команд вроде incus exec или lxc-attach требует от демона выполнения сложной хореографии системных вызовов. Чтобы запустить процесс внутри уже работающего контейнера, демон должен:

  • Открыть дескрипторы для пространств имен целевого контейнера (через /proc/PID/ns/*).
  • Вызвать setns() для присоединения к этим пространствам.
  • Выполнить fork() для создания нового процесса.
  • Сбросить привилегии (drop privileges) до уровня пользователя контейнера.
  • Вызвать execve() для запуска целевого бинарного файла (например, /bin/bash).
  • Уязвимость CVE-2026-34178 возникла на этапе подготовки к fork(). Демон Incus открывал служебный файл на хостовой файловой системе (например, лог-файл или сокет управления cgroups), но не устанавливал для этого файлового дескриптора флаг O_CLOEXEC.

    Флаг O_CLOEXEC (Close-on-Exec) — это критически важный механизм ядра Linux. Если он установлен, ядро автоматически закроет этот файловый дескриптор при успешном вызове execve(). Если флага нет, дескриптор наследуется новым процессом.

    !Механизм перехвата файлового дескриптора при отсутствии флага O_CLOEXEC

    В случае с данной уязвимостью, вредоносный процесс внутри контейнера мог постоянно мониторить директорию /proc в ожидании появления нового процесса (результата fork() от incusd, но до вызова execve()). Как только администратор хоста выполнял легитимную команду incus exec container -- /bin/bash, атакующий процесс успевал прочитать унаследованный файловый дескриптор через /proc/PID/fd/.

    Так как дескриптор указывал на ресурс хоста (например, на корневой каталог хоста, открытый демоном для внутренних нужд), атакующий получал прямой канал чтения/записи в обход всех ограничений chroot, AppArmor и User Namespaces. Для эксплуатации достаточно было написать скрипт, который в цикле пытается выполнить системный вызов openat() относительно утекшего дескриптора.

    Защита от подобных векторов требует строгой дисциплины программирования на уровне исходного кода демонов: использование атомарных вызовов вроде open(..., O_CLOEXEC) и внедрение статических анализаторов, блокирующих компиляцию при создании дескрипторов без этого флага. На уровне системного администратора минимизировать риск можно только ограничением использования интерактивных сессий (exec) в production-средах.

    Отравление образов и Path Traversal: CVE-2026-33897

    Если атаки через рантайм требуют, чтобы администратор совершил действие (например, подключился к контейнеру), то уязвимости этапа инициализации эксплуатируются автоматически в момент создания контейнера. CVE-2026-33897 иллюстрирует разрушительный потенциал атак на процесс распаковки образов (tarball extraction).

    Образ контейнера LXC/Incus физически представляет собой сжатый tar-архив, содержащий корневую файловую систему (rootfs). Когда пользователь выполняет команду incus launch ubuntu:22.04 my-container, демон скачивает архив и распаковывает его в директорию пула хранения на хосте (например, /var/lib/incus/storage-pools/default/containers/my-container/rootfs/). Распаковку выполняет процесс incusd, работающий с правами root.

    Формат tar поддерживает сохранение символических ссылок (symlinks). Злоумышленник, контролирующий сервер образов или сумевший подменить архив через атаку Man-in-the-Middle, может создать специальный образ. В этом архиве первым файлом идет символическая ссылка, а вторым — обычный файл, который должен быть записан по пути этой ссылки.

    !Схема выхода за пределы rootfs через вредоносный симлинк

    Вектор атаки CVE-2026-33897 выглядит следующим образом:

  • Вредоносный архив содержит запись: симлинк etc/cron.d/malicious_link, указывающий на ../../../../etc/cron.d/.
  • Демон incusd при распаковке создает этот симлинк внутри целевой директории rootfs. На этом этапе нарушения безопасности нет, так как симлинк просто существует на диске.
  • Далее в архиве идет файл с путем etc/cron.d/malicious_link/payload.sh и содержимым вредоносного скрипта.
  • Уязвимый код распаковщика пытается открыть путь для записи. Он видит директорию etc/cron.d/malicious_link, которая является симлинком. Ядро Linux, обрабатывая системный вызов open(), прозрачно следует по симлинку.
  • Поскольку путь симлинка содержит последовательность ../, ядро выходит за пределы директории rootfs и резолвит путь в абсолютный /etc/cron.d/ на хостовой системе.
  • Демон, обладая правами root, успешно записывает payload.sh в системную директорию планировщика задач хоста. Через минуту cron выполняет скрипт, и хост полностью скомпрометирован.
  • В современных версиях Incus эта проблема решается на уровне использования новых возможностей ядра Linux. Вместо классического open() для создания файлов при распаковке используется системный вызов openat2(). Этот вызов принимает структуру open_how, в которой можно передать флаг RESOLVE_BENEATH.

    Флаг RESOLVE_BENEATH заставляет ядро принудительно прерывать резолвинг пути с ошибкой EXDEV, если в процессе следования по симлинкам или обработки ../ путь пытается выйти за пределы базовой директории (в данном случае — директории rootfs контейнера). Это переносит ответственность за безопасность распаковки из пространства пользователя (где легко допустить ошибку в логике Go-кода) на уровень ядра, делая атаки класса Path Traversal при распаковке архивов практически невозможными.

    Утечка данных через шаблонизатор: CVE-2026-23954

    LXD и Incus поддерживают механизм шаблонов (Templates), который позволяет динамически изменять конфигурационные файлы внутри контейнера при его запуске. Это удобно для автоматической подстановки IP-адресов, hostname или ключей SSH внутрь cloud-init или /etc/network/interfaces.

    Шаблоны обрабатываются встроенным шаблонизатором (например, pongo2) прямо перед стартом init-процесса контейнера. Уязвимость CVE-2026-23954 связана с архитектурным просчетом: на каком этапе и с какими правами происходит рендеринг шаблона.

    В уязвимых версиях обработка шаблонов происходила до того, как демон полностью изолировал процесс в User Namespace и выполнил chroot в корневую файловую систему контейнера. Шаблонизатор имел доступ к функциям чтения файлов, таким как include.

    Атакующий, имеющий право изменять метаданные образа (например, через загрузку собственного образа в локальный registry Incus), мог внедрить в файл template.yaml инструкцию: {{ include "/etc/shadow" }}

    Когда incusd (работающий от root) готовил контейнер к запуску, он парсил этот шаблон. Функция include выполнялась в контексте хостовой файловой системы. Демон читал файл /etc/shadow хоста и вставлял его содержимое прямо в конфигурационный файл внутри контейнера (например, в /etc/motd).

    После успешного старта контейнера атакующему оставалось лишь прочитать /etc/motd внутри гостевой системы, чтобы получить хэши паролей всех пользователей хоста. Это классическая уязвимость класса Arbitrary File Read, ставшая возможной из-за нарушения принципа наименьших привилегий при проектировании пайплайна запуска.

    Исправление этой архитектурной ошибки потребовало разделения процесса инициализации. Теперь рендеринг шаблонов происходит строго после вызова chroot (или pivot_root) в директорию контейнера, либо функциям шаблонизатора жестко ограничивается область видимости файловой системы с помощью механизмов, аналогичных openat2() с флагом RESOLVE_IN_ROOT.

    Векторы удаленного управления и API

    Разобранные выше уязвимости требуют от атакующего либо наличия локального доступа к контейнеру (для эксплуатации race condition), либо возможности внедрить вредоносный образ в систему (для эксплуатации path traversal и шаблонов). Однако архитектура современных демонов контейнеризации подразумевает сетевое взаимодействие.

    Incus и LXD предоставляют мощный REST API, который по умолчанию слушает локальный UNIX-сокет, но часто открывается на сетевых интерфейсах для организации кластеров или удаленного управления. Выставление API в сеть радикально меняет модель угроз.

    Ошибки в обработке сетевых запросов демоном открывают двери для атак класса Server-Side Request Forgery (SSRF), как это было продемонстрировано в CVE-2026-35527. В этом сценарии злоумышленник заставляет демон incusd отправлять HTTP-запросы во внутреннюю сеть предприятия, используя сервер виртуализации как прокси. Аналогичным образом, уязвимости аутентификации в самом REST API (CVE-2026-40243) могут привести к полному перехвату управления над кластером без необходимости взаимодействия с образами или рантаймом контейнеров.

    Понимание того, как локальные механизмы изоляции могут быть обойдены через манипуляции с файловыми дескрипторами и симлинками, формирует базис для проектирования защищенной инфраструктуры. Любой инструмент, работающий с привилегиями root и оперирующий недоверенными данными, является потенциальной точкой отказа. Надежная архитектура не должна полагаться исключительно на User Namespaces; она требует эшелонированной защиты, где ограничения cgroups, профили AppArmor/Seccomp и строгая изоляция сетевого API работают совместно, компенсируя уязвимости друг друга.

    5. Сетевая безопасность и риски удаленного управления через REST API и SSRF-атаки

    Перенос управления контейнерами с локального UNIX-сокета на сетевой интерфейс превращает систему из локальной среды в распределенную инфраструктуру, но одновременно меняет класс критичности любых уязвимостей. Если доступ к локальному сокету lxd/incus-admin требует изначального присутствия атакующего на хосте, то публикация REST API на TCP-порту открывает вектор для удаленного выполнения кода (RCE) с правами root. Демоны-гипервизоры LXD и Incus написаны на Go и реализуют собственный HTTP-сервер, что требует глубокого понимания того, как именно обрабатываются входящие соединения и исходящие запросы демона.

    Архитектура сетевого доступа и эволюция аутентификации

    По умолчанию демоны LXD и Incus слушают только локальный UNIX-сокет. Сетевое взаимодействие активируется явным указанием параметра core.https_address, после чего демон начинает принимать соединения на указанном IP-адресе и порту (традиционно 8443).

    Взаимодействие с API строится на основе протокола HTTPS. Исторически базовая модель аутентификации в LXD опиралась на взаимную аутентификацию TLS (mTLS). Клиент генерирует собственный самоподписанный сертификат и предъявляет его серверу. Чтобы сервер начал доверять этому сертификату, клиент должен передать его отпечаток через API, подтвердив операцию специальным паролем доверия (Trust Password), который заранее задается на сервере параметром core.trust_password.

    Эта архитектура имеет фундаментальный изъян с точки зрения корпоративной безопасности. Пароль доверия является долгоживущим секретом, единым для всех администраторов. Его утечка или успешный брутфорс позволяет злоумышленнику добавить свой сертификат в хранилище доверенных (trust store) демона и получить полные права. Более того, отзыв скомпрометированного сертификата требует ручного вмешательства, а аудит действий затруднен, так как сертификаты часто не привязаны к конкретным физическим пользователям в корпоративном каталоге.

    !Архитектура аутентификации REST API

    Развитие проекта Incus привело к радикальному пересмотру этой модели. В Incus механизм Trust Password был признан устаревшим (deprecated) и полностью удален. Вместо него основным стандартом аутентификации для распределенных сред стала интеграция с OpenID Connect (OIDC).

    При использовании OIDC демон Incus делегирует проверку подлинности внешнему провайдеру идентификации (Identity Provider, например, Keycloak или Authelia). Клиент получает короткоживущий JWT (JSON Web Token), который передается в заголовке Authorization: Bearer. Это решает сразу несколько проблем:

  • Устраняется единый разделяемый секрет (пароль доверия).
  • Появляется возможность гранулярного контроля доступа на основе групп (claims), передаваемых в токене.
  • Жизненный цикл доступа управляется централизованно: при увольнении сотрудника его учетная запись блокируется в OIDC-провайдере, и доступ к API Incus прекращается немедленно, без необходимости искать и удалять его локальные TLS-сертификаты.
  • Риски удаленного REST API: обход авторизации (CVE-2026-40243)

    Даже при использовании надежных механизмов аутентификации, реализация самого HTTP-сервера и маршрутизатора (multiplexer) может содержать логические уязвимости. Уязвимость CVE-2026-40243 иллюстрирует класс атак, связанных с некорректной нормализацией путей (Path Normalization) в REST API.

    API демонов имеет строгую иерархию. Эндпоинты разделены на публичные (например, /1.0/), доступные без аутентификации для проверки статуса сервера, и защищенные (например, /1.0/containers), требующие валидного mTLS-сертификата или JWT-токена. Проверка прав доступа выполняется middleware-компонентом, который анализирует запрошенный URL до того, как передать его обработчику (handler).

    Суть уязвимости заключалась в расхождении между тем, как middleware проверяет путь, и тем, как маршрутизатор Go (net/http.ServeMux или сторонние роутеры) его интерпретирует. Атакующий формировал HTTP-запрос со специальным образом закодированными символами обхода директорий:

    GET /1.0/events/../../1.0/certificates HTTP/1.1

    В уязвимых версиях middleware, отвечающий за авторизацию, видел префикс /1.0/events (который в определенных конфигурациях мог быть открыт для чтения метрик или логов без строгой проверки) и пропускал запрос. Однако внутренний маршрутизатор перед вызовом конечного обработчика производил нормализацию пути, схлопывая .., и перенаправлял запрос на защищенный эндпоинт /1.0/certificates.

    Это позволяло неаутентифицированному пользователю взаимодействовать с критическими функциями API. В контексте демона, работающего от пользователя root, компрометация API означает возможность вызова функции создания контейнера с параметром security.privileged=true и монтированием корневой файловой системы хоста, что приводит к мгновенному захвату узла.

    Защита от подобных векторов на уровне эксплуатации требует не только своевременной установки патчей, но и использования Reverse Proxy (например, Nginx или HAProxy) перед демоном Incus/LXD. Reverse Proxy берет на себя задачу строгой нормализации URI и фильтрации аномальных запросов до того, как они достигнут внутреннего HTTP-сервера на Go.

    Server-Side Request Forgery (CVE-2026-35527)

    Одной из самых опасных уязвимостей для систем, управляющих инфраструктурой, является Server-Side Request Forgery (SSRF). В архитектуре LXD и Incus демон регулярно выступает в роли HTTP-клиента: он скачивает образы контейнеров из удаленных реестров (remote image servers), обращается к серверам ключей GPG и взаимодействует с кластерными узлами.

    Уязвимость CVE-2026-35527 эксплуатирует механизм добавления удаленных серверов образов и обработки HTTP-перенаправлений (redirects). Когда администратор или пользователь с ограниченными правами (через Restricted Projects) дает команду на запуск контейнера из удаленного источника, демон формирует HTTP-запрос к указанному URL.

    !SSRF атака через механизм загрузки образов

    Стандартный HTTP-клиент в языке Go (net/http.Client) по умолчанию автоматически следует за HTTP-редиректами (до 10 раз). Атакующий может поднять собственный веб-сервер в интернете и инициировать скачивание образа с него:

    incus launch my-malicious-server:ubuntu/22.04 test-container

    Когда демон Incus (работающий на хосте с правами root) обращается к серверу атакующего, тот возвращает HTTP-статус 302 Found и заголовок Location, указывающий на внутренний ресурс корпоративной сети, к которому у атакующего нет прямого доступа, но есть доступ у самого хоста.

    Наиболее критичный сценарий разворачивается в облачных средах. Если хост расположен в AWS, атакующий может вернуть редирект на IP-адрес службы метаданных инстанса (IMDS): http://169.254.169.254/latest/meta-data/iam/security-credentials/.

    В результате демон Incus послушно выполняет GET-запрос к службе метаданных AWS. Если в логике обработки ошибок или логирования демона предусмотрен возврат тела ответа при неудачной загрузке образа (например, «Не удалось разобрать манифест образа: [содержимое ответа]»), атакующий получает временные учетные данные IAM-роли хоста прямо в выводе консоли.

    Векторы развития SSRF

    Помимо кражи облачных метаданных, SSRF через демона контейнеризации позволяет:

  • Сканирование внутренних портов (Port Scanning): Возвращая редиректы на разные порты внутреннего IP-адреса (например, http://192.168.1.10:2379 для etcd), атакующий может по времени ответа (timeout) или тексту ошибки определить, открыт ли порт.
  • Атака на внутренние REST API: Многие внутренние сервисы (например, Docker-сокеты, доступные по TCP, или неаутентифицированные базы данных Redis) принимают команды через HTTP GET-запросы или игнорируют некорректные HTTP-заголовки, реагируя на параметры в URL.
  • Обход сетевой сегментации: Демон находится в управляющем VLAN. Заставляя его делать запросы, атакующий преодолевает межсетевой экран, который блокирует прямой доступ из интернета в управляющую сеть.
  • Патч для CVE-2026-35527 потребовал переопределения стандартного поведения http.Client в исходном коде демона. Была внедрена функция CheckRedirect, которая анализирует целевой IP-адрес перед переходом по ссылке. Если оригинальный запрос был направлен на публичный IP-адрес, а редирект ведет на адреса из диапазонов RFC 1918 (локальные сети), RFC 3927 (link-local, включая 169.254.x.x) или loopback (127.0.0.0/8), демон принудительно обрывает соединение.

    Эшелонированная защита сетевого взаимодействия

    Архитектурные различия и проанализированные векторы атак диктуют необходимость применения строгих регламентов hardening (укрепления) при публикации API. Защита не должна опираться исключительно на отсутствие известных уязвимостей в коде демона.

    Ограничение сетевых интерфейсов и TLS

    Первое правило — отказ от прослушивания всех интерфейсов (0.0.0.0). Параметр core.https_address должен содержать конкретный IP-адрес выделенного управляющего интерфейса (Management VLAN), недоступного из сетей, где работают сами контейнеры (Data VLAN).

    Для защиты от атак типа Man-in-the-Middle (MitM) и обеспечения криптографической стойкости, необходимо настроить параметр core.tls_ciphers. По умолчанию Go поддерживает широкий набор шифров для обеспечения обратной совместимости. В высокозащищенных средах следует оставить только современные наборы шифров TLS 1.3, исключив устаревшие алгоритмы на базе RSA и CBC:

    incus config set core.tls_ciphers "TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256"

    Для жесткой привязки mTLS-сертификатов используется параметр core.trust_ca_certificates. Если он установлен, демон будет принимать только те клиентские сертификаты, которые подписаны внутренним корпоративным удостоверяющим центром (CA), игнорируя любые самоподписанные сертификаты, даже если злоумышленник попытается их внедрить через уязвимость.

    Эгресс-фильтрация (Egress Filtering)

    Защита от SSRF на уровне приложения (патчи в коде) подвержена регрессиям. Надежный архитектурный барьер создается на уровне ядра сети хоста. Демон Incus работает от пользователя root, но это не означает, что ему разрешен любой исходящий трафик.

    Используя iptables или nftables, можно ограничить исходящие соединения (egress), инициируемые самим демоном. Поскольку демон должен скачивать образы только с доверенных серверов (например, images.linuxcontainers.org), межсетевой экран настраивается на разрешение соединений только к определенным IP-адресам или FQDN (если используется брандмауэр с поддержкой разрешения имен).

    Любые попытки демона инициировать соединение с внутренними подсетями (10.0.0.0/8) или адресами облачных метаданных должны блокироваться на уровне хоста. Это полностью нейтрализует SSRF-уязвимости, так как даже если атакующий заставит демона сформировать вредоносный HTTP-запрос, ядро Linux отбросит сетевой пакет.

    Разделение плоскостей управления и данных

    В крупных кластерах Incus следует строго разделять Control Plane (плоскость управления) и Data Plane (плоскость данных). Узлы, на которых выполняются контейнеры, не должны иметь прямого доступа к API друг друга, кроме портов, необходимых для внутренней кластерной синхронизации (обычно это тот же порт 8443, но защищенный отдельным набором кластерных сертификатов).

    Миграция с LXD на Incus в контексте сетевой безопасности обоснована именно архитектурным подходом к аутентификации. Отказ от паролей доверия в пользу OIDC позволяет интегрировать контейнерную инфраструктуру в общую систему мониторинга аномалий (SIEM). Если OIDC-провайдер фиксирует попытку входа администратора с необычного IP-адреса, он может запросить многофакторную аутентификацию (MFA) до того, как токен будет выдан и API Incus станет доступен. В классической модели LXD с mTLS и паролями доверия реализация MFA на уровне самого демона невозможна.

    Сетевая изоляция демона — это базовая линия обороны. Однако даже при защищенном API злоумышленник, легитимно получивший права на управление непривилегированными контейнерами, может попытаться атаковать саму инфраструктуру изнутри, исчерпывая ресурсы хоста. Механизмы противодействия таким локальным атакам, включая лимитирование через подсистему cgroups, требуют отдельного детального анализа.

    6. Противодействие атакам на ресурсы: использование cgroups для предотвращения DoS-состояний

    Противодействие атакам на ресурсы: использование cgroups для предотвращения DoS-состояний

    Вы безупречно настроили изоляцию: ядро использует User Namespaces, процесс внутри контейнера работает от непривилегированного пользователя, а доступ к сокету управления строго ограничен. С точки зрения классического разграничения прав система выглядит неприступной. Однако злоумышленник, получивший возможность выполнения кода внутри контейнера (например, через уязвимость в веб-приложении), запускает скрипт из десяти строк на Python. Через минуту хост-сервер перестает отвечать на сетевые запросы, базы данных в соседних контейнерах падают с ошибками, а SSH-соединение обрывается.

    Причина кроется в фундаментальном различии между видимостью и потреблением. Пространства имен (Namespaces) создают иллюзию изолированной операционной системы, скрывая от контейнера чужие процессы и файлы. Но они никак не ограничивают физическое потребление ресурсов процессора, оперативной памяти или дисковой подсистемы. Для предотвращения состояний отказа в обслуживании (Denial of Service, DoS), вызванных ресурсным голоданием, применяется другой механизм ядра Linux — контрольные группы (cgroups).

    Анатомия ресурсного голодания и архитектура cgroups v2

    Когда контейнер запускается без явных ограничений, он наследует доступ ко всем аппаратным ресурсам хоста. В мультитенантной среде это порождает проблему «шумного соседа» (noisy neighbor), которая в контексте информационной безопасности классифицируется как вектор локальной DoS-атаки. Злоумышленник может намеренно исчерпать вычислительные мощности, чтобы нарушить доступность критически важных сервисов, работающих на том же физическом сервере.

    Инструментом сдерживания выступает подсистема cgroups. Современные дистрибутивы Linux и демоны Incus/LXD по умолчанию используют унифицированную иерархию cgroups v2.

    !Тэджон Хо, ключевой разработчик подсистемы cgroups

    В отличие от первой версии, где каждый ресурс (память, процессор, блочный ввод-вывод) имел собственное независимое дерево процессов, cgroups v2 использует единое иерархическое дерево.

    !Архитектура унифицированной иерархии cgroups v2

    Процесс всегда принадлежит только одному узлу в этом дереве, а контроллеры ресурсов (cpu, memory, io, pids) активируются для конкретной ветви сверху вниз. Когда Incus запускает контейнер, он создает для него отдельную директорию (cgroup) внутри иерархии (обычно по пути /sys/fs/cgroup/lxc.payload/<имя_контейнера>) и записывает PID init-процесса контейнера в файл cgroup.procs. Все дочерние процессы автоматически наследуют эту принадлежность.

    Рассмотрим конкретные векторы атак на ресурсы и методы их нейтрализации через конфигурацию Incus.

    Атака на планировщик: CPU-голодание и криптомайнеры

    Один из самых частых сценариев после компрометации контейнера — скрытая установка программного обеспечения для майнинга криптовалют (например, XMRig) или запуск бесконечных циклов, потребляющих процессорное время. Если планировщик ядра не имеет инструкций по ограничению, вредоносный процесс с высоким приоритетом или множеством потоков может захватить 100% времени всех доступных ядер процессора хоста.

    В Incus управление процессорным временем реализуется через ключ limits.cpu. Существует два принципиально разных подхода к ограничению:

  • Привязка к ядрам (CPU Pinning). Конфигурация вида limits.cpu=2 или limits.cpu=0,2 жестко привязывает контейнер к конкретным логическим ядрам хоста (используя механизм cpuset в cgroups).
  • Этот метод эффективен для изоляции производительности баз данных, но с точки зрения безопасности мультитенантных сред он неоптимален. Жесткая привязка приводит к фрагментации ресурсов: если контейнер простаивает, выделенные ему ядра не могут быть использованы другими.

  • Квотирование времени (CFS Quota). Более гибкий и безопасный подход — использование Completely Fair Scheduler (CFS) через параметр limits.cpu.allowance. Конфигурация limits.cpu.allowance=50ms/100ms означает, что контейнеру разрешено использовать 50 миллисекунд процессорного времени каждые 100 миллисекунд реального времени (эквивалент 50% одного ядра).
  • Математика CFS выражается формулой:

    где — утилизация (в долях ядра), — квота (Quota, разрешенное время), а — период (Period, окно времени).

    На уровне ядра Incus транслирует эту настройку в файл cpu.max соответствующей cgroup. Если вредоносный процесс попытается превысить квоту, планировщик ядра принудительно приостановит его выполнение (throttle) до начала следующего периода. Это гарантирует, что даже при максимальной нагрузке внутри скомпрометированного контейнера, хост и соседние среды получат свое процессорное время.

    Исчерпание памяти и битва с OOM Killer

    Атака на оперативную память значительно опаснее атаки на процессор. Если процессорное голодание приводит к замедлению работы (деградации), то исчерпание памяти вызывает немедленный отказ в обслуживании. Злоумышленник может использовать скрипт, который бесконечно выделяет память без ее освобождения (memory leak attack).

    Когда физическая память хоста заканчивается, ядро Linux вызывает механизм Out-Of-Memory (OOM) Killer. Его задача — найти и убить процесс, потребляющий больше всего памяти, чтобы спасти систему от полного зависания (kernel panic). Проблема заключается в том, что без cgroups ядро оценивает все процессы глобально. Вредоносный скрипт может спровоцировать ситуацию, при которой OOM Killer решит завершить критический хостовый процесс, например, сам демон incusd или sshd, основываясь на эвристике oom_score.

    Установка параметра limits.memory в Incus (например, limits.memory=2GB) меняет правила игры. Она активирует контроллер memory в cgroups.

    !Динамика работы OOM Killer на уровне cgroup

    При достижении лимита события развиваются по другому сценарию:

  • Контейнер пытается выделить страницу памяти сверх установленного лимита memory.max.
  • Ядро блокирует эту операцию.
  • Просыпается локальный cgroup OOM Killer.
  • Он анализирует процессы только внутри данной cgroup (то есть внутри конкретного контейнера) и убивает виновника.
  • Хост и другие контейнеры даже не замечают скачка потребления.
  • Важный нюанс безопасности: память неразрывно связана с файлом подкачки (swap). Если вы ограничили RAM, но забыли про swap, злоумышленник продолжит выделять память. Ядро начнет агрессивно сбрасывать страницы памяти контейнера на диск (swap thrashing). Это приведет к колоссальной нагрузке на дисковую подсистему (I/O storm), и весь хост снова окажется в состоянии DoS. Для предотвращения этого необходимо контролировать и подкачку. В Incus параметр limits.memory.swap=false полностью запрещает контейнеру использовать swap хоста, заставляя OOM Killer срабатывать немедленно при исчерпании RAM.

    Форк-бомбы и исчерпание таблицы процессов

    Существует класс атак, который не требует ни больших объемов памяти, ни длительных вычислений. Классическая форк-бомба (например, знаменитая bash-конструкция :(){ :|:& };:) работает за счет экспоненциального создания новых процессов.

    В Linux существует глобальный лимит на количество одновременно существующих идентификаторов процессов (PID), определяемый параметром ядра kernel.pid_max (по умолчанию часто равен 32768 или 4194304 на современных 64-битных системах). Если форк-бомба внутри контейнера исчерпает этот пул, ядро не сможет создать ни одного нового процесса глобально. Администратор хоста не сможет даже выполнить команду ls или kill, получив ошибку Resource temporarily unavailable.

    Для защиты от этого вектора используется контроллер pids в cgroups. В Incus он управляется параметром limits.processes. Установка limits.processes=500 записывает значение 500 в файл pids.max контрольной группы контейнера. Как только количество процессов (и потоков, так как для ядра это концептуально схожие сущности) внутри контейнера достигает 500, системный вызов clone() или fork() начнет возвращать ошибку EAGAIN (повторите попытку позже). Форк-бомба «захлебнется» в собственных ошибках, не повлияв на глобальную таблицу процессов хоста.

    Для большинства микросервисных контейнеров лимит в 100–300 процессов является более чем достаточным. Системные контейнеры с полноценным systemd могут требовать 500–1000 процессов в зависимости от количества запущенных служб.

    Дисковый шторм: исчерпание пропускной способности I/O

    Атака на подсистему ввода-вывода (I/O) — одна из самых сложных для детектирования и ограничения. Злоумышленник может запустить утилиту dd или генератор случайных данных, который непрерывно пишет информацию на диск, забивая очередь команд накопителя (NVMe/SSD/HDD). В результате легитимные операции чтения/записи из других контейнеров (например, транзакции PostgreSQL) начинают выполняться с огромными задержками (latency), что приводит к тайм-аутам на уровне приложений.

    Управление I/O в cgroups v2 осуществляется через контроллер io. Incus предоставляет два механизма контроля:

  • Абсолютные лимиты (Throttling). Вы можете жестко ограничить количество операций в секунду (IOPS) или пропускную способность (байт в секунду). Это делается через конфигурацию конкретного дискового устройства контейнера:
  • incus config device set <container> root limits.read.iops=1000 incus config device set <container> root limits.write.bps=50MB На уровне ядра это транслируется в файл io.max. Если контейнер превышает скорость, его I/O запросы принудительно ставятся на паузу.

  • Относительные приоритеты (Weight). Если жесткие лимиты приводят к недоиспользованию ресурсов (когда диск простаивает, но контейнер упирается в лимит), используется параметр limits.disk.priority. Он задает вес (от 1 до 10) для распределения доступной пропускной способности при возникновении конкуренции. Транслируется в файл io.weight.
  • Сложность ограничения I/O заключается в механизме page cache (кэш страниц памяти). Когда процесс пишет данные в файл, ядро сначала сохраняет их в оперативную память (page cache), а физическая запись на диск происходит позже, асинхронно (writeback). До появления cgroups v2 ядро теряло информацию о том, какой именно контейнер сгенерировал эти «грязные» страницы (dirty pages), и I/O лимиты часто не срабатывали. В cgroups v2 контроллеры memory и io тесно интегрированы: ядро точно знает владельца кэшированных данных и корректно применяет I/O лимиты даже при асинхронной записи.

    Практическое применение: профили как эшелон защиты

    В архитектуре безопасности инфраструктуры ручное назначение лимитов каждому контейнеру является антипаттерном. Это неизбежно приводит к человеческим ошибкам и появлению незащищенных сред. В Incus и LXD эшелонированная защита строится на базе профилей (Profiles).

    Профиль — это именованный набор конфигураций, который применяется к контейнерам. Рекомендуется создать базовый профиль безопасности (например, restricted-resources), который будет применяться ко всем новым инстансам по умолчанию.

    Пример конфигурации профиля, защищающего от базовых DoS-атак:

    Применение такого профиля гарантирует, что даже в случае полной компрометации контейнера и получения внутри него прав root (с учетом того, что это root только внутри User Namespace), злоумышленник окажется в жестких физических рамках. Он не сможет потребить больше 2 ГБ оперативной памяти, не сможет запустить форк-бомбу, способную обрушить хост, и не сможет монополизировать дисковую подсистему.

    Контрольные группы (cgroups) формируют физические стены контейнера, в то время как пространства имен (namespaces) служат лишь односторонними зеркалами, скрывающими внешнюю среду. Только совместное использование обоих механизмов, подкрепленное строгими политиками квотирования, превращает контейнер в действительно изолированную и безопасную единицу вычислений, устойчивую к атакам на отказ в обслуживании.

    7. Эшелонированная защита: практический Hardening и глубокая изоляция через security.idmap.isolated

    Эшелонированная защита: практический Hardening и глубокая изоляция через security.idmap.isolated

    Представьте ситуацию: в вашей инфраструктуре работают два непривилегированных контейнера — web-frontend и data-processor. Оба изолированы от хоста, их внутренний пользователь root не имеет реальных прав в основной системе. Злоумышленник находит уязвимость Remote Code Execution (RCE) в web-frontend и получает права внутреннего суперпользователя. Поскольку контейнер непривилегированный, ядро Linux надежно защищает хост. Однако злоумышленник начинает сканировать доступные файловые системы и обнаруживает общую директорию резервных копий, примонтированную в оба контейнера. Он создает в ней вредоносный бинарный файл с установленным SUID-битом. Когда процесс из data-processor обращается к этому файлу, злоумышленник получает контроль над вторым контейнером.

    Этот вектор атаки — горизонтальная эскалация привилегий — возможен, потому что по умолчанию системы контейнеризации используют единый пул идентификаторов для всех непривилегированных сред. Защита от подобных угроз требует перехода от базовой изоляции к архитектуре эшелонированной защиты (Defense in Depth).

    Архитектура общего пула: почему базовой изоляции недостаточно

    Когда демон Incus или LXD инициализируется на хосте, он читает файлы /etc/subuid и /etc/subgid для определения доступного диапазона идентификаторов. По умолчанию операционная система выделяет демону непрерывный блок из 65536 адресов, обычно начиная с .

    При создании стандартного непривилегированного контейнера демон применяет линейное смещение. Внутренний (root) транслируется в хостовый . Внутренний транслируется в . Формула трансляции выглядит так:

    Проблема мультиарендности (multi-tenancy) возникает из-за того, что значение по умолчанию одинаково для всех контейнеров. Если у вас запущено пятьдесят контейнеров, внутренний root каждого из них на уровне ядра хоста является одним и тем же пользователем с .

    !Сравнение общего пула UID и изолированных диапазонов

    Если злоумышленник сбегает из пространства имен (Namespace escape) одного контейнера, но остается ограниченным правами хостового , он автоматически получает Discretionary Access Control (DAC) права на чтение и модификацию файлов всех остальных контейнеров на этом хосте. В облачных средах, где в соседних контейнерах могут выполняться рабочие нагрузки разных клиентов, это критическая архитектурная уязвимость.

    Глубокая изоляция: механика security.idmap.isolated

    Для устранения риска горизонтального перемещения применяется параметр security.idmap.isolated. При его активации демон меняет логику выделения идентификаторов: вместо использования единого значения для всех, он динамически нарезает глобальный пул на уникальные непересекающиеся поддиапазоны для каждого контейнера.

    Если активировать этот параметр для трех контейнеров, распределение будет выглядеть так:

  • Контейнер A: . Диапазон: .
  • Контейнер B: . Диапазон: .
  • Контейнер C: . Диапазон: .
  • Теперь внутренний root Контейнера A () и внутренний root Контейнера B () — это абсолютно разные пользователи с точки зрения ядра Linux. Даже если злоумышленник преодолеет границы User Namespace Контейнера A, базовая модель DAC ядра Linux заблокирует ему доступ к файловой системе Контейнера B, так как их владельцы не совпадают.

    Нюансы эксплуатации и истощение пула

    Включение security.idmap.isolated требует тщательного планирования емкости. Стандартный пул в /etc/subuid содержит всего 65536 адресов. Этого хватит ровно на один изолированный контейнер.

    Для поддержки плотной упаковки (например, 100 контейнеров на узел), администратор обязан вручную расширить выделенный демону диапазон. Формула расчета требуемого размера пула:

    где — максимальное количество планируемых контейнеров. Для 100 контейнеров потребуется пул размером адресов. В /etc/subuid необходимо внести запись вида: incus:1000000:6553600

    Проблема общих томов и VFS idmapped mounts

    Глубокая изоляция ломает классический механизм совместного использования данных. Если вы попытаетесь примонтировать одну и ту же директорию хоста в Контейнер A и Контейнер B, возникнет конфликт прав доступа. Файлы, созданные Контейнером A, получат на хосте владельца . Когда Контейнер B попытается их прочитать, его столкнется с отказом в доступе (Permission Denied).

    Решение этой проблемы заключается в использовании параметра shift=true при монтировании дисковых устройств. Этот флаг инструктирует Incus использовать механизм ядра VFS idmapped mounts. Ядро будет динамически «на лету» переводить UID/GID файлов при обращении к ним через конкретную точку монтирования, не меняя реальных метаданных на диске. Контейнер A будет видеть файлы как принадлежащие своему пулу, а Контейнер B — своему, при этом физически на диске они могут принадлежать хостовому пользователю.

    Эшелонированная защита: за пределами Namespaces

    User Namespaces и изолированные карты UID — это лишь первый эшелон защиты, основанный на идентификации. Однако ядро Linux содержит миллионы строк кода, и уязвимости в обработчиках системных вызовов (например, в подсистемах io_uring или eBPF) появляются регулярно. Если скомпрометированный процесс внутри контейнера сможет вызвать уязвимую функцию ядра, изоляция UID не поможет — эксплойт выполнится в контексте самого ядра (Ring 0).

    Концепция Hardening (ужесточения) в LXC/Incus строится на презумпции, что первый слой изоляции может быть пробит. Поэтому вокруг процесса выстраиваются дополнительные независимые барьеры: ограничение Capabilities, Mandatory Access Control (MAC) и фильтрация системных вызовов (Seccomp).

    Слой 1: Linux Capabilities

    Традиционно в Linux пользователь root () обходит все проверки DAC. Чтобы разделить монолитную власть суперпользователя, ядро использует Capabilities — набор из более чем 40 независимых привилегий.

    Непривилегированные контейнеры по умолчанию лишены наиболее опасных Capabilities (например, CAP_SYS_MODULE для загрузки модулей ядра или CAP_SYS_TIME для изменения системных часов). Однако для максимального Hardening профиль должен быть минимизирован до абсолютного предела, необходимого приложению.

    Особое внимание стоит уделить CAP_NET_RAW. Эта привилегия позволяет процессу создавать сырые (raw) сокеты. Она необходима для работы утилиты ping (ICMP-пакеты), но злоумышленник может использовать её для ARP-спуфинга, внедрения пакетов (packet injection) или создания скрытых каналов связи (covert channels) внутри виртуального коммутатора incusbr0.

    Если контейнер выполняет роль веб-сервера или базы данных, сырые сокеты ему не нужны. Принудительный сброс этой привилегии через конфигурацию демона: incus config set <container> raw.lxc "lxc.cap.drop = net_raw" гарантирует, что даже внутренний root не сможет сформировать произвольный сетевой пакет на уровне L2/L3.

    Слой 2: Mandatory Access Control (AppArmor)

    В то время как DAC проверяет права на основе владельца файла, AppArmor (MAC) контролирует доступ на основе пути к файлу и профиля, привязанного к процессу. AppArmor работает на уровне Linux Security Modules (LSM) и перехватывает попытки доступа уже после того, как они прошли проверки DAC.

    Incus автоматически генерирует и применяет профиль AppArmor для каждого запускаемого контейнера. Этот профиль решает критическую задачу: он запрещает операции, которые формально разрешены внутри User Namespace, но глобально опасны для хоста.

    Яркий пример — монтирование файловых систем. Внутри своего пространства имен внутренний root имеет право выполнять системный вызов mount. Однако монтирование некоторых псевдофайловых систем (например, /sys/kernel/debug) может раскрыть структуру памяти ядра. Профиль AppArmor от Incus жестко блокирует попытки записи в /sys и /proc/sys, а также запрещает монтирование блочных устройств, сводя на нет целый класс эксплойтов, направленных на модификацию параметров ядра изнутри контейнера.

    Слой 3: Seccomp (Secure Computing Mode)

    Если AppArmor контролирует объекты (файлы, пути, сокеты), то Seccomp контролирует сами действия — системные вызовы к ядру.

    Перед тем как процесс init запускается внутри контейнера, библиотека liblxc компилирует BPF-программу (Berkeley Packet Filter) и загружает её в ядро через механизм Seccomp. Эта программа становится неотделимой от процесса и всех его потомков. При каждом обращении процесса к ядру, BPF-фильтр проверяет номер системного вызова и его аргументы.

    !Слои изоляции: от системного вызова до ядра

    По умолчанию профиль Seccomp в Incus блокирует более 40 системных вызовов. Например, вызов kexec_load (позволяющий загрузить новое ядро поверх текущего) или open_by_handle_at (часто используемый для обхода директорий) будут немедленно прерваны ядром с возвратом ошибки EPERM (Operation not permitted) или принудительным завершением процесса (SIGKILL), даже если процесс обладает нужными Capabilities и прошел проверки AppArmor.

    Важно отличать блокировку Seccomp от перехвата системных вызовов (syscall interception). В рамках базового Hardening уязвимые вызовы просто отбрасываются. Механизм, при котором Seccomp перенаправляет вызов демону Incus для безопасной эмуляции (например, эмуляция mknod для создания устройств), является более сложной архитектурной надстройкой, которая требует отдельного глубокого анализа.

    Проектирование профиля Hardening

    Современный подход к безопасности требует формализации всех описанных механизмов в виде переиспользуемого профиля. В Incus профили позволяют задать базовый уровень доверия (baseline) для всех новых проектов.

    Пример создания профиля strict-isolation, объединяющего лучшие практики эшелонированной защиты:

    Развертывание контейнеров с использованием такого профиля создает среду, в которой компрометация приложения приводит злоумышленника в «комнату без дверей». У него нет прав на файлы соседей (благодаря idmap.isolated), он не может сканировать сеть (из-за отсутствия CAP_NET_RAW), он не может изменить конфигурацию ядра (блокировка AppArmor) и не может использовать экзотические системные вызовы для эксплуатации 0-day уязвимостей (фильтрация Seccomp).

    Именно комбинация независимых, перекрывающих друг друга подсистем ядра Linux превращает легковесный системный контейнер в надежный изолятор, пригодный для запуска недоверенного кода в production-средах.

    8. Продвинутые техники контроля: перехват системных вызовов и тонкая настройка профилей безопасности

    Представьте ситуацию: внутри непривилегированного контейнера работает устаревшее приложение или сложный оркестратор (например, вложенный Docker), которому жизненно необходимо выполнить системный вызов mount для монтирования файловой системы overlay или mknod для создания специального файла устройства. В классической парадигме изоляции у нас лишь два пути. Первый — заблокировать вызов, что приведет к аварийному завершению приложения с ошибкой Operation not permitted. Второй — выдать контейнеру привилегию CAP_SYS_ADMIN или CAP_MKNOD, что мгновенно разрушит периметр безопасности, превратив контейнер в эквивалент root-доступа на хосте. Однако современные ядра Linux и демоны-гипервизоры предлагают третий, неочевидный путь: мы можем «обмануть» приложение, перехватив его запрос и безопасно эмулировав результат.

    Проблема TOCTOU и ограничения классического Seccomp

    Чтобы понять необходимость продвинутого перехвата, нужно разобраться в фундаментальных ограничениях классического Seccomp-BPF. Как известно, Seccomp позволяет фильтровать системные вызовы на основе их номеров и аргументов. Однако архитектура ядра накладывает жесткое ограничение: Seccomp может анализировать только значения, лежащие непосредственно в регистрах процессора в момент системного вызова. Он категорически не умеет разыменовывать указатели (dereference pointers).

    Если приложение вызывает mount("source", "target", "ext4", 0, NULL), в регистрах передаются не сами строки, а адреса памяти, где эти строки лежат. Если бы Seccomp попытался прочитать строку по адресу, проверить её и разрешить вызов, возникла бы классическая уязвимость состояния гонки — TOCTOU (Time-of-Check to Time-of-Use).

    Механика атаки TOCTOU в этом контексте выглядит так:

  • Поток А вызывает mount, передавая указатель на строку /safe/path.
  • Seccomp читает память по указателю, видит /safe/path и одобряет вызов.
  • В микросекундный зазор между проверкой и фактическим выполнением вызова ядром, параллельный вредоносный Поток Б внутри контейнера перезаписывает память по этому же указателю строкой /dev/sda1 (корневой диск хоста).
  • Ядро выполняет mount, монтируя корень хоста в контейнер.
  • Именно из-за невозможности безопасно работать с указателями классический Seccomp является бинарным инструментом: он может либо полностью запретить mount, либо полностью разрешить его. Для тонкой настройки безопасности этого недостаточно.

    Архитектура Seccomp Notifier

    Для решения проблемы контекстно-зависимой фильтрации в ядро Linux был добавлен механизм Seccomp Notifier (действие SECCOMP_RET_USER_NOTIF). Он кардинально меняет логику: вместо того чтобы ядро принимало решение самостоятельно, оно ставит системный вызов на паузу и делегирует принятие решения процессу в пространстве пользователя (userspace) — в нашем случае, демону Incus.

    !Пошаговый процесс перехвата системного вызова через Seccomp Notifier

    Этот процесс разворачивается в несколько этапов, обеспечивая криптографически строгую изоляцию:

  • Процесс в контейнере выполняет запрещенный системный вызов.
  • Ядро, руководствуясь правилом Seccomp, приостанавливает поток выполнения контейнера и отправляет уведомление через специальный файловый дескриптор (notifier fd) демону Incus.
  • Incus получает PID процесса и номер системного вызова.
  • Чтобы избежать уязвимости TOCTOU, Incus использует системный вызов process_vm_readv. Он безопасно копирует аргументы (например, пути к файлам) из памяти приостановленного процесса в свою собственную, защищенную память. Контейнер не может изменить данные в памяти демона.
  • Incus анализирует скопированные аргументы. Если действие безопасно (например, создание loop-устройства, разрешенного профилем), Incus выполняет необходимые операции на хосте от имени контейнера, но со своими привилегиями.
  • Incus отправляет ядру ответ: «операция успешна, верни процессу код возврата 0».
  • Процесс в контейнере «просыпается», получая нулевой код возврата, будучи в полной уверенности, что он сам выполнил системный вызов.
  • Этот подход реализует концепцию безопасности через эмуляцию. Приложение получает ожидаемый результат, но опасный системный вызов в реальности внутри контейнера не выполнялся.

    Практика применения: security.syscalls.intercept.*

    В Incus управление этим механизмом вынесено в пространство ключей конфигурации security.syscalls.intercept.*. Рассмотрим наиболее критичные векторы, где перехват предотвращает компрометацию хоста.

    Эмуляция mknod

    Системный вызов mknod используется для создания блочных и символьных файлов устройств. В непривилегированных контейнерах создание устройств запрещено на уровне ядра (в User Namespace), так как злоумышленник мог бы создать устройство /dev/sda с мажорным и минорным номерами диска хоста и прочитать сырые данные в обход файловых прав.

    Однако некоторым приложениям требуется создавать устройства, например, /dev/null или /dev/loopX при сборке образов. Включение security.syscalls.intercept.mknod=true решает эту дилемму. Когда контейнер вызывает mknod, Incus перехватывает вызов, проверяет, разрешен ли запрошенный тип устройства (major/minor) политикой cgroup устройства (device controller) для данного контейнера. Если разрешен, Incus не создает устройство с нуля (что невозможно в непривилегированном пространстве), а создает пустой файл и делает bind-mount реального устройства с хоста поверх этого файла. Приложение видит устройство и может с ним работать, но безопасность хоста не нарушена.

    Управление расширенными атрибутами: setxattr

    При запуске вложенных контейнеров (например, Docker внутри Incus) часто возникает потребность в установке расширенных атрибутов файлов, в частности security.capability. Это необходимо для корректной работы утилит вроде ping, которым требуется CAP_NET_RAW на бинарном файле.

    По умолчанию хостовая файловая система может отклонить запрос на установку security.capability от непривилегированного пользователя (каковым является root контейнера после трансляции UID). Параметр security.syscalls.intercept.setxattr=true заставляет Incus перехватить этот вызов. Демон на лету транслирует запрашиваемую capability в формат VFS idmapped, корректно записывая атрибут на диск так, чтобы он был действителен только внутри User Namespace данного контейнера. Это предотвращает горизонтальную эскалацию привилегий: capability, выданная файлу внутри контейнера А, не будет иметь силы, если этот файл попытаются запустить в контейнере Б или на хосте.

    Изоляция eBPF и перехват bpf()

    Технология eBPF (Extended Berkeley Packet Filter) стала стандартом де-факто для сетевой фильтрации, профилирования и observability (например, Cilium или Tetragon). Выполнение программ eBPF требует загрузки байт-кода непосредственно в ядро, что традиционно требует CAP_BPF или CAP_SYS_ADMIN в начальном (хостовом) пространстве имен. Выдача таких прав контейнеру недопустима.

    Incus предоставляет экспериментальный, но мощный механизм security.syscalls.intercept.bpf=true. Перехват системного вызова bpf() — одна из самых сложных задач демона.

    !Архитектура AppArmor Stacking и eBPF-карт внутри cgroup контейнера

    Когда процесс в контейнере пытается загрузить eBPF-программу (например, типа BPF_PROG_TYPE_CGROUP_SKB для фильтрации трафика), Incus перехватывает системный вызов, копирует байт-код программы и её метаданные в свою память. Далее демон проводит верификацию:

  • Проверяет тип программы (разрешены только безопасные типы, не имеющие доступа к структурам ядра хоста).
  • Убеждается, что программа пытается прикрепиться (attach) строго к иерархии cgroups v2, принадлежащей этому конкретному контейнеру.
  • Изолирует eBPF-карты (maps), используемые для хранения состояний, гарантируя, что контейнер не сможет прочитать карты хоста или других сред.
  • После успешной валидации Incus загружает программу в ядро от своего имени, прикрепляет её к cgroup контейнера и возвращает процессу файловый дескриптор программы. Таким образом, современные cloud-native инструменты могут работать внутри непривилегированного системного контейнера без ущерба для безопасности гипервизора.

    Тонкая настройка профилей: AppArmor Stacking и raw.lxc

    Механизмы перехвата покрывают большинство потребностей, но иногда требуется более гранулярный контроль, выходящий за рамки стандартных ключей security.*. В таких случаях применяется прямое взаимодействие с низкоуровневой библиотекой через параметр raw.lxc.

    Одним из мощнейших инструментов эшелонированной защиты является AppArmor Stacking (стекирование профилей). В стандартной конфигурации профиль AppArmor применяется к контейнеру целиком. Но что, если внутри контейнера запущен Systemd, который должен применять собственные, более строгие профили к своим службам (например, изолировать Nginx от базы данных внутри одного контейнера)?

    Ранее это было невозможно, так как загрузка политик AppArmor в ядро требовала привилегий на хосте. Современные ядра поддерживают пространства имен AppArmor. Используя raw.lxc, мы можем настроить контейнер так, чтобы он имел собственное пространство политик:

    В сочетании с конфигурацией Incus это позволяет процессу init внутри контейнера монтировать securityfs и загружать собственные профили AppArmor. Ядро применяет логику пересечения (intersection): если хостовый профиль контейнера запрещает доступ к /etc/shadow, а внутренний профиль разрешает, доступ будет запрещен. Если хостовый профиль разрешает доступ к /var/www, а внутренний профиль Nginx ограничивает его только /var/www/html, ядро применит наиболее строгое ограничение. Это позволяет администраторам контейнера реализовывать собственные политики безопасности (MAC), не нарушая глобальные запреты, установленные администратором хоста.

    Производительность и архитектурные компромиссы

    При проектировании защищенной архитектуры важно учитывать цену эмуляции. Перехват системных вызовов через Seccomp Notifier — ресурсоемкая операция. Обычный системный вызов требует двух переключений контекста (userspace -> kernel -> userspace). Перехват требует как минимум шести переключений контекста, плюс накладные расходы на process_vm_readv и межпроцессное взаимодействие через UNIX-сокеты.

    По этой причине Incus никогда не перехватывает высокочастотные вызовы, такие как read, write или recvfrom. Перехват применяется исключительно к редким, конфигурационным вызовам (mount, mknod, bpf), которые происходят на этапе инициализации приложения. Если архитектура приложения подразумевает создание тысяч loop-устройств в секунду, использование security.syscalls.intercept.mknod приведет к деградации производительности (CPU throttling) демона Incus и задержкам внутри контейнера. В таких узкоспециализированных случаях архитекторам ИБ приходится искать компромисс между производительностью и безопасностью, возможно, перенося специфическую нагрузку на уровень легковесных виртуальных машин (MicroVMs), где изоляция обеспечивается аппаратными расширениями процессора, а не программной фильтрацией системных вызовов.

    Тонкая настройка профилей безопасности и перехват системных вызовов демонстрируют зрелость современных систем контейнеризации. Переход от жестких запретов к интеллектуальной эмуляции позволяет запускать сложнейшие нагрузки (от вложенных оркестраторов до eBPF-агентов) в строго изолированных, непривилегированных средах. Безопасность больше не означает отказ в обслуживании; она означает прозрачный контроль над каждым взаимодействием процесса с ядром операционной системы.

    9. Сравнительный анализ безопасности и лицензирования: обоснование перехода на Incus в современных проектах

    В июле 2023 года изменение одного текстового файла в репозитории проекта привело к тектоническому сдвигу в экосистеме системных контейнеров. Решение Canonical изменить лицензию LXD с Apache 2.0 на AGPLv3 с обязательным подписанием CLA (Contributor License Agreement) казалось сугубо юридическим шагом. Однако для инженеров по информационной безопасности это стало триггером к пересмотру всей архитектуры. Лицензирование базового инфраструктурного компонента напрямую определяет модель угроз цепочки поставок (Supply Chain), прозрачность аудита и скорость доставки критических исправлений.

    Переход сообщества на Incus — это не просто смена названия бинарного файла. Это возвращение к предсказуемой модели безопасности, отказ от избыточной поверхности атаки, навязываемой пакетным менеджером Snap, и внедрение современных стандартов авторизации.

    Влияние CLA на аудит и исправление уязвимостей

    В мире open-source безопасности скорость реакции на 0-day уязвимости зависит от количества независимых исследователей, анализирующих код. Исторически проект LXC/LXD развивался под эгидой Linux Containers, где действовали стандартные правила: разработчик сохранял авторские права на свой патч, предоставляя его под лицензией Apache 2.0.

    Внедрение Canonical CLA изменило механику работы с уязвимостями. CLA требует от контрибьютора передать коммерческие права на свой код корпорации. Для многих независимых исследователей (Bug Bounty хантеров) и инженеров крупных корпораций подписание такого соглашения прямо запрещено внутренними политиками их работодателей (NDA и IP-политиками).

    Следствием этого стал отток независимых ИБ-специалистов из кодовой базы LXD. Если исследователь находит способ обхода изоляции в LXD, он с большей вероятностью отправит патч в форк Incus, где сохранен стандартный процесс Developer Certificate of Origin (DCO) без передачи коммерческих прав.

    !Стефан Грабер, лидер проекта Linux Containers

    Разделение кодовых баз привело к ситуации, когда исправления уязвимостей (например, из серии GHSA-gc7j-g665-rxr9, связанных с обработкой некорректных запросов к API, ведущих к отказу в обслуживании) интегрируются в Incus напрямую сообществом, в то время как LXD вынужден бэкпортировать эти исправления силами внутренней команды Canonical. В условиях, когда время реакции критично для защиты периметра, зависимость от проприетарного CLA-буфера становится неприемлемым риском для Enterprise-инфраструктуры.

    Прозрачность патчей и политика CVE

    Механизм публикации информации об уязвимостях — ключевой элемент эшелонированной защиты. Инженеру необходимо точно знать, какая версия демона уязвима, каков вектор атаки (CVSS) и как применить митигацию до установки патча.

    Проект Incus использует открытую инфраструктуру GitHub Security Advisories. Процесс прозрачен:

  • Исследователь сообщает об уязвимости через приватный канал.
  • Команда Incus формирует патч в приватном форке.
  • Запрашивается номер CVE.
  • В согласованный день публикуется релиз, исходный код патча и подробный Security Advisory (например, GHSA-8gw4-p4wq-4hcv).
  • Политика Canonical в отношении LXD стала менее прозрачной после интеграции продукта в экосистему Ubuntu Pro. В некоторых случаях исправления безопасности для пакетов, не входящих в основную (Main) ветку Ubuntu, могут задерживаться для пользователей без коммерческой подписки, либо публиковаться без детального технического разбора, ограничиваясь общими формулировками в changelog.

    !Сравнение скорости доставки патчей bezpieczeństwa

    Для проектирования защищенной архитектуры отсутствие прозрачности означает невозможность корректно обновить правила IDS/IPS (Intrusion Detection/Prevention Systems) или WAF для фильтрации эксплойтов на уровне сети, так как сигнатуры атак остаются неизвестными до момента реверс-инжиниринга бинарного обновления.

    Поверхность атаки: Snap-демон против нативных пакетов

    Одним из главных архитектурных преимуществ Incus с точки зрения безопасности является полный отказ от системы дистрибуции Snap. LXD жестко привязан к Snap, что вводит в систему дополнительный слой абстракции и управления привилегиями, существенно расширяющий поверхность атаки (Attack Surface).

    Риски инфраструктуры Snap

    Демон snapd работает с правами root и выполняет множество низкоуровневых операций:
  • Динамическое монтирование loop-устройств для каждого snap-пакета.
  • Генерация и компиляция профилей AppArmor «на лету».
  • Управление собственными mount namespaces.
  • Каждая из этих операций — потенциальный вектор для эскалации привилегий. Если в snapd обнаруживается уязвимость (например, обход ограничений через манипуляции с сокетами или жесткими ссылками, что исторически случалось неоднократно), компрометируется весь хост, даже если сам LXD настроен идеально.

    Incus поставляется в виде классических нативных пакетов (.deb, .rpm, .apk) или собирается из исходных кодов. Демон Incus запускается напрямую через systemd. Это позволяет ИБ-отделу применять стандартные инструменты аудита файловой системы (AIDE, Tripwire) и использовать статические, заранее верифицированные профили AppArmor, не зависящие от скрытой логики пакетного менеджера.

    Угрозы цепочки поставок (Supply Chain) и Air-Gapped среды

    Snap Store — это централизованная, проприетарная инфраструктура. Клиент snapd спроектирован с расчетом на постоянное подключение к серверам Canonical и автоматическое фоновое обновление пакетов.

    В высокозащищенных изолированных сетях (Air-gapped environments) автоматические обновления недопустимы. Любой код, попадающий в production, должен пройти через внутренний репозиторий (например, Nexus или Artifactory), сканирование SAST/DAST и ручное утверждение. Проксирование Snap Store для локальной сети — сложная задача, требующая развертывания Snap Store Proxy (коммерческого продукта). Более того, механизм автоматических обновлений Snap может привести к тому, что LXD обновится до мажорной версии в непредсказуемое время, вызвав отказ в обслуживании (DoS) из-за несовместимости конфигураций.

    Incus решает эту проблему возвратом к детерминированности. Пакеты загружаются в локальный APT-репозиторий. Инженер использует стандартную команду apt-mark hold incus, гарантируя, что версия бинарного файла не изменится без явного запуска пайплайна развертывания. Подписи пакетов проверяются стандартными ключами GPG, что полностью укладывается в классические регламенты Hardening.

    Эволюция авторизации: от Trust Passwords к OpenFGA

    Как мы рассматривали ранее, доступ к API демона эквивалентен правам root на хосте. Поэтому механизмы аутентификации и авторизации клиентов (CLI, Terraform-провайдеров, CI/CD систем) критически важны.

    В LXD долгое время использовался механизм Trust Passwords — статический пароль, при вводе которого клиентский TLS-сертификат добавлялся в доверенное хранилище демона. Это создавало риск брутфорса и утечки пароля, который часто хардкодили в скриптах инициализации.

    Incus полностью удалил механизм Trust Passwords. Добавление новых клиентов теперь осуществляется исключительно через токены доверия (Trust Tokens) — одноразовые криптографические строки, ограниченные по времени жизни, или через прямую интеграцию с OIDC (OpenID Connect).

    Гранулярный контроль с OpenFGA

    Настоящим прорывом в безопасности Incus стала интеграция с OpenFGA — системой Fine-Grained Authorization, основанной на архитектуре Google Zanzibar.

    Традиционные системы RBAC (Role-Based Access Control) в контейнерных гипервизорах часто ограничены плоской моделью: пользователь либо имеет доступ к проекту, либо нет. Если инфраструктура масштабируется до и тысяч контейнеров, плоский RBAC приводит к выдаче избыточных прав.

    !Архитектура авторизации OpenFGA в Incus

    OpenFGA позволяет строить графы отношений (Relationship Tuples). Вместо проверки «имеет ли токен роль admin», Incus отправляет в OpenFGA запрос: «Может ли субъект X выполнить действие Y над объектом Z?».

    Это позволяет реализовать сложнейшие политики безопасности:

  • Разработчик alice может перезагружать (action: restart) только контейнеры с тегом env:dev в проекте frontend.
  • CI/CD пайплайн может создавать снепшоты, но не может их удалять или изменять конфигурацию сети.
  • Аудитор имеет право на чтение логов (action: view_logs) всех контейнеров, но не имеет доступа к консоли (action: exec).
  • В LXD подобный уровень гранулярности доступен только через интеграцию с проприетарным сервисом Canonical RBAC, который требует развертывания инфраструктуры Juju и часто коммерческой лицензии. Incus предоставляет интеграцию с полностью открытым OpenFGA «из коробки», позволяя ИБ-отделу хранить политики авторизации в виде кода (Policy-as-Code) в Git-репозитории.

    Миграция: безопасность переноса состояния

    Обоснование перехода на Incus требует понимания того, как безопасно перенести существующую инфраструктуру LXD без потери данных и компрометации изоляции. Для этого сообщество разработало утилиту incus-migrate.

    Процесс миграции спроектирован с учетом минимизации рисков:

  • Аудит перед миграцией: Утилита анализирует конфигурацию LXD. Если обнаруживаются специфические фичи, привязанные к Canonical (например, интеграция с Ubuntu MAAS или Canonical RBAC), миграция блокируется до ручного разрешения конфликта.
  • Изоляция состояния: incus-migrate не модифицирует исходные базы данных LXD напрямую. Она подключается к сокету LXD, экспортирует схему SQLite, транслирует её в формат Incus (удаляя устаревшие поля) и записывает в новую директорию /var/lib/incus/.
  • Перенос хранилищ (Storage Pools): Утилита переназначает точки монтирования ZFS, Btrfs или LVM. Данные контейнеров не копируются физически (что заняло бы часы и вызвало бы длительный даунтайм), а переподключаются на уровне метаданных файловой системы.
  • Сетевые мосты: Существующие интерфейсы lxdbr0 аккуратно переименовываются в incusbr0 с сохранением таблиц маршрутизации и правил nftables, предотвращая разрыв установленных TCP-сессий на уровне ядра.
  • С точки зрения ИБ, критически важно, что после завершения миграции и удаления snap-пакета LXD, система очищается от остаточных профилей AppArmor и mount-пространств, связанных со snapd. Инфраструктура переходит в детерминированное состояние, где каждый компонент (бинарный файл, база данных, сетевой мост) контролируется стандартными системными утилитами.

    Стратегия LTS и долгосрочная стабильность

    Для корпоративных проектов выбор инструмента диктуется горизонтом планирования. В апреле 2024 года Стефан Грабер анонсировал выпуск Incus 6.0 LTS — версии с долгосрочной поддержкой (5 лет), которая вышла параллельно с LXD 5.21 LTS.

    Наличие синхронизированных LTS-релизов делает переход технически обоснованным именно сейчас. Incus 6.0 LTS зафиксировал стабильное API, очищенное от легаси-кода, и предоставил предсказуемый цикл выпуска патчей безопасности.

    Отказ от проприетарных зависимостей, возврат к нативным пакетам, интеграция с современными стандартами авторизации (OIDC/OpenFGA) и прозрачная модель обработки уязвимостей делают Incus объективно более защищенным и контролируемым решением для построения контейнерной инфраструктуры. Переход с LXD на Incus — это не просто смена вендора, это архитектурное решение по снижению рисков цепочки поставок и возврату полного контроля над средой исполнения в руки инженеров эксплуатации и безопасности.