Перейти к содержанию

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 используются токены с ограниченными правами.


Дополнительные материалы