SELinux enforcing на CentOS 7: audit2allow и кастомные политики вместо setenforce 0
Переход на CentOS 7 - SELinux enforcing ломает несколько сервисов. Разбираемся с audit2allow, пишем custom policy modules и объясняем, почему отключать не вариант.
RHEL 7 / CentOS 7 переводит SELinux enforcing в дефолт, что требует новых знаний от администраторов при переходе с шестёрки
Когда переводили первые серверы на CentOS 7, systemd забрал всё наше внимание. Unit-файлы, journalctl, firewalld - это было ново и бросалось в глаза. SELinux при этом как-то остался на периферии. Он же был и на шестёрке, привыкли. Казалось, ничего принципиально нового.
Оказалось, принципиально новое там было - просто другого рода. Не в самом SELinux, а в том, что на CentOS 7 он enforcing по умолчанию, и Red Hat явно сигнализирует: теперь это не рекомендация, а базовая позиция. А значит, чинить «включи и забудь» не получается - надо разбираться.
Где сломалось
Первый звонок поступил от одного из клиентов на сопровождении через несколько дней после перехода на CentOS 7. Веб-приложение на базе nginx + php-fpm отказывалось читать загруженные пользователями файлы из нестандартной директории. nginx отдавал 502, php-fpm в логах молчал, права на директорию были выставлены правильно.
tail -f /var/log/audit/audit.log показал сразу:
type=AVC msg=audit(1427720481.123:456): avc: denied { read } for pid=12345 comm="php-fpm" name="uploads" dev="sda1" ino=789012 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:var_t:s0 tclass=dir
SELinux заблокировал доступ php-fpm к директории, потому что её контекст - var_t, а httpd-процессы по умолчанию имеют право читать только httpd_sys_content_t и несколько смежных типов. Логично с точки зрения MAC-модели. Неудобно с точки зрения того, что директория создавалась скриптом деплоя без учёта SELinux-контекстов.
Решение в этом случае простое - chcon -R -t httpd_sys_content_t /var/www/uploads или, лучше, прописать через semanage fcontext, чтобы контекст переживал restorecon:
semanage fcontext -a -t httpd_sys_content_t "/var/www/uploads(/.*)?"
restorecon -Rv /var/www/uploads
Но это был простой случай - типовой контекст, документированный в man-странице httpd_selinux. Дальше пошло интереснее.
Когда готового контекста нет
Второй случай оказался сложнее. Java-демон собственной разработки клиента пытался открыть сокет на нестандартном порту и писать в /opt/app/run/. Обе операции SELinux блокировал. Готового типа для такого приложения в системной политике не было.
Тут пришёл черёд audit2allow. Инструмент входит в пакет policycoreutils-python, на CentOS 7 надо доставить отдельно:
yum install policycoreutils-python
Базовый сценарий: берём отказы из audit.log, скармливаем audit2allow, получаем текст правила.
grep AVC /var/log/audit/audit.log | audit2allow -m myapp
Вывод - это .te-файл (Type Enforcement), примерно такой:
module myapp 1.0;
require {
type httpd_t;
type var_t;
class dir { read open };
}
#============= httpd_t ==============
allow httpd_t var_t:dir { read open };
Дальше компилируем в бинарный модуль и загружаем:
audit2allow -M myapp < /var/log/audit/audit.log
semodule -i myapp.pp
semodule -i загружает модуль немедленно, без перезагрузки. Сервис подняли, проверили - работает.
Почему нельзя просто брать вывод audit2allow напрямую
Это ключевой момент, который легко пропустить. audit2allow механически преобразует отказы в разрешения. Он не думает за вас о том, разумно ли это разрешение. Если приложение по ошибке попыталось открыть что-то лишнее - аудит это зафиксирует, audit2allow это разрешит, и вы получите политику, которая легитимизирует некорректное поведение.
Поэтому перед semodule -i мы делаем несколько вещей:
- Читаем
.te-файл руками. Смотрим какие типы задействованы, какие классы и права. Если видим что-то неожиданное вродеexecmemилиptrace- это повод задать вопрос, откуда это взялось. - Фильтруем audit.log по конкретному процессу.
grep "comm=\"myapp\""вместоgrep AVC- так в политику не попадают случайные чужие отказы из лога. - Проверяем есть ли стандартный boolean. Перед написанием кастомного модуля смотрим
getsebool -a | grep <что-то релевантное>. Иногда нужное уже есть, просто выключено. Например,httpd_can_network_connect_dbвключает доступ httpd к базе данных по сети - гораздо чище, чем писать правило самому.
Перманентный аудит
Ещё один практический момент: /var/log/audit/audit.log по умолчанию ротируется и может не сохранить отказы с первого запуска сервиса. Для отладки удобно временно поставить auditd на подробный режим, а ещё лучше - на время тестирования запускать ausearch -m avc -ts today вместо прямого grep. ausearch понимает форматы времени и умеет фильтровать по pid, comm, типу события.
Почему setenforce 0 - это не вариант
Каждый раз когда SELinux что-то блокирует, в комментариях или в чатах появляется совет «просто выключи SELinux». В RHEL/CentOS-мире это устойчивая традиция, идущая ещё с шестёрки - там SELinux тоже был включён по умолчанию, но в реальности большинство отключало при первых проблемах.
Мы этого не делаем. Причина не в принципиальности ради принципиальности. На серверах с персональными данными или финансовой информацией SELinux - это отдельный слой контроля доступа, который работает даже если приложение или веб-сервер скомпрометированы. Shellshock в прошлом году хорошо это показал: на системах с SELinux enforcing часть векторов эскалации просто не срабатывала, потому что MAC-политика запрещала нужные действия ещё до того, как до неё добирался exploit.
Написать точечный модуль политики занимает 20-40 минут. Выключить SELinux - 30 секунд. Но второе означает убрать защитный слой со всех процессов сразу, а не решить конкретную проблему конкретного сервиса.
Где мы сейчас
Кастомные политики для нескольких клиентских серверов уже лежат в репозитории как часть Ansible-роли: .te-файлы хранятся вместе с конфигами приложения, компиляция и загрузка модуля - шаг провизионирования. Это значит, что при пересборке сервера политика поднимается автоматически.
Покрыты не все приложения - часть сервисов досталась в наследство с конфигами, писавшимися при выключенном SELinux. Разбираемся по мере того, как серверы мигрируют на CentOS 7. Чем раньше начинаешь считать SELinux нормой, а не помехой - тем меньше работы потом.