Безопасность контейнеров на примере платформы Docker

akravchenko.jpgАКАлександр Кравченко

О безопасности платформы Docker

Платформа Docker используется для упрощения разработки, тестирования и развертывания приложений. Она позволяет разработчикам создавать контейнеры с приложениями и их зависимостями, которые могут быть легко перенесены между различными средами, такими как разработка, тестирование и производство. Платформа Docker — одно из самых распространенных средств контейнеризации приложений.

Контейнер — изолированное пространство, в котором запущено приложение со всеми необходимыми зависимостями.

Контейнеры обладают рядом преимуществ:

  • они запускаются быстрее и занимают меньше места, чем виртуальные машины;
  • можно отделить приложение от инфраструктуры и запускать его изолированно в контейнере;
  • благодаря изоляции на одном узле можно запустить множество контейнеров одновременно.

Основные компоненты контейнера Docker:

  • Host — это узел (компьютер или виртуальный сервер), на котором установлен Docker;
  • Docker daemon — центральный системный компонент, который управляет всеми процессами: созданием образов, запуском и остановкой контейнеров, скачиванием образов. Работает как фоновый процесс (демон) и постоянно следит за состоянием других компонентов;
  • Dockerfile — текстовый файл с инструкциями по сборке Docker-образа;
  • Docker image (образ) — файл, содержащий все необходимые компоненты для работы приложения (код, зависимости, параметры и т. д.);
  • Docker Hub — облачный сервис для хранения и распространения Docker-образов;
  • Docker client — инструмент для взаимодействия с Docker.

Визуально это можно представить следующим образом:

Рисунок 1. Структура Docker

Несмотря на преимущества, связанные с удобством использования контейнеров Docker, не стоит терять из фокуса вопросы, непосредственно связанные с безопасностью, а именно:

  1. Безопасность узла, на котором развертываются контейнеры.
  2. Состав образа и необходимость его скачивания из доверенных источников (чтобы исключить вероятность того, что образ может содержать вредоноcное ПО).
  3. Контроль привилегий, выдаваемых пользователям, и доступ к конфиденциальным данным;
  4. Предотвращение несанкционированного доступа к контейнеру и отдельным его компонентам.
  5. Обновление образов и контейнеров (чтобы убедиться в отсутствии уязвимостей).
  6. Мониторинг Docker-контейнеров (чтобы быстро выявлять и устранять уязвимости и предотвращать атаки).

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

Усиление безопасности контейнеров Docker

Основные требования и рекомендации по работе с контейнерами Docker касаются случаев, когда команда разработки самостоятельно развертывает приложение или выступает заказчиком работ для сторонней организации. Это связано с тем, что кибербезопасность решений «из коробки» в основном зависит от компетенций вендора, и на стороне заказчика остается всего несколько действий, о которых нельзя забывать:

  • контроль привилегий, с которыми запускается образ;
  • контроль доступа к узловым пространствам имен (namespaces);
  • сканирование контейнеров на наличие уязвимостей, ошибок и иных проблем с помощью соответствующего программного обеспечения (например, Trivy, Clair, Docker Bench for Security, Docker-bench);
  • обсуждение полученных результатов с вендором коробочного решения и устранение проблем. Имеет смысл взять модель угроз (MITRE или пример с GitHub) и учесть, что из этого вендор не покрывает в своем решении.

При самостоятельном развертывании контейнера следует учитывать:

1. Требование. Обеспечить безопасность узла

Узел, на котором разворачивается контейнер Docker, должен быть защищен. Отсутствие антивируса или его некорректная настройка, уязвимости устаревшего ПО, уязвимости операционной системы или некорректные параметры стороннего ПО — всем этим может воспользоваться злоумышленник. Требуется проводить сканирование узла на уязвимости и устранять выявленные проблемы безопасности.

2. Рекомендация. Использовать безопасные образы

Инфраструктура всей организации может быть скомпрометирована из-за использования в контейнере образа, содержащего вредоносное ПО. При выборе базового образа есть вероятность того, что его параметры будут содержать проблемы безопасности, такие как неправильно настроенные значения по умолчанию или уязвимые системные библиотеки. Злоумышленник может использовать эти недостатки для получения доступа к узлу, на котором развернут Docker, чтобы далее развить атаку вглубь инфраструктуры.

Определить уязвимости в используемых библиотеках контейнера можно с помощью сканеров уязвимостей, например Trivy. Для определения соответствия конфигурации контейнеров общепринятым best practices можно использовать Docker Bench for Security.

Хорошая практика — использование минимального базового образа, насколько это возможно: например, Alpine Linux — один из самых легких доступных образов. Если проекту не требуются общие системные библиотеки или системные утилиты, следует избегать использования полнофункциональной операционной системы в качестве базового образа. Кроме того, требуется определить подход к загрузке контейнеров из регистров контейнеров.

3. Рекомендация. Использовать версии с фиксированным тегом

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

  • python:3.9.6-alpine3.14;
  • python:3.9.6-alpine;
  • python:3.9-alpine;
  • python:alpine.

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

4. Требование. Использовать принцип минимальных привилегий

Чтобы уменьшить поверхность атаки, нужно не дать злоумышленнику получить привилегированный доступ в контейнере. Когда в Dockerfile не указан пользователь, по умолчанию для запуска контейнера используется root — специальный аккаунт с правами суперпользователя, который имеет доступ ко всем функциям операционной системы. На практике существует мало причин, по которым контейнер должен иметь привилегии такого уровня. Когда это пространство имен сопоставляется с суперпользователем root в работающем контейнере, это означает, что контейнер потенциально имеет root-доступ на узле Docker. Наличие в контейнере приложения, запущенного от имени суперпользователя, может привести к повышению привилегий. Необходимо создать для контейнера отдельного пользователя и запускать этот контейнер только с правами этого пользователя. Это можно сделать с помощью параметра \-u \<произвольный идентификатор пользователя\>, которого нет в запущенном контейнере:

docker run -u 4000 \<image\>

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

Кроме того, можно заранее создать пользователя в Dockerfile:

FROM \<базовый образ\>

RUN addgroup -S appgroup \</span\>

&& adduser -S appuser -G appgroup



USER appuser

... \<продолжение Dockerfile\> ...

Для управления пользователями и группами используйте утилиты, входящие в базовый образ.

5. Требование. Проверить подписи используемых образов

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

Требуется всегда проверять образы перед их извлечением, независимо от политики. Необходимо включить Docker Content Trust с помощью команды: export DOCKER_CONTENT_TRUST=1.

6. Требование. Безопасно хранить конфиденциальные данные

Нельзя хранить конфиденциальные данные (secrets), такие как токены, ключи, пароли, в Dockerfile, потому что эти данные кэшируются. При необходимости следует использовать команду secret для монтирования конфиденциальных файлов без их кэширования.

При копировании файлов в создаваемый образ команда COPY рекурсивно копирует всю папку контекста сборки в образ Docker, что может также привести к копированию конфиденциальных файлов. Если в папке есть конфиденциальные файлы (private.key; settings.json и т. д.), необходимо их удалить или использовать \*.\*dockerignore, чтобы игнорировать их.

Оптимальным решением для хранения конфиденциальной информации является использование сторонних утилит по типу HashiСorp Vault. Подробнее о работе с конфиденциальной информацией в Docker можно ознакомиться на официальном портале.

7. Рекомендация. Ограничить удаленный доступ

Нужно строго ограничить и тщательно контролировать удаленный доступ к контейнеру Docker, так как попытка удаленного подключения — одно из базовых действий злоумышленника при проведении атаки. Контроль доступа должен отдельно осуществляться для пользователей с разным уровнем привилегий на основе RBAC-модели, c помощью Service Mesh — механизма взаимодействия между сервисами на уровне сети; или же следует добавить ограничивающие правила в iptables.

8. Требование. Использовать отдельный User namespace

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

9. Рекомендация. Запретить доступ к сокету docker

Docker взаимодействует с сокетом домена UNIX, который называется /var/run/docker.sock*.* Это основная точка входа для Docker API. Предоставление доступа к нему равносильно предоставлению неограниченного root-доступа к узлу. Установка сокета Docker внутри контейнера не ограничивает его привилегированным доступом внутри контейнера. Это позволяет ему полностью контролировать узел и все другие контейнеры.

10. Рекомендация. Запретить общий доступ к чувствительным частям файловой системы

Получив доступ к приложению в контейнере, злоумышленник сможет изменить системные папки или удалить критически важные файлы. Нельзя открывать общий доступ для чувствительной части узловой файловой системы: (/); (/dev); (/proc); (/sys), соблюдая контроль предоставления доступа и принцип наименьших привилегий. Доступ должен быть открыт только для специалистов по информационным технологиям, чтобы исключить вероятность внесения неконтролируемых изменений.

11. Рекомендация. Ограничить доступ к ресурсам

Следует ограничивать доступ контейнеров к процессору, памяти и операциям ввода-вывода для стабильной работы каждого из контейнеров. Это позволит избежать ситуаций, когда ошибка в коде нарушает работу узла, и лишить злоумышленника возможности реализовать атаку типа «отказ в обслуживании» или осуществлять майнинг на ресурсах организации.

12. Требование. Запретить изменение корневой файловой системы

Любой отдельно взятый контейнер не должен иметь возможности влиять на корневую файловую систему согласно принципу изоляции — это позволяет предотвратить внесение неконтролируемых изменений с скомпрометированного контейнера. Поэтому файловую систему необходимо монтировать только для чтения: docker run --read-only \<image\>.

13. Рекомендация. Отключить bridge-интерфейс docker0

Для сокращения поверхности атаки, возможность перемещения через сетевой доступ из одного контейнера в другой должная быть ограничена. Docker0 — это сетевой мост, который создается автоматически и используется для изоляции сети узла от сети контейнера. По умолчанию контейнеры подключаются к сети docker0 и могут взаимодействовать друг с другом. Лучше всегда отключать это поведение по умолчанию с помощью параметра - bridge=none и создавать отдельные сети для каждого соединения.

14. Требование. Обеспечить журналирование событий

Для выявления аномальной активности или атаки злоумышленника следует обеспечить мониторинг событий. Журналы событий Docker изначально хранятся локально; необходимо настроить выгрузку событий в SIEM-систему.

Для экспорта событий из Docker-контейнеров необходимо перенаправить потоки STDERR и STDOUT на внешний сервис журналирования, например syslog, используя параметр - log-driver=\<logging_driver\>, и отправлять события в Smart Monitor.

15. Требование. Не отключать профили безопасности

По умолчанию Docker уже использует профили для модулей безопасности Linux. Эти правила можно ужесточать, но не наоборот. Используйте seccomp — механизм ядра Linuх, позволяющий определять доступные системные вызовы. Если злоумышленник получит возможность выполнить произвольный код, seccomp не даст ему использовать системные вызовы, которые не были заранее разрешены. В стандартной поставке Docker блокирует около 44 вызовов более чем из 300. Кроме seccomp можно использовать также профили AppArmor или SELinux.

16. Рекомендация. Обеспечить безопасность развертываемых приложений

Уровень безопасности контейнера характеризуется безопасностью самого развертываемого приложения. Следует провезти анализ кода (например, с помощью SonarQube) для выявления уязвимостей и следить за своевременным удалением старых зависимостей приложения.