Готовим NSA SELinux

  • Tutorial
NSA logoПривет Хабр! Данным постом я хочу немножно отвлечь многоуважаемое сообщество от пересуд на тему АНБ, и вместо этого заполнить пробел в описании одной их технологии, написав нечто среднее между «отключайте SELinux» и «посвятите ему лучшие годы чтобы понять малую часть». На самом деле, обе эти точки зрения одинаково далеки от правды — технология достаточно проста, прозрачна и позволяет сделать очень многое. Однако, хочу предупредить об огромном количестве букв, и достаточно узкой целевой аудитории, т.к. нижеописанное будет интересно далеко не всем. Если вы давно хотели понять, что такое SELinux, но не знали с какой стороны подступиться — эта статья для вас. Если вы давно все это знаете и успешно применяете, то я допустил достаточно неточностей, чтобы мы смогли обсудить это в комментариях. Ну а эксперты по ИБ с мировым именем могут смело проматывать в самый конец и начинать играть, у меня есть планы на продолжение :-)
Я не буду в статье касаться тем связанных с АНБ в целом, способностью расшифровывать RSA, прослушкой и прочими медийными аспектами — no hype, no FUD, only technology. Мы будем с разной степенью активности влезать в разные исходники, добавлять свои условия в само сердце MLS, возможно внедряя свои уязвимости (мы тоже делаем ошибки), и после этого попытаемся взлететь проведем тесты. Иными словами, я описываю что и как, а вы после этого уже не смотрите на SELinux как на неведому зверушку и обитель зла от вероятного противника, а смело начинаете использовать эту технологию во благо. Особенно учитывая, что она уже включена во всех ваших андроидах (>4.3) и многих дистрибутивах.
Итак, если вам все еще интересно, и вы не боитесь просидеть неделю в одном из многочисленных спойлеров, то

Предварительные чтения

Я подразумеваю, что у вас уже есть опыт работы с Linux в достаточном объёме, чтобы развернуть свой любимый дистрибутив в виртуальном окружении. Я буду делать все на примере Debian, но если вы решите повторить сей путь, то все это можно (и очень даже нужно) проделать на наиболее вам удобном и привычном дистрибутиве — в процессе вы узнаете про него много нового. Я постарался написать эту статью как обучающий материал, чтобы любой желающий мог пошагово повторить. Так же я подразумеваю, что вас не затруднит читать техническую документацию на английском — информации по SELinux на русском пока что крайне мало.

Общая информация по технологии
Вокруг SELinux вертится столько слухов, что вы будете удивлены, насколько небольшая по объему у нас вводная, всего три ссылки:
  1. RH Guide: если какая-то команда непонятна, с большой вероятностью вы найдете описание в нем. Откройте его в отдельном табе, пригодится.
  2. Конспект лекции Eli Billauer: рассматривайте как основной сборник фактов. По нему можно быстро понять что к чему, и знать, что именно спрашивать у гугла.
  3. Написание политик. Несмотря на десятилетнюю давность документа, в нем описано достаточно ключевых моментов для понимания внутреннего устройства SELinux, и как его ковырять.

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

Итак, когда вы все это прочитали, можем проверить себя простыми вопросами:
  1. Что такое unconfined_t/unconfined_u, и почему SELinux нельзя на нем тестировать?
  2. Что является частным случаем, MLS или MCS?
  3. Чем отличается *.te от *.if от *.fc?
Ответы
  1. Неограниченный домен/пользователь. С таким-же успехом можно настраивать SELinux на другой машине.
  2. MCS. MLS == MCS при MLS_SENS=1.
  3. Принципиально — ничем. Хоть в txt пишите, главное Makefile поправить не забудьте.

Постановка задачи и предварительная настройка

Теперь, когда мы уже знаем что мы хотим, но не знаем как мы это будем реализовывать, можем сформулировать цели эксперимента:
  • Хотим настроить SELinux MLS (раз уж взялись, давайте по максимуму, а не готовое из репозитория next->next->agree);
  • За основу хотим взять RefPolicy;
  • Ну и после этого хотим проверить worst case scenario — нас поломали, и не просто поломали, а получили UID=0, да не просто получили, а с постоянным shell доступом, а root мы на user_u перемапить то и забыли. Я намеренно специально делаю ряд подобных допущений, рассматривать будем наихудший сценарий;
  • Мы будем настраивать минимально необходимый экземпляр, иначе будет не статья, а сага о пятиста страницах;
Сервер
С вашего позволения, уберу под спойлер. YMMV, у вас может быть и не Debian, да и установка в KVM ничем особенным не отличается. Подойдет любой дистрибутив, установленный в минимальной конфигурации в виртуальном окружении. Виртуальном — потому что удобнее, минимальном — потому что быстрее.
Детали
Обычная экспертная установка Debian, небольшие нюансы:
  • Разбивка дискa (целых 4GB!):
    • /dev/vda1 64MB as /boot, ext2.
    • rest as LUKS: aes256:cbc-essiv:passphrase, все настройки максимально по умолчанию.
    • внутри остатка — все под LVM.
  • Вот сразу fstab
    root@sandbox:~# cat /etc/fstab
    # /etc/fstab: static file system information.
    # <file system> <mount point>   <type>  <options>       <dump>  <pass>
    /dev/vda1               /boot   ext2    defaults        0       2
    /dev/mapper/vg0-root    /       btrfs   defaults        0       1
    /dev/mapper/vg0-usr     /usr    btrfs   defaults        0       2
    /dev/mapper/vg0-var     /var    btrfs   defaults        0       2
    /dev/mapper/vg0-tmp     /tmp    btrfs   defaults        0       2
    /dev/mapper/vg0-rhome   /root   btrfs   defaults        0       2
    /dev/mapper/vg0-swap    none    swap    sw              0       0
    
  • Отдельные разделы вынесены для последующего удобства тестирования.
  • Ставим минимальную систему с SSH-сервером, больше ничего.
  • Перед завершением установки, вызываем сразу шелл, и запоминаем ключ системы:
    root@sandbox:~# ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key
    256 f6:9b:ad:dd:93:cb:3d:c2:83:76:45:c3:02:e8:6a:1d  root@sandbox (ECDSA)
    
После установки, заходим по ssh, и приводим систему к базовой для наших экспериментов версии, в моем случа было примерно так:
sed -i 's/wheezy/jessie/g' /etc/apt/sources.list  # that's no bloody enterprise
aptitude update && aptitude dist-upgrade -VR # let's go testing, it's stable enough
aptitude install vim bash-completion deborphan -VR # a little comfort couldn't hurt
aptitude install policycoreutils auditd setools selinux-basics -VR # last is just helper scripts, optional
vim /etc/network/interfaces # make interfaces static
aptitude purge isc-dhcp-client console-setup console-setup-linux kbd iproute module-init-tools $(deborphan)
Создаем ключи для ssh, прописываем их на сервере для root:
@local$ ssh-keygen -b 521 -t ecdsa -f selinux-test
@remote# mkdir /root/.ssh && cat selinux-test.pub > /root/.ssh/authorized_keys2 && chown && chmod
Ну и под занавес, собираем и ставим свое ядро — мы же хотим поддержку последней версии политики, минимально необходимого набора модулей, экспериментировать с патчами PaX и GRSecurity (которые кстати отлично уживаются с SELinux, но это наверное в другой раз опишу). В общем, vanilla kernel нам подходит лучше всего на текущем этапе. Да, голос из зала, говорящий про Debian way, я тебя слышу — но сегодня путь самурая не ограничивается подобными рамками. В этом эксперименте мы пока еще UID=0 без ограничений, и мы делаем все, что хотим. Итак, нагреем немножко Аризону (или локальную виртуалку):
mkdir src && cd src && wget -c http://kernel.org/pub/linux/kernel/v3.0/linux-3.10.18.tar.bz2 && tar jxf linux*tar.bz2 && cd linux* &&  make menuconfig && make -j$((2* $(grep processor /proc/cpuinfo  | wc -l))) deb-pkg && make clean
На этапе конфигурации, включаем SELinux (yes, this pun is intended!):selinux kernel opts, sorry for imageshack, habrastorage is not ok with my id
.config
# if you are lazy to configure yourself, here's my .config, usable on KVM+libvirt
wget -O - $aboveimage | dd bs=1 skip=3991 | xzcat 
Считаем, что основа для экспериментов готова.
Автоматизируем сборку политик
Политики мне было удобнее собирать на локальной машине и в виде deb-пакета уже устанавливать на сервер. Поэтому я пошел по пути наименьшего сопротивления.
up'n'enter style
wget http://oss.tresys.com/files/refpolicy/refpolicy-2.20130424.tar.bz2
tar jxf refpolicy-2.20130424.tar.bz2
cp -rp refpolicy custom #all our modifications
asroot# mkdir /usr/share/selinux/custom # so we can 'make install' here
asroot# mkdir /etc/selinux/custom
asroot# chown $USER:$USER /etc/selinux/custom /usr/share/selinux/custom
asroot# touch /etc/selinux/custom/setrans.conf && chown $USER:$USER /etc/selinux/custom/setrans.conf # we'll need it later
asroot# aptitude install selinux-utils python-selinux policycoreutils checkpolicy # these are for policy build
Далее, скрипт сборки пакета:
#!/bin/bash
# sample deb build for custom selinux policy
# harvests policy from local system

version='0.0.1'
name='selinux-policy-custom'
description='Custom MLS SELinux policy'
cf="${name}-control"
cc="${name}-Copyright"
# depends and conflicts shamessly ripped from selinux-policy-mls
read -d '' cheader << EOF
Section: non-free
Priority: optional
Homepage: http://selinux/
Standards-Version: 3.9.2
Package: ${name}
Version: ${version}
Maintainer: secadm_r <here.can+be@your.email>
Pre-Depends:
Depends: policycoreutils (>= 2.1.0), libpam-modules (>= 0.77-0.se5), python, libselinux1 (>= 2.0.35), libsepol1 (>= 2.1.0)
Conflicts: cron (<= 3.0pl1-87.2sel), fcron (<= 2.9.3-3), logrotate (<= 3.7.1-1), procps (<= 1:3.1.15-1), selinux-policy-refpolicy-strict, selinux-policy-refpolicy-targeted, sysvinit (<= 2.86.ds1-1.se1)
Architecture: all
Copyright: ./selinux-policy-custom-Copyright
Description: ${description}

EOF
read -d '' postinst << "EOF"
File: postinst 755
 #!/bin/sh -e
 set -e
 if [ "$1" = configure ]; then
        /usr/sbin/semodule -s custom -b /usr/share/selinux/custom/base.pp $(find /usr/share/selinux/custom/ -type f ! -name base.pp | xargs -r -n1 echo -n " -i")
 fi
 #DEBHELPER#
 exit 0
EOF

function make_policy() {
        cd custom
        make clean
        rm -rf /usr/share/selinux/custom/*
        make install
        cd ..
}

function make_files() {
        echo 'SELinux custom policy copyright:TODO' > ${cc}
        echo -e "$cheader" > ${cf}
        echo -e "$postinst" >> ${cf}
        echo -en "\nFiles:  " >> ${cf}
        # our setrans file
        echo -e " /etc/selinux/custom/setrans.conf /etc/selinux/custom" >> ${cf}
        # /etc/selinux dir
        find /etc/selinux/custom -type f ! -name \*LOCK | xargs -r -n1 -If -- sh -c 'echo " f $(dirname f)"' >> ${cf}
        # /usr/share/selinux/custom dir
        find /usr/share/selinux/custom -type f | xargs -r -n1 -If -- sh -c 'echo " f $(dirname f)"' >> ${cf}
}

function cleanup() {
        rm -f ${cc} ${cf}
}

function build_deb() {
        equivs-build ${cf}
        [ $? -eq 0 ] && cleanup
}

rm ./${name}*deb # glob is ok
make_policy
make_files
build_deb

scp -P 22 -i ~/.ssh/selinux-test selinux*deb root@selinux:/tmp/
Время полной пересборки оказалось ~30 секунд, поэтому выбран общий принцип работы скрипта — «в лоб», что называется, я думаю, адаптировать для сборки rpm труда не составит:
  • Чистим все (make clean)
  • Собираем и ставим политики (make install)
  • Находим все, что установилось (мы знаем, где искать), собираем пакет
  • Заливаем на сервер в /tmp
  • В postinst он уже сам найдет что у него обновилось, дернет semodule и перезагрузит политики

SELinux, первое знакомство.

Сервер готов, система сборки готова, reference policy загружена, вот теперь можно приступать к самому интересному. (Примерно на этом этапе, оценив уже существующий объем статьи, закралась крамольная мысль разделить ее на 25 :-).
Для первой сборки, определимся с параметрами, я выбрал такие:
$ sed '/^#/d;/^$/d' build.conf  
TYPE = mls
NAME = custom
DISTRO = debian
UNK_PERMS = reject
DIRECT_INITRC = n
MONOLITHIC = n
UBAC = y
CUSTOM_BUILDOPT =
MLS_SENS = 4
MLS_CATS = 32
MCS_CATS = 32
QUIET = n
Отличия от апстрима минимальны: включен MLS (значит при сборке будут включаться все параметры из policy/mls и config/appconfig-mls); включены дистро-специфичные макросы для debian, что на самом деле не обязательно; политика не будет загружаться, если в ядре будут определены разрешения, не отраженные в политике — вдруг у нас ядро намного новее; ну и я существенно снизил количество уровней и категорий — у нас будет всего 4 уровня секретности, в каждом по 32 категории. Пока что нам этого хватит.
суть нумеро уно
В качестве эксперимента, попробуйте выставить MONOLITHIC = y и собрать политику, не устанавливая ее — make policy. Результатом будет policy.conf, текстовое представление политики. Вот как раз тут, в простом виде, любезно развернутом m4 из всего нагромождения макросов, описано все, что будет разрешать SELinux. Иными словами (Warning: bad analogy time!): если secadm_r это навроде начальника СБ, утверждающий уровни доступа и допуски, то SELinux это рядовой сотрудник охраны, проверяющий эти списки, а в policy.conf, собственно, списки с полями:
1. кто(scontext) — куда(tcontext) — к кому(class) — зачем(call) (плюс, в случае MLS: покажите-ка еще и ваш уровень допуска, и если он меньше положенного, я в правила даже не посмотрю.)

Создаем все необходимые конфиги, которые мы будем править под свои нужды: make conf. Вначале правим правим появившийся policy/modules.conf — я отключил (modulename=off) почти все модули в группе contrib. Плюс — быстрее сборка, меньше модулей. Минус — возможное недоопределение контекстов. Поясню на примере:
  • Контекст /dev/xconsole, хоть и относится больше к логгированию, определяется в модуле xserver;
  • Отключив его, контекст стал наследоваться с директории /dev/;
  • И с большой вероятностью все, что хотело писать в /dev/xconsole, и было учтено в RefPolicy, тут же сломалось. Поправить — на ваш выбор: либо включаем модуль xserver, либо переопределяем контекст в любом своем локальном модуле.
contrib_off
grep -A5 contrib policy/modules.conf | grep "= module$" | wc -l # total number
grep -A5 contrib policy/modules.conf | grep "= module$" | sed 's/ = module//' | xargs -r -n1 -I__n -- sh -c 'sed -i "s/^__n = module$/__n = off/" policy/modules.conf' # kekeke
# turn some servicess off too (xserver + postgresql)
# turn _on_ logrotate,mta,postfix,ulogd, and whatever you think you need
Как только мы начали править modules.conf, мы прошли точку невозврата, после которой мы должны понимать, что мы делаем и почему. Возможное недоопределение контекстов — как раз первый пример того, как наши действия влияют на систему.
Забегая вперед сразу скажу немного о замечательной утилите audit2allow: она кушает audit.log, и в достаточно понятной форме (особенно с ключами -Rev) выдает нам, что нам надо добавить в политике, чтобы данные сообщения в логе больше не появлялись.Так вот, если вы где-либо (а это практически везде) в интернете встретите рекомендацию
grep something-something /var/log/audit/audit.log | audit2allow -M mymegamodule
semodule -i mymegamodule
то следуйте ей только если вы отдаете себе отчет, что именно вы сейчас сотворите — этот набор команд означает, что SELinux будет разрешать все, до чего (потенциально жадное) something-something попросило доступ, и даже немножко больше. Более того, в случае MLS данный метод вообще не сработает — потому что в MLS недостаточно создать разрешающее правило, нужно чтобы доступ удовлетворял всем наложенным ограничениям по допускам и категориям. Подобные действия равнозначны чистосердечному признанию: «да, я сегодня совсем не хочу думать головой, мне проще разрешить все». Не делайте из своей системы театр, и не настраиватёте SELinux подобным образом — это все равно что отлавливать все пакеты на фаерволе и скриптом превращать их в разрешающие правила.

Теперь самое время запустить make install, и если все хорошо, то собрать наш пакет и поставить на сервер:
dpkg -i /tmp/selinux-policy-custom*deb
sed -i 's/^SELINUX=.*$/SELINUX=enforcing/;s/^SELINUXTYPE=.*$/SELINUXTYPE=custom/' /etc/selinux/config 
selinux-activate # if you installed helper package selinux-basics
# if not: touch /.autorelabel
# add 'selinux=1 security=selinux' to cmdline
reboot # let's rock!
Система перезагрузится, применит контексты согласно определенным в установленной политике (/etc/selinux/custom/contexts/files/*), перезагрузится еще раз и любезно предложит зайти.

When is rocking «rocking» and when is it «shaking» *

Шеф, все пропало. Ничего не работает. Мы даже не можем зайти по ssh — connection closed by host. Знакомьтесь, SELinux. Как замечательно точно сформулировал Eli Billauer:
What is SELinux?
In a nutshell: a machine that tells you permission is denied.
Тем не менее, хорошо, если вы дошли до этого момента. Это именно то поведение, которое нам нужно, и сейчас мы начнем разбираться, почему нас не пускает.
суть нумеро дуо, на этот раз без плохих аналогий
Если вы внимательно читали предварительную документацию, то наверняка помните порядок принятия решений:
  1. Сначала DAC. Если запрещено, то до SELinux дело даже не дойдет, permission denied будет обычный, юниксовый, всем нам знакомый по временам, когда мы только-только знакомились со своей первой *nix системой.
  2. Потом MAC. Если не найдено ни одного подходящего разрешающего правила, permission denied будет уже от SELinux. В некоторых дистрибутивах (RH)в логах появятся строки, содержащие "SELinux is preventing", в некоторых нет, но во всех будет что-то в audit.log.
Итого, скорее всего в RefPolicy просто нет чего-то, что есть в политике дистрибутива. Давайте найдем это и добавим.
Ах да, забыл сказать, что начиная с этого момента, вам понадобится доступ к серверу не только по ssh, он может не работать. Благо, в нашем случае это виртуальный сервер, всегда есть VNC/SPICE/etc (ссылка спецом для ФСКН). Пробуем зайти локально — не пускает. Отличная ситуация, чтобы сразу проиллюстрировать, как из нее
выходить
  1. Don't panic.
  2. Перегружаемся — например, послав Ctrl+Alt+Del, acpid все сделает за нас.
  3. Ловим grub на этапе загрузки, меняем selinux=1 на selinux=0
  4. Грузимся, заходим под root.
На этом этапе, audit.log содержит в себе все причины наших неудач, почему мы не смогли зайти. Т.к. сейчас мы загрузились с отключенным SELinux, первое, что имеет смысл сделать, это скопировать audit.log с прошлой загрузки для последующего анализа, ведь при включенном SELinux у нас это сделать просто так не получится.
cp /var/log/audit/audit.log /root
wc -l /root/audit.log
195
Масштаб бедствия небольшой, двести строк. Настало время медленно спуститься с горы:
  • Как читать логи
    type=DAEMON_START msg=audit(1383338997.597:1957): auditd start, ver=2.3.2 format=raw kernel=3.10.17-vm-slnx auid=4294967295 pid
    =1319 subj=system_u:system_r:auditd_t:s3:c0.c31 res=success
    
    Первая же строчка говорит нам о том, что auditd успешно (res) запустился, причем от имени system_u, роли system_r, в домене auditd_t, и относится ко всем категориям (c0.c31) нашего максимального уровня (s3). Согласно BLP, это означает что информация с любого уровня может успешно попадать в лапы auditd (write up), а сам он может читать с любого уровня (read down). Если не совсем понятно, то давайте вспомним, кем эта архитектура разрабатывалась, и что они имели ввиду под записью информации — передача информации от источника (кто пишет) к получателю (куда/кому пишет). И тогда все становится на свои места — уровень Top Secret действительно не может записать свои данные в уровень Secret (т.е. вниз, down) — они станут скомпрометированы, отсюда "no write down". Про "read up", надеюь, более очевидно. Так же в MLS есть дополнительные ограничения, но об этом меня просили молчать далее.
    type=SYSCALL msg=audit(1383338997.620:219): arch=40000003 syscall=102 success=no exit=-13 a0=3 a1=afbe1c10 a2=a779b000 a3=ffffffc8 items=0 ppid=1338 pid=1346 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 ses=4294967295 tty=(none) comm="acpid" exe="/usr/sbin/acpid" subj=system_u:system_r:initrc_t:s0-s3:c0.c31 key=(null)
    
    Вторая строчка говорит нам о том, что демон acpid, со всеми возможными максимальными регалиями (uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0), рут рутов и бофх сисопов, используя контекст initrc_t (стартовал) обратился (type=SYSCALL) к сокету (syscall=102), и (внезапно) был не опознан, не зван, и, как следствие, послан (success=no exit=-13). Хотя, это не должно удивлять, ведь мы все знаем, что в Linux root не самый главный, есть и поважнее :-)
    Загадка для пытливых умов — к какому сокету он обратился?*
    type=AVC msg=audit(1383338997.810:233): avc:  denied  { search } for  pid=1470 comm="restorecond" name="/" dev="tmpfs" ino=376 scontext=system_u:system_r:restorecond_t:s0-s3:c0.c31 tcontext=system_u:object_r:var_run_t:s3:c0.c31 tclass=dir
    
    Ну и возьмем третью строчку, из серединки. Логи AVC (Access Vector Cache) это для нас самое интересное. Вышеуказанная, например, говорит о том, что в установленной политике нет разрешающего правила для того, чтобы источник (scontext) c вышеприведенным допуском, работающий в домене restorecond_t, выполнил поиск ({search} и tclass=dir) в директории c номером inode=376 с контекстом var_run_t. Иллюстрация чего? Правильно, no read up. Чего искал? На этот вопрос ответит find /var/run -inum 376. Как раз из подобной строчки audit2allow сделает разрешающее правило.

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

    * Отгадку давать не буду, напишите в комменты.
  • Как исправлять
    Есть два основных варианта, с которыми вы можете столкнуться:
    • Некорректный контекст
    • Отсутствие разрешающего правила
    Они покрывают 90% всех случаев permission denied, и в них прекрасно работает audit2allow. Во многих случаях, выбор, как именно поправить, первым вариантом или вторым, остается за вами.
    Третий вариант, редко встречаемый но самые неочевидный, это нарушение ограничений MLS (policy constrain violation), и добавление разрешающего правила в этом случае не поможет, придется лезть в самое сердце MLS и править ограничения. Вот тут уже каждое изменение должно делаться с полным пониманием, зачем оно делается и что именно оно должно решать. Бездумные изменения гарантированно снизят вам уровень безопасности. You have been warned (again).
    Теперь полотна про про методы решения, подкат в подкате ввиду размера:
    • решение для некорректного контекста
      Пример некорректного контекста:
      root@sandbox:~# ls -laZ /lib/systemd/systemd-udevd 
      -rwxr-xr-x. 1 root root system_u:object_r:bin_t:s0 210380 Sep 23 12:24 /lib/systemd/systemd-udevd
      @local$ grep systemd-udevd custom/policy/ -R
      custom/policy/modules/system/udev.fc:/usr/lib/systemd/systemd-udevd -- gen_context(system_u:object_r:udev_exec_t,s0)
      
      В дебиане /lib, в RefPolicy /usr/lib. Правим:
      root@sandbox:~# semanage fcontext -m -t udev_exec_t /lib/systemd/systemd-udevd # try to modify
      /usr/sbin/semanage: File context for /lib/systemd/systemd-udevd is not defined
      root@sandbox:~# semanage fcontext -a -t udev_exec_t /lib/systemd/systemd-udevd # ok, add
      root@sandbox:~# grep udev  /etc/selinux/custom/contexts/files/file_contexts.local
      /lib/systemd/systemd-udevd    system_u:object_r:udev_exec_t:s0
      
      semanage — один из способов. Подобное изменение целесообразно, но в нашем случае оно может не пережить обновление политики (если мы начнем поставлять свой /etc/selinux/custom/contexts/files/file_contexts.local). Другой вариант — переопределяем у себя локально, пересобираем политику, накатываем (и заодно ставим политику).
    • решение для отсутствия разрешающего правила
      Возьмем вот эту строку, для примера:
      type=AVC msg=audit(1383338997.860:251): avc:  denied  { module_request } for  pid=1524 comm="sshd" kmod="net-pf-10" scontext=system_u:system_r:sshd_t:s0-s3:c0.c31 tcontext=system_u:system_r:kernel_t:s3:c0.c31 tclass=system
      
      Расшифровка лога проста, но постоянное занятие этим со временем утомляет. Скинем ее в log, и пусть за нас поработает машина:
      root@sandbox:~# audit2allow -Rev -i /root/log
      
      require {
              type kernel_t;
              type sshd_t;
              class system module_request;
      }
      
      #============= sshd_t ==============
      # audit(1383338997.860:251):
      #  scontext="system_u:system_r:sshd_t:s0-s3:c0.c31" tcontext="system_u:system_r:kernel_t:s3:c0.c31"
      #  class="system" perms="module_request"
      #  comm="sshd" exe="" path=""
      #  message="type=AVC msg=audit(1383338997.860:251): avc:  denied  {
      #   module_request } for  pid=1524 comm="sshd" kmod="net-pf-10"
      #   scontext=system_u:system_r:sshd_t:s0-s3:c0.c31
      #   tcontext=system_u:system_r:kernel_t:s3:c0.c31 tclass=system "
      allow sshd_t kernel_t:system module_request;
      
      А вот тут уже, начинаем думать — задаем себе простые вопросы:
      • Что происходит? sshd попросил загрузить модуль в ядро. Ок, net-pf-10 не сильно нужен, т.к. ipv6 у нас нет.
      • Что нам предложили? Разрешить домену sshd_t загружать модули в ядро. Разумеется, если мы разрешим, то подобной ошибки не будет. А если он попросит вражеский модуль?
      • Что пишут в интернетах? Хе-хе. Спасибо, но нет, наличие булевой переменной для разрешения подобного функционала нам тоже не сильно нужно.
      • Что делаем? Да запрещаем sshd попрошайничать в этом направлении, пусть работает на том, что дали. Когда нам понадобится ipv6, мы его сами загрузим, еще до старта ssh.
      Решаем путем написания собственного минимодуля, это просто. Читаем описание структуры. Заодно создаем каркас для всех наших модулей (локально):
      mkdir policy/modules/local && cd policy/modules/local
      echo '<summary>Local layer -- differences from reference policy.</summary>' > metadata.xml
      echo '## <summary>sshd local policy</summary>' > sshd_local.if
      echo '## no file contexts redefined here' > sshd_local.fc
      cat > sshd_local.te <<EOF
      > policy_module(sshd_local, 0.0.1)
      > ##################################################################
      > require {
      >         type kernel_t;
      >         type sshd_t;
      >         class system module_request;
      > }
      > #============= sshd_t ==============
      > # dont audit requests for module load
      > # NOTE: this may hide some denials in the future
      > dontaudit sshd_t kernel_t:system module_request;
      > 
      > EOF
      
      Как видите, правило мы поменяли на dontaudit sshd_t kernel_t:system module_request; — это значит запретить, и в лог не писать. Кстати, если вы столкнетесь с тем, что какой-либо функционал не работает, а в логе пусто, то скорее всего это как раз правило dontaudit. Просто пересоберите политику без них: semodule -DB, и готовьтесь к потоку сообщений в лог.
      Указываем наш модуль в modules.conf, cобираем политику, заливаем на сервер, смотрим:
      root@sandbox:/tmp# sesearch --allow -s sshd_t -t kernel_t | grep system
      root@sandbox:/tmp# sesearch --dontaudit -s sshd_t -t kernel_t | grep system
      root@sandbox:/tmp# dpkg -i selinux-policy-custom_0.0.1_all.deb 
      (Reading database ... 20371 files and directories currently installed.)
      Preparing to replace selinux-policy-custom 0.0.1 (using selinux-policy-custom_0.0.1_all.deb) ...
      Unpacking replacement selinux-policy-custom ...
      Setting up selinux-policy-custom (0.0.1) ...
      root@sandbox:/tmp# sesearch --dontaudit -s sshd_t -t kernel_t | grep system
         dontaudit sshd_t kernel_t : system module_request ; 
      root@sandbox:/tmp# semodule -l | grep sshd_local
      sshd_local      0.0.1
      
      Правило появилось, модуль загружен. Сложно? Да ну. Долго и муторно? О да.
    • решение для ограничений MLS
      Вот проблема (the level of spoilers is over nine thousand!!1one):
      the problem
      type=AVC msg=audit(1383338997.630:221): avc:  denied  { sendto } for  pid=1351 comm="acpid" path="/dev/log" scontext=system_u:system_r:initrc_t:s0-s3:c0.c31 tcontext=system_u:system_r:syslogd_t:s3:c0.c31 tclass=unix_dgram_socket
      type=SYSCALL msg=audit(1383338997.630:221): arch=40000003 syscall=102 success=no exit=-13 a0=3 a1=afbe15d0 a2=a779b000 a3=ffffffc8 items=0 ppid=1 pid=1351 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 ses=4294967295 tty=(none) comm="acpid" exe="/usr/sbin/acpid" subj=system_u:system_r:initrc_t:s0-s3:c0.c31 key=(null)
      
      Вот описание:
      the decription
      root@sandbox:~# audit2allow -Rev -i /tmp/x 
      
      require {
              type syslogd_t;
              type initrc_t;
              class unix_dgram_socket sendto;
      }
      
      #============= initrc_t ==============
      # audit(1383338997.630:221):
      #  scontext="system_u:system_r:initrc_t:s0-s3:c0.c31" tcontext="system_u:system_r:syslogd_t:s3:c0.c31"
      #  class="unix_dgram_socket" perms="sendto"
      #  comm="acpid" exe="" path=""
      #  message="type=AVC msg=audit(1383338997.630:221): avc:  denied  { sendto } for
      #   pid=1351 comm="acpid" path="/dev/log"
      #   scontext=system_u:system_r:initrc_t:s0-s3:c0.c31
      #   tcontext=system_u:system_r:syslogd_t:s3:c0.c31 tclass=unix_dgram_socket "#!!!! This avc is a constraint violation.  You will need to add an attribute to either the source or target type to make it work.
      #Constraint rule: 
      #       Possible cause source context and target context 'level' differ
      allow initrc_t syslogd_t:unix_dgram_socket sendto;
      
      Вот существующие правила:
      the sesearch
      root@sandbox:~# sesearch --allow -s initrc_t -t syslogd_t -c unix_dgram_socket
      Found 2 semantic av rules:
         allow initrc_t syslogd_t : unix_dgram_socket sendto ; 
         allow unconfined_domain_type domain : unix_dgram_socket { ioctl read write create getattr setattr lock relabelfrom relabelto append bind connect listen accept getopt setopt shutdown recvfrom sendto recv_msg send_msg name_bind } ;
      
      Как видим, разрешающее правило уже есть. Более того, не в MLS варианте данный доступ был бы разрешен.
      offtopic
      Заодно можете оценить всю прелесть unconfined домена. Можно все, на то он и «неограничен». Именно поэтому, тестирование SELinux на чем-то, отличном от strict, смысла особого не имеет. И даже если у вас strict, но тестируемый объект — unconfined, то, в общем-то, выводы о нужности и надежности SELinux делать рановато, даже если очень-очень хочется :-)
      Решение. Находим искомое ограничение, читаем:
      mlsconstrain unix_dgram_socket sendto
          (( l1 eq l2 ) or
          (( t1 == mlsnetwriteranged ) and ( l1 dom l2 ) and ( l1 domby h2 )) or
          (( t1 == mlsnetwritetoclr ) and ( h1 dom l2 ) and ( l1 domby l2 )) or
          ( t1 == mlsnetwrite ) or
          ( t2 == mlstrustedobject ));
      # scontext=system_u:system_r:initrc_t:s0-s3:c0.c31
      # tcontext=system_u:system_r:syslogd_t:s3:c0.c31
      
      Итого, sendto в сокет (t1 пишет в t2) разрешено если:
      • нижний уровень доступа t1 равен таковому у t2 (s0 != s3), или
      • t1 помечен как mlsnetwriteranged (нет, смотрим список seinfo -amlsnetwriteranged -x), остальные условия уже не важны, или
      • t1 помечен как mlsnetwritetoclr (нет, аналогично), или
      • t1 помечен как mlsnetwrite (нет, такой только setrans_t), или
      • t2 помечен как mlstrustedobject (нет, syslogd_t там нет, но есть devlog_t)
      Как видим, ничего из этого не выполняется. Кстати, в последних версиях команды audit2allow она нам сама развернет все условия, подставит все метки и проверит на правду. Теперь смотрим сам файл /dev/log:
      root@sandbox:~# ls -laZ /dev/log 
      srw-rw-rw-. 1 root root system_u:object_r:devlog_t:s3:c0.c31 0 Nov  1 23:06 /dev/log
      
      «WTF?!» — скажет внимательный читатель. В логе же tcontext~syslogd_t, а у файла devlog_t? Посмотрим ps:
      root@sandbox:~# ps -auxZ  | grep [r]syslog
      system_u:system_r:syslogd_t:s3:c0.c31 root 1338 0.0  0.3  30784   972 ?        Ssl  Nov01   0:00 /usr/sbin/rsyslogd
      
      Следите за руками: rsyslog, запущеный в домене syslogd_t, создает сокет, который наследует его домен, по адресу /dev/log; но файл по адресу /dev/log имеет контекст devlog_t согласно определенным контекстам для файлов. Иными словами, мы sendto не в файл делаем, а в сокет, и именно это нарушает ограничения. Вот подробно на этот вопрос ответил автор SELinux, Stephen Smalley. А вот один из вариантов решения, сразу с патчами. А вот тут обсуждается нежелательность объявления домена syslogd_t как mlstrustedobject, поскольку все объекты /proc/`pidof rsyslog`/ тоже станут mlstrustedobject. Но, несмотря на это, в Fedora именно так это и решили. Чтож, не будем сопротивляться — это вполне продемонстрирует как решать задачу, описываемую в данном спойлере, а провалиться детали мы и так можем на каждом шагу, надеюсь, я это тоже показал. Создаем свой второй модуль, вот содержимое файлов:
      $ grep '' syslogd_local.*
      syslogd_local.fc:# no file contexts redefined here
      syslogd_local.if:## <summary>syslogd local policy</summary>
      syslogd_local.te:policy_module(syslogd_local, 0.0.1)
      syslogd_local.te:##################################################################
      syslogd_local.te:require {
      syslogd_local.te:       type syslogd_t;
      syslogd_local.te:}
      syslogd_local.te:
      syslogd_local.te:#============= syslogd_t ==============
      syslogd_local.te:# mark syslogd_t as mlstrustedobject
      syslogd_local.te:# this is possible security hole, TODO: get some heavy brain augmentation and investigate
      syslogd_local.te:mls_trusted_object(syslogd_t);
      
      Указанный макрос все сделает за нас. Не забываем добавить в modules.conf перед сборкой политики.
  • Что исправлять
    Все что не работает, у вас список может отличаться. Наперво, переключаемся в режим permissive (/etc/selinux/config), и добиваемся того, чтобы audit.log c момента загрузки вышеописанных ошибок не содержал, особенно проверяем сам вход, newrole, ssh. Потом переключаемся в enforcing, и проверяем что все ок. Вот как выглядел auditd лог у меня после загрузки и входа по ssh:
    root@sandbox:~# sestatus 
    SELinux status:                 enabled
    SELinuxfs mount:                /sys/fs/selinux
    SELinux root directory:         /etc/selinux
    Loaded policy name:             custom
    Current mode:                   enforcing
    Mode from config file:          enforcing
    Policy MLS status:              enabled
    Policy deny_unknown status:     denied
    Max kernel policy version:      28
    root@sandbox:~# cat /var/log/audit/audit.log 
    type=DAEMON_START msg=audit(1383360996.062:2774): auditd start, ver=2.3.2 format=raw kernel=3.10.17-vm-slnx auid=4294967295 pid=1278 subj=system_u:system_r:auditd_t:s3:c0.c31 res=success
    type=CONFIG_CHANGE msg=audit(1383360996.180:20): audit_backlog_limit=320 old=64 auid=4294967295 ses=4294967295  subj=system_u:system_r:auditctl_t:s0-s3:c0.c31 res=1
    type=LOGIN msg=audit(1383361036.430:21): login pid=1568 uid=0 old auid=4294967295 new auid=0 old ses=4294967295 new ses=1
    type=LOGIN msg=audit(1383361038.410:22): login pid=1571 uid=0 old auid=4294967295 new auid=0 old ses=4294967295 new ses=2
    root@sandbox:~# id -Z
    root:secadm_r:secadm_t:s0-s3:c0.c31
    
    Все, что мне пришлось для этого поменять, я выложу патчем на самом сервере, здесь не привожу, так как, во первых, многовато, и во-вторых, попробуйте сами. Не пожалеете.

Итак, мы все настроили, система загружается в режиме enforcing. К этому моменту, как правило, внимательный читатель уже обладает обширными знаниями доработке модулей, бегло ориентируется в структуре политики, искренне любит (или не менее искренне ненавидит) синтаксис m4, подписан на рассылку NSA, знает два десятка источников информации по SELinux и дюжину разработчиков поименно.
Настало время залезть еще немного глубже, на территорию, не сильно описанную в документациях.

MLS

Если вы немного знакомы с всякими критериями оценки защищенности, то наверняка знаете, что их, во первых, превеликое множество (c отдельными(PDF) профилями(PDF), большая часть из которых разработана разного рода околовоенными организациями), и что на выходе получается некое количество попугаев, характеризующее жесткость предъявленных требований при прохождении. MLS добавляет к уже существующим ограничениям SELinux еще два уровня контроля, вертикальный (levels) и горизонтальный (categories). Первый — это ни что иное как «допуски», где вышестоящий допуск подразумевает доступ к нижестоящему («совершенно секретно» может читать документы с грифом «секретно»), а второй — различные категории одного и того-же уровня, где разрешение читать одну категорию вовсе не означает разрешения читать остальные.
Поскольку оба этих уровня контроля могут быть назначены любым объектам, с которыми работает SELinux, то это позволяет реализовывать практически любые требования по классификации информации и ее потоков:
  • Иерархический доступ, TopSecret -> Secret -> Unclassified, для любых объектов. Полный список есть в директории flask;
  • Маркировка как файлов, так и, например, сетевых соединений или таблиц в БД;
  • Предотвращение утечки информации на низлежащие уровни независимо от прав пользователя в системе;
  • Ограничение доступов по умолчанию для любых пользователей (в том числе и root), с дальнейшим разграничением ролей в зависимости от аутентификации.
  • И прочий оверкилл для 99% систем.
Разумеется, все это требует тщательной проработки, прежде всего, архитектуры системы, иначе у нас потом контрактные работники будут раскладывать документы с грифом «Top Secret» по инстаграммам :-)
В рамках данного эксперимента я использовал вот такие уровни и категории:
root@sandbox:~# cat /etc/selinux/custom/setrans.conf
Domain=Playbox

# levels
s0=SystemLow
s3:c0.c31=SystemHigh
s0-s3:c0.c31=SystemLow-SystemHigh

s1=Confidential
s2=Secret

# employee categories
s1.c0=Ninjas
s1.c1=Pirates
s1.c2=Jesuses
# secret stuff
s2.c0=Aliens
s2.c1=BigBrother
Теперь настроим наш веб-сервер, как будто бы он предназначен исключительно для внутреннего доступа, т.е. работает строго на уровне s1 (Confidential). Это не необходимо для демонстрации, но полезно для общего развития. Разумеется, IPSec и маркировку пакетов настраивать не будем, иначе никто его не увидит, ограничимся локальным контекстом. Поскольку у нас сейчас на тестовой машине настроен только ssh, давайте выберем сервер, который не описан в RefPolicy:
nginx
В интернетах есть модуль для nginx, но он нам не совсем подходит, поскольку он написан для MCS (только s0). Поэтому воспользуемся возможностью почесать NIH и написать модуль с нуля. Для начала, определяемся с контекстами, используя dpkg -L и lsof, я отобрал вот такие:
/usr/sbin/nginx         --      gen_context(system_u:object_r:nginx_exec_t,s1:c0.c2)
/etc/init.d/nginx               gen_context(system_u:object_r:nginx_initrc_exec_t,s1:c0.c2)
/etc/nginx(/.*)?                gen_context(system_u:object_r:nginx_etc_t,s1:c0.c2)
/var/log/nginx(/.*)?            gen_context(system_u:object_r:nginx_var_log_t,s1:c0.c2)
/var/run/nginx(/.*)?            gen_context(system_u:object_r:nginx_var_run_t,s1:c0.c2)
/var/www(/.*)?                  gen_context(system_u:object_r:nginx_var_www_t,s1:c0.c2)
/var/lib/nginx(/.*)?            gen_context(system_u:object_r:nginx_var_lib_t,s1:c0.c3)
Все, что не попало сюда, но принадлежит пакету (доки, маны, и т.п.), будет наследовать контексты родительских каталогов. Сам же сервис будет работать на уровне s1 (Confidential), и в категориях с первой по третью. Для простоты все контексты я назначил одинаковыми, но вы можете сделать по другому. Сперва собираем политику с модулем, содержащим только контексты, меняем роль (newrole -r secadm_r), переходим в premissive режим (setenforce 0), ставим пакет и принудительно обновляем контексты (restorecon -RFvv /), после чего стартуем nginx из под sysadm_r (run_init /etc/init.d/nginx start). Теперь у нас в audit.log достаточно информации, чтобы настроить правила. Можно их собрать в modname.if, создав каркас, чтобы потом нагенерить много серверов, это «красивый» способ:
template(`web_server_template',`
       type $1_t, web_server;
       allow blah blah;
       # so we can call web_server_template(nginxN) in modname.te
')
А можно оставить modname.if пустым и написать все по мере анализа, это «понятный» способ. Я для наглядности пошел вторым путем. Сначала определяем все необходимые нам типы, попутно обвешав их самыми простыми макросами, это сильно сократит нам количество правил в дальнейшем:
root@sandbox:~# cat nginx_local.te
policy_module(nginx_local, 0.0.1)
##################################################################
type nginx_t;
type nginx_exec_t;
type nginx_initrc_exec_t;
type nginx_etc_t;
type nginx_var_log_t;
type nginx_var_run_t;
type nginx_var_www_t;
type nginx_var_lib_t;

corecmd_executable_file(nginx_exec_t);
init_script_file(nginx_initrc_exec_t)
files_type(nginx_etc_t)
logging_log_file(nginx_var_log_t)
files_pid_file(nginx_var_run_t)
files_type(nginx_var_www_t)
files_type(nginx_var_lib_t)

init_ranged_daemon_domain(nginx_t, nginx_exec_t, s1:c0.c2)
Большинство из этих макросов определены в файле corecommands.if, там можно подробно прочитать во что они раскроются. Последняя строчка — макрос, поддерживающий MLS, и определяющий уровень и категории, на которые nginx будет телепортирован инитом при запуске.
После этого, пробегаемся по логу, маленькими частями вытаскивая запросы (grep nginx /var/log/audit/audit.log | grep 'sysctl'), анализируя их и добавляя, например, для sysctl:
# /read kernel sysctl values
require {
     type sysctl_kernel_t;
     class dir { search };
     class file { open read };
}
allow nginx_t sysctl_kernel_t:dir { search };
allow nginx_t sysctl_kernel_t:file { open read };
Для socket:
# socket bind
require {
     type node_t;
     type http_port_t;
     class tcp_socket { name_bind setopt bind create listen node_bind };
     class capability { net_bind_service setuid setgid };
}
allow nginx_t http_port_t:tcp_socket { name_bind };
allow nginx_t node_t:tcp_socket { node_bind };
allow nginx_t self:tcp_socket { bind create setopt listen };
allow nginx_t self:capability { net_bind_service setuid setgid };
И так далее. Большую часть работы сделает audit2allow, в последних версиях программа даже дает исчерпывающие подсказки с случаях нарушений ограничений MLS. Я намеренно пишу каждый раз блок require перед очередной порцией правил, мне так удобнее, но все это можно свернуть в пару страниц, используя соответствующие макросы. В конечном счете, у вас получится что-то вроде
такого
policy_module(nginx_local, 0.0.1)
##################################################################
type nginx_t;
type nginx_exec_t;
type nginx_initrc_exec_t;
type nginx_etc_t;
type nginx_var_log_t;
type nginx_var_run_t;
type nginx_var_www_t;
type nginx_var_lib_t;

corecmd_executable_file(nginx_exec_t);
init_script_file(nginx_initrc_exec_t)
files_type(nginx_etc_t)
logging_log_file(nginx_var_log_t)
files_pid_file(nginx_var_run_t)
files_type(nginx_var_www_t)
files_type(nginx_var_lib_t)

init_ranged_daemon_domain(nginx_t, nginx_exec_t, s1:c0.c2)

# rules
# /sys and /sys/devices/systemcpu/online
require {
        type sysfs_t;
        class dir { search };
        class file { read open };
}
allow nginx_t sysfs_t:dir { search };
allow nginx_t sysfs_t:file { read open };
# /read kernel sysctl values
require {
        type sysctl_kernel_t;
        type sysctl_t;
        class dir { search };
        class file { open read };
}
allow nginx_t sysctl_kernel_t:dir { search };
allow nginx_t sysctl_kernel_t:file { open read };
allow nginx_t sysctl_t:dir search;
# self configs and symlinks
require {
        type nginx_etc_t;
        class dir { open read search };
        class file { open read getattr };
        class lnk_file { read };
}
allow nginx_t nginx_etc_t:dir { open read search };
allow nginx_t nginx_etc_t:file { open read getattr };
allow nginx_t nginx_etc_t:lnk_file { read };
# /etc/localtime, /etc/passwc, etc (no pun intended)
require {
        type locale_t;
        type etc_t;
        class file { read open getattr };
}
allow nginx_t locale_t:file { read open getattr };
allow nginx_t etc_t:file { read open getattr };
# pid file
require {
        type var_run_t;
        class dir { search write add_name remove_name } ;
        class file { write read create open unlink };
}
allow nginx_t var_run_t: dir { search };
allow nginx_t nginx_var_run_t: file { read write create open unlink };
allow nginx_t nginx_var_run_t: dir { search write add_name remove_name };
# libs
require {
        type var_lib_t;
        class dir { search getattr };
}
allow nginx_t var_lib_t:dir search;
allow nginx_t nginx_var_lib_t: dir { search getattr };
# socket bind
require {
        type node_t;
        type http_port_t;
        class tcp_socket { name_bind setopt bind create listen node_bind };
        class capability { net_bind_service setuid setgid };
}
allow nginx_t http_port_t:tcp_socket { name_bind };
allow nginx_t node_t:tcp_socket { node_bind };
allow nginx_t self:tcp_socket { bind create setopt listen };
allow nginx_t self:capability { net_bind_service setuid setgid };
# socket accept
require {
        class tcp_socket { read write accept };
}
allow nginx_t self:tcp_socket { read write accept };
# logs
require {
        type var_log_t;
        class dir { search };
        class file { open append };
}
allow nginx_t var_log_t:dir { search };
allow nginx_t nginx_var_log_t:dir { search };
allow nginx_t nginx_var_log_t:file { open append };
# www
require {
        class dir { search getattr };
        class file { read getattr open };
}
allow nginx_t nginx_var_www_t:dir { search getattr };
allow nginx_t nginx_var_www_t:file { read getattr open };
, что и является нашей финальной целью.
Заводим пользователей и роли, например так:
root/sysadm_r@sandbox:~# adduser alice
...skipped...
root/sysadm_r@sandbox:~# adduser bob
...skipped...
root/secadm_r@sandbox:~# semanage user -a -R user_r -L s1 -r s1-s1:c0 ninjas
root/secadm_r@sandbox:~# semanage user -a -R user_r -L s2 -r s2-s2:c0 aliens
root/secadm_r@sandbox:~# semanage login -a -s ninjas alice
root/secadm_r@sandbox:~# semanage login -a -s aliens bob # or, ninjas to supervise alice
root/secadm_r@sandbox:~# restorecon -RFvv /home/
# thats all, folks.
Итого, получаем:
  • оба пользователя не могут писать данные в директории ниже своего минимального уровня;
  • оба пользователя не могут читать из объектов выше своего уровня допуска;
  • оба пользователя ограничены своей категорией. При добавлении разрешющих правил на чтение других доменов они смогут читать только файлы с категорией c0;
  • root не может читать файлы пользователей, не изменив контекст;
  • если у боба будет тот-же SELinux ID, что и у alice, он сможет читать ее файлы (если DAC разрешает) и видеть ее процессы;
  • веб сервер не сможет писать никуда, кроме своих директорий, даже если его попросят выложить core — у нас практически вся система в s0, а он — s1.

Funky time

Ну и теперь, наконец-то, будут слайды. Для полноценной демонстрации, я купил под эту статью небольшую впску, рядышком с АНБ, и все проделанное быстренько развернул на ней. Непосредственно на этой системе можно посмотреть, что такое SELinux, зайти под рутом и первым делом набрать rm -rf /* всенепременнейше, позапускать всяких скриптов/сплойтов и руткитов, в общем, показать этим АНБ кузькину мать. Но прежде, чем вы займетесь этим увлекательным делом, давайте еще раз пробежимся как по допущениям, так и по ограничениям:
В рамках этого обучающего курса, мы:
  • Считаем, что root доступ к серверу получил кто-угодно.
  • Считаем, что ему можно входить по ssh и запускать интерактивный shell.
  • Считаем, что root у нас незамаплен на user_u, как это сделал Russell Coker в своей play machine. Разумеется, этого допущения не рекомендуется делать в production (как и все предыдущие, конечно-же :-)
  • Считаем, что мы не кастомизировали ядро (нет grsec, это я решил не включать в статью и тесты)
  • Считаем, что у нас почти нет фаервола.
Если и есть в ИБ термин для состояния безопасности, в описании которого есть слова «раздвигать» и «булки», то это именно оно. Все, что отделяет от полной компрометации, это SELinux. Компрометация неизбежна, но очень интересно, за какое время.
Но так-же, есть то, для чего SELinux не преднасначен, а именно:
  • SELinux это не средство ограничения ресурсов. Он не спасет от :(){ :|:& };:. Поэтому мне пришлось настроить небольшую защиту от fork bombs, но все-же — не пробуйте; в лучшем случае, вас кикнет и вы больше не сможете зайти, в худшем — если будете проявлять упрство — то и другие не смогут зайти, пока я все не почищу.
  • SELinux это не средство защиты от атак на другие ресурсы. Поэтому мне пришлось ограничить доступ наружу с демо сервера. Если вы сможете продемонстрировать, как вы отключаете SELinux или iptables — вы большой молодец, но к следующей версии я это поправлю. Скорее всего, это косяк не SELinux, а мой :-)
  • Мы рассматриваем сервер в минимальной конфигурации, там нет компиляторов/дебагеров и всего того, чего обычно на проде итак не бывает. Версия полноценной MLS Play Machine будет позже, когда я разверну это не на VPS, а на более контроллируемой инфраструктуре. Зато есть scp — можно интересное копировать.
  • И да, в лучших традициях организации, разработавшей SELinux, на сервере заодно тестируется запись консольных сеансов :-) А то сами понимаете, NSA, Аризона, Area51 недалеко, а тут рутовый доступ. Надо писать, вдруг мне туда центральных процессоров понапихают вагон. Снимете запись — вы тоже большой молодец, и тоже напишите в комментарии.
  • 0day — на ваше усмотрение. Если всплывет, я конечно буду польщен. Хотя, кому я рассказываю :-)

Домен я не заводил, это для игрушки версии 0.0.2. Версия 0.0.1
тут
специально ссылку не даю: http://162.213.198.69
И да, отдельная просьба — please behave. Не надо убивать все рутовые процессы и мешать другим, пользователь — один на всех.

Примечания

* Tim Minchin, Lullaby
Поделиться публикацией

Похожие публикации

Комментарии 31

  • НЛО прилетело и опубликовало эту надпись здесь
      0
      Зачем все эти сложности, когда есть простой AppArmor? en.wikipedia.org/wiki/AppArmor
        0
        Зачем сложный AppArmor, когда можно взять mac?

        Но я ждал этого вопроса. Жаль что у меня не получилось донести что это не «сложно», а скорее просто «долго», и делать это имеет смысл только если точно определена необходимость.
        По сути: я считаю что это, во-первых, совершенно разные средства (особенно в контексте MLS), и во-вторых, когда AppArmor научится полноценной поддержке FLASK, то он обречен превратиться в SELinux.
        +2
        мне вот другое интересно — есть ли места, где это всё действительно используется и настраивается для реальных, а не «галочных» целей?

        статья отличная и дико интересная, но вот практического применения на одном отдельно взятом linux-сервере (а это не мейнфрейм, прошу заметить) ей я, к сожалению, не наблюдаю.
          0
          Применяется там, где есть соответствующие требования или заказчик хочет уровень «не ниже чем». Список конечно не смогу привести — ни одна организация не будет кичиться своими плюшками в ИБ. Trusted Solaris вон тоже был «сложен» в настройке, но многим нравился.
          По умолчанию SELinux в режиме Enforcing вроде как в следующем RHEL планируют, но я за RH не сильно слежу, могу путать. Тоже и про Android 4.4 — чем сейчас принципиально смартфон отличается от linux-сервера? У меня VPSка слабее :-)

          Один маленький сервер был выбран исключительно из соображений объема статьи.
            +1
            не, я не о том. Списки не нужны, порядки тоже, я прекрасно понимаю деликатность данных вопросов.

            собственно, разговор только о том, используется ли оно в самом деле так, как должно использоваться или нет :)
              0
              Я бы сказал что да.

              Разумеется, настройка «для галочки» возможна для любого ПО. Но есть ненулевое множество систем с настройками намного более жесткими, чем описанные в статье.
                +1
                ну, настройки-настройками, но я вполне себе вижу реальную ситуацию, когда замначальника идёт к админу и говорит ему «вытащи срочно такой-то документ из каталога шефа, вот тебе он на телефоне», и админ лезет и с фейспалмами рушит тщательно выстроенную систему привилегий и ограничений…

                вот если есть системы, для которых такая ситуация невозможна — тогда и в самом деле это отлично :)
                  0
                  А, в этом смысле. Ну тогда я просто вопрос не так понял и в дебри полез.
                  На самом деле, в организации либо есть ролевая модель, и подобная ситуация просто не может быть, либо модели нет. Это не техническая проблема, ее софтом не решить.

                  В качестве аналогии — та же ситуация, только нужен файл, шифрованный приватным ключем шефа. И что, админ должен на такие случаи копии всех приватных ключей держать у себя?
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      С деталями-то ясно, а доступ-то к хранилищу тоже по звонку или все-таки процедуры специальные есть?
                      Я не знаю как нетехнические задачи решать техническими средствами в общих случаях, вот честно :-)
                      • НЛО прилетело и опубликовало эту надпись здесь
              0
              В RHEL/CentOS 6 версии selinux включен по умолчанию в enforcing. Правда, его дефолтные политики не дадут даже зайти по ssh по ключу.
                0
                Не удивлюсь, если это решается через setsebool.
                Может быть у RH получится продавить на пользователей strict, в целом это позитивное изменение для Linux, его глупо не поддерживать.
                Ну а самое интересное (MCS+), как обычно, все равно останется для узкого круга.
                  +2
                  Не удивлюсь, если это решается через setsebool.

                  Вполне возможно. Я не сисадмин, а программист, вынужденный иногда заниматься серверами.

                  Понятно, что можно потратить некоторое количество времени, разобраться с selinux, написать пачку политик под испольуземый софт. Но зачем мне это на dev или staging?

                  Поэтому первой делом выполняется
                  setenforce 0
                  sed 's/SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux
                  

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

                  Также и для большинства задач MCS и MLS являются сильно избыточными и не оправдывают затраты на их настройку и поддержание. Удел любой специализированной системы.
                • НЛО прилетело и опубликовало эту надпись здесь
                    +1
                    Проверил на чистой виртуалке.

                    CentOS 6 по умолчанию ограничивает только сетевые сервисы. Т. е. пользователи имеют профиль unconfined_u.

                    Проблема: обычные пользователи могут логиниться по ключу, root — нет.

                    В audit.log видно следующее:
                    type=AVC msg=audit(1384247101.011:82): avc:  denied  { read } for  pid=1531 comm="sshd" name="authorized_keys" dev=dm-0 ino=9382 scontext=system_u:system_r:sshd_t:s0-s0:c0.c1023 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file
                    


                    Понятно, что это сделано для того, чтобы скомпроментированный sshd не утащил чего-нибудь из /root (которая admin_home_t). Для того, чтобы всё работало нормально — достаточно проставить/восстановить на /root/.ssh правильный контекст.

                    % restorecon -R -v /root/.ssh/
                    restorecon reset /root/.ssh context unconfined_u:object_r:admin_home_t:s0->unconfined_u:object_r:ssh_home_t:s0
                    restorecon reset /root/.ssh/authorized_keys context unconfined_u:object_r:admin_home_t:s0->unconfined_u:object_r:ssh_home_t:s0
                    


                    Почему это не сделано по умолчанию — не понятно. О том, что надо проставить такой контекст в CentOS SELinux howto ни слова.

                    По идее, в случае user_home_t доступа тоже быть не должно и пользователь должен также проставлять контекст ssh_home_t на $HOME/.ssh. А нет, для user_home_t такого ограничения нет. Непонятно, почему при компроментации sshd доступ к admin_home_t будет ограничен, а к user_home_t — нет?

                    Поэтому обычный человек через 10 минут сделает setenforce 0.

                    Стоит сказать, что я не админ.
                      0
                      Почему это не сделано по умолчанию — не понятно.

                      Объясняю: если у вас получилось восстановить контексты, используя restorecon, и при этом вы руками их не дополняли, значит откуда-то эту информацию restorecon получил? :-)
                      А получил он ее из контекста, определенного для директории ~/.ssh/.
                      Теперь вопрос — а почему файл ключа имел контекст admin_home_t? Потому что контекст наследуется от процесса, который этот файл создал. Признайтесь, вы же руками создавали ключ, верно?
                      Плюс, есть restorecond — он вполне мог отледить и выровнять контекст для /home/user/.ssh, но не сделать этого для /root/.ssh, от чего могло сложиться впечатление что SELinux по разному разрешает вход для рута и для пользователя, или защищает от кражи ключа. Да и зачем компрометировать sshd, чтобы украсть public часть ключа?
                      Опять же, вышеописанное — мои предположения, причины вполне могут быть и другими.

                      Поэтому обычный человек через 10 минут сделает setenforce 0.

                      Если это ему разрешено. И если да — то зачем он вообще себя так ограничивал изначально?

                      Стоит сказать, что я не админ.

                      Статус данного звания несколько преувеличен :-)
                      Либо есть желание разобраться и понять, либо нет.
                        0
                        Признайтесь, вы же руками создавали ключ, верно?

                        Я проверил несколько вариантов:
                        1. scp, потом создание .ssh (с унаследованным admin_home_t), cp $key .ssh/authorized_keys (или cat $key >> .ssh/authorized_keys) и, как следствие, неправильный контекст у создаваемого authorized_keys
                        2. создание .ssh (с унаследованным admin_home_t), scp в него
                        3. создание .ssh, восстановление правильного контества ssh_home_t, cp/scp в него
                        4. ssh-copy-id при отсутствующем .ssh
                        5. создание .ssh (с унаследованным admin_home_t), ssh-copy-id

                        Варианты 3-5 приводят в нужному результату (логину root по ssh с использованием ключа).

                        На centos 6.4 restorecond установлен, но не запущен по умолчанию. И для $HOME/.ssh контекст остался user_home_t. Т. е. работают варианты 1-5. Соответственно, sshd имеет право читать файлы с контекстом user_home_t, что, согласитесь, выглядит странным.

                        Id est скомпрометированный sshd может прочитать произвольные пользовательские файлы (включая закрытые ssh и gpg ключи), если программы их создавшие не проставили контекст, недоступный sshd в силу каких-либо политик.

                        Если это ему разрешено. И если да — то зачем он вообще себя так ограничивал изначально?

                        Установленная чистая centos-minimal имеет такие ограничения (в моем кейсе). Это скорее к ссылкам типа stopdisablingselinux.com/.

                        Статус данного звания несколько преувеличен :-)
                        Либо есть желание разобраться и понять, либо нет.

                        Скорее, либо есть обоснованная необходимость, либо нет.

                        Администрирование для меня — вынужденное побочное занятие (поэтому я и предупреждаю, что не являюсь сисадмином), поэтому тратить время на настройку selinux (безусловно полезного в стабильном production-окружении) под часто меняющиеся (разрабатываемые) программы для меня нецелесообразно.

                        В качестве системы на рабочей станции у меня arch linux, но настройка selinux на нем также является очень затратной по времени (учитывая разработку софта). Последнее время склоняюсь в сторону qubes (с arch linux в качестве template vm), которая позволяет добиться изоляции проще и дешевле в плане настройки и поддержания.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Лучше, конечно, по паролю. На штук шесть dev-серверов во внутренней сети за файерволом))

                          А если серьезно, то, например, скрипты всяких cassandra/hadoop/hbase и прочих часто работают в изолированном доверенном домене. Из-под рута. И используют pssh, for+ssh или аналогичные методы; им необходим paswordless login. И здесь спасает исключительно контурная модель.

                          Плюс плодить пользователей для разработчиков не всегда удобно. Тем более, когда все всё-равно вынуждены регулярно повышать привелегии до рута. По хорошему, конечно, нужно настроить ldap/kerberos, сделать централизованный логин. Но когда выделенного админа нет, то все эти прекрасные мечты о нормальной инфраструктуре уходят в небытие.
                            +1
                            централизованный логин — это далеко не так страшно, как кажется. Тот же openldap настраивается под всю эту радость чуть ли не с полпинка.

                            там, ЕМНИП, даже дефолтная схема уже заточена под использование совместно в pam_ldap.

                            kerberos не нужен, если не нужен SSO (single sign-on) (а нужен он чуть более чем никогда).
                              0
                              спасибо, попробую
                                0
                                главное — делать логин из ldap sufficient в pam, а не required, иначе при отвале openldap ты не сможешь залогиниться совсем.
                                  0
                                  Это понятно)

                                  Как и классический совет: при настройке pam иметь терминал с залогиненным рутом.
                  • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  -missed-
                    0
                    > Ловим grub на этапе загрузки, меняем selinux=1 на selinux=0
                    А как защититься от этого?
                      0
                      Пароль на grub запретит изменения параметров загрузки.
                        0
                        LiveCD поможет. Но, на самом деле, речь конечно о защите от физического доступа.
                          +3
                          От физического доступа спасет шифрование диска, но с оговорками:
                          * вы должны убедиться что никто не подменил информацию на нешифрованном разделе /boot
                          * вы должны убедиться что в оперативке ничего не осталось
                          * вы должны убедиться что никаких хардварных модификаций не делалось
                          * и еще кучка подобных допущений в зависимости от уровня секретности/паранойи :-)

                          Rule of thumb: физический доступ означает компрометацию и точка.
                          Проценты случаев, где это не так, проще не рассматривать.

                          С угрозой физического доступа должна справляться физическая безопасность.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое