AppArmor в Linux: песочница для сервисов в 50 строк профиля
Привет! Я, Семёнов Евгений Сергеевич, директор ITFresh. Хочу поделиться нашим опытом. Знаете, если на каком-нибудь домашнем сервере ещё можно махнуть рукой на LSM-подсистему ядра, то на продакшене такая роскошь просто недопустима. Мы в нашей практике каждый день видим, как AppArmor эффективно защищает сервисы клиентов. Он помогает избежать очень серьёзных проблем: представьте, блокирует последствия RCE в PHP-библиотеке, не пропускает shell-инъекции в webhook-скриптах и не даёт "убежать" за пределы Docker-контейнеров через их volume. В этой статье я расскажу вам, как именно мы подходим к написанию и сопровождению AppArmor-профилей, чтобы сервер не превратился в какую-то непонятную, неотлаживаемую крепость, с которой потом никто не разберётся.
Mandatory Access Control простыми словами
Классические права Linux (rwx+owner+group) — это DAC, дискреционное управление. Владелец решает, кому можно. MAC устроен иначе: политику назначает система, и даже root не может её обойти. AppArmor — одна из реализаций LSM-хуков, которая говорит ядру: «процесс nginx может читать /etc/nginx/, писать только в /var/log/nginx/, открывать только 80/443 — остальное блокировать». Если злоумышленник пролез через CVE в PHP-FPM, AppArmor не даст ему прочитать /etc/shadow, даже если пользователь www-data получит временный root.
Где AppArmor уже работает и где его надо включать
Кстати, приятный момент: в Ubuntu Server и Debian (версии 11/12) AppArmor уже стоит по умолчанию. Он даже включен для nginx, mysql, cups, tcpdump и ещё пары десятков других пакетов. Хотите убедиться сами? Вот как проверить его состояние:
sudo aa-status
# 34 profiles are loaded.
# 22 profiles are in enforce mode.
# /usr/sbin/cupsd
# /usr/sbin/nscd
# ...
# 12 profiles are in complain mode.
# 8 processes have profiles defined.
А если у вас RHEL-семейство, например, AlmaLinux или Rocky? Там уже свой "сторож" – SELinux. Мы настоятельно не советуем ставить туда AppArmor. Почему? Всё очень просто: две MAC-подсистемы в одном ядре – это рецепт для катастрофы, они просто не уживаются. Так что выбирайте, что вам ближе по духу.
Анатомия профиля
Профиль — текстовый файл в /etc/apparmor.d/. Читается легко, даже если не знаешь синтаксиса. Вот пример для самописного Python-демона invoicer:
# /etc/apparmor.d/opt.invoicer.bin.invoicer
#include <tunables/global>
@{INVOICER_HOME} = /opt/invoicer
@{INVOICER_LOG} = /var/log/invoicer
/opt/invoicer/bin/invoicer {
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
capability net_bind_service,
network tcp,
network udp,
@{INVOICER_HOME}/** r,
@{INVOICER_HOME}/bin/** mrix,
@{INVOICER_HOME}/var/** rwk,
@{INVOICER_LOG}/** rw,
/etc/invoicer/config.yaml r,
/etc/ssl/certs/** r,
/proc/*/stat r,
/proc/*/status r,
/sys/kernel/mm/transparent_hugepage/enabled r,
deny @{HOME}/** rwkx,
deny /etc/shadow r,
deny /root/** rwkx,
}
Суть: демону разрешено делать только то, что явно перечислено. Всё остальное — запрет. Правила deny здесь декоративные (итак неявно запрещено), но я их пишу специально — чтобы в audit-логе видеть попытки, даже если кто-то потом случайно расширит профиль.
aa-genprof: автогенерация под реальную нагрузку
Писать профиль с нуля — путь долгий. Я делаю так: запускаю приложение в learning-режиме, выполняю типовые сценарии (старт, обработка запроса, бэкап), и утилита aa-genprof сама строит правила.
sudo apt install apparmor-utils
sudo aa-genprof /opt/invoicer/bin/invoicer
# Параллельно в другом окне — запускаем демон и прогоняем тесты
# После каждой операции — возвращаемся в aa-genprof и нажимаем S (scan)
# Утилита предлагает варианты: Abstraction? Include? Glob? Allow/Deny?
# Когда нагрузочный прогон пройден — F (Finish), профиль сохраняется
У нас есть железное правило: перед тем как перевести профиль в режим `enforce`, мы держим его в `complain mode` как минимум неделю. Зачем так долго? За это время успевают спокойно отработать все редкие сценарии: ротация логов, еженедельные cron-задачи или, скажем, разовые операции из админки. Всё, что системе действительно нужно, она "покажет", а мы потом просто добавляем соответствующие правила.
| Режим | Что делает | Когда использовать |
|---|---|---|
| enforce | Блокирует запрещённое, пишет в лог | Продакшен после обкатки |
| complain | Разрешает всё, пишет в лог | Обучение, отладка, недельный прогон |
| disable | Профиль выгружен из ядра | Диагностика конкретной проблемы |
| audit | Enforce + логирование разрешённых операций | Форензика, расследование |
Реальный кейс: взлом Bitrix и последствия
В ноябре 2024 года к нам прибежал клиент — оптовая компания в Подольске, на их Битриксе 24.0 нашли свежий webshell. Атакующий сидел уже четыре дня, прочитал базу, успел залить nested-эксплойт в backup-папку. Что его остановило — у нас на этом сервере был AppArmor-профиль для PHP-FPM, который запрещал запись куда-либо, кроме /var/www/site/upload/ и /tmp. Попытки сделать exec shell-скрипта из /tmp аккуратно ломали закладки злоумышленника — в audit.log мы позже разобрали 47 таких попыток.
Без AppArmor ущерб был бы в разы больше: читать почту из /var/spool/mail, подложить SSH-ключ в /root/.ssh/authorized_keys, установить крон — все эти сценарии упёрлись в MAC. Выложить пост-инцидентный разбор клиент не разрешил, но в ключевом месте там одна строчка: «Ущерб: утечка данных клиентской базы (1220 записей), простой сайта 4 часа. Стоимость реагирования — 92 000 руб». Без AppArmor было бы тысяч 400 и две недели работ.
AppArmor + systemd: изящная интеграция
systemd умеет требовать AppArmor-профиль для юнита — и это отличная страховка! Представьте: кто-то случайно выгрузил профиль, а потом сервис опять запустил. systemd не даст этому случиться.
# /etc/systemd/system/invoicer.service
[Service]
ExecStart=/opt/invoicer/bin/invoicer
AppArmorProfile=opt.invoicer.bin.invoicer
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
Проще говоря: нет профиля — нет старта. systemd просто откажется запускать сервис. Это как раз то, что нужно для параноидального продакшена, верно?
AppArmor в Docker и LXC
Docker по умолчанию применяет профиль docker-default. Если нужен свой — генерируете, загружаете и указываете в запуске:
sudo apparmor_parser -r -W /etc/apparmor.d/docker-nginx
docker run --security-opt apparmor=docker-nginx -p 80:80 nginx:stable
Для LXC правило ещё проще — одна строчка в конфиге контейнера /var/lib/lxc/webhost/config:
lxc.apparmor.profile = lxc-container-restricted
Отладка: разбор audit-логов
Итак, профиль в `enforce`, но вдруг что-то перестало работать? Куда смотреть в первую очередь? Конечно, сюда:
sudo dmesg | grep DENIED | tail
sudo journalctl -k | grep apparmor="DENIED"
# или парсим аккуратно:
sudo aa-logprof
Утилита aa-logprof предложит добавить правила для каждого зафиксированного DENY — остаётся только подтвердить или отклонить. Я всегда делаю так: в staging-среде прогнали тест-набор, просмотрели денаи, сформировали финальный профиль, выкатили в прод.
Чек-лист внедрения AppArmor на новом сервере
- Включить
apparmorв параметрах GRUB (apparmor=1 security=apparmor) — на Debian иногда забывают. - Проверить
aa-status, убедиться, что стандартные профили загружены. - Для каждого самописного сервиса — сгенерировать профиль через
aa-genprof, недельный complain, потом enforce. - В systemd-юнитах всех собственных демонов прописать
AppArmorProfile=. - Настроить logrotate для
/var/log/audit/и alert в Zabbix/LibreNMS на слово DENIED в последние 5 минут. - Очень важный момент: все AppArmor-профили обязательно храните в вашем репозитории Ansible или Salt. Ведь при восстановлении сервера вы же хотите, чтобы вместе с ним вернулись и все политики безопасности? Без этого просто никуда.
Закроем Linux-сервисы по правилам безопасности
Внедрим AppArmor или SELinux на рабочие сервера — под ключ, с отладкой и полгода гарантии стабильной работы профилей. Поможем с форензикой, если на сервере уже инцидент: разберём audit-логи, найдём каналы и точки входа.
Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш
FAQ — AppArmor на практике
- AppArmor или SELinux — что выбрать?
- AppArmor использует пути файлов, SELinux — метки inode. AppArmor проще читать и править, его профили похожи на fstab. SELinux мощнее, но входной порог выше. В Ubuntu и Debian по умолчанию AppArmor, в RHEL/CentOS — SELinux.
- Насколько AppArmor замедляет систему?
- На типичных веб-нагрузках разница меньше 2% — в пределах погрешности. На I/O-интенсивных приложениях вроде Ceph OSD лучше замерить до и после.
- Как быстро потушить AppArmor при инциденте?
- Правильно: перевести конкретный профиль в complain mode через aa-complain. Не трогать всю подсистему.
- AppArmor работает с Docker и LXC?
- Да. Docker идёт со встроенным профилем docker-default, LXC — с lxc-container-default. Оба можно переопределить.
- Что делать, если приложение пишет в непредсказуемые пути?
- Использовать tunables и glob-шаблоны. Объявляете @{MYAPP_DATA}=/srv/myapp и в профиле пишете @{MYAPP_DATA}/** rw.
