SELinux и rootless Podman: безопасный запуск контейнеров без отключения защиты | AdminWiki

SELinux и rootless Podman: безопасный запуск контейнеров без отключения защиты

29 августа 2026 12 мин. чтения
Содержание статьи

Rootless Podman можно запускать при включённом SELinux в режиме enforcing. Для этого нужны корректные user namespaces, записи subuid и subgid, Unix-права на файлы и подходящий SELinux-контекст для bind mount. Отключение защиты через setenforce 0 не исправляет конфигурацию, а только скрывает причину отказа.

Базовый порядок такой: проверить окружение, запустить минимальный контейнер, отдельно проверить UID/GID mapping, подключить каталог с :z или :Z, затем диагностировать AVC-события через ausearch и audit2why. Локальную политику SELinux создают только после проверки контекста, пути и обычных разрешений.

Команды ниже рассчитаны на Linux с установленными Podman и SELinux. Команды без sudo выполняются от обычного пользователя, под которым будет работать rootless-контейнер. Административные проверки запускаются с правами root.

Как запустить rootless Podman с SELinux в режиме enforcing

Проверка окружения и режима SELinux

Сначала зафиксируйте версии и состояние системы. Версии Podman, libpod, SELinux policy и ядра влияют на сетевой стек, работу user namespaces и обработку volume.

podman version
podman info
id
getenforce
sestatus
cat /etc/subuid | grep "^$(id -un):"
cat /etc/subgid | grep "^$(id -un):"
loginctl user-status "$(id -u)"

Ожидаемый результат команды getenforce - Enforcing. Для rootless-пользователя должны существовать диапазоны в /etc/subuid и /etc/subgid, например:

devops:100000:65536

Первое число задаёт начало диапазона, второе - его размер. Конкретные значения зависят от дистрибутива и политики организации. Если записей нет, Podman может завершить запуск с ошибкой создания user namespace или не сможет корректно отображать пользователей контейнера.

Проверьте аудит SELinux:

sudo systemctl is-active auditd
sudo ausearch -m AVC -ts today

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

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

loginctl show-user "$(id -un)" -p Linger
sudo loginctl enable-linger "$(id -un)"

Для тестового VDS можно использовать облачную инфраструктуру Timeweb Cloud, если требуется изолировать проверку от рабочего сервера. Перед экспериментом зафиксируйте образ, версии пакетов и параметры ядра.

Минимальный rootless-контейнер для проверки

Начните с контейнера без bind mount и публикации портов. Такой тест отделяет проблемы базового запуска от ошибок доступа к файлам и сети.

podman pull docker.io/library/alpine:3.20
podman run --name selinux-rootless-test --rm docker.io/library/alpine:3.20 \
  sh -c 'id; cat /proc/self/uid_map; echo container-started'

В выводе появится пользователь контейнера и таблица uid_map. Затем запустите контейнер в фоне:

podman run -d --name selinux-http-test -p 8080:8080 \
  docker.io/library/python:3.12-alpine \
  python -m http.server 8080 --bind 0.0.0.0

podman ps
podman inspect selinux-http-test
podman port selinux-http-test
curl http://127.0.0.1:8080

Команда podman ps должна показывать работающий контейнер, podman port - соответствие порта хоста и порта контейнера, а curl - ответ HTTP-сервера. После теста удалите контейнер:

podman rm -f selinux-http-test

Этот тест подтверждает базовый rootless-запуск. Для проверки SELinux-контекста нужны операции с файлами хоста.

User namespaces Podman: UID/GID, subuid и subgid

Как проверить отображение идентификаторов

Rootless Podman запускает контейнер в user namespace. Пользователь с UID 0 внутри контейнера не получает UID 0 на хосте. Его действия сопоставляются с диапазоном, выделенным в /etc/subuid и /etc/subgid.

podman run --rm docker.io/library/alpine:3.20 sh -c 'id; cat /proc/self/uid_map; cat /proc/self/gid_map'
podman unshare id
podman unshare cat /proc/self/uid_map

Для работающего контейнера можно проверить фактический процесс:

podman top selinux-http-test user huser group hgroup pid

На хосте сравните владельца каталога и его содержимое:

stat -c '%A %u:%g %n' /srv/containers/app-data /srv/containers/app-data/*
ls -ln /srv/containers/app-data

Для записи в bind mount нужны одновременно доступ к каждому элементу пути, подходящие Unix-разрешения, корректное сопоставление UID/GID и разрешение SELinux. Ошибка в любом из четырёх уровней может выглядеть как Permission denied.

Почему chmod не всегда решает проблему

Диагностируйте отказ по трём независимым направлениям:

  • Путь и Unix-права: namei -l /srv/containers/app-data, ls -ld, stat.
  • UID/GID mapping: podman unshare, /proc/self/uid_map, владелец процесса и файлов.
  • SELinux: getenforce, ls -Zd, ls -Z, журнал AVC.

chmod 777 не устраняет неправильный SELinux-контекст и не меняет UID/GID mapping. При этом он разрешает чтение, изменение и удаление данных большему числу локальных пользователей. Сначала определите конкретный уровень отказа, затем меняйте только нужную настройку.

Подробное сравнение UID/GID, user namespaces и bind mounts есть в статье о правах доступа контейнеров. Эти принципы применимы и к rootless Podman.

Bind mounts в rootless Podman: выбор между :z и :Z

Общий контекст :z для нескольких контейнеров

Параметр :z просит Podman подготовить каталог для совместного использования контейнерами. Он применяет общий SELinux-контекст, совместимый с доступом контейнеров, обычно с типом container_file_t.

mkdir -p "$HOME/containers/shared-data"
echo "rootless podman" > "$HOME/containers/shared-data/health.txt"

podman run --rm \
  -v "$HOME/containers/shared-data:/data:z" \
  docker.io/library/alpine:3.20 \
  sh -c 'cat /data/health.txt; echo checked > /data/container.txt'

ls -Zd "$HOME/containers/shared-data"
ls -Z "$HOME/containers/shared-data"

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

podman run -d --name app-a \
  -v "$HOME/containers/shared-data:/data:z" \
  docker.io/library/alpine:3.20 sleep 3600

podman run --rm --name app-b \
  -v "$HOME/containers/shared-data:/data:z" \
  docker.io/library/alpine:3.20 \
  cat /data/container.txt

Используйте :z, когда несколько контейнеров действительно должны обращаться к одним данным. Совместный label расширяет круг контейнеров, которым политика разрешает доступ к объекту, поэтому для изолированного хранилища он не нужен.

Приватный контекст :Z для одного контейнера

Параметр :Z назначает приватный SELinux-контекст для каталога, который подключает конкретный контейнер. Это подходит для данных одного приложения.

mkdir -p "$HOME/containers/private-data"
echo "private" > "$HOME/containers/private-data/input.txt"

podman run --rm \
  -v "$HOME/containers/private-data:/data:Z" \
  docker.io/library/alpine:3.20 \
  sh -c 'cat /data/input.txt; echo processed > /data/output.txt'

ls -Zd "$HOME/containers/private-data"

Повторное подключение этого каталога с :Z из другого контейнера может привести к конфликту доступа, потому что приватная метка предназначена для изоляции. Перед relabel проверьте, какие контейнеры, службы и пользователи работают с каталогом:

podman ps -a
findmnt -T "$HOME/containers/private-data"
sudo lsof +D "$HOME/containers/private-data"

Не применяйте :Z к общему каталогу, который используется несколькими контейнерами, без проверки последствий для всех потребителей. Relabel меняет extended attributes файловой системы, а не только параметры запуска Podman.

Когда нужны semanage fcontext и restorecon

Параметры :z и :Z удобны для bind mount, но постоянную настройку пути лучше оформлять через правило file context. Сначала проверьте текущую метку:

ls -Zd /srv/containers/app-data
ls -Z /srv/containers/app-data

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

sudo semanage fcontext -a -t container_file_t '/srv/containers/app-data(/.*)?'
sudo restorecon -Rv /srv/containers/app-data
ls -Zd /srv/containers/app-data

Пакет с командой semanage может называться policycoreutils-python-utils или аналогично, в зависимости от дистрибутива. После restorecon ожидайте тип container_file_t. Если правило уже существует, используйте -m вместо -a.

Перед изменением системного каталога сделайте резервную копию данных и сохраните исходную метку. Для нестандартных файловых систем проверьте поддержку extended attributes и сохранение SELinux labels при монтировании.

Проброс портов в rootless-контейнере

Проверка публикации и доступности сервиса

Порт приложения внутри контейнера и порт хоста - разные значения. В примере ниже приложение слушает 8080 в контейнере, а Podman публикует его на 8080 хоста:

podman run -d --name web-test -p 8080:8080 \
  docker.io/library/python:3.12-alpine \
  python -m http.server 8080 --bind 0.0.0.0

podman ps
podman port web-test
podman exec web-test ss -lnt
ss -lntp | grep ':8080'
curl -v http://127.0.0.1:8080

Проверяйте цепочку по порядку:

  1. Контейнер работает и не перезапускается с ошибкой.
  2. Приложение слушает нужный порт внутри контейнера.
  3. Приложение привязано к 0.0.0.0, если доступ нужен через сетевой интерфейс.
  4. podman port показывает ожидаемое сопоставление.
  5. Firewall хоста разрешает внешний порт.

Для анализа сетевого периметра пригодится практическая методика аудита портов и правил firewall.

Что делать с портами ниже 1024

Rootless-процесс обычно не может напрямую открыть привилегированный порт ниже 1024. Самый простой вариант - опубликовать непривилегированный порт, например 8080, и разрешить доступ к нему через firewall.

podman run -d --name web-test -p 8080:80 \
  docker.io/library/nginx:stable-alpine

Для внешнего порта 80 или 443 используйте reverse proxy, работающий с нужными правами и отдельной политикой доступа, а rootless-контейнер оставьте на непривилегированном порту. Системное перенаправление порта допустимо после оценки риска, документирования правила и проверки его поведения после перезагрузки.

Отключение SELinux не решает ограничение привилегированных портов. Это разные механизмы контроля.

Диагностика AVC-отказов: ausearch, audit2why и контексты

Проверка журнала SELinux через ausearch

Сообщение Permission denied само по себе не доказывает отказ SELinux. Сначала зафиксируйте время, повторите операцию и извлеките события AVC:

date
podman exec web-test sh -c 'cat /data/input.txt'
sudo ausearch -m AVC -ts recent -i

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

sudo ausearch -m AVC -ts 14:30:00 -i

В записи ищите поля scontext, tcontext, tclass, comm, exe, name и действие в блоке { read write getattr open }. Они показывают субъект, объект, класс объекта, процесс, путь и запрещённую операцию.

Сопоставляйте AVC с конкретным контейнером по времени, процессу и пути. Один сервер может одновременно генерировать отказы для нескольких служб.

Интерпретация audit2why

Передайте релевантные события в audit2why:

sudo ausearch -m AVC -ts recent | audit2why

Утилита объясняет возможную причину отказа и показывает, какое правило политики сработало. Ее вывод не означает, что нужно немедленно создать разрешение. Сначала проверьте:

  • правильно ли выбран путь в volume;
  • используется ли :z или :Z по назначению;
  • имеет ли каталог тип container_file_t;
  • проходит ли контейнерный пользователь проверку Unix-разрешений;
  • не изменился ли процесс после обновления образа.

Если отказ исчезает после корректного relabel, локальная политика не нужна.

Разделение проблем SELinux, прав и user namespaces

СимптомПервичная проверкаБезопасное действие
Permission denied без AVCnamei -l, ls -ln, statИсправить владельца, группу или режим доступа с учётом UID/GID mapping.
Отказ сопровождается AVCausearch -m AVC, ls -ZПроверить :z, :Z, container_file_t и путь.
Файл принадлежит неожиданному UIDcat /proc/self/uid_map, podman unshareСопоставить UID контейнера с диапазоном subuid.
Путь отсутствует в контейнереpodman inspect, podman exec ... lsИсправить source и destination в bind mount.
Сервис недоступен по сетиpodman port, ss -lntp, firewallПроверить binding приложения, публикацию порта и правила firewall.

Минимальная локальная SELinux-политика: когда она оправдана

Почему audit2allow не следует применять вслепую

audit2allow преобразует выбранные AVC-события в правила, но не определяет, была ли исходная конфигурация безопасной. Неправильный label, ошибочный путь, неожиданный процесс или лишняя операция записи могут породить формально рабочее, но чрезмерное разрешение.

Не создавайте правило, пока не проверили стандартный контейнерный контекст. Для обычного bind mount сначала исправьте конфигурацию:

podman inspect CONTAINER_NAME
ls -Zd /path/to/data
sudo ausearch -m AVC -ts recent -i

Порядок создания и проверки локального модуля

Если нестандартное приложение действительно требует отдельного разрешения, работайте в тестовой среде и собирайте только AVC, относящиеся к одной проверяемой операции.

sudo ausearch -m AVC -ts recent > /tmp/podman-avc.log
sudo audit2allow -a -w
sudo audit2allow -a -M podman_local_test
sudo semodule -i podman_local_test.pp
sudo semodule -l | grep podman_local_test

Перед установкой изучите сгенерированные .te и .fc файлы. Удалите правила, которые не связаны с целевым контейнером или разрешают широкие действия. После установки повторите тест, проверьте журнал и убедитесь, что приложение получило только необходимый доступ.

Локальный модуль нужно описать в конфигурации или журнале изменений: назначение, дата, контейнер, образ, исходное AVC, разрешённые действия и ответственный за пересмотр.

Как удалить локальное правило

Сначала получите список модулей и удалите только модуль, созданный для этой задачи:

sudo semodule -l | grep podman_local_test
sudo semodule -r podman_local_test
sudo semodule -l | grep podman_local_test

Если менялся постоянный контекст каталога, удалите или скорректируйте правило fcontext, затем восстановите label:

sudo semanage fcontext -l | grep '/srv/containers/app-data'
sudo semanage fcontext -d '/srv/containers/app-data(/.*)?'
sudo restorecon -Rv /srv/containers/app-data

Команду удаления выполняйте только для правила, которое вы точно создавали. Системные правила дистрибутива удалять нельзя.

План проверки и безопасного отката

Чек-лист перед изменениями

Сохраните исходное состояние в отдельный файл с ограниченными правами:

umask 077
mkdir -p "$HOME/podman-selinux-before"
getenforce > "$HOME/podman-selinux-before/getenforce.txt"
podman info > "$HOME/podman-selinux-before/podman-info.txt"
podman ps -a > "$HOME/podman-selinux-before/containers.txt"
ls -l /srv/containers/app-data > "$HOME/podman-selinux-before/ls.txt"
ls -Z /srv/containers/app-data > "$HOME/podman-selinux-before/ls-z.txt"
cat /etc/subuid > "$HOME/podman-selinux-before/subuid.txt"
cat /etc/subgid > "$HOME/podman-selinux-before/subgid.txt"
sudo ausearch -m AVC -ts today > "$HOME/podman-selinux-before/avc.txt"

Для production-каталогов дополнительно зафиксируйте резервную копию данных, список потребителей и текущие правила fcontext. Не меняйте label каталога, если не знаете, какие процессы используют его одновременно с контейнером.

Проверка после исправления

  • SELinux остаётся в режиме Enforcing.
  • Контейнер запускается после удаления и повторного создания.
  • Приложение читает и изменяет только разрешённые пути.
  • Фактический UID/GID процесса соответствует ожидаемому mapping.
  • Команда ls -Z показывает подходящий контекст.
  • podman port и сетевой тест подтверждают публикацию порта.
  • После повторения операции не появляются новые релевантные AVC.
  • После перезапуска контейнера данные остаются доступными.
podman restart CONTAINER_NAME
podman inspect CONTAINER_NAME
getenforce
sudo ausearch -m AVC -ts recent -i

Откат :z, :Z, fcontext и локальной политики

Перед изменением volume остановите контейнер, чтобы исключить запись во время relabel:

podman stop CONTAINER_NAME
podman inspect CONTAINER_NAME > /tmp/container-inspect-after-stop.json

Для отката параметров :z или :Z удалите контейнер и создайте его с исходной строкой volume. Если вы добавляли правило fcontext, удалите только это правило и примените исходный контекст через restorecon. Если создавали локальный модуль, удалите его через semodule -r.

После отката проверьте владельцев, extended attributes, доступ приложения и отсутствие новых AVC. setenforce 0 не используйте как постоянное исправление. Временно изменить режим можно только по согласованной процедуре диагностики, с фиксацией времени, причины и обязательным возвратом:

sudo setenforce 1
getenforce

Шпаргалка: типовые ошибки SELinux и rootless Podman

Быстрый алгоритм за пять проверок

  1. Режим SELinux: выполните getenforce. При Enforcing переходите к проверке контекста, при другом результате сначала восстановите штатный режим согласно политике организации.
  2. Параметры volume: проверьте podman inspect. Для общего каталога используйте :z, для приватного каталога одного контейнера - :Z.
  3. Контекст файлов: выполните ls -Zd PATH и ls -Z PATH. Для постоянного пути проверьте правило semanage fcontext и примените restorecon.
  4. Unix-права и mapping: используйте namei -l, stat, podman unshare, subuid и subgid. Не заменяйте анализ командой chmod 777.
  5. AVC и сеть: выполните ausearch -m AVC -ts recent, затем проверьте audit2why, podman port, binding приложения и firewall.
ОшибкаВероятная причинаПроверкаИсправление
Контейнер не читает bind mountНеверный label или Unix-праваls -Z, namei -l, AVCИсправить :z/:Z, права и mapping.
После :Z другой контейнер потерял доступПриватный контекст конфликтует с общим использованиемpodman ps -a, ls -ZdСогласовать потребителей и выбрать :z либо разделить каталоги.
Нет записи subuid или subgidНе настроен диапазон rootless-пользователя/etc/subuid, /etc/subgidДобавить диапазоны по правилам дистрибутива и повторить запуск.
Порт опубликован, но сервис недоступенПриложение слушает localhost или закрыт firewallpodman port, ss -lnt, firewallИсправить binding приложения и сетевое правило.
AVC появился после обновления образаИзменился процесс, путь или тип операцииausearch, audit2why, inspectСравнить версии и контексты, не создавать широкое правило автоматически.
Хочется выполнить setenforce 0Причина отказа не определенаgetenforce, ausearch, ls -ZРазделить проблему прав, mapping, label и сети.

Для комплексной проверки журналов контейнерной среды используйте материал об аудите Docker и контейнеров. Подход с фиксацией событий, времени и параметров запуска подходит и для rootless Podman.

Рабочая конфигурация rootless Podman с SELinux строится вокруг правильного контекста и ограниченных прав. Сначала проверьте getenforce, mapping и volume, затем повторите операцию и изучите AVC. Локальная политика остаётся последним средством для подтверждённого нестандартного сценария, а каждое изменение должно иметь проверку и понятный откат.

Поделиться:
Сохранить гайд? В закладки браузера