
В статье пойдет речь о SELinux. Главная моя задача — провести быстрый онбординг о том, как работать с политиками доступов. Я хочу показать, что это вовсе не так сложно, как может показаться на первый взгляд. Буквально 15 минут ознакомления с базовыми понятиями дает 80% понимания как работать с SELinux более уверенно.
Статья не совсем про написание политик, скорее про их понимание. Все описанное необходимо, чтобы уверенно пользоваться утилитами, более осмысленно работать с системой, решать проблемы и задачи, с которыми придется столкнуться в процессе эксплуатации SELinux.
Оглавление
Полезные утилиты
restoreconsemanagesesearchaudit2allowsealert
Синтаксис политик
Контекст SELinux
Синтаксис файлов при описании политик
Структура правил (
.teфайлы)Типы
Атрибуты
Классы
Макросы (макропроцессор
m4, интерфейсы,.ifфайлы)Домен
Соглашение по именованию
Разбираемся на практике
Полезные утилиты
Я использую Fedora.
Для начала убедимся, что у нас есть необходимый набор утилит:
which restorecon semanage sesearch audit2allow sealert
В моем случае все на месте за исключением sesearch — полезной утилиты, которая позволяет осуществлять поиск правил SELinux политик. Она входит в пакет setools-console. Установим:
dnf install setools-console
Краткий онбординг по утилитам:
restorecon
Восстанавливает контекст безопасности файлов/каталогов согласно политике. Иными словами устанавливает в файловой системе рекомендованный контекст, который предустановлен в политиках. Несколько примеров, когда она используется:
Были изменены пути в системе.
Что-то скопировали. При копировании наследуется контекст каталога назначения (куда переместили такой контекст и получаем), при перемещении наследуется контекст источника (откуда перемещали тот контекст и сохраниться).
Что-то создали по нестандартному пути.
Распаковали архив (архив хранит исходные контексты).
После команды
semanage fcontext— базу рекомендованных контекстов изменили, но сама файловая система осталась нетронутой.
Рекомендованный (эталонный контекст) указывается в файлах .fc их можно посмотреть в репозитории SELinuxProject refpolicy. Или соответствующими командами. Например, известно что, по умолчанию файлам в домашней директории присваивается тип user_home_t. Мы можем увидеть это правило командой:
semanage fcontext -l | grep user_home_t
/home/[^/]+/.+ all files unconfined_u:object_r:user_home_t:s0
Или в исходниках refpolicy.
semanage
Управляет политикой SELinux без перекомпиляции: порты, контексты файлов, булевы, пользователи.
Несколько примеров использования:
# устанавливаем контекст `httpd_sys_content_t` всем файлам в /srv/web/ semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?" # разрешаем порт 8080 для типа http_port_t semanage port -a -t http_port_t -p tcp 8080 # список булевых semanage boolean -l
Чтобы понимать, как устроена команда, нужно понимать жизненный цикл появления модулей с политиками безопасности SELinux:
Сначала описываются файлы:
.te,.fcи.if(о каждом будет рассказано ниже).Затем они компилируются в модуль политик — бинарный файл
.pp.А затем устанавливаются через
semodule -i
semanage позволяет перепрыгнуть через весь этот цикл и создать правила, которые переопределяют правила уже установленных модуль-политик. Файлы с переопределенными правилами хранятся в:
/var/lib/selinux/targeted/active/ ├── file_contexts.local # контексты файлов ├── ports.local # порты ├── booleans.local # булевы └── users_extra.local # пользователи
sesearch
Позволяет делать поисковые запросы по всем действующим политикам SELinux. Она показывает правила, которые в текущей системе применены. Иными словами мы можем подсмотреть через нее фрагменты.te файлов.
Возможен поиск по правилам политики (allow, dontaudit, type_transition и др.).
audit2allow
Генерирует allow-правила из отказов которые хранятся в audit.log. С ней следует быть осторожным, фактически это конвертер журнала отказов в политику с разрешениями, которые обходят полученные отказы.
sealert
Дает большие и понятные отчеты AVC-отказов с рекомендациями по их устранению (часть пакета setroubleshoot).
Пример использования:
sealert -a /var/log/audit/audit.log
Синтаксис политик
Перед знакомством с этим разделом рекомендую ознакомиться со статьей — Права в Linux: chown/chmod, SELinux context, символьная/восьмеричная нотация, DAC/MAC/RBAC/ABAC
Теперь необходимо разобраться с языком описания политик, чтобы что-то понимать в том, чем нам будут отвечать утилиты.
Первоисточником документации считается репозиторий selinux-notebook (англ).
Так же много полезной информации в документации Red Hat (англ)
Контекст SELinux
Перед знакомством со структурой политик, вспомним о контексте SELinux. У всех файлов и процессов в SELinux есть так называемый контекст. Его можно увидеть разными способами, для файлов ls -Z или stat, для процессов ps Z.
Рассмотрим пример:
gtosss@laptop-dc:~/playground$ ls -Z common/message.txt unconfined_u:object_r:user_home_t:s0 common/message.txt
Так выглядит контекст: unconfined_u:object_r:user_home_t:s0. Здесь:
user —
unconfined_uроль —
object_rтип —
user_home_tуровень доступа —
s0
Синтаксис файлов при описании политик
Политика SELinux — это набор скомпилированных бинарных модулей (.pp файлы), которые загружаются в ядро. Их исходники пишутся на языке CIL или M4-макросах, затем компилируются.
Основная часть политик SELinux описывается в файлах .te — Type Enforcement.
SELinux следует принципу наименьших привилегий, иными словами по умолчанию запрещено всё, но есть правила, которые кому-то что-то разрешают. Если быть точнее — то не просто что-то, а минимально необходимый набор пермишинов для работы. Именно такие правила мы и встречаем в файлах с расширением .te.
Чтобы пользователи системы не столкнулись с тотальным запретом на все действия для всех процессов, система хранит множество предустановленных политик для системных и общеизвестных сервисов.
Репозитории с предустановленными политиками можно отыскать здесь:
Структура правил (.te файлы)
Шаблон правил из .te можно описать так:
allow субъект объект:класс { действие_1, действие_2 };
Фигурные скобки ненужны, если описывается только 1 действие.
Перед описанием самих правил объявляют типы:
type my_app_t;
Помимо ключевого слова allow есть другие правила:
dontaudit— Подавляет сообщения от отказов в журнале AVCauditallow— Принудительно записывать информацию о попытке получения доступа в журнал. Независимо есть ли allow-правило. Таким образом успешное получение доступа, так же попадет в лог.neverallow— Запретить саму возможность написатьallow, применяется при компиляции политик (compile-time).
Подробнее описано в selinux-notebook.
Типы
В правилах фигурируют типы, следовательно, сначала они должны быть объявлены. Объявление типов осуществляется ключевым словом type:
type httpd_t
Атрибуты
Когда типов много, чтобы не писать для каждого отдельные allow-правила, их объединяют в атрибуты. Атрибут — именованное множество типов.
# объявить атрибут attribute web_server_type; # назначить тип атрибуту typeattribute httpd_t web_server_type; typeattribute nginx_t web_server_type; # теперь можно написать правило для всех типов атрибута 'web_server_type' allow web_server_type cert_t:dir search;
Атрибут может содержать сотни типов, таким образом, имея 1 аттрибут с множеством типов, нам достаточно только 1 allow-правило.
Классы
Классы посмотреть в исходниках репозитория.
В работающей системе, все классы, зарегистрированные в действующих политиках, можно посмотреть командой:
seinfo --class
Макросы (макропроцессор m4, интерфейсы, .if файлы)
Часто можно встретить макросы такого вида:
manage_dirs_pattern(httpd_t, httpd_log_t, httpd_log_t)
Макросы это заранее определенные наборы allow-правил, они реализованы через интерфейсы M4 (язык макропроцессора m4). Интерфейсы определены в файлах .if. Есть макросы, которые наиболее часто используются между модулями, они могут быть расположены в файлах .spt.
Попробуем найти определение manage_dirs_pattern в репозитории, в поисковой строке GitHub пишем: repo:SELinuxProject/refpolicy manage_dirs_pattern define и находим данный шаблон по адрессу refpolicy/policy/support/file_patterns.spt:
define(`manage_dirs_pattern',` allow $1 $2:dir rw_dir_perms; allow $1 $3:dir manage_dir_perms; ')
Таким образом. Вызов макроса:
manage_dirs_pattern(httpd_t, httpd_log_t, httpd_log_t)
Будет преобразован в:
allow httpd_t httpd_log_t:dir rw_dir_perms; allow httpd_t httpd_log_t:dir manage_dir_perms;
Но и это не финальная форма, rw_dir_perms это тоже макрос, ищем define rw_dir_perms и находим:
define(`rw_dir_perms', `{ open read getattr lock search ioctl add_name remove_name write }')
А manage_dir_perms:
define(`manage_dir_perms',`{ create open getattr setattr read write link unlink rename search add_name remove_name reparent rmdir lock ioctl }')
Итого финальное правило manage_dirs_pattern(httpd_t, httpd_log_t, httpd_log_t) будет выглядеть так:
allow httpd_t httpd_log_t:dir { open read getattr lock search ioctl add_name remove_name write }; allow httpd_t httpd_log_t:dir { create open getattr setattr read write link unlink rename search add_name remove_name reparent rmdir lock ioctl };
На примере первой строки, объясняю как это читать:
Разрешить субъекту:
httpd_t(тип процесса)Работать с объектом:
httpd_log_t:dir(httpd_log_t— тип файла;dir— класс директория)Выполняя:
openreadgetattrlocksearchioctladd_nameremove_namewrite
У каждого класса свой набор возможных операций, шпаргалку по ним можно посмотреть здесь. Например, для расшифровки данного примера смотрим разделы:
Домен
Доменом в SELinux называют определенный тип, которому назначены пермишины необходимые для процесса. Если был объявлен какой-то тип, чтобы его сделать доменом, его нужно обернуть в соотвествующий макрос или присвоить к domain типу.
Ссылка на исходник domain интерфейсов.
Это нам может потребоваться, если мы являемся разработчиком собственного ПО и настраиваем какие-то свои нестандартные политики для собственных процессов.
Соглашение по именованию
Во избежание путаницы принято соглашение об именовании, пользователь, роль или тип, всегда имеет соответствующее окончание в имени: _u, _r и _t соответственно.
Разбираемся на практике
Допустим мы хотим разрешить podman’у читать файл из домашней директории, чтобы пробросить его в volume (bind mount). На самом деле, волумы в docker/podman дружат с SELinux и там уже заготовлены специальные метки:
:z- Разрешает доступы между хостом, контейнером и другими контейнерами.:Z- Разрешает доступы между хостом и контейнером
Но мы специально в академических целях, попробуем сделать все в ручную не используя :z.
И так есть файл в домашней директории с контекстом:
unconfined_u:object_r:user_home_t:s0
Вспоминаем, что здесь 4 значения, разделенных двоеточием: user:role:type:level
Главным ограничением является тип — user_home_t. Для Podman нет разрешений на файлы такого типа, так что сначала изучим, что вообще ему разрешено.
Опишем простой docker-compose.yml
services: alpine-playground: image: alpine:3.22 container_name: alpine-playground volumes: - ./common:/common command: ["sleep", "infinity"] restart: unless-stopped
Создадим директорию common и внутри какой-нибудь файл, выдав полные права на эту директорию в рамках DAC:
mkdir common touch common/message.txt chmod -R 777 common
Поднимем контейнер через podman compose up -d.
Теперь выясним какой тип у процесса контейнера (домена):
ps auxZ | grep podman
Нам выводится 2 процесса, у каждого следующий контекст:
unconfined_u:unconfined_r:container_runtime_t:s0
Посмотрим так же context у самого бинарника podman:
sudo ls -Z /usr/bin/podman
system_u:object_r:container_runtime_exec_t:s0 /usr/bin/podman
Сам бинарь имеет тип container_runtime_exec_t, а вот процесс container_runtime_t. В первом фигурирует слово exec что говорит о том, что это тип исполняемого файла, то есть файла, который можно выполнять, его мы увидели в файловой системе командой ls. А вот процесс, который будет запущен в результате выполнения бинарника — container_runtime_t, его мы увидели при просмотре процессов через ps.
Посмотреть правила, в которых фигурирует данный тип процесса, можно командой:
sesearch --allow -s container_runtime_t
Разбор команды
--allow — Флаг, указывающий на то, что мы ищем правила-разрешения.
-s container_runtime_t — -s сокращение от --source. Это значит, что мы ищем правила, где тип container_runtime_t является субъектом, то есть тем, кто получает права.
В результате получаем много всего, попробуем сузить поиск, добавив класс “file”:
sesearch --allow -s container_runtime_t -c file
Разбор команды
Добавляется флаг -c сокращение от --class — класс объекта.
Все еще много, еще сузим, уточнив конкретный пермишен на запись:
sesearch --allow -s container_runtime_t -c file -p write
Разбор команды
Добавляется флаг -p сокращение от --perm — поиск по хотя бы одному из указанных пермишенов. Можно указывать несколько через запятую: -p read,write
Нет, вывод меня не устраивает, по-прежнему слишком много правил.
Попробуем подступиться с другой стороны. Посмотрим атрибуты типа container_runtime_t:
seinfo -t container_runtime_t -a
Разбор команды
seinfo — это похожая утилита на sesearch, но если sesearch удобен для поиска конкретных разрешений в политиках, то seinfo скорее подходит для просмотра структуры политик.
-t container_runtime_t — указание конкретного типа
-a — показывает список атрибутов, к которым принадлежит указанный тип
Список атрибутов не малый, но глаз зацепился за container_domain и svirt_sandbox_domain.
Смотрим правила через этот атрибут:
sesearch --allow -s container_domain -c file -p write,read | grep container
Разбор команды
Здесь связка двух команд, первая sesearch, мы ее разобрали выше. Вторая grep для поиска во всем что нашел sesearch конкретной строки container.
В списке я заметил наиболее интересное правило:
allow container_device_t container_file_t:file { append create execute execute_no_trans getattr ioctl link lock map mounton open read relabelfrom relabelto rename setattr unlink watch watch_reads write };
В нем фигурирует container_file_t, похоже мы можем использовать такой тип, чтобы предоставить права.
Перед тем как что-то менять, напомню, что контейнер сейчас поднят с volume(bind mount) на директорию common. Изнутри контейнера ls common/ выводит:
ls: can't open 'common/': Permission denied
При этом по системе контроля доступа DAC права на common и ее содержимое установлены разрешенными для всех (777).
Для просмотра отказов SELinux используется команда:
sudo ausearch -m avc
Но наш запрет туда не попал, потому что есть правило, которое я обнаружил такой командой:
sesearch --dontaudit --source container_t --target user_home_t --class dir
Разбор команды
--dontaudit — вместо правила --allow которое позволяло найти разрешения. В данном случае мы ищем правило политики, которое подавляет логирование в случае отказа доступа.
Получив вывод:
dontaudit domain file_type:dir map; dontaudit svirt_sandbox_domain file_type:dir getattr; dontaudit svirt_sandbox_domain mountpoint:dir { ioctl lock open read search };
Посмотрим, какие типы входят в этот домен (в этот атрибут):
seinfo -a svirt_sandbox_domain -x
Разбор команды
-a svirt_sandbox_domain — указываем атрибут svirt_sandbox_domain -x — смотрим список типов, которые в него входят
Вывод команды:
Type Attributes: 1 attribute svirt_sandbox_domain; container_t openshift_initrc_t svirt_kvm_net_t svirt_qemu_net_t
Видим svirt_sandbox_domain — значит правило подавления логов распространяется на podman контейнеры. Напоминаю что svirt_sandbox_domain встречался в результате вывода seinfo -t container_runtime_t -a.
Попробуем через semanage изменить тип нашей директории чтобы политики SELinux разрешили читать этот файл внутри контейнера.
Есть 2 варианта.
1-й вариант временный через chcon, он не переживет команду restorecon:
chcon -R -t container_file_t /home/gtosss/playground/common
2-й вариант установит рекомендуемый контекст на определенный путь перманентно, а через restorecon применит его. Итого получается 2 команды:
semanage fcontext -a -t container_file_t "/home/gtosss/playground/common(/.*)?"
А затем:
restorecon -Rv /home/gtosss/playground/common
Мы делаем это в академических целях, так что просто используем первый вариант, затем заходим в контейнер и пробуем прочитать файл:
gtosss@laptop-dc:~/playground$ chcon -R -t container_file_t /home/gtosss/playground/common gtosss@laptop-dc:~/playground$ ls -lZ common/ total 4 -rwxrwxrwx. 1 gtosss gtosss unconfined_u:object_r:container_file_t:s0 16 May 9 10:01 message.txt gtosss@laptop-dc:~/playground$ podman exec -it alpine-playground sh / # ls common/ message.txt / # cat /common/message.txt hello from host
Спасибо, что дочитали! Надеюсь теперь вы понимаете SELinux глубже.
Кто уже подписан на телеграм-канал — отдельная благодарность. Помимо технических статей поднимаю и социально важные темы.
Если материал полезен и изложен понятным языком — не стесняйтесь ставить плюсы. Это поможет выделиться на фоне остальных статей, так же это хороший способ отблагодарить и поддержать меня.
С вами был Тимофей. Кто я?
Разрабатываю с 2015 года. Стартовал как front-end разработчик на React, после 6-лет переключился на full-stack, последние годы — чаще DevOps. Мой публичный WakaTime.
