Знакомая боль: накатил веб-сервер плейбуком, поменял DocumentRoot на /srv/web, открыл порт 8080, перезапустил httpd - а в браузере 403 или вообще "Permission denied" в логах. Руки тянутся к setenforce 0, и проблема "решается". А потом этот хост приезжает в прод с выключенным SELinux, и безопасник смотрит на тебя как на врага народа.
SELinux - это не лишний слой, который надо отключать. Это мандатный контроль доступа, и на RHEL/Fedora он включён по умолчанию в режиме enforcing. Правильный путь - не глушить его, а научить пускать твоё приложение туда, куда нужно. И делать это декларативно, через Ansible, чтобы конфигурация была воспроизводимой на сотне машин, а не набором ручных команд, которые ты забудешь через неделю.
Связка ansible selinux - это три кита: булевы переключатели (seboolean), контексты файлов (sefcontext) и метки портов (seport). Плюс управление самим режимом. Разберём каждый, и сразу скажу: setype и sefcontext почти гарантированно встретятся на экзамене RHCE (EX294), потому что без них реальную задачу "разверни сервис" в enforcing-режиме не закрыть.
Маленькая оговорка по дистрибутивам. SELinux - это мир RHEL-семейства (RHEL 9, Fedora, RED OS, AlmaLinux, Rocky). В Debian/Ubuntu по умолчанию AppArmor, и эти модули там просто не применимы. Все примеры ниже - для RHEL 9 с ansible-core 2.14, как на актуальном EX294.

Режим и состояние: модуль ansible.posix.selinux
Сначала база. Узнать текущее состояние руками - getenforce и sestatus. Из Ansible режимом управляет модуль ansible.posix.selinux. Он умеет ставить enforcing, permissive или disabled и менять политику (обычно targeted).
Код: Выделить всё
- name: SELinux в enforcing, политика targeted
ansible.posix.selinux:
policy: targeted
state: enforcing
Код: Выделить всё
- name: Настроить SELinux
ansible.posix.selinux:
state: enforcing
register: selinux_state
- name: Перезагрузить, если SELinux этого требует
ansible.builtin.reboot:
when: selinux_state.reboot_required
Булевы переключатели: ansible seboolean
Булевы (booleans) - это готовые тумблеры в политике SELinux, которыми Red Hat заранее предусмотрел частые сценарии. Не надо писать свою политику - надо просто включить нужный флаг. Посмотреть все: getsebool -a, найти описание: semanage boolean -l.
Классика: твой PHP/веб хочет ходить в сеть (коннект к БД, внешний API) - блокируется, пока не включён httpd_can_network_connect. Или NFS-домашние каталоги - use_nfs_home_dirs. Модуль ansible seboolean делает это идемпотентно:
Код: Выделить всё
- name: Разрешить httpd сетевые подключения (persistent)
ansible.posix.seboolean:
name: httpd_can_network_connect
state: true
persistent: true
Параметр state здесь булев (true/false), не путай с состоянием present/absent из других модулей. Есть ещё ignore_selinux_state на случай, когда SELinux выключен и модуль иначе упал бы с ошибкой, но в норме он не нужен.
Контексты файлов: selinux ansible через sefcontext и restorecon
Вот где срезается большинство. Каждый файл в RHEL имеет SELinux-метку (контекст), и тип в ней (setype) определяет, что с файлом можно делать. httpd читает только файлы с типом httpd_sys_content_t. Положил сайт в нестандартный /srv/web - там тип default_t или var_t, и httpd получает отказ.
Тут важно понять механику, иначе будешь биться головой. Контекст файла живёт в двух местах:
- на самом файле (xattr на диске) - его показывает ls -Z;
- в правилах политики - "файлам по такому-то пути назначать такой-то тип". Это база fcontext.
Код: Выделить всё
- name: Правило контекста для каталога веб-сервера
community.general.sefcontext:
target: '/srv/web(/.*)?'
setype: httpd_sys_content_t
state: present
notify: restore web context
# в handlers:
- name: restore web context
ansible.builtin.command: restorecon -Rv /srv/web
Почему restorecon вынесен в handler, а не отдельной задачей? Чтобы перемечать только когда правило реально изменилось. Сам по себе ansible.builtin.command неидемпотентен (Ansible не знает, надо ли перемечать), а notify запускает его лишь при changed от sefcontext. restorecon же по своей природе идемпотентен: если метки уже правильные - он ничего не трогает. Альтернатива restorecon - ansible.builtin.command с chcon, но chcon меняет метку только на файле и не переживёт общего restorecon, так что для постоянной настройки всегда sefcontext + restorecon.
Цельный фрагмент роли, который реально разворачивает сайт в нестандартном каталоге в enforcing-режиме:
Код: Выделить всё
- name: Каталог сайта
ansible.builtin.file:
path: /srv/web
state: directory
mode: '0755'
- name: Правило SELinux-контекста
community.general.sefcontext:
target: '/srv/web(/.*)?'
setype: httpd_sys_content_t
state: present
notify: restore web context
- name: Индексная страница
ansible.builtin.copy:
content: "Cyberlake works\n"
dest: /srv/web/index.html
mode: '0644'
notify: restore web context
- name: Открыть порт во firewalld
ansible.posix.firewalld:
service: http
permanent: true
immediate: true
state: enabled
SELinux метит не только файлы, но и сетевые порты. По умолчанию httpd разрешено слушать 80, 443, 8080 и ещё несколько (тип http_port_t). Решил повесить веб на 8000 - SELinux заблокирует bind, и сервис не стартанёт. Посмотреть, какие порты к какому типу привязаны: semanage port -l. Из Ansible это лечит модуль ansible.posix.seport:
Код: Выделить всё
- name: Разрешить httpd слушать порт 8000
ansible.posix.seport:
ports: 8000
proto: tcp
setype: http_port_t
state: present
Типичные грабли
- Забыл restorecon после sefcontext. Правило есть, метки на файлах старые - сервис всё равно в отказе. Лечится handler-ом, как выше.
- Забыл persistent: true в seboolean - после ребута всё разваливается.
- Перепутал target в sefcontext: написал glob /srv/web/* вместо регулярки /srv/web(/.*)? - часть путей не покрылась.
- Поставил setenforce 0 "на попробовать" и забыл вернуть. На EX294 это провал автоматом: проверка идёт в enforcing.
- Лечил chcon вместо sefcontext - метка слетит при следующем restorecon -R или релейбле ФС.
- Диагностика вслепую. Смотри причину: ausearch -m AVC -ts recent или sealert -a /var/log/audit/audit.log из пакета setroubleshoot-server. AVC-запись прямо подскажет нужный тип или булев.
Подними тестовую RHEL 9 (или RED OS) и руками-через-Ansible проведи путь целиком:
- Установи httpd, переопредели DocumentRoot на /srv/web, положи туда index.html.
- Напиши плейбук: ansible.posix.selinux (enforcing), community.general.sefcontext на /srv/web с httpd_sys_content_t + handler restorecon, ansible.posix.seport на нестандартный порт, ansible.posix.firewalld.
- Включи нужный булев через ansible.posix.seboolean с persistent: true (например httpd_can_network_connect, если сайт стучится наружу).
- Запусти, проверь ls -Z /srv/web и getsebool -a, дёрни curl. Затем удали из плейбука restorecon, накати на чистую машину - убедись, что ловишь 403. Это закрепит, зачем он нужен.
- Почему один модуль community.general.sefcontext не чинит доступ к файлам и какой второй шаг обязателен?
- Что делает persistent: true в ansible.posix.seboolean и что будет без него после перезагрузки?
- Чем отличается permissive от disabled и почему на EX294 нельзя ставить disabled?
- Тебе надо повесить веб-сервер на TCP-порт 8000 - какой модуль и какой setype используешь, и что ещё открыть, чтобы соединение дошло?
SELinux из Ansible - это четыре инструмента под одну задачу: ansible.posix.selinux держит режим enforcing, ansible.posix.seboolean щёлкает готовые тумблеры (с persistent), community.general.sefcontext плюс restorecon правят метки файлов, ansible.posix.seport - метки портов. Запомни главную связку "sefcontext + restorecon" и параметр persistent - на этом сыпется большинство и в проде, и на экзамене. Не выключай SELinux. Научи его пускать твой сервис - это ровно то, что проверяет RHCE.