В статье пойдет речь о SELinux. Главная моя задача — провести быстрый онбординг о том, как работать с политиками доступов. Я хочу показать, что это вовсе не так сложно, как может показаться на первый взгляд. Буквально 15 минут ознакомления с базовыми понятиями дает 80% понимания как работать с SELinux более уверенно.

Статья не совсем про написание политик, скорее про их понимание. Все описанное необходимо, чтобы уверенно пользоваться утилитами, более осмысленно работать с системой, решать проблемы и задачи, с которыми придется столкнуться в процессе эксплуатации SELinux.

Оглавление
  • Полезные утилиты

    • restorecon

    • semanage

    • sesearch

    • audit2allow

    • sealert

  • Синтаксис политик

    • Контекст SELinux

    • Синтаксис файлов при описании политик

    • Структура правил (.te файлы)

    • Типы

    • Атрибуты

    • Классы

    • Макросы (макропроцессор m4, интерфейсы, .if файлы)

    • Домен

    • Соглашение по именованию

  • Разбираемся на практике

Полезные утилиты

Я использую Fedora.

Для начала убедимся, что у нас есть необходимый набор утилит:

which restorecon semanage sesearch audit2allow sealert

В моем случае все на месте за исключением sesearch — полезной утилиты, которая позволяет осуществлять поиск правил SELinux политик. Она входит в пакет setools-console. Установим:

dnf install setools-console

Краткий онбординг по утилитам:

restorecon

Восстанавливает контекст безопасности файлов/каталогов согласно политике. Иными словами устанавливает в файловой системе рекомендованный контекст, который предустановлен в политиках. Несколько примеров, когда она используется:

  1. Были изменены пути в системе.

  2. Что-то скопировали. При копировании наследуется контекст каталога назначения (куда переместили такой контекст и получаем), при перемещении наследуется контекст источника (откуда перемещали тот контекст и сохраниться).

  3. Что-то создали по нестандартному пути.

  4. Распаковали архив (архив хранит исходные контексты).

  5. После команды 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:

  1. Сначала описываются файлы: .te, .fc и .if (о каждом будет рассказано ниже).

  2. Затем они компилируются в модуль политик — бинарный файл .pp.

  3. А затем устанавливаются через 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 — Подавляет сообщения от отказов в журнале AVC

  • auditallow — Принудительно записывать информацию о попытке получения доступа в журнал. Независимо есть ли 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 — класс директория)

  • Выполняя: open read getattr lock search ioctl add_name remove_name write

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

Домен

Доменом в 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.