Стандартная изоляция контейнеров в Docker опирается на namespaces и cgroups и даёт только базовый уровень защиты. Она не блокирует попытки процессов внутри контейнера получить доступ к ресурсам хоста, обратиться к Docker-сокету или вызвать привилегированные системные операции. Уязвимость в приложении или ошибка в образе легко превращаются в плацдарм для атак на хостовую систему: от чтения чувствительных файлов до эскалации привилегий.
AppArmor решает эту проблему за счёт принудительной политики на уровне ядра Linux. Для каждого процесса можно задать профиль с явными разрешениями на файлы, сети, системные вызовы и устройства. В контексте Docker это позволяет точечно ограничивать контейнер: запретить запись в определённые каталоги, закрыть опасные capabilities, ограничить сетевые действия и взаимодействие с устройствами. Ниже — как создавать и применять такие профили в Ubuntu 24.04.
Как работает AppArmor
Механизм основан на профилях, которые описывают, что процессу дозволено. Всё, что не разрешено явно, блокируется. При запуске приложения ядро перехватывает системные вызовы и сравнивает их с правилами профиля. Нарушения фиксируются в журнале, а операция отменяется. Для Docker по умолчанию применяется встроенный профиль docker-default, который активируется, если при запуске контейнера не указан иной профиль.
Проверить состояние службы и загруженных профилей можно стандартными утилитами:
sudo systemctl status apparmor sudo aa-status
После правок профиль перезагружают в ядро:
sudo apparmor_parser -r /etc/apparmor.d/имя_профиля
Актуальные события удобно смотреть в журнале ядра:
sudo journalctl -k -f -g apparmor
Стандартный профиль Docker и когда его не хватает
docker-default закрывает часть типичных векторов атак: ограничивает доступ к критичным участкам файловой системы, запрещает монтирование, срезает ряд опасных действий в сети и по устройствам. Для сервисов с невысоким риском этого хватает. Но критичные приложения — API-шлюзы, платёжные компоненты, системы обработки персональных данных — требуют ужесточения правил под конкретную логику работы.
Проверить, какой профиль применён к работающему контейнеру, можно через инспекцию:
docker inspect --format '{{ .HostConfig.SecurityOpt }}'
Кастомный профиль: от шаблона к строгой политике
Инструмент aa-genprof предназначен для процессов на самом хосте и не видит изоляцию контейнера, поэтому для контейнеров проще начинать с минимального собственного профиля, подключив необходимые абстракции. Создаём файл /etc/apparmor.d/my-app со строгими правилами и типовыми исключениями.
#include profile my-app flags=(attach_disconnected,mediate_deleted) { # Базовые разрешения #include #include # Опасные возможности ядра deny capability sys_admin, deny capability sys_module, deny capability sys_ptrace, # Критичные области файловой системы и монтирование deny /etc/shadow rw, deny /etc/sudoers rw, deny /proc/*/mem r, deny /sys/kernel/security/** rw, deny mount, # Сеть network inet stream, network inet dgram, deny network raw, deny network inet6, # Временные каталоги и состояние /tmp/** rw, deny /tmp/** x, /var/tmp/** rw, /run/** rw, # Базовые устройства /dev/null rw, /dev/zero r, /dev/urandom r, # Исполняемые файлы /usr/bin/** mr, /bin/** mr, # Межпроцессные взаимодействия deny ptrace, deny signal, }
Загружаем профиль и переводим его в «обучающий» режим, чтобы посмотреть, чего не хватает под реальные сценарии:
sudo apparmor_parser -r /etc/apparmor.d/my-app sudo aa-complain /etc/apparmor.d/my-app
Стартуем контейнер с новым профилем и прорабатываем все типовые запросы приложения:
docker run --security-opt apparmor=my-app --rm -it your-application
На основе сгенерированных записей в журнале донастраиваем правила интерактивно:
sudo aa-logprof
Когда профиль собран, включаем принудительный режим:
sudo aa-enforce /etc/apparmor.d/my-app
Для контроля корректности продолжаем мониторить журнал:
sudo journalctl -k -f | grep apparmor
Продвинутая настройка: сети, файловая система, процессы
Строгий профиль ограничивает capabilities, сети и файловую систему ровно настолько, насколько это нужно приложению. Опасные возможности ядра — такие как sys_module, sys_time, sys_rawio, sys_ptrace — должны быть запрещены, если приложение их не требует. Сетевой доступ описывается по протоколам и типам сокетов; если IPv6 не используется, его стоит закрыть, а raw и netlink оставить под запретом. В файловой системе необходимо явно указать каталоги на запись и исполнение, запретить запуск из временных директорий, закрыть доступ к SSH-ключам, системным пользовательским базам и блочным устройствам. Для процессов имеет смысл запретить отправку сигналов наружу и любые попытки трассировки соседних процессов. При необходимости правила можно условно уточнять для отдельных бинарников — например, предоставить nginx доступ только к своим кэшу и логам и лишь к TCP-сокетам.
Ниже — пример компактного профиля под nginx в контейнере:
#include profile nginx-container { #include #include deny capability sys_module, deny capability dac_override, network inet stream, network inet dgram, deny network inet6, /usr/sbin/nginx mr, /var/log/nginx/** rw, /var/cache/nginx/** rw, /run/nginx.pid rw, deny /etc/shadow r, deny /root/** r, }
Применение профиля:
sudo apparmor_parser -r /etc/apparmor.d/containers/nginx-container sudo journalctl -k -f -g apparmor | grep -E "(DENIED|ALLOWED)"
Интеграция с Docker и Compose
При запуске контейнера указываем профиль через --security-opt:
docker run -d --name nginx-container \ --security-opt "apparmor=my-app" \ -p 80:80 \ nginx:latest
В Docker Compose профиль передаётся в параметре security_opt:
version: '3.8' services: web: image: nginx:latest security_opt: - apparmor=my-app ports: - "80:80"
Автоматизация обслуживания профилей
Чтобы переинициализировать набор профилей после правок, удобно использовать простой сервисный скрипт:
#!/bin/bash # /usr/local/bin/reload-apparmor-profiles.sh PROFILES_DIR="/etc/apparmor.d/containers/" for profile in "$PROFILES_DIR"*; do if [ -f "$profile" ]; then sudo apparmor_parser -r "$profile" echo "Reloaded profile: $(basename "$profile")" fi done
Сделайте его исполняемым и добавьте ночное обновление по cron:
sudo chmod +x /usr/local/bin/reload-apparmor-profiles.sh sudo crontab -l | { cat; echo "0 3 * * * /usr/local/bin/reload-apparmor-profiles.sh"; } | sudo crontab -
Вывод и практическая польза
AppArmor — надёжный уровень принудительной изоляции, который дополняет базовые механизмы Docker и закрывает критичные векторы атак на уровне ядра. В продакшене разумно сочетать docker-default для обычно-рисковых сервисов и кастомные строгие профили для критичных компонентов. Регулярный аудит журналов, обновление правил и автоматизация загрузки профилей поддерживают защиту в актуальном состоянии.
Если вы строите инфраструктуру на VPS/VDS и хотите, чтобы сегрегация сервисов, профили AppArmor и базовые политики безопасности были настроены правильно с первого дня, разворачивайте контейнерные проекты на серверах Webhost1 с NVMe-хранилищем и пред-настроенными рекомендациями по безопасности. Это экономит время на ручной отладке и снижает операционные риски без компромиссов по производительности.