Найти в Дзене
WebHOST1.ru

Docker и AppArmor: закрываем опасные права и защищаем VPS

Стандартная изоляция контейнеров в Docker опирается на namespaces и cgroups и даёт только базовый уровень защиты. Она не блокирует попытки процессов внутри контейнера получить доступ к ресурсам хоста, обратиться к Docker-сокету или вызвать привилегированные системные операции. Уязвимость в приложении или ошибка в образе легко превращаются в плацдарм для атак на хостовую систему: от чтения чувствительных файлов до эскалации привилегий. AppArmor решает эту проблему за счёт принудительной политики на уровне ядра Linux. Для каждого процесса можно задать профиль с явными разрешениями на файлы, сети, системные вызовы и устройства. В контексте Docker это позволяет точечно ограничивать контейнер: запретить запись в определённые каталоги, закрыть опасные capabilities, ограничить сетевые действия и взаимодействие с устройствами. Ниже — как создавать и применять такие профили в Ubuntu 24.04. Механизм основан на профилях, которые описывают, что процессу дозволено. Всё, что не разрешено явно, блоки
Оглавление

Стандартная изоляция контейнеров в 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-хранилищем и пред-настроенными рекомендациями по безопасности. Это экономит время на ручной отладке и снижает операционные риски без компромиссов по производительности.