Короткий ответ

На Ubuntu 22.04/24.04 AppArmor — основной MAC. DENIED читают из journalctl -k / dmesg, профиль правят (aa-logprof или ручной include), затем enforce. aa-complain — окно отладки с датой возврата. aa-disable и apparmor=0 — не решение. Не чините DENIED через chmod 777 и не путайте с SELinux (setenforce): SELinux. Хост host.example (10.0.20.10), пользователь admin.

Симптомы и как отличить

  • В логе приложения Permission denied, unix-mode нормальные;
  • apparmor="DENIED" operation="open" comm="mysqld" name="/var/lib/mysql-custom/";
  • после aa-complain usr.sbin.mysqld «всё заработало» и так оставили.
СлойКак отличить
unix 640stat, пользователь сервиса
UFWtimeout с сети, локально ок
AppArmorDENIED в kernel log
seccomp/Dockerконтейнер, --security-opt

Возможные причины

  1. Данные переехали в /srv, профиль знает только /var/lib.
  2. Обновление пакета ужесточило профиль.
  3. Свой бинарь без профиля работает unconfined — наоборот, не confined.
  4. Overlay в контейнере vs хостовый aa.
  5. Кто-то aa-disable в прошлом инциденте.
  6. Tunable не включён (@{PROC} и т.п. — не выдумывайте, смотрите /etc/apparmor.d/tunables).

Диагностика

sudo aa-status
sudo systemctl is-enabled apparmor
sudo dmesg --ctime | grep -i DENIED | tail -n 30
sudo journalctl -k --since '1 hour ago' | grep -i apparmor

Какой профиль у процесса:

ps -eZ | grep -E 'nginx|mysql|myservice'
sudo aa-status | grep -A2 'enforce mode'

Файлы профилей: /etc/apparmor.d/. Отключённые лежат в disable/ симлинками.

ls /etc/apparmor.d/disable/ 2>/dev/null
sudo apparmor_parser -Q /etc/apparmor.d/usr.sbin.nginx 2>/dev/null || true

Права unix всё равно проверьте: 777. Сервис от root маскирует часть проблем: User=.

Решение

Сценарий A. Недостающий путь в профиле пакета

Временное окно:

sudo aa-complain /usr/sbin/mysqld
# воспроизвести ошибку
sudo dmesg | grep DENIED | tail
sudo aa-logprof
sudo aa-enforce /usr/sbin/mysqld
sudo aa-status | grep mysqld

aa-logprof интерактивен: принимайте только нужные path, не «allow all». Локальные дельты лучше в /etc/apparmor.d/local/usr.sbin.mysqld, чтобы пакет не затёр.

Пример идеи local (путь свой):

# /etc/apparmor.d/local/usr.sbin.mysqld
/srv/mysql/data/** rwk,
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld

Сценарий B. Свой сервис без профиля

Создайте профиль aa-genprof /opt/myservice/bin/app на копии/staging, прогоните сценарии, enforce. Не оставляйте unconfined навсегда «потому что сложно». Минимальный профиль лучше отсутствия, если вы его прочитали.

Сценарий C. Кто-то выключил профиль

sudo rm /etc/apparmor.d/disable/usr.sbin.nginx
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
sudo aa-enforce /usr/sbin/nginx

Проверьте, что сайт жив, затем оставьте enforce.

Сценарий D. Docker

Хостовый AppArmor может вешать профиль на контейнер (docker --security-opt apparmor=...). apparmor=unconfined в compose — понижение защиты. Чините профиль, не unconfined навсегда. Сокет хоста: docker.sock.

Разбор строки DENIED. Поля profile=, operation=, name= (путь), requested_mask= (r/w/k/m), comm=. Часто винят nginx, а comm="php-fpm8.3" — другой профиль (www-data vs /usr/sbin/php-fpm8.3). Правите тот файл в /etc/apparmor.d/, который совпал с profile=.

r vs w vs k (lock) vs m (mmap/exec). Для лог-файла нужен w, для библиотек mr. Не ставьте rwlkix на / «чтобы заработало».

Пакетные профили после apt-get upgrade могут перезаписать /etc/apparmor.d/usr.sbin.mysqld, но не должны затирать /etc/apparmor.d/local/*, если вы туда положили дельту. Проверьте, что include <local/usr.sbin.mysqld> есть в основном профиле (в штатных пакетах Ubuntu обычно да). Если вы правили основной файл — при апдейте потеряете и получите новые DENIED или наоборот дыру.

aa-complain на время отладки запишите в тикет с at/systemd-run --on-active=2h напоминанием вернуть enforce. Без таймера complain живёт до следующего инцидента.

Snap vs classic: snap-сервисы несут свои профили. DENIED с snap.foo не лечится правкой /etc/apparmor.d/usr.sbin.nginx. Смотрите snap connections.

Отладка без отключения MAC: aa-notify -s 1 (пакет apparmor-notify) на staging. На проде — journal. Не apparmor=0. Параллельный SELinux не нужен.

Если DENIED на unix сокет между nginx и php-fpm — разрешите конкретный путь сокета в обоих профилях, не unix, без границ если можете избежать.

После parser -r существующие процессы могут держать старый профиль до restart. Перезапустите unit в окно, не только parser.

Профили в /etc/apparmor.d/abstractions/ не копируйте в свой сервис целиком без чтения: abstractions/base нужен, abstractions/user-tmp может открыть /tmp/** шире политики. Лучше явные пути данных.

aa-status «processes are unconfined» для bash админа — норма. Смотрите mysqld, nginx, containerd. После внедрения нового бинаря в /opt добавьте профиль в тот же sprint, не «потом». В git храните local-include, не надейтесь, что live-сервер — источник истины: следующий apt затрёт ручные правки основного файла.

Если DENIED только при systemctl start и нет при ручном запуске от того же User — отличаются environment и systemd sandbox (ProtectHome, PrivateTmp). Сверяйте systemd-analyze security myservice как подсказку, но не включайте все 50 галочек сразу вместе с новым AppArmor.

Типичные ложные «выключим AppArmor» кейсы. PHP не пишет в wp-content/uploads — сначала uid пула и unix-права, DENIED будет с name= этого пути. MySQL data на отдельном LV /srv/mysql — штатный профиль знает /var/lib/mysql; local include обязателен. Nginx alias вне /var/www — то же. Docker storage driver overlayfs иногда даёт DENIED на хостовом runc; это профиль docker, не «сломался mysql».

aa-status «0 processes are in complain mode» — цель после отладки. Ненулевой complain без тикета = регрессия.

На 24.04 пакет apparmor-utils нужен для aa-complain/aa-logprof; на минимальном образе его может не быть. Поставьте utils, не отключайте MAC из-за отсутствия команды.

Профиль unconfined для /opt/myservice/bin/app означает, что baseline MAC на ваш бинарь не действует. Запланируйте genprof на staging, не считайте «нет DENIED» победой.

Не путайте systemctl stop apparmor (снимет профили до reboot в части сценариев) с лечением. Unit должен быть enabled. Политика: любой disable apparmor в тикете отклоняется, как и ufw disable.

Как проверить, что проблема устранена

sudo aa-status
sudo journalctl -k --since '10 min ago' | grep -i DENIED || echo 'no denied'
systemctl is-active apparmor
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1/

Профиль сервиса в enforce. Нет свежих DENIED при рабочей нагрузке. apparmor.service enabled. Firewall/SELinux не «выключены» как побочный эффект.

Если не помогло

  • DENIED на capabilities: в профиле capability, не User=root.
  • DENIED ptrace от сканера — не allow всем ptrace.
  • Профиль не грузится: синтаксис apparmor_parser -r -v.
  • Контейнер snap vs classic — разные профили.
  • Это был UFW: timeout, нет DENIED.

Профилактика

  • Local includes в git.
  • После apt смотреть новые DENIED сутки.
  • Запрет disable профилей в политике.
  • Staging с тем же aa-status.

FAQ

complain vs disable

complain пишет события и пускает. disable не ограничивает. Оба не цель. Цель enforce.

aa-unconfined показывает bash

Интерактивные оболочки часто unconfined. Смотрите долгоживущие сервисы.

Нужен ли SELinux параллельно?

На Ubuntu — нет. Не включайте второй MAC без проекта.

logprof предлагает /proc/**

Не принимайте слепые broad rules. Сверяйте comm= в DENIED.

После parser -r сервис всё равно DENIED

Не тот профиль (имя бинаря vs profile name), или кэш контейнера, или unix-права.