Александр КравченкоБезопасность контейнеров на примере платформы Docker
Александр КравченкоПлатформа Docker используется для упрощения разработки, тестирования и развертывания приложений. Она позволяет разработчикам создавать контейнеры с приложениями и их зависимостями, которые могут быть легко перенесены между различными средами, такими как разработка, тестирование и производство. Платформа Docker — одно из самых распространенных средств контейнеризации приложений.
Контейнер — изолированное пространство, в котором запущено приложение со всеми необходимыми зависимостями.
Контейнеры обладают рядом преимуществ:
Основные компоненты контейнера Docker:
Визуально это можно представить следующим образом:
Рисунок 1. Структура Docker
Несмотря на преимущества, связанные с удобством использования контейнеров Docker, не стоит терять из фокуса вопросы, непосредственно связанные с безопасностью, а именно:
Вопросам безопасности всегда стоит уделять пристальное внимание, но в контексте результативной кибербезопасности контроль и внимание должны быть повышенными. Контейнер может стать точкой проникновения на периметре сети, и если он будет сконфигурирован некорректно, это может привести к тому, что в случае успешной атаки злоумышленник получит доступ к ресурсам организации.
Основные требования и рекомендации по работе с контейнерами Docker касаются случаев, когда команда разработки самостоятельно развертывает приложение или выступает заказчиком работ для сторонней организации. Это связано с тем, что кибербезопасность решений «из коробки» в основном зависит от компетенций вендора, и на стороне заказчика остается всего несколько действий, о которых нельзя забывать:
При самостоятельном развертывании контейнера следует учитывать:
Узел, на котором разворачивается контейнер Docker, должен быть защищен. Отсутствие антивируса или его некорректная настройка, уязвимости устаревшего ПО, уязвимости операционной системы или некорректные параметры стороннего ПО — всем этим может воспользоваться злоумышленник. Требуется проводить сканирование узла на уязвимости и устранять выявленные проблемы безопасности.
Инфраструктура всей организации может быть скомпрометирована из-за использования в контейнере образа, содержащего вредоносное ПО. При выборе базового образа есть вероятность того, что его параметры будут содержать проблемы безопасности, такие как неправильно настроенные значения по умолчанию или уязвимые системные библиотеки. Злоумышленник может использовать эти недостатки для получения доступа к узлу, на котором развернут Docker, чтобы далее развить атаку вглубь инфраструктуры.
Определить уязвимости в используемых библиотеках контейнера можно с помощью сканеров уязвимостей, например Trivy. Для определения соответствия конфигурации контейнеров общепринятым best practices можно использовать Docker Bench for Security.
Хорошая практика — использование минимального базового образа, насколько это возможно: например, Alpine Linux — один из самых легких доступных образов. Если проекту не требуются общие системные библиотеки или системные утилиты, следует избегать использования полнофункциональной операционной системы в качестве базового образа. Кроме того, требуется определить подход к загрузке контейнеров из регистров контейнеров.
Теги Docker работают от менее специфичных к более специфичным, например:
Указывая более конкретную версию и фиксируя ее, вы защищаете себя от любых критических изменений (breaking change). Кроме того, использование последней версии гарантирует, что большое количество уязвимостей будет исправлено. Хорошая практика — привязка к стабильной версии. То же самое относится к пакетам, устанавливаемым в процессе сборки вашего образа.
Чтобы уменьшить поверхность атаки, нужно не дать злоумышленнику получить привилегированный доступ в контейнере. Когда в 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\> ...
Для управления пользователями и группами используйте утилиты, входящие в базовый образ.
Для снижения вероятности атаки на цепочку поставок нужно убедиться, что образ, который скачивается из интернета, распространяется издателем, и что ни одна из сторон образ не изменила. Параметры Docker по умолчанию позволяют извлекать образы Docker без проверки их подлинности, что потенциально может привести к появлению образов, происхождение и автор которых не проверены.
Требуется всегда проверять образы перед их извлечением, независимо от политики. Необходимо включить Docker Content Trust с помощью команды: export DOCKER_CONTENT_TRUST=1.
Нельзя хранить конфиденциальные данные (secrets), такие как токены, ключи, пароли, в Dockerfile, потому что эти данные кэшируются. При необходимости следует использовать команду secret для монтирования конфиденциальных файлов без их кэширования.
При копировании файлов в создаваемый образ команда COPY рекурсивно копирует всю папку контекста сборки в образ Docker, что может также привести к копированию конфиденциальных файлов. Если в папке есть конфиденциальные файлы (private.key; settings.json и т. д.), необходимо их удалить или использовать \*.\*dockerignore, чтобы игнорировать их.
Оптимальным решением для хранения конфиденциальной информации является использование сторонних утилит по типу HashiСorp Vault. Подробнее о работе с конфиденциальной информацией в Docker можно ознакомиться на официальном портале.
Нужно строго ограничить и тщательно контролировать удаленный доступ к контейнеру Docker, так как попытка удаленного подключения — одно из базовых действий злоумышленника при проведении атаки. Контроль доступа должен отдельно осуществляться для пользователей с разным уровнем привилегий на основе RBAC-модели, c помощью Service Mesh — механизма взаимодействия между сервисами на уровне сети; или же следует добавить ограничивающие правила в iptables.
По умолчанию демон Docker использует User namespace узла. Следовательно, любое успешное повышение привилегий внутри контейнера будет также означать получение root-доступа как к узлу, так и к другим контейнерам. Чтобы снизить этот риск, необходимо настроить узел и демон Docker на использование отдельного пространства имен с помощью параметра userns-remap.
Docker взаимодействует с сокетом домена UNIX, который называется /var/run/docker.sock*.* Это основная точка входа для Docker API. Предоставление доступа к нему равносильно предоставлению неограниченного root-доступа к узлу. Установка сокета Docker внутри контейнера не ограничивает его привилегированным доступом внутри контейнера. Это позволяет ему полностью контролировать узел и все другие контейнеры.
Получив доступ к приложению в контейнере, злоумышленник сможет изменить системные папки или удалить критически важные файлы. Нельзя открывать общий доступ для чувствительной части узловой файловой системы: (/); (/dev); (/proc); (/sys), соблюдая контроль предоставления доступа и принцип наименьших привилегий. Доступ должен быть открыт только для специалистов по информационным технологиям, чтобы исключить вероятность внесения неконтролируемых изменений.
Следует ограничивать доступ контейнеров к процессору, памяти и операциям ввода-вывода для стабильной работы каждого из контейнеров. Это позволит избежать ситуаций, когда ошибка в коде нарушает работу узла, и лишить злоумышленника возможности реализовать атаку типа «отказ в обслуживании» или осуществлять майнинг на ресурсах организации.
Любой отдельно взятый контейнер не должен иметь возможности влиять на корневую файловую систему согласно принципу изоляции — это позволяет предотвратить внесение неконтролируемых изменений с скомпрометированного контейнера. Поэтому файловую систему необходимо монтировать только для чтения: docker run --read-only \<image\>.
Для сокращения поверхности атаки, возможность перемещения через сетевой доступ из одного контейнера в другой должная быть ограничена. Docker0 — это сетевой мост, который создается автоматически и используется для изоляции сети узла от сети контейнера. По умолчанию контейнеры подключаются к сети docker0 и могут взаимодействовать друг с другом. Лучше всегда отключать это поведение по умолчанию с помощью параметра - bridge=none и создавать отдельные сети для каждого соединения.
Для выявления аномальной активности или атаки злоумышленника следует обеспечить мониторинг событий. Журналы событий Docker изначально хранятся локально; необходимо настроить выгрузку событий в SIEM-систему.
Для экспорта событий из Docker-контейнеров необходимо перенаправить потоки STDERR и STDOUT на внешний сервис журналирования, например syslog, используя параметр - log-driver=\<logging_driver\>, и отправлять события в Smart Monitor.
По умолчанию Docker уже использует профили для модулей безопасности Linux. Эти правила можно ужесточать, но не наоборот. Используйте seccomp — механизм ядра Linuх, позволяющий определять доступные системные вызовы. Если злоумышленник получит возможность выполнить произвольный код, seccomp не даст ему использовать системные вызовы, которые не были заранее разрешены. В стандартной поставке Docker блокирует около 44 вызовов более чем из 300. Кроме seccomp можно использовать также профили AppArmor или SELinux.
Уровень безопасности контейнера характеризуется безопасностью самого развертываемого приложения. Следует провезти анализ кода (например, с помощью SonarQube) для выявления уязвимостей и следить за своевременным удалением старых зависимостей приложения.