Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи?

Написать
Войти
Дайджесты новостей
Иллюстрация к статье об изоляции User Namespaces в Kubernetes 1.36

User Namespaces в Kubernetes 1.36: почему изоляция UID/GID через idmap mounts стала стандартом безопасности

В релизе Kubernetes 1.36 поддержка User Namespaces получила статус General Availability: изоляция процессов через флаг hostUsers: false и механизм idmap mounts в Linux-ядре устраняет риски побега из контейнеров с правами root и решает проблему владения файлами в общих томах без оверхеда chown.

User Namespaces в Kubernetes 1.36: почему изоляция UID/GID через idmap mounts стала стандартом безопасности

В экосистеме контейнеризации долго сохранялся компромисс: процессы внутри контейнеров запускались с правами суперпользователя (root). Хотя пространства имён Linux (namespaces) скрывали процессы и сеть хоста, идентификатор пользователя (UID 0) оставался нулём и для хостового ядра. При побеге из контейнера атакующий мгновенно получал полные права root на всей ноде.

В релизе Kubernetes 1.36 механизм пользовательских пространств имён (User Namespaces, или userns) перешел в статус General Availability (GA). Изоляция включается директивой hostUsers: false в спецификации пода. За этой внешней простотой стоит фундаментальная доработка подсистемы виртуальной файловой системы (VFS) ядра Linux, сред исполнения контейнеров и оркестратора.

Пределы прежних мер защиты и новая модель изоляции

Исторически безопасность root-процессов пытались обеспечить внешними ограничениями:

  • Seccomp и AppArmor фильтруют системные вызовы к ядру. Но если в фильтрах остаётся брешь или в ядре обнаруживается уязвимость, root-процесс сохраняет высокий потенциал для побега на хост.
  • Запуск без root (runAsNonRoot) требует модификации образов и назначения непривилегированного UID (например, 65534). Многие утилиты жестко завязаны на root внутри контейнера. Кроме того, все непривилегированные поды на ноде часто делят один и тот же UID на хосте, сохраняя риски горизонтального перемещения атак.

User Namespaces меняют подход: вместо попыток ограничить всесильного root механизм изолирует права пределами контейнера. Процесс видит себя как root (UID 0) внутри пода, но для ядра хоста он работает под отдельным непривилегированным UID (например, 100 000).

Проблема общих томов и решение через idmap mounts

Внедрение userns в Kubernetes годами блокировалось вопросом владения файлами (file ownership). Обычная трансляция userns жестко записывает хостовый UID на диск. Если контейнер создает файл под внутренним UID 0, на диске создается inode с хостовым UID 100 000. В результате возникала дилемма:

  1. Назначить всем контейнерам единую карту сопоставления UID — это позволяет делить тома, но уничтожает изоляцию между подами.
  2. Выделять каждому поду уникальный диапазон — тогда общий том нельзя передать соседнему поду без рекурсивного chown. На больших накопителях операция chown занимает часы и блокирует ввод-вывод.

Прорыв произошел благодаря интеграции в ядро Linux механизма монтирования с сопоставлением идентификаторов (idmapped mounts), разработанного мейнтейнером VFS Кристианом Браунером.

Idmap mount выполняет трансляцию UID/GID в момент обращения к файловой системе в оперативной памяти (со сложностью O(1)), не меняя физические inode на диске:

  • При записи файл сохраняет исходного владельца на диске.
  • При чтении через idmapped mount ядро на лету преобразует хостовый UID во внутренний UID контейнера.
  • Поды с разными диапазонами userns и поды без userns могут делить один том без вызова chown.

Поддержка idmap mounts стабилизирована в ядре Linux 6.3+ для ext4, xfs, btrfs, tmpfs, overlayfs и f2fs. Важное исключение: сетевая файловая система NFS idmap mounts не поддерживает.

Архитектура распределения UID в kubelet и защита rootfs

В 32-битном пространстве идентификаторов Linux доступно 4,29 млрд UID. Чтобы избежать фрагментации, Kubernetes делит пространство на блоки по 16 бит (по 65 536 идентификаторов):

  • Диапазон 0–65535 зарезервирован за хостом.
  • Остальные диапазоны выделяются подам блоками по 65 536 UID. На одной ноде kubelet может разместить до 65 536 изолированных подов.
  • Битовая карта (bitmap) в kubelet для отслеживания диапазонов занимает всего ~8 КБ памяти.

Корневая файловая система (rootfs) контейнера работает поверх OverlayFS. Изменения сохраняются в верхнем слое (upper_dir) с владением реального непривилегированного GID контейнера на хосте. Это предотвращает атаки с подбросом бинарников с флагом SetUID (SUID) для повышения привилегий на хосте.

Матрица совместимости компонентов стека

Для работы User Namespaces в режиме GA необходимы следующие версии компонентов:

Компонент стекаМинимальная версияПримечания
Kubernetes1.36+Статус GA, флаги feature gate не нужны
containerd2.0+Необходим для работы с idmap mounts в K8s 1.27+
CRI-O1.25+Поддерживает функционал соответствующих версий K8s
runc1.2+Нативная поддержка idmap через расширения монтирования
crun1.9+Рекомендуется 1.13+ для информативных сообщений об ошибках
Ядро Linux6.3+Полная поддержка idmap на ext4, xfs, btrfs, overlayfs, tmpfs

Среда исполнения сообщает о поддержке idmap mounts через runc features. Если рантайм не поддерживает расширение, создание пода с hostUsers: false завершится явной ошибкой.

Пошаговое руководство: включение и верификация изоляции

Процедура основана на официальной документации Kubernetes Task: Use a User Namespace With a Pod.

1. Создание и применение манифеста

Создайте манифест пода с директивой spec.hostUsers: false:

apiVersion: v1
kind: Pod
metadata:
  name: userns-demo
spec:
  hostUsers: false
  containers:
  - name: shell
    image: debian
    command: ["sleep", "infinity"]

Примените манифест:

kubectl apply -f userns-demo.yaml
2. Проверка изоляции внутри пода

Проверьте уникальный идентификатор пользовательского пространства имён:

kubectl exec -it userns-demo -- readlink /proc/self/ns/user

Команда вернет дескриптор вида user:[4026532850], отличный от хостового.

Затем проверьте таблицу сопоставления UID:

kubectl exec -it userns-demo -- cat /proc/self/uid_map

Вывод подтвердит отображение внутреннего UID 0 на внешний пул ноды (размер 65536):

         0     100000      65536
3. Проверка процессов со стороны хоста

Команда ps aux | grep sleep на рабочей ноде покажет, что процесс выполняется от пользователя 100000, тогда как внутри контейнера whoami сообщает о пользователе root.

Ограничения и рекомендации по внедрению

  1. Pod Security Standards (PSS). Проверки PSS ослабляют требование runAsNonRoot при включенном userns. Однако контроль за привилегиями ядра сохраняется: получение CAP_SYS_ADMIN по-прежнему требует привилегированного режима пода.
  2. Типы хранилищ. Перед переводом stateful-нагрузок на userns проверьте, что хранилище не использует NFS. Тома на ext4, xfs и tmpfs работают прозрачно.
  3. Гетерогенные кластеры. Если часть нод имеет старые ядра Linux, используйте nodeSelector, чтобы поды с hostUsers: false попадали только на совместимые узлы.
  4. Настройки kubelet. Изменение диапазона UID требует предварительного перевода ноды в обслуживание (kubectl drain) и пересоздания подов.

Релиз User Namespaces в Kubernetes 1.36 делает запуск контейнеров от root безопасным для хоста, устраняя фундаментальный риск компрометации инфраструктуры.