Хватит отключать SELinux: пора доверить управление Puppet

Давным-давно я написал статью под названием Stop Disabling SELinux!. И вот что побудило меня к этому. Многие пользователи, хостинговые компании и разработчики без колебаний отключают SELinux, добровольно и, на мой взгляд, совершенно напрасно отказываясь от важного инструмента обеспечения безопасности. В этой статье я описал несколько простых шагов, позволяющих настроить SELinux для типовой установки Drupal. Но они также подойдут для любого LAMP-приложения (плюс memcached).

Я все еще являюсь ярым сторонником использования SELinux и стараюсь убеждать в этом других. Поэтому я бы хотел рассказать о том, как в Tag1 Consulting мы управляем настройками SELinux с помощью Puppet. Использование системы управления конфигурацией позволяет нам настраивать SELinux на большом количестве хостов с минимальными усилиями, а также быть уверенными, что мы ничего не напутали и нигде не ошиблись (как это бывает при ручной настройке).

Краткая версия

«Я, знаете ли, не собираюсь читать многостраничную статью только для того, чтобы узнать, что этот парень скажет про Puppet и SELinux — давайте сразу код!»

Не вопрос! Да, иногда я бываю чересчур многословным. Если вы хотите сразу посмотреть на связку Puppet и Selinux в действии, вот модуль site_selinux в нашем git-репозитории Puppet CentOS.

А вот краткое описание классов модуля:

  • init.pp — должен вызываться на каждом сервере; для включения SELinux используется класс puppet/selinux;
  • drupal.pp — типовые настройки SELinux для веб-серверов Drupal; может быть модифицирован с учетом специфики вашей системы;
  • newrelic.pp — нужен для установки модуля SELinux, выдающего необходимые разрешения на доступ к веб-серверу, на котором развернут New Relic;
  • php-systemd.pp — конфигурация SELinux для веб-серверов, где php-fpm работает на сокетах systemd. Для получения более подробной информации об этой конфигурации см. статью Грега (Greg): Zero Downtime PHP-FPM Restarts Using Systemd;
  • puppetmaster.pp — конфигурация SELinux для серверов Puppet Master.

Далее в этой статье я буду говорить исключительно о init.pp и drupal.pp, рассматривая настройку SELinux на веб-сервере Drupal.

О SELinux-модуле для Puppet

Мы используем модуль puppet/selinux (GitHub repo), который изначально был написан James Fryman, но теперь поддерживается группой сопровождения модулей Puppet Vox Pupuli.

Puppet/selinux предоставляет простой фреймворк для работы с SELinux в манифестах Puppet, включая: разворачивание пользовательских (custom) модулей SELinux, контроль логических значений (booleans) SELinux, а также управление контекстами SELinux, касающимися файлов и портов.

В файле README дана краткая инструкция по использованию модуля. Я расскажу об этом подробнее чуть позже, когда буду описывать настройку под CentOS веб-серверов, обслуживающих приложения на Drupal.

Обзор типовой конфигурации SELinux для Drupal

Модуль site_selinux::drupal, разработанный Tag1, содержит типовую конфигурацию SELinux для веб-серверов, обслуживающих приложения на Drupal. Несмотря на то что в каждом конкретном случае могут понадобиться специфические изменения, основные задачи остаются схожими:

  • Установить необходимые логические значения SELinux: например, разрешить httpd доступ к MySQL и Memcached по сети или отправлять email.
  • Настроить касающиеся работы с файловой системой контексты SELinux, чтобы разрешить httpdдоступ «только на чтение» или «чтение-запись» к различным директориям (отдельно от разрешений UNIX).
  • Загрузить пользовательские модули SELinux, необходимые для вашего окружения. В нашем случае это Solr и Varnish, позволяющие httpd (или php) получить доступ к сетевым портам, по которым осуществляется управление Solr и Varnish.
  • Управлять настройками пользовательских типов портов SELinux. В нашем примере мы конфигурируем типы портов, используемые для управления доступом к Solr.

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

Нулевой шаг: включаем SELinux

Включение SELinux выполняется путем добавления класса site_selinux в конфигурацию Puppet. Установка в hiera значения selinux::mode: 'enforcing' обеспечивает включение SELinux (в CentOS по умолчанию будет режим targeted).

Если система SELinux до этого была выключена, может потребоваться перезагрузка.

Шаг первый: управление логическими значениями

Логические значения SELinux — это быстрый и легкий способ управления разрешениями, которые обычно выдаются приложениям. В CentOS по умолчанию уже сконфигурировано несколько логических значений для httpd (выполните getsebool -a | grep httpd, чтобы вывести их полный список).

Для Drupal обычно включается насколько логических значений SELinux, имеющих отношение к httpd: httpd_can_network_connect_db, httpd_can_network_memcache и httpd_can_sendmail. В hiera они указаны следующим образом:

site_selinux::drupal::selbooleans:
  'httpd_can_network_connect_db':
    persistent: true
    ensure: 'on'
  'httpd_can_sendmail':
    persistent: true
    ensure: 'on'
  'httpd_can_network_memcache':
    persistent: true
    ensure: 'on'

Эти настройки могут быть заданы в hiera per-host, per-server-group или там, где вы считаете нужным. Главное использовать хеш site_selinux::drupal::selbooleans. Этот хеш считывается модулем site_selinux::drupal с помощью следующего фрагмента кода:

$drupal_selbooleans = hiera_hash('site_selinux::drupal::selbooleans', {})
create_resources('selinux::boolean', $drupal_selbooleans)

Вместо передачи значений в виде параметров класса мы получаем их из hiera с помощью hiera_hash. Это упрощает подгонку конфигурации под свои нужды. Например, вы можете сделать так, чтобы хеш применялся ко всем веб-серверам, а дальше продолжать кастомизацию настроек hiera уже для отдельных хостов, расширяя существующий хеш и/или переопределяя интересующие значения. При этом вам не нужно включать первоначальный хеш в определение каждого хоста.

Шаг второй: управление контекстами файлов

В поставке SELinux по умолчанию есть большое количество контекстов файлов. Это позволяет свести к минимуму необходимость дополнительной настройки. Например, если у вас есть директория Drupal sites/default/files/ в /var/www/html, возможно, не придется вообще что-то делать, так как fcontext httpd_sys_rw_content_t позволяет httpd изменять файлы в этой директории (что необходимо для работы Drupal). Однако многие сайты используют и другие директории, и, возможно, потребуется установить контексты файлов по умолчанию, чтобы обеспечить требуемый уровень доступа к файлам со стороны httpd. Чтобы настроить эти контексты с помощью Puppet, перечислим в hiera необходимые пути, используя следующие хеши: site_selinux::drupal::drupal_file_paths, которому по умолчанию присваивается httpd_sys_rw_content_t, если в hiera не указаны другие контексты, а также site_selinux::drupal::httpd_readable_paths, которому по умолчанию присваивается httpd_sys_content_t (httpd сможет прочитать, но не сможет записать). Поскольку эти два содержащих пути хеша обрабатываются практически одинаково, мы посмотрим только на rw-вариант:

# Хеш, содержащий путь (включает регулярное выражение) для присваивания контекстов selinux.
# Используется контекст по умолчанию 'httpd_sys_rw_content_t' (httpd-writable), если не переопределено ниже.
site_selinux::drupal::drupal_file_paths:
  'localdev-private-files':
    pathname: '/var/www/drupal_private_files(/.*)?'
    restorecond_path: '/var/www/drupal_private_files'
  'localdev-public-files':
    pathname: '/var/www/drupal_public_files(/.*)?'
    restorecond_path: '/var/www/drupal_public_files'

Эти настройки считываются классом site_selinux::drupal с помощью следующего фрагмента кода:

$httpd_readable_paths = hiera_hash('site_selinux::drupal::httpd_readable_paths', {})
create_resources('selinux::fcontext', $httpd_readable_paths, { context => 'httpd_sys_content_t' })

Это очень похоже на работу с логическими значениями SELinux, но в данном случае мы устанавливаем контекст файлов по умолчанию равным httpd_sys_rw_content_t. Если для какого-то пути нужно установить другой context, добавьте его в хеш hiera, и он будет использован вместо дефолтного. Если в строке пути есть подстановочные символы (что обычно и случается), необходимо добавить установку restorecond_path без регулярных выражений. Этот путь будет передан команде restorecon, которая regexp не понимает.

Шаг третий: установка пользовательских модулей SELinux

Иногда появляется необходимость установки пользовательского модуля SELinux. Вопрос написания пользовательских модулей мы оставим для другой статьи, а сейчас сконцентрируемся на их развертывании с помощью Puppet. В этом примере мы установим два модуля: первый позволяет httpd взаимодействовать с Solr по сети, а второй — с портами управления Varnish (это может оказаться полезным при программном удалении объектов из кэша при помощи, например, модуля Drupal Varnish).

Модуль puppet/selinux содержит определение selinux::module, которым мы воспользуемся для установки пользовательского модуля. Следующий фрагмент hiera определяет, какие модули мы хотим установить на целевом сервере:

# Хеш устанавливаемых пользовательских модулей SELinux.
site_selinux::drupal::selinux_modules:
  'httpdsolr':
    source: 'puppet:///modules/site_selinux/httpdsolr/httpdsolr.te'
  'httpdvarnish':
    source: 'puppet:///modules/site_selinux/httpdvarnish/httpdvarnish.te'

Эти настройки считываются модулем site_selinux::drupalс помощью следующего фрагмента кода:

$drupal_selinux_modules = hiera_hash('site_selinux::drupal::selinux_modules', {})
create_resources('selinux::module', $drupal_selinux_modules)

Каждая запись хеша hiera содержит значение source, определяющее путь к копируемому на сервер файлу с расширением .te(относительно структуры каталогов Puppet). Тип selinux::moduleопределяет копирование файла на целевой сервер. При этом человекочитаемый файл .te компилируется в подгружаемый модуль SELinux с расширением .pp, а затем файл модуля .pp загружается в SELinux.

Такая конфигурация значительно облегчает добавление и обновление модулей SELinux: внесите изменения в файле модуля .te (не забудьте изменить номер версии!), поместите его в структуру каталогов Puppet, и во время следующего запуска Puppet выполнит установку/обновление!

Шаг четвертый: пользовательские типы портов SELinux

Необходимость присвоения портам пользовательских меток с контекстами SELinux ­ — это еще более редкий случай. Однако это нужно для нашего модуля Solr, поэтому не стоит удивляться. Мы изменим метки портов с помощью блоков exeс, поскольку в зависимости от интересующего порта может потребоваться выполнение различных команд (например, если ему уже назначен какой-либо контекст по умолчанию).

В этом примере мы установим пользовательский тип solr_port_t(определенный в пользовательском модуле SELinux httpdsolr.te, который мы скопировали ранее). Мы ищем порт в /etc/selinux/targeted/modules/active/ports.local, и если его там нет, выполняем semanage port, чтобы его настроить. Процедура одинакова для всех трех интересующих нас портов, поэтому код приведен только для одного из них:

exec { 'semanage-port-8112':
  command => '/usr/sbin/semanage port -a -t solr_port_t -p tcp 8112',
  unless  => '/bin/grep solr_port_t /etc/selinux/targeted/modules/active/ports.local | /bin/grep -q 8112',
  require => Selinux::Module['httpdsolr'],
}

Exec требует установленный модуль httpdsolr, поскольку в нем определен тип solr_port_t.

Шаг пятый: наслаждайтесь безопасностью

Это все! В большинстве случаев описанный класс может быть использован лишь с незначительными изменениями в hiera. Нужно совсем немного, чтобы ваши серверы начали работать с включенным SELinux. Изложенные здесь идеи с минимальными изменениями могут быть адаптированы и для работы с другими приложениями, поэтому нет смысла откладывать этот вопрос на потом. Берите Puppet и автоматизируйте свою работу с SELinux!