AppArmor: профили для сервисов и разбор блокировок
Если SELinux в Ubuntu и Debian не используется, то это не значит, что обязательного контроля доступа там нет. Роль защитника исполняет AppArmor: он включён по умолчанию в Ubuntu и в современных Debian, а профили для самых популярных сервисов поставляются вместе с пакетами. Идея проста и более прозрачна, чем у SELinux: вместо меток на файлах AppArmor привязывает профиль к пути исполняемого файла и перечисляет, какие файлы, сети и возможности этой программе разрешены. Всё остальное запрещено. В этой статье мы разберём AppArmor на примере srv-web01 и srv-db01 из домена itpages.lab.local (сеть 192.168.10.0/24): посмотрим состояние, поймаем блокировку, создадим собственный профиль для приложения и научимся читать отказы.
Главная разница двух подходов в том, как они «видят» объекты. SELinux работает по меткам: файл с неправильной меткой недоступен независимо от того, где он лежит. AppArmor работает по путям: правило говорит «программа /usr/sbin/nginx может читать /srv/site/**». Поэтому профили AppArmor проще писать и читать глазами, но они зависят от расположения файлов: если ты перенёс каталог, правило нужно обновить. Профиль может работать в двух режимах: enforce (нарушения блокируются и записываются в журнал) и complain (нарушения только записываются, программа продолжает работать). Режим complain удобен для обкатки нового профиля.
Шаг 1. Проверяем состояние AppArmor
Для начала убедимся, что подсистема загружена и посмотрим, какие профили действуют:
sudo aa-status
apparmor module is loaded.
34 profiles are loaded.
32 profiles are in enforce mode.
/usr/bin/man
/usr/sbin/mysqld
/usr/sbin/tcpdump
docker-default
nginx-custom
2 profiles are in complain mode.
/usr/sbin/nginx
3 processes have profiles defined.
3 processes are in enforce mode.
/usr/sbin/mysqld (1422)
0 processes are unconfined but have a profile defined.
В выводе видно, сколько профилей в каком режиме и какие процессы сейчас под защитой. Полезная строка — «processes are unconfined but have a profile defined»: она означает, что профиль есть, но процесс запущен раньше, чем профиль загружен, и защиты на нём нет. Решается перезапуском сервиса. Если утилиты aa-status нет, установи пакеты apparmor и apparmor-utils; кроме того, apparmor-profiles и apparmor-profiles-extra содержат дополнительные готовые профили.
Профили лежат в каталоге /etc/apparmor.d/, и имя файла строится из пути программы, где слэши заменены точками: профиль для /usr/sbin/mysqld называется usr.sbin.mysqld. Компоненты, которые используются многими профилями, лежат в подкаталогах abstractions/ и tunables/. Посмотри на каталог, чтобы понимать, что уже есть:
ls /etc/apparmor.d/
abstractions local tunables usr.bin.man usr.sbin.mysqld usr.sbin.tcpdump
ls /etc/apparmor.d/local/
usr.sbin.mysqld
Каталог local/ предназначен именно для твоих правок: туда добавляют локальные дополнения к штатным профилям, которые не затираются при обновлении пакетов. Штатный файл профиля лучше не править напрямую.
Шаг 2. Типичный отказ: база данных на другом диске
Классическая история на srv-db01: данные MySQL переносят с системного диска на отдельный том /data/mysql, правят datadir в конфигурации, а сервер при старте падает. Права на каталог выставлены верно, владелец mysql, а запуск всё равно не удаётся:
sudo systemctl restart mysql
Job for mysql.service failed because the control process exited with error code.
sudo journalctl -u mysql -n 5 --no-pager
mysqld: [ERROR] Can't create/write to file '/data/mysql/ibdata1' (Errcode: 13 - Permission denied)
Errcode 13 — отказ в доступе, но права файловой системы в порядке. Это подсказка, что виноват AppArmor. Подтверждение лежит в журнале ядра:
sudo journalctl -k | grep -i apparmor | tail -2
audit: type=1400 audit(1760351021.411:97): apparmor="DENIED" operation="open" profile="/usr/sbin/mysqld" name="/data/mysql/ibdata1" pid=2210 comm="mysqld" requested_mask="rwk" denied_mask="rwk" fsuid=114 ouid=114
Читаем сообщение: apparmor=DENIED, операция open, профиль /usr/sbin/mysqld, имя файла /data/mysql/ibdata1, запрошенные права rwk (чтение, запись, блокировка). Профиль mysqld просто не знает про новый каталог данных. На системах с auditd те же сообщения лежат в /var/log/audit/audit.log, а без него в /var/log/syslog или через dmesg. Для удобного разбора есть утилита aa-notify, а на десктопах — всплывающие уведомления.
Шаг 3. Правим профиль правильно
Вместо того чтобы отключать профиль, дополним его через локальный файл, который переживёт обновления. Открываем /etc/apparmor.d/local/usr.sbin.mysqld и добавляем правила для нового каталога:
sudo tee -a /etc/apparmor.d/local/usr.sbin.mysqld <<'EOF'
/data/mysql/ r,
/data/mysql/** rwk,
EOF
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld
sudo systemctl restart mysql
sudo systemctl is-active mysql
active
Разберём синтаксис. Строка /data/mysql/ r, разрешает чтение самого каталога. Шаблон /data/mysql/** rwk, охватывает всё внутри на любую глубину и выдаёт чтение, запись и блокировку файлов. Каждое правило заканчивается запятой, это частая причина синтаксических ошибок. Команда apparmor_parser -r перезагружает профиль без перезапуска системы. Права обозначаются буквами: r чтение, w запись, a дозапись, k блокировка, x исполнение, l создание жёстких ссылок, m отображение в память с исполнением.
Шаг 4. Создаём профиль для собственного приложения
Теперь сценарий поинтереснее. На srv-web01 работает самописный сервис /opt/itpages-app/bin/app, который читает конфиг и пишет логи. Профиля у него нет, поэтому он работает без ограничений. Создадим профиль в интерактивном режиме утилитой aa-genprof:
sudo aa-genprof /opt/itpages-app/bin/app
Writing updated profile for /opt/itpages-app/bin/app.
Setting /opt/itpages-app/bin/app to complain mode.
Before you begin, you may wish to check if a
profile already exists for the application you
wish to confine. See the following wiki page for
more information: https://gitlab.com/apparmor/apparmor/wikis/Profiling_with_tools
Profiling: /opt/itpages-app/bin/app
[(S)can system log for AppArmor events] / (F)inish
Не закрывая это окно, открой второе и по-максимуму упражни приложение: запусти, перезапусти, вызови все функции, которые оно выполняет. Затем вернись в первое окно и нажми S: утилита прочитает журнал и предложит для каждого обращённого ресурса разрешить, запретить или поправить правило. Например, она спросит, разрешить ли чтение /etc/itpages-app/config.yml и запись в /var/log/itpages-app/. Отвечай осознанно: разрешай только то, что приложению действительно необходимо. Когда закончишь, нажми F и получи готовый файл:
cat /etc/apparmor.d/opt.itpages-app.bin.app
#include <tunables/global>
/opt/itpages-app/bin/app {
#include <abstractions/base>
#include <abstractions/nameservice>
network inet stream,
/etc/itpages-app/config.yml r,
/opt/itpages-app/bin/app mr,
/var/log/itpages-app/*.log w,
/run/itpages-app.pid rw,
}
Профиль читается как список разрешений: подключаем базовые абстракции, разрешаем сетевые TCP-соединения IPv4, чтение конфигурации, запись логов и PID-файла. Всё, что здесь не названо, запрещено. Обрати внимание на строку network inet stream: без неё программа не сможет открыть сокеты, даже если файлы разрешены. Профиль можно также дописать вручную, а синтаксис проверить командой apparmor_parser -Q -T /etc/apparmor.d/opt.itpages-app.bin.app.
Шаг 5. Режимы complain и enforce в работе
Новый профиль всегда сначала запускай в complain, дай приложению поработать пару дней в реальной обстановке и только потом включай enforce. Переключение выполняют утилиты из пакета apparmor-utils:
sudo aa-complain /opt/itpages-app/bin/app
Setting /opt/itpages-app/bin/app to complain mode.
# после наблюдения: разбор накопившихся событий и обновление профиля
sudo aa-logprof
sudo aa-enforce /opt/itpages-app/bin/app
Setting /opt/itpages-app/bin/app to enforce mode.
Команда aa-logprof читает журнал, находит события, которые профиль не покрывает, и предлагает дополнить правила. Это основной инструмент поддержки: после обновления приложения или добавления новой функции запусти aa-logprof в режиме complain, обнови профиль и снова переведи его в enforce. Если нужно полностью отключить профиль на время расследования (а это крайняя мера), используй aa-disable, а после работ верни защиту через aa-enforce:
sudo aa-disable /opt/itpages-app/bin/app # профиль выгружен, ссылка в disable/
sudo aa-enforce /opt/itpages-app/bin/app # вернуть защиту
Шаг 6. Профили для сервисов systemd и контейнеров
Для сервисов, которыми управляет systemd, иногда удобно привязать профиль прямо в юните, не полагаясь на путь исполняемого файла. Для этого служит директива AppArmorProfile в override-файле юнита:
sudo systemctl edit itpages-app.service
[Service]
AppArmorProfile=opt.itpages-app.bin.app
sudo systemctl daemon-reload
sudo systemctl restart itpages-app
ps -eZ | grep app
/opt/itpages-app/bin/app (enforce) 3107 ? 00:00:02 app
Колонка с меткой в выводе ps -Z показывает профиль и его режим: так быстро проверить, что процесс действительно под защитой. Аналогично работает защита контейнеров: Docker по умолчанию подключает профиль docker-default, и в списке aa-status ты увидишь его. Для особых случаев в запуск добавляется опция —security-opt apparmor=имя_профиля. Одна из полезных практик — отдельный профиль на каждый внешний сервис: nginx, mysqld, и то, что смотрит в сеть.
Шаг 7. Расширенные возможности профилей
Когда базовые правила освоены, пригодятся приёмы, которые делают профили гибче. Первый: переменные. В /etc/apparmor.d/tunables/ определены, например, @{HOME} и @{PROC}, а свои переменные добавляются в tunables/home.d или в локальные файлы. Это удобно, когда пути различаются между серверами: правило /data/mysql/** можно заменить на @{MYSQL_DATA}/** и переопределять переменную на каждом хосте отдельно. Второй: дочерние профили. Правило Px, Cx или Ux в профиле определяет, в каком режиме запускаются порождённые программы. Px запускает потомка под его собственным профилем, Cx под дочерним профилем внутри текущего, а Ux снимает ограничения вообще и поэтому применяется только в исключительных случаях.
/usr/bin/convert Cx -> convert,
profile convert {
#include <abstractions/base>
/usr/bin/convert mr,
/tmp/upload/** r,
/var/cache/app/** rw,
}
Третий приём: запрет. В AppArmor есть правило deny, которое перекрывает любые разрешения и дополнительно подавляет сообщения в журнале. Например, deny /etc/shadow rwklx, надёжно закрывает доступ даже при ошибке в абстракциях. Четвёртый: возможности (capabilities). Профиль может разрешить только нужные: capability net_bind_service, позволяет занимать привилегированные порты, а все остальные возможности root остаются недоступными.
Теперь про сопровождение. Профили имеет смысл хранить в системе контроля версий и раскладывать по серверам инструментом конфигурационного управления вроде Ansible, а не править руками на каждом хосте. Для проверки перед выкладкой используй apparmor_parser -Q -T: он разберёт файл и сообщит об ошибках без загрузки в ядро. Для регулярного контроля добавь в мониторинг проверку aa-status —json: она вернёт структуру, из которой видно, не исчезли ли профили и не перешёл ли какой-то из них в complain после чьей-то отладки.
sudo aa-status --json | python3 -c 'import sys,json; d=json.load(sys.stdin); print(len(d["profiles"]), "profiles")'
34 profiles
И последнее предостережение: AppArmor не заменяет остальные слои. Он ограничивает последствия взлома конкретной программы, но не исправляет уязвимость, не заменяет обновления и не отменяет нормальную модель прав и фаервол. Рассматривай его как ремень безопасности: пригодится, когда что-то пошло не так, но ехать без тормозов всё равно не стоит.
Частые ошибки и как их избежать
- Править штатный профиль вместо local/. Обновление пакета перезапишет твои изменения, и через месяц сервис снова упадёт. Складывай дополнения в /etc/apparmor.d/local/ и перезагружай профиль через apparmor_parser -r.
- Сразу включать enforce на новом профиле. Недообученный профиль режет нужные операции в самый неподходящий момент. Сначала aa-complain, потом наблюдение в течение нескольких дней, aa-logprof для доработки и лишь затем aa-enforce.
- Забыть запятую и перенос каталога. Каждое правило заканчивается запятой, а правила привязаны к путям: если каталог данных переехал, профиль нужно обновить. Проверяй синтаксис командой apparmor_parser -Q -T и ищи DENIED в journalctl -k после любых переносов.
Что дальше
Администрирование Linux — это набор связанных навыков, вот соседние темы серии: