Статья по безопасности Docker (Docker Security Cheat Sheet)
Введение
Docker — самая распространённая технология контейнеризации. При грамотной настройке безопасность может быть выше, чем при запуске приложений напрямую на хосте. Однако ошибки конфигурации снижают уровень защиты или добавляют новые уязвимости.
Цель этой статьи — дать простой перечень типичных ошибок и лучших практик для защиты Docker-контейнеров.
Правила
ПРАВИЛО №0 — держите хост и Docker в актуальном состоянии
Чтобы защититься от известных уязвимостей выхода из контейнера (например, Leaky Vessels), после которых атакующий часто получает root на хосте, необходимо регулярно обновлять и хост, и Docker Engine, в том числе ядро хоста.
Контейнеры используют ядро хоста: если ядро уязвимо, уязвимы и контейнеры. Например, эксплойт повышения привилегий в ядре Dirty COW, выполненный внутри изолированного контейнера, на уязвимом хосте всё равно может привести к root на хосте.
ПРАВИЛО №1 — не открывайте сокет демона Docker (даже контейнерам)
Сокет Docker /var/run/docker.sock — это UNIX-сокет, на котором слушает демон. Это главная точка входа в Docker API. Владелец сокета — root. Доступ к сокету эквивалентен неограниченному root-доступу к хосту.
Не включайте TCP-сокет демона Docker. Запуск с -H tcp://0.0.0.0:XXX или аналогом открывает нешифрованный и неаутентифицированный доступ к демону; при выходе хоста в интернет демоном на вашей машине может воспользоваться кто угодно. Если без этого совсем никак — защитите канал по официальной документации Docker.
Не монтируйте /var/run/docker.sock в другие контейнеры. Если образ запускается с -v /var/run/docker.sock:/var/run/docker.sock, измените подход. Монтирование только для чтения не решает проблему, а лишь усложняет эксплуатацию. В Compose это может выглядеть так:
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
ПРАВИЛО №2 — задайте пользователя
Запуск контейнера от непривилегированного пользователя — лучший способ снизить риск эскалации привилегий. Три основных варианта:
- Во время запуска — опция
-uуdocker run, например:
docker run -u 4000 alpine
- При сборке образа — пользователь в Dockerfile:
FROM alpine
RUN groupadd -r myuser && useradd -r -g myuser myuser
# <ЗДЕСЬ ДЕЙСТВИЯ ОТ ROOT: УСТАНОВКА ПАКЕТОВ И Т.Д.>
USER myuser
- Включить пользовательские пространства имён (
--userns-remap=default) в настройках демона Docker.
Подробнее — в документации Docker. Дополнительно можно использовать rootless-режим (см. правило №11).
В Kubernetes это задаётся в Security Context полем runAsUser:
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: example
image: gcr.io/google-samples/node-hello:1.0
securityContext:
runAsUser: 4000 # UID процесса в поде
Администратор кластера может задать жёсткий профиль по умолчанию уровня Restricted через встроенный Pod Security admission; при необходимости тонкой настройки — Admission Webhooks или сторонние решения.
ПРАВИЛО №3 — ограничивайте capabilities (выдавайте только нужные)
Capabilities ядра Linux — набор привилегий для привилегированных операций. Docker по умолчанию запускает контейнер с подмножеством capability. Можно ужесточить образ, отбрасывая лишние (--cap-drop) или добавляя необходимые (--cap-add). Не запускайте контейнеры с флагом --privileged — он добавляет все capabilities ядра.
Наиболее безопасная схема: --cap-drop all и затем точечный --cap-add:
docker run --cap-drop all --cap-add CHOWN alpine
Не используйте --privileged!
В Kubernetes — в Security Context, поле capabilities:
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: example
image: gcr.io/google-samples/node-hello:1.0
securityContext:
capabilities:
drop:
- ALL
add: ["CHOWN"]
Администратор кластера снова может опереться на уровень Restricted и Pod Security admission, при необходимости — webhooks и альтернативы из документации Kubernetes.
ПРАВИЛО №4 — запретите повышение привилегий внутри контейнера
Запускайте образы с --security-opt=no-new-privileges, чтобы блокировать получение новых привилегий через бинарники с setuid/setgid.
В Kubernetes — поле allowPrivilegeEscalation в Security Context:
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: example
image: gcr.io/google-samples/node-hello:1.0
securityContext:
allowPrivilegeEscalation: false
Снова имеет смысл профиль Restricted и встроенный admission controller по ссылкам выше.
ПРАВИЛО №5 — учитывайте связность между контейнерами
Межконтейнерная связность (icc) по умолчанию включена: все контейнеры могут общаться через мост docker0. Вместо полного отключения --icc=false на демоне чаще лучше задать отдельные пользовательские сети Docker и явно подключать к ним только нужные контейнеры.
Подробнее — документация Docker по сетям.
В Kubernetes для изоляции трафика между подами используются Network Policies; Network Policy Editor упрощает составление правил.
ПРАВИЛО №5a — осторожно с пробросом портов на хост при UFW и аналогах
UFW — популярный хостовый фаервол в Linux. Часто думают, что правила UFW защищают весь входящий трафик, включая контейнеры. На самом деле Docker сам управляет iptables/nftables и обходит UFW. Другие инструменты на базе iptables/nftables могут вести себя схоже — проверяйте фактическое поведение.
При публикации порта -p 8000:8000 Docker добавляет правила, открывающие порт на всех интерфейсах и для всех источников, и они часто срабатывают раньше явных DENY в фаерволе хоста. Сервис в контейнере может оказаться доступен из интернета вопреки ожиданиям.
Рекомендуемые меры
Вариант 1 — привязка только к localhost: укажите 127.0.0.1 на стороне хоста в маппинге порта:
# Небезопасно: порт на всех интерфейсах
docker run -p 8000:8000 myimage
# Безопаснее: только localhost
docker run -p 127.0.0.1:8000:8000 myimage
В Docker Compose:
services:
web:
image: myimage
ports:
- "127.0.0.1:8000:8000" # только localhost
Вариант 2 — ufw-docker или аналог, чтобы согласовать правила фаервола с сетями Docker. Для UFW проект ufw-docker добавляет правила, с которыми UFW начинает учитывать трафик к контейнерам:
sudo ufw-docker install
sudo ufw-docker allow mycontainer 8000/tcp
Детальнее о взаимодействии Docker и фаервола — документация Docker.
ПРАВИЛО №6 — Linux Security Module (seccomp, AppArmor, SELinux) для защиты в рантайме
Не отключайте профиль безопасности по умолчанию. Отправная точка — дефолтный профиль Docker или дистрибутива.
Рекомендации по профилям:
- Seccomp — ограничьте syscalls минимально необходимым набором; за основу возьмите дефолтный профиль Docker и доработайте под нагрузку. Docker Seccomp
- AppArmor — профили на контейнер для mandatory access control. Docker AppArmor
- SELinux — включите на хосте и корректно размечайте контейнеры. SELinux и Docker
Усиление в рантайме:
- Мониторинг поведения — Falco, Tetragon, Cilium (eBPF) для аномалий: неожиданные exec, попытки эскалации, нехарактерные сетевые соединения.
- Детекция аномалий — непрерывный контроль процессов, ФС и сети контейнера.
- Security Context в Kubernetes — seccomp и AppArmor в манифестах: туториал Kubernetes.
ПРАВИЛО №7 — лимитируйте ресурсы (память, CPU, дескрипторы, процессы, рестарты)
Против DoS помогают лимиты: память, CPU, число перезапусков (--restart=on-failure:<n>), лимиты файловых дескрипторов (--ulimit nofile=<n>) и процессов (--ulimit nproc=<n>). Документация по ulimit.
В Kubernetes: память, CPU, расширенные ресурсы.
ПРАВИЛО №8 — только для чтения: корневая ФС и тома
Корневая ФС контейнера только для чтения — флаг --read-only:
docker run --read-only alpine sh -c 'echo "whatever" > /tmp'
Если приложению нужен временный writable слой, совместите --read-only с --tmpfs:
docker run --read-only --tmpfs /tmp alpine sh -c 'echo "whatever" > /tmp/file'
Эквивалент в Compose:
version: "3"
services:
alpine:
image: alpine
read_only: true
В Kubernetes — Security Context:
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: example
image: gcr.io/google-samples/node-hello:1.0
securityContext:
readOnlyRootFilesystem: true
Тома, которые должны быть только для чтения, монтируйте с суффиксом :ro:
docker run -v volume-name:/path/in/container:ro alpine
Или --mount:
docker run --mount source=volume-name,destination=/path/in/container,readonly alpine
ПРАВИЛО №9 — сканирование образов в CI/CD
Конвейеры CI/CD — ключевая часть жизненного цикла; в них стоит включать линтеры, SAST и сканирование контейнеров.
Много проблем снимается хорошим Dockerfile; дополнительно подключите security-linter в пайплайн. Часто проверяют:
- задана ли директива
USER; - закреплена ли версия базового образа;
- закреплены ли версии пакетов ОС;
- предпочтение
COPYвместоADD; - отсутствие «curl | bash» в
RUN.
Ссылки: Docker Baselines (DevSec), CLI Docker, Compose CLI, драйверы логирования, просмотр логов, практики безопасности Dockerfile.
Сканеры образов выявляют известные уязвимости, секреты и misconfiguration. Примеры:
- бесплатные: Clair, ThreatMapper, Trivy;
- коммерческие и гибридные: Snyk, Anchore Grype, Docker Scout, JFrog XRay, Qualys.
Секреты в образах: ggshield, SecretScanner.
Misconfiguration в Kubernetes: kubeaudit, kubesec.io, kube-bench.
Misconfiguration в Docker: InSpec, dev-sec.io, Docker Bench for Security.
ПРАВИЛО №10 — уровень логов демона Docker: info
По умолчанию базовый уровень логирования демона — info. Проверьте /etc/docker/daemon.json на ключ log-level; если его нет, действует значение по умолчанию. Флаг --log-level при запуске демона переопределяет файл. Проверка фактического уровня:
ps aux | grep '[d]ockerd.*--log-level' | awk '{for(i=1;i<=NF;i++) if ($i ~ /--log-level/) print $i}'
Уровень info и выше фиксирует нужные события без шума отладочных логов; уровень debug включайте только при необходимости.
ПРАВИЛО №11 — rootless-режим Docker
Rootless: демон и контейнеры работают от непривилегированного пользователя — при выходе из контейнера атакующий не получает root на хосте. Это не то же самое, что userns-remap, где демон по-прежнему может требовать root.
Оцените поверхность атаки и модель угроз. Если безопасность критична, а ограничения rootless приемлемы — режим рекомендуется. Альтернатива — Podman.
Rootless позволяет запускать демон и контейнеры без root и снижает риск при уязвимостях рантайма. Установка демона в rootless не требует root при выполнении предварительных условий.
Подробности, ограничения и установка — документация Docker.
ПРАВИЛО №12 — Docker Secrets для чувствительных данных
Secrets в Swarm дают способ хранить пароли, токены и ключи без засвета в образе или аргументах командной строки.
docker secret create my_secret /path/to/super-secret-data.txt
docker service create --name web --secret my_secret nginx:latest
Compose (пример):
version: "3.8"
secrets:
my_secret:
file: ./super-secret-data.txt
services:
web:
image: nginx:latest
secrets:
- my_secret
В Kubernetes «секреты по умолчанию» в etcd — отдельная тема: шифрование etcd, внешние хранилища и т.д. См. Secrets Management Cheat Sheet.
ПРАВИЛО №13 — усиление безопасности цепочки поставки
На базе правила №9 добавьте:
- происхождение образа (provenance);
- SBOM (перечень компонентов образа);
- подпись образов;
- доверенный реестр с жёстким контролем доступа;
- безопасное развёртывание — проверка образов, рантайм-защита, мониторинг.
Podman как альтернатива Docker
Podman — OCI-совместимый открытый инструмент (Red Hat) с CLI, совместимым с Docker, и средствами управления контейнерами. Ориентирован на более безопасные умолчания. Среди плюсов для ИБ:
- Без центрального демона — в отличие от
dockerd, Podman использует модель fork/exec: при старте контейнера процесс форкается и exec в рантайм. - Rootless — та же модель упрощает запуск без root от обычного пользователя.
- Интеграция с SELinux — дополнительный слой mandatory access control.
Ссылки и дальнейшее чтение
© Перевод на русский язык. Оригинальные материалы: OWASP Cheat Sheet Series.
Этот проект использует материалы OWASP, распространяемые по лицензии CC BY-SA 4.0.