Когда вы в очередной раз видите в логах загадочное «Permission denied», хотя права 777 уже выданы, а владелец файла — ваш пользователь, наступает момент истины. Это не призрак, не проклятие и не сбой в матрице. Это SELinux — точный механизм, который работает как швейцарские часы, только латунные, с паровым приводом и без права на ошибку.
Многие администраторы воспринимают SELinux как врага: «выключить и забыть». Но это всё равно что снять предохранительный клапан с парового котла — пока давление в норме, всё работает, но однажды… В этой статье мы разберёмся, как приручить этот механизм и заставить его работать на вас.
Что такое SELinux и почему он пугает
SELinux (Security-Enhanced Linux) — это система принудительного контроля доступа (MAC, Mandatory Access Control), разработанная NSA и встроенная в ядро Linux. В отличие от традиционной модели DAC (Discretionary Access Control), где владелец файла решает, кто имеет доступ, SELinux работает как строгий диспетчер на железнодорожной станции: даже если у вас есть билет (права доступа), вы не попадёте на путь без разрешения диспетчера (политики SELinux).

Три режима работы: дроссель безопасности
SELinux может работать в трёх режимах, как паровой двигатель с регулируемым дросселем:
- Enforcing (принудительный) — полная защита, нарушения блокируются и логируются. Это рабочий режим для продакшена.
- Permissive (предупреждающий) — нарушения только логируются, но не блокируются. Идеален для отладки.
- Disabled (отключён) — механизм остановлен. Как котёл без огня.
Проверка текущего режима:
# Проверяем статус SELinux
getenforce
# Подробная информация
sestatus
# Вывод:
# SELinux status: enabled
# SELinuxfs mount: /sys/fs/selinux
# SELinux root directory: /etc/selinux
# Loaded policy name: targeted
# Current mode: enforcing
# Mode from config file: enforcing
# Policy MLS status: enabled
# Policy deny_unknown status: allowed
# Memory protection checking: actual (secure)
# Max kernel policy version: 33Переключение режимов «на лету» (до перезагрузки):
# Включаем режим предупреждений для отладки
setenforce 0
# Возвращаем принудительный режим
setenforce 1

Для постоянного изменения отредактируйте конфигурационный файл:
# /etc/selinux/config
SELINUX=enforcing
# или
SELINUX=permissive
# или
SELINUX=disabled⚡ Важно
Изменение в конфиге вступает в силу только после перезагрузки. Переключение между enforcing и permissive через setenforce работает мгновенно, но переход из disabled в enabled требует перезагрузки и может занять время из-за перемаркировки файлов.
Контексты безопасности: адресная система SELinux
Каждый файл, процесс и порт в системе с SELinux имеет контекст безопасности — своеобразный паспорт с несколькими полями. Контекст выглядит так:
user:role:type:levelНа практике чаще всего используется поле type (тип), которое и определяет правила доступа.
Просмотр контекстов файлов:
# Показываем контексты файлов
ls -Z /var/www/html/
# Вывод:
# system_u:object_r:httpd_sys_content_t:s0 index.html
# system_u:object_r:httpd_sys_script_exec_t:s0 script.cgiЗдесь httpd_sys_content_t — это тип, который говорит SELinux: «это контент для веб-сервера, его можно читать процессу httpd».

Просмотр контекста процесса:
# Контекст процесса Apache
ps -eZ | grep httpd
# Вывод:
# system_u:system_r:httpd_t:s0 1234 ? 00:00:01 httpdЗдесь httpd_t — тип процесса, который может взаимодействовать только с определёнными типами файлов (например, httpd_sys_content_t).
Изменение контекстов: chcon и restorecon
Иногда нужно изменить контекст файла. Для этого есть два инструмента:
chcon (change context) — меняет контекст вручную, но изменение может быть потеряно при перемаркировке:
# Меняем тип файла на httpd_sys_content_t
chcon -t httpd_sys_content_t /var/www/html/custom.html
# Рекурсивно меняем контекст директории
chcon -R -t httpd_sys_content_t /var/www/html/uploadsrestorecon (restore context) — восстанавливает контекст согласно политике — это «сброс к заводским настройкам»:
# Восстанавливаем контекст файла
restorecon -v /var/www/html/custom.html
# Рекурсивно восстанавливаем всю директорию
restorecon -Rv /var/www/html/
# Вывод:
# Relabeled /var/www/html/custom.html from httpd_user_content_t to httpd_sys_content_t
Золотое правило: Если вы скопировали файл в директорию с особым контекстом, всегда запускайте restorecon для этой директории.
Булевы переменные: переключатели на панели управления
Булевы переменные SELinux — это флаги, которые включают или отключают определённые аспекты политики. Представьте их как рычаги на панели управления паровым механизмом: каждый рычаг отвечает за конкретную функцию.
Просмотр всех булевых переменных:
# Все булевы переменные
getsebool -a
# Фильтруем по интересующей теме (например, httpd)
getsebool -a | grep httpd
# Вывод:
# httpd_can_network_connect --> off
# httpd_can_sendmail --> off
# httpd_enable_cgi --> on
# httpd_read_user_content --> offПоиск конкретной переменной:
# Ищем булевы, связанные с FTP
getsebool -a | grep ftp
# Вывод:
# allow_ftpd_anon_write --> off
# allow_ftpd_full_access --> off
# ftpd_enable_home_dirs --> onВключение булевой переменной:
# Временно (до перезагрузки)
setsebool httpd_can_network_connect on
# Постоянно (сохраняется после перезагрузки)
setsebool -P httpd_can_network_connect on
# Выключение
setsebool -P httpd_can_network_connect off
Практические сценарии с булевыми переменными
Сценарий 1: Веб-сервер должен соединяться с базой данных
Если Apache или Nginx не могут подключиться к MySQL/PostgreSQL по сети:
# Разрешаем httpd инициировать сетевые соединения
setsebool -P httpd_can_network_connect on
# Для соединения с базой данных
setsebool -P httpd_can_network_connect_db onСценарий 2: FTP-сервер с доступом к домашним директориям
# Разрешаем FTP доступ к home директориям
setsebool -P ftpd_enable_home_dirs on
# Если нужен полный доступ (опасно!)
setsebool -P allow_ftpd_full_access onСценарий 3: Nginx читает пользовательский контент
# Разрешаем веб-серверу читать пользовательские файлы
setsebool -P httpd_read_user_content onДиагностика нарушений: читаем журналы как детектив
Когда SELinux блокирует доступ, он оставляет следы в логах. Ваша задача — научиться их читать.
audit.log: чёрный ящик SELinux
Все нарушения записываются в /var/log/audit/audit.log:
# Смотрим последние нарушения
tail -f /var/log/audit/audit.log
# Фильтруем только сообщения SELinux
grep "AVC" /var/log/audit/audit.log | tail -20
# Вывод типичного нарушения:
# type=AVC msg=audit(1704123456.789:123): avc: denied { read } for pid=1234 comm="httpd" name="index.html" dev="sda1" ino=56789 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0Разберём это сообщение:
- denied { read } — запрещено чтение
- comm=”httpd” — процесс httpd
- name=”index.html” — файл index.html
- scontext=…:httpd_t:s0 — контекст источника (процесса)
- tcontext=…:user_home_t:s0 — контекст цели (файла в домашней директории)
- tclass=file — класс объекта: файл
Проблема: httpd пытается прочитать файл из домашней директории (user_home_t), что запрещено политикой.
Решение: Переместить файл в правильную директорию с контекстом httpd_sys_content_t или изменить булеву переменную.
ausearch: поиск по журналу
Утилита ausearch позволяет удобно искать нарушения:
# Ищем нарушения за последние 2 часа
ausearch -ts recent
# Ищем нарушения для конкретного процесса
ausearch -m AVC -c httpd
# Ищем нарушения за конкретный период
ausearch -ts today -te now
# Экспорт в читаемый формат
ausearch -m AVC --start today | audit2whyaudit2why и audit2allow: переводим с языка SELinux
Эти утилиты помогают понять причину нарушения и создать правило:
# Объясняем причину нарушения
cat /var/log/audit/audit.log | audit2why
# Вывод:
# type=AVC msg=audit(1704123456.789:123): avc: denied { read } for ...
# Allow httpd to read user_home_t files.
# Boolean allow_httpd_read_user_content is currently off.
# Try: setsebool -P allow_httpd_read_user_content on
# Генерируем модуль политики для разрешения
cat /var/log/audit/audit.log | audit2allow -M mypol
# Создаётся два файла:
# mypol.te — исходный код правила
# mypol.pp — скомпилированный модуль
# Устанавливаем модуль
semodule -i mypol.pp⚡ Важно
Используйте audit2allow с осторожностью! Слепое применение сгенерированных правил может открыть дыры в безопасности. Всегда анализируйте, что именно вы разрешаете.
Практические сценарии: от теории к работе механизмов
Сценарий 1: Развёртывание веб-приложения
Вы скопировали файлы сайта в /var/www/html, но Apache выдаёт 403 Forbidden.
Диагностика:
# Проверяем контексты
ls -Z /var/www/html/
# Видим:
# unconfined_u:object_r:default_t:s0 index.html
# Проверяем логи
tail /var/log/audit/audit.log | grep httpdРешение:
# Восстанавливаем контексты
restorecon -Rv /var/www/html/
# Или вручную устанавливаем правильный тип
chcon -R -t httpd_sys_content_t /var/www/html/
# Если приложение должно писать в директорию (загрузки)
chcon -R -t httpd_sys_rw_content_t /var/www/html/uploads
# Для скриптов
chcon -t httpd_sys_script_exec_t /var/www/html/cgi-bin/script.shСценарий 2: Перенос домашней директории веб-приложения
Разработчик хочет хранить файлы приложения в /home/app/www вместо /var/www/html.
Проблема: SELinux не разрешит httpd читать файлы из home директории.
Решение 1 (правильное): Использовать стандартную директорию.
Решение 2 (через контексты):
# Создаём директорию
mkdir -p /home/app/www
# Устанавливаем контекст
chcon -R -t httpd_sys_content_t /home/app/www
# Делаем постоянное правило (fcontext)
semanage fcontext -a -t httpd_sys_content_t "/home/app/www(/.*)?"
# Применяем
restorecon -Rv /home/app/wwwРешение 3 (через булеву переменную):
# Разрешаем httpd читать пользовательский контент
setsebool -P httpd_read_user_content onСценарий 3: MySQL и сетевые соединения
Приложение на PHP не может подключиться к MySQL.
Диагностика:
# Проверяем логи
ausearch -m AVC -c httpd | audit2why
# Вывод говорит: httpd пытается соединиться с портом 3306, но не можетРешение:
# Разрешаем httpd сетевые соединения
setsebool -P httpd_can_network_connect on
# Или более специфично — только к БД
setsebool -P httpd_can_network_connect_db onСценарий 4: Docker и SELinux
Docker может конфликтовать с SELinux при монтировании томов.
Проблема: Контейнер не может прочитать данные из хоста.
Решение:
# При запуске docker используйте суффикс :z или :Z
docker run -v /host/data:/container/data:z nginx
# :z — общий контент (может использоваться несколькими контейнерами)
# :Z — приватный контент (только для этого контейнера)
# Или отключите SELinux для конкретного контейнера (не рекомендуется)
docker run --security-opt label:disable nginx
Создание собственных модулей политики: для продвинутых механиков
Иногда стандартных булевых переменных и контекстов недостаточно. В этом случае можно создать собственный модуль политики.
Пример: кастомное приложение
Допустим, у вас есть приложение myapp, которое должно:
- Читать файлы из
/opt/myapp/data - Писать логи в
/var/log/myapp - Слушать порт 8888
Шаг 1: Создаём контексты
# Определяем контексты для файлов
semanage fcontext -a -t myapp_data_t "/opt/myapp/data(/.*)?"
semanage fcontext -a -t myapp_log_t "/var/log/myapp(/.*)?"
# Применяем
restorecon -Rv /opt/myapp/data
restorecon -Rv /var/log/myappШаг 2: Создаём модуль политики
# Создаём файл с правилами
cat > myapp.te << 'EOF'
policy_module(myapp, 1.0.0)
# Типы
type myapp_t;
type myapp_exec_t;
type myapp_data_t;
type myapp_log_t;
# Инициализация домена
init_daemon_domain(myapp_t, myapp_exec_t)
# Разрешаем читать данные
allow myapp_t myapp_data_t:file { read open getattr };
allow myapp_t myapp_data_t:dir { read open search };
# Разрешаем писать логи
allow myapp_t myapp_log_t:file { create write append open getattr };
allow myapp_t myapp_log_t:dir { add_name write search };
# Сетевой доступ
allow myapp_t self:tcp_socket { create bind listen accept };
EOF
# Компилируем
checkmodule -M -m -o myapp.mod myapp.te
# Создаём пакет
semodule_package -o myapp.pp -m myapp.mod
# Устанавливаем
semodule -i myapp.ppШаг 3: Добавляем порт
# Разрешаем приложению слушать порт 8888
semanage port -a -t myapp_port_t -p tcp 8888
Отладка в режиме Permissive: безопасный полигон
Прежде чем разворачивать приложение в продакшене с SELinux в режиме Enforcing, протестируйте его в Permissive.
# Включаем режим предупреждений
setenforce 0
# Запускаем приложение, выполняем все сценарии использования
# SELinux будет логировать нарушения, но не блокировать их
# Анализируем логи
ausearch -m AVC -ts today | audit2why
# Создаём необходимые правила
ausearch -m AVC -ts today | audit2allow -M myapp_policy
semodule -i myapp_policy.pp
# Возвращаем принудительный режим
setenforce 1Этот подход позволяет «обучить» SELinux работе с вашим приложением без простоев.
Чек-лист администратора по SELinux
- Никогда не отключайте SELinux полностью без крайней необходимости. Используйте Permissive для отладки.
- Всегда используйте
restoreconпосле копирования файлов в системные директории. - Проверяйте логи
/var/log/audit/audit.logпри любых странных «Permission denied». - Используйте
audit2whyперед применениемaudit2allow. - Документируйте изменения в политике — через полгода вы не вспомните, зачем создали то правило.
- Тестируйте в Permissive перед развёртыванием в Enforcing.
- Используйте стандартные директории (
/var/www,/var/lib) — для них уже настроены контексты.
Заключение: SELinux — ваш союзник
SELinux — это не проклятие системного администратора, а точный инструмент, который при правильном использовании защищает систему от самых изощрённых атак. Да, он требует времени на изучение, как любой сложный механизм. Но однажды настроенный, он работает как швейцарские часы — бесшумно, надёжно, безупречно.
Не выключайте предохранительный клапан. Научитесь им управлять. И ваш сервер будет работать как отлаженный паровой двигатель — мощно, безопасно, без остановок.
⚙️ Машинное отделение ROADIT благодарит за прочтение.
Больше команд, шпаргалок и обзоров — на roadit.ru и в нашем Телеграф-канале.
📋 Все команды