8. Безопасность Docker
Цель темы: понимать реальные риски при работе с контейнерами: чем контейнер отличается от VM с точки зрения изоляции, что такое privileged-режим и capabilities, как ограничивают системные вызовы (seccomp) и доступ (AppArmor/SELinux), зачем rootless Docker, как обращаться с секретами и сканировать образы.
Определения терминов
Контейнер ≠ VM (напоминание)
Контейнер использует то же ядро, что и хост; изоляция обеспечивается namespaces и cgroups. При уязвимости ядра или при достаточных привилегиях процесс в контейнере теоретически может выйти за пределы контейнера или повлиять на хост. Виртуальная машина работает на отдельном ядре в гипервизоре, изоляция сильнее. Нельзя полагаться на контейнер как на «полноценную VM» с точки зрения безопасности.
Privileged-контейнер
Privileged (--privileged) — контейнер получает почти все capabilities и отключаются многие ограничения (в т.ч. часть seccomp). По сути процесс в контейнере может делать почти то же, что root на хосте: монтировать устройства, менять сеть, получать доступ к устройствам. Использовать только в исключительных сценариях (например, отдельные инструменты для хоста) и на доверенных образах.
Capabilities (Linux)
Capabilities — разбиение прав root на отдельные возможности (например, CAP_NET_RAW для raw-сокетов, CAP_SYS_ADMIN для админ-операций). По умолчанию Docker убирает часть capabilities у контейнера; можно добавить или убрать явно: --cap-add, --cap-drop. Минимизация: сбрасывать все и добавлять только нужные (--cap-drop=ALL --cap-add=NET_BIND_SERVICE).
Seccomp
Seccomp (secure computing mode) — профиль ядра, ограничивающий набор системных вызовов, доступных процессу. Docker по умолчанию применяет к контейнерам профиль, блокирующий часть опасных вызовов. Можно задать свой профиль (--security-opt seccomp=profile.json) или отключить (не рекомендуется): --security-opt seccomp=unconfined.
AppArmor / SELinux
AppArmor и SELinux — системы принудительного контроля доступа (MAC). Ограничивают, какие файлы и ресурсы доступны процессу. Docker может применять профиль AppArmor к контейнеру; на дистрибутивах с SELinux часто используется контекст container_t. Дополнительная линия защиты поверх namespaces и capabilities.
Rootless Docker
Rootless mode — запуск демона Docker (dockerd) от непривилегированного пользователя. Контейнеры тоже работают без root на хосте; используются user namespaces и другие механизмы. Снижает последствия компрометации демона и контейнеров (нет полного root на хосте).
Секреты (secrets)
Секреты — пароли, ключи API, сертификаты. Не должны попадать в образы (не коммитить в репо, не копировать в образ через COPY). Передавать в контейнер через переменные окружения (из защищённого хранилища), монтирование файлов (tmpfs или read-only volume) или механизмы оркестратора (Docker Swarm secrets, Kubernetes Secrets).
Сканирование образов (image scanning)
Image scanning — проверка образов на известные уязвимости (CVE) в установленных пакетах и базовых образах. Инструменты: Docker Scout (ранее Docker Scan), Trivy, Clair, встроенные в registry (GitHub, GitLab). Используется в CI перед push и перед деплоем.
Можно ли «выйти» из контейнера?
Теоретически — да, при сочетании факторов:
- Уязвимость ядра — код в контейнере может использовать баг ядра и выйти из namespace или получить права на хосте.
- Privileged или избыточные capabilities — процесс может делать операции, ведущие к доступу к хосту (монтирование, доступ к устройствам).
- Монтирование чувствительных путей хоста — например,
/или/var/run/docker.sockдают возможность управлять хостом или демоном.
Поэтому контейнер не считается жёсткой изоляцией как VM. Меры: не использовать privileged без необходимости, минимизировать capabilities, не монтировать socket и корень хоста в контейнеры, обновлять ядро и базовые образы, использовать rootless где возможно.
Почему нельзя запускать Docker socket в контейнере
Docker socket (/var/run/docker.sock) — канал связи с демоном Docker. Тот, кто имеет к нему доступ, может вызывать Docker API: запускать и останавливать любые контейнеры, монтировать любые каталоги хоста, читать логи, подключаться к контейнерам. Если контейнер с приложением получает доступ к socket (через bind mount), скомпрометированное приложение или злоумышленник получает полный контроль над демоном и, по сути, над хостом (запуск privileged-контейнера с монтированием хоста).
Внимание
Никогда не монтируйте /var/run/docker.sock в контейнеры, если этот контейнер не является доверенным компонентом самой системы контейнеризации (например, отдельный служебный агент с минимальными правами). Для сборки образов в CI используйте Docker-in-Docker (DinD) или вынос сборки на отдельный runner с доступом к демону, но не давайте доступ к socket приложению из образа пользователя.
Privileged и capabilities
Privileged
docker run --privileged myimage
Эквивалентно большому набору capabilities и отключению части ограничений. В production избегать; только для специальных задач (сборка ядра, тесты драйверов и т.п.) на изолированных хостах.
Управление capabilities
# Убрать все и добавить только нужные
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
# Добавить одну (осторожно)
docker run --cap-add=CAP_NET_RAW app
В Dockerfile задать нельзя; только при запуске или в Compose: cap_drop: [ALL], cap_add: [NET_BIND_SERVICE].
Seccomp
Профиль по умолчанию отключает часть системных вызовов. Свой профиль (JSON) задаётся так:
docker run --security-opt seccomp=myprofile.json myimage
Отключение профиля (unconfined) сильно повышает риски — не использовать для производственных нагрузок.
AppArmor и SELinux
На хостах с AppArmor Docker может применять профиль docker-default. На RHEL/Fedora с SELinux контейнеры работают в контексте container_t; том по умолчанию монтируется с меткой svirt_sandbox_file_t. Переопределение контекста: --security-opt label=.... Для жёстких требований безопасности настраивают кастомные профили и контексты.
Rootless Docker
Установка и запуск демона от обычного пользователя (без root). Контейнеры изолированы через user namespace; часть функций ограничена (например, не все сетевые режимы). Подходит для разработки и окружений, где не хотят давать root на хосте. Инструкции: Docker Rootless mode.
Обращение с секретами
Чего избегать
- Не хранить пароли и ключи в образе (COPY, ENV в Dockerfile).
- Не коммитить файлы с секретами (.env с паролями) в репозиторий.
- Не передавать секреты в командной строке контейнера (видны в
docker inspect, в логах).
Что делать
- Переменные окружения из защищённого источника: CI/CD variables, vault; передавать в контейнер через
-eилиenv_fileс хоста, куда файл попал безопасно. - Монтирование файлов с секретами (сертификат, ключ) — read-only volume или tmpfs; права доступа только для нужного пользователя в контейнере.
- Docker Swarm secrets — в режиме Swarm секреты передаются в контейнер как файлы в RAM.
- Kubernetes Secrets — при деплое в K8s использовать Secret и монтировать как env или как файл.
Сканирование образов
Проверка на уязвимости перед использованием в production:
docker scout cves myimage:tag
trivy image myimage:tag
В CI: шаг после сборки образа; не пушить образы с критическими CVE или блокировать деплой. Регулярно обновлять базовые образы и пересобирать.
Практика
В пайплайне добавьте шаг сканирования (Trivy, Scout, встроенный в registry) и не допускайте в prod образы с критическими уязвимостями без плана по обновлению.
Паттерны использования
| Паттерн | Описание |
|---|---|
| Не использовать privileged | Только в исключительных сценариях на изолированных хостах. |
| Минимизировать capabilities | --cap-drop=ALL и добавлять только необходимые. |
| Не монтировать docker.sock | В прикладные контейнеры не давать доступ к демону. |
| Секреты только извне | Env/файлы из vault или CI; не в образ и не в репо. |
| Сканировать образы в CI | Блокировать или помечать образы с критическими CVE. |
| Rootless где возможно | Для dev и вспомогательных окружений снизить риски. |
Антипаттерны
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Монтировать docker.sock в приложение | Полный доступ к демону и хосту при компрометации. | Убрать mount; сборку и администрирование выносить на отдельные доверенные компоненты. |
| Запуск от root в контейнере без нужды | Усиливает последствия уязвимости в приложении. | USER в Dockerfile с непривилегированным пользователем. |
| Privileged «для удобства» | Фактически root на хосте. | Найти минимальный набор capabilities или пересмотреть задачу. |
| Секреты в ENV в Dockerfile | Попадают в образ и в историю слоёв. | Передавать секреты только в runtime (env, файлы). |
| Игнорировать CVE в образах | Известные уязвимости в production. | Сканировать, обновлять базовые образы, не деплоить критические CVE без митигации. |
Вопросы на собеседовании
Можно ли выйти из контейнера?
Теоретически да: при уязвимости ядра, при privileged или избыточных capabilities, при монтировании чувствительных ресурсов хоста (в т.ч. docker.sock). Контейнер не даёт такой же изоляции, как VM; нужны минимизация привилегий и жёсткая настройка.
Почему нельзя запускать Docker socket в контейнере?
Доступ к socket даёт полный доступ к Docker API: запуск любых контейнеров (в т.ч. privileged с монтированием хоста), чтение данных, управление сетью и томами. Скомпрометированное приложение в таком контейнере получает контроль над хостом. Socket не монтировать в прикладные контейнеры.
Примеры из production
Аудит образов перед деплоем
В CI после сборки образа запускают Trivy/Scout; при наличии критических CVE пайплайн падает или образ не тегируется для production. Отдельный процесс — регулярное обновление базовых образов и пересборка.
Секреты из vault
Секреты хранятся в HashiCorp Vault (или аналоге); при запуске контейнера или оркестратора секреты подставляются в env или во временные файлы. В образ и в репозиторий секреты не попадают.
Rootless на build-агентах
Сборка образов выполняется на агентах с rootless Docker: даже при компрометации сборки процесс не имеет root на хосте. Для доступа к registry используются токены с ограниченными правами.