Как повысить безопасность Docker-контейнеров

Kate

Administrator
Команда форума
Контейнеры Docker уже довольно давно стали неотъемлемой частью инструментария разработчика, позволяя собирать, распространять и развертывать приложения стандартизированным способом.

Неудивительно, что при такой популярности наблюдается всплеск проблем безопасности, связанных с контейнеризацией. Также контейнеры представляют собой стандартизированную поверхность атаки: злоумышленники могут легко эксплуатировать ошибки в конфигурации и проникать из контейнера в хост-машину.

Кроме того, термин "контейнер" часто понимается неправильно: многие разработчики склонны ассоциировать концепцию изоляции с ложным чувством безопасности, полагая, что эта технология безопасна по своей сути.

Ключевым моментом здесь является то, что конфигурация контейнеров по умолчанию не ориентирована на безопасность. Их защищенность полностью зависит от:

  • сопутствующей инфраструктуры (ОС и платформа);
  • встроенных в них программных компонент;
  • конфигурации в рантайме.
Безопасность контейнеров — это очень обширная тема, но хорошая новость состоит в том, что многие лучшие практики лежат на поверхности и их очень просто использовать для уменьшения поверхности атаки.

Именно поэтому мы подготовили рекомендации по сборке и запуску Docker-контейнеров.

e40dcfa0dc7db17c2d5a5c4c1a5236e2.png

Скачать шпаргалку по безопасности Docker

Примечание: в управляемой среде, такой как Kubernetes, большинство этих параметров могут быть переопределены контекстом безопасности или другими правилами безопасности более высокого уровня. Подробнее здесь.

Сборка образа​

Проверьте свои образы​

Внимательно выбирайте базовый образ, когда выполняете docker pull image:tag

Лучше всегда использовать проверенный образ, предпочтительно из Docker Official Images, чтобы смягчить атаки на цепочки поставок (supply-chain attacks).

В качестве базового дистрибутива рекомендуется выбирать Alpine Linux, поскольку это один из самых легких доступных дистрибутивов, что обеспечивает уменьшение поверхности атаки.

Что лучше: использовать версию с фиксированным тегом или latest?

Необходимо понимать, что теги Docker работают от менее специфичных к более специфичным, например:

python:3.9.6-alpine3.14

python:3.9.6-alpine

python:3.9-alpine

python:alpine
Все эти теги относятся к одному и тому же образу (на момент написания статьи).

Указывая более конкретную версию и фиксируя ее, вы защищаете себя от любых будущих критических изменений (breaking change). С другой стороны, использование последней версии гарантирует исправление большего количества уязвимостей. Это компромисс. Хорошая практика — привязка к стабильной версии.

Учитывая это, мы бы выбрали python:3.9-alpine.

Примечание: то же самое относится к пакетам, устанавливаемым в процессе сборки вашего образа.

Всегда используйте непривилегированного пользователя​

По умолчанию процесс внутри контейнера запускается от root (id = 0).

Для реализации принципа наименьших привилегий вы должны настроить пользователя. Сделать это можно двумя способами:

  • Указать с помощью параметра -u произвольный ID пользователя, которого нет в запущенном контейнере:
docker run -u 4000 <image>
Примечание: если впоследствии вам понадобится смонтировать файловую систему, то для получения доступа к файлам вам потребуется сопоставить используемый ID пользователя с пользователем хоста.
  • Или заранее создать пользователя в Dockerfile:
FROM <базовый образ>
RUN addgroup -S appgroup </span>
&& adduser -S appuser -G appgroup

USER appuser
... <продолжение Dockerfile> ...
Примечание: для управления пользователями и группами используйте утилиты, входящие в ваш базовый образ.

Используйте отдельный User ID namespace​

По умолчанию демон Docker использует User namespace хоста. Следовательно, любое успешное повышение привилегий внутри контейнера будет также означать получение root-доступа как к хосту, так и к другим контейнерам.

Чтобы снизить этот риск, необходимо настроить хост и демон Docker на использование отдельного пространства имен с помощью параметра --userns-remap. Подробнее здесь.

Внимательно обращайтесь с переменными окружения​

Никогда не указывайте конфиденциальную информацию в открытом виде в директиве ENV. Это место небезопасно для хранения информации, которую вы не хотите видеть в последнем слое образа. Например, если вы думаете, что использование unset следующим образом обеспечит вам безопасность.

ENV $VAR
RUN unset $VAR
Вы ошибаетесь! $VAR все еще будет присутствовать в контейнере и может быть получен в любое время!

Чтобы предотвратить доступ к переменной в рантайме, используйте одну команду RUN для установки и очистки переменной в одном слое (не забывайте, что переменную все еще можно извлечь из образа).

RUN export ADMIN_USER="admin" \
&& ... \
&& unset ADMIN_USER
Лучше используйте директиву ARG (значения ARG недоступны после создания образа).

К сожалению, секреты слишком часто жёстко закодированы в слоях Docker-образов, поэтому для их поиска мы разработали инструмент сканирования, использующий движок секретов GitGuardian:

ggshield scan docker <image>
Подробнее о сканировании образов на наличие уязвимостей в другой статье.

Не предоставляйте доступ к сокету демона Docker​

Если вы не уверены абсолютно в том, что делаете, никогда не открывайте UNIX-сокет, который слушает Docker: /var/run/docker.sock

Это основная точка входа для Docker API. Предоставление доступа к нему равносильно предоставлению неограниченного root-доступа к вашему хосту.

Никогда не открывайте его другим контейнерам:

-v /var/run/docker.sock://var/run/docker.sock

Привилегии, возможности (capabilities) и общие ресурсы​

Во-первых, ваш контейнер никогда не должен запускаться как привилегированный, иначе у него будут все права root на хост-машине.

Лучше явно запретите добавление новых привилегий после создания контейнера с помощью опции --security-opt=no-new-privileges.

Во-вторых, capabilities — это механизм Linux, используемый Docker для превращения двоичной дихотомии "root / не root" в детализированную систему контроля доступа: ваши контейнеры запускаются с определенным набором capabilities по умолчанию, которые, скорее всего, вам все не нужны.

Рекомендуется не использовать capabilities по умолчанию, а удалить их все и явно указать только нужные: см. список capabilities по умолчанию.

Например, веб-серверу, вероятно, потребуется только NET_BIND_SERVICE для привязки к порту ниже 1024 (например, к порту 80).

В-третьих, не расшаривайте чувствительные части хостовой файловой системы:

  • корень (/);
  • устройства (/dev);
  • процессы (/proc);
  • виртуальные точки монтирования (/sys).
Если вам нужен доступ к устройствам хоста, будьте внимательны и выборочно включите доступ с помощью флагов [r|w|m] (чтение, запись и используйте mknod).

Используйте контрольные группы для ограничения доступа к ресурсам​

Контрольные группы (Control Groups, cgroup) — это механизм, используемый для управления доступом контейнеров к процессору, памяти и операциям ввода-вывода.

По умолчанию для контейнера выделяется отдельная cgroup, но если вы укажете параметр --cgroup-parent, то подвергните ресурсы хоста риску DoS-атаки, поскольку появляются разделяемые ресурсы между хостом и контейнером .

По той же причине рекомендуется ограничивать использование памяти и процессора с помощью параметров:

--memory=”400m”
--memory-swap=”1g”

--cpus=0.5
--restart=on-failure:5
--ulimit nofile=5
--ulimit nproc=5
Подробнее об ограничении ресурсов см. здесь

Файловая система​

Запретите изменение корневой файловой системы​

Контейнеры должны быть эфемерными и без состояния. Поэтому часто файловую систему можно монтировать только для чтения.

docker run --read-only <image>

Используйте временную файловую систему​

Если вам нужно только временное хранилище, то используйте соответствующий параметр.

docker run --read-only --tmpfs /tmp:rw ,noexec,nosuid <image>

Долговременное хранение данных​

Для долговременного хранения данных у вас есть два варианта:

  • монтирование каталогов хоста (bind mount) с ограничением доступного пространства (--mount type=bind, o=size)
  • использование томов (volume) (--mount type=volume).
В обоих случаях, если контейнер не должен изменять данные, монтируйте его только для чтения.

docker run -v <volume-name>:/path/in/container:ro <image>
или

docker run --mount source=<volume-name>,destination=/path/in/container,readonly <image>

Сеть​

Не используйте bridge-интерфейс по умолчанию docker0​

docker0 — это сетевой мост, который создается автоматически и используется для изоляции сети хоста от сети контейнера.

По умолчанию контейнеры подключаются к сети docker0 и могут взаимодействовать друг с другом.

Лучше всегда отключать это поведение по умолчанию через параметр --bridge=none, и создавать отдельные сети для каждого соединения с помощью команды:

docker network create <network_name>

docker run --network=<network_name>
Простой пример сети Docker
Простой пример сети Docker
Например, для веб-сервера, взаимодействующего с базой данных (запущенной в другом контейнере), лучше всего создать bridge-сеть WEB для маршрутизации входящего трафика с сетевого интерфейса хоста и еще один bridge — DB для связи между контейнерами веб-сервера и базы данных.

Не используйте network namespace хоста​

То же самое, изолируйте сетевой интерфейс хоста: не используйте host-сеть (--net=host).

Логирование​

По умолчанию уровень логирования — INFO, но вы можете указать другой с помощью параметра:

--log-level="debug"|"info"|"warn"|"error"|"fatal"
Менее известна возможность экспорта логов Docker: можно перенаправить потоки STDERR и STDOUT на внешний сервис логирования, используя параметр --log-driver=<logging_driver>

Также можно настроить двойное логирование, чтобы при использовании внешнего сервиса сохранить доступ Docker к логам. Если ваше приложение пишет логи в специальные файлы (обычно в /var/log), то их тоже можно перенаправить (см. официальную документацию).

Сканирование на уязвимости и секреты​

И последнее, но не менее важное: надеюсь, теперь стало понятно, что ваши контейнеры настолько безопасны, насколько безопасно выполняемое в них программное обеспечение. Для проверки на уязвимости есть много разных инструментов как платных, так и бесплатных.

Сканеры уязвимостей:

Сканирование секретов:

  • ggshield (open source, доступна бесплатная версия)
  • SecretScanner (бесплатная)
 
Сверху