Часто забываемые правила безопасности Docker
Сложно сформулировать универсальные правила безопасности для инфраструктуры контейнеров, потому что в первую очередь важен контекст. Защита должна учитывать, какая это система, какие задачи решает и в каком окружении. Иначе можно как угодно защищать контейнеры, но пострадать из-за уязвимости в ядре или человеческой ошибки в конфигурации системы.
Не так давно я столкнулся с сервисом, где о безопасности данных очень переживали. Все приложения с ключевыми данными были построены на базе контейнеров и настраивались опытными инженерами. Продумали грамотную конфигурацию контейнеров, мониторинг авторизации, регулярные обновления, блокировку по черному списку IP-адресов и так далее. Но уязвимость нулевого дня обнаружилась за пределами приложений, на почтовом сервере. В результате злоумышленники могли читать любые пользовательские письма и использовали эту информацию против компании. Несмотря на старания разработчиков, вся безопасность оказалась равна самому слабому звену инфраструктуры.
Чтобы было проще выстроить защиту со всех сторон, нужен инструмент для анализа возможных проблем. Например, подойдет модель угроз для контейнеризированных систем отсюда:
Я буду использовать диаграмму безопасности отсюда.
С одной стороны, мы рассматриваем риски, которые относятся к контейнерам и хосту. С другой стороны, анализируем внешние компоненты, которые влияют на безопасность контейнеризированной системы. К внешнему слою отнесем сеть и межсетевые экраны, систему проактивной защиты, всевозможные WAF’ы, а также репозиторий, где находится Docker, и образы распределенной системы.
Посмотрим, какие проблемы возникают на разных уровнях этой диаграммы.
“Внутренние” уязвимости
Уязвимости хоста. Безопасность хоста — обширная тема. Формально не всегда “проблемы на нашей стороне”. Но угрозы на этом уровне остаются одними из самых неприятных.
Многие считают, что размещение в облачной инфраструктуре сервис-провайдера решит эту проблему. Но провайдер может только предложить дополнительные инструменты ИБ: фильтрацию трафика на хост с помощью межсетевых экранов, сканеры. Конечная безопасность системы остается на совести клиента.
Вот с какими проблемами на хосте можно столкнуться:
- Заражение хоста из-за отсутствия антивируса.
- Уязвимости в старых версиях софта, который не обновили вовремя.
- Неправильные настройки приложений на хосте.
От многих проблем защитит регулярное тестирование по OpenSCAP или другие SCAP-совместимые сканеры уязвимостей.
Ошибки настройки Docker Daemon. Конфигурацию Docker Daemon выделю в отдельный пункт. Дефолтная настройка демона зачастую небезопасна и может создать дополнительный вектор атаки, например, если при использовании распределенной системы нам нужно открыть доступ к сокет-файлу Docker по сети. По дефолту он не имеет защиты, не имеет авторизации и может открыть доступ абсолютно ко всей системе.
Бывают даже до глупого простые ошибки: защитили сеть межсетевым экраном, но не сохранили его настройки — и после рестарта система захвачена.
Уязвимости уровня Docker images. Проблемы на этом уровне чаще всего возникают на этапе сборки образов. Что может случиться:
- Риски ICC — внутренней коммуникации Docker-контейнеров. На уровне настроек межсетевого экрана общение контейнеров происходит в рамках одной бриджевой сети. Если ICC включен, то с доступом в один из контейнеров мы сможем увидеть соседей. Так что, если общение контейнеров между собой не жизненно необходимо, стоит его запретить.
- Уязвимости базового образа и системных пакетов, включенных в этот образ.
Например, внутри контейнера могут оказаться библиотеки уже с уязвимостями. - Уязвимости в сторонних зависимостях, которые подтягиваются извне и используются в приложениях.
Не раз видел ситуацию, когда контейнер брали откуда-то из сети, добавляли в приложение, открывали ему доступ наружу — и уже на следующий день обнаруживали там море запущенных ненужных процессов.Сам контейнер при этом был легитимен, просто не обновлен. А дальше в игру вступают различные сканеры.
- Проблемы и уязвимости в коде самих приложений.
- Уязвимости Dockerfile: некорректные и небезопасные инструкции, которые могут использоваться при сборке образа. Например, мы создаем нового юзера в рамках Docker-файла и дальше спускаем контент от этого юзера.
- Уязвимости самого Docker Daemon, которые могут сыграть свою роль при компрометации контейнера. Например, позапрошлогодняя уязвимость с побегом из контейнера в систему эксплуатировалась при условии маунта на этой системе (подключенного диска).
Проблемы runtime-конфигурации. В рамках runtime могут возникнуть на первый взгляд некритичные уязвимости. Но в сочетании с другими факторами получаются более серьезные угрозы. Что бывает:
- Некорректное и избыточное выделение привилегий контейнерам.
- Избыточно открытые сетевые порты. Даже если порты не проброшены вовне, это может стать риском при включенном ICC, когда контейнеры общаются друг с другом.
- Небезопасное использование разделяемых томов. В частности, сюда относится специфический механизм “докер-в-докере”, который бывает нужен на этапе сборки. В этой ситуации при получении доступа к репозиторию теоретически можно изменить задачи и, благодаря избыточным привилегиям сборщиков, получить дополнительные права доступа.
Наблюдал такую ситуацию: есть база данных MongoDB, порт открыт только у базы, доступ исключительно по авторизации. Но в самой базе открыты порты для админок и прочих систем. Проблема в том, что в админке базы довольно много утилит и теоретически через нее можно получить полный доступ к системе. Если при этом контейнер работает от root, то это уже ощутимый риск.
Влияние внешних компонентов
Репозитории образов. Здесь проблемы часто связаны с безопасным хранением и доставкой образов из репозитория к целевой системе. Есть риск столкнуться с подменой контейнера.
В идеале стоит ограничиться двумя-тремя доверенными репозиториями. Но это не всегда удобно в системах вокруг продакшн-окружения: на тесте или стейдже внешние образы используются часто. В этом случае используем Docker Content Trust (DCT): подписываем контейнеры электронной подписью и настраиваем хосты на работу только с правильно подписанными образами.
Распределенные системы. Тут самый основной риск — слишком открытый доступ к Docker-сокету (об этом как раз писал выше).
В Enterprise-сегменте я встречал довольно много инструкций, которые нацелены на быстрый запуск ПО, но не делают важных оговорок по ИБ. Например, на одном из шагов нужно открыть доступ к контейнеру Docker по TCP/IP. При этом межсетевой экран настроен по умолчанию, а необходимость защитить Docker-сокет в инструкции никак не прописана. За 5 минут, пока TCP-порт открыт, автоматические боты успевают его просканировать. В лучшем случае туда устанавливаются майнеры, в худшем — какой-то зловред. Даже когда мы закроем порт и настроим межсетевой экран, в системе останется уязвимость. Не надо так.
Мониторинг контейнеризированной системы. Система мониторинга поможет нам быстро реагировать на аномальное поведение каких-то параметров. Важно отслеживать показатели и контейнеров, и всей системы. Дополнительно к мониторингу можно подключить аналитику логов, например, чтобы вычленять нетипичные запросы и так далее.
Бэкап данных контейнеризированной системы. Помимо самого бэкапа также важна защита хранящихся данных.
У нас был клиент — довольно популярный сервис в конкурентной сфере. Неизвестно, была ли это направленная атака, но в итоге хакеры получили доступ в контейнеризированную систему. Сами контейнеры были с определенной защитой. Но вот бэкапы данных делались на S3, а из контейнеров можно было достать данные ключей и получить полный доступ. При этом сами политики на запись бэкапа не запрещали удаление.Дальше дело техники. В первую очередь атакующие подменили бэкапы данных на какой-то мусор. Только после этого уничтожили систему и начали шантажировать владельцев: «Вы нам биткоинов переведите, и мы тогда, возможно, вам что-то вернем».
Итоговые правила безопасности
- Ограничить доступ к сокету Docker Daemon. Владельцем сокет-файла должен быть пользователь root. Нужно понимать, что любой пользователь с доступом к сокету может получить доступ к системе. И запретить это можно, только если запретить маунтить файлы из определенных директорий.
- Не прокидывать сокет в контейнер (Docker-in-Docker). Вместо Docker-in-Docker для сборки можно использовать kaniko — систему, которая позволит собрать образ без использования Docker. В этом случае для подписи образов можно использовать стороннюю систему и подключить ее через API.
- Помнить о настройке авторизации Docker TCP. Авторизацию настраиваем через сертификат SSL, то есть используем открытые и закрытые ключи и подписанные ключи по иерархии. Таким образом это все реализовано в Kubernetes.
- Настроить непривилегированного пользователя. Запуск контейнеров всегда производим от непривилегированного пользователя. Если речь о Docker в чистом виде, мы просто указываем UID юзера и запускаем контейнер.
docker run -u 4000 alpine
Во время сборки контейнера мы создаем юзера и переходим на него.FROM alpine RUN groupadd -r myuser && useradd -r -g myuser <Здесь еще можно выполнять команды от root-пользователя, например, ставить пакеты> USER myuser
Также включаем поддержку user namespace в Docker daemon.--userns-remap=default
Либо можно это сделать в json-файле, который относится к Docker. - Отключить все возможности ядра (capabilities). Например, так:
docker run --cap-drop all --cap-add CHOWN alpine
- Запретить эскалацию привилегий (смену юзера на uid0). Используем опцию при запуске:
--security-opt=no-new-privileges
- Отключить межконтейнерное взаимодействие. По умолчанию такое возможно через сеть docker0, опция при старте демона Docker:
--icc=false
- Ограничить ресурсы. Так мы сократим риск несанкционированного майнинга:
-m или --memory — доступная память до OOM;
--cpus — сколько процессоров доступно, например 1.5;
--cpuset-cpus — можно указать, какие именно процессоры доступны (ядра);
--restart=on-failure:<number_of_restarts> — убираем вариант Restart Always, чтобы контролировать количество перезапусков и вовремя обнаруживать проблемы;
--read-only — файловая система настраивается только на чтение при запуске, особенно если контейнер отдает статику. - Не отключать профили безопасности. По умолчанию Docker уже использует профили для модулей безопасности Linux. Эти правила можно ужесточать, но не наоборот.Отдельно скажу несколько слов про seccomp — механизм ядра Linuх, позволяющий определять доступные системные вызовы. Если злоумышленник получит возможность выполнить произвольный код, seccomp не даст ему использовать системные вызовы, которые не были заранее разрешены. В стандартной поставке Docker блокирует около 44 вызовов из 300+.Кроме Seccomp можно использовать также профили AppArmor или SELinux.
- Анализировать содержимое контейнера. Для обнаружения контейнеров с уже известными уязвимостями есть инструменты: бесплатный Clair, условно бесплатные Snyk, anchore, платные JFrog XRay и Qualys. Бесплатно уязвимости можно также учесть в Harbor: он ищет известные эксплойты и сообщает о надежности контейнера. Также можно использовать системы асессмента ИБ в целом, например, Open Policy agent.