Немного о безопасности Kubernetes

8566b3c0edddf6bd0957062de980f651.png

Сегодняшняя статья будет посвящена обеспечению безопасности Kubernetes. Мы рассмотрим специфику защиты Kubernetes, и начнем мы с рассмотрения вопросов безопасности контейнеров.

Зло из контейнера

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

Также необходимо ограничивать доступ к сокету docker и не оставлять его без надлежащей защиты. Демон Docker использует сокет /var/run/docker.sock для входящих соединений API. Владельцем данного ресурса должен быть только пользователь root. По сути, предоставление прав доступа к сокету другому аккаунту означает предоставление этой учетке root доступа к хостовому узлу.

Также не стоит забывать о том, что в случае совместного использования сокета с контейнерами, компрометация /var/run/docker.sock приведет к захвату контроля над хостовой системой. Не стоит возлагать особые надежды на использование для сокетов read‑only mode, данный механизм не является надежной защитой от компрометации, и лучше все‑таки полностью отказаться от совместного использования сокетов с контейнерами.

И продолжая тему сетевой безопасности контейнеров напомним о еще одной рукотворной «дыре в защите». Docker TCP сокет открывает порт 2375 для HTTP и 2376 для HTTPS и позволяет полностью контролировать контейнеры, то есть по сути и хостовую систему тоже.

Разработка контейнеризированного приложения начинается с выбора подходящего образа, то есть с выбора дистрибутива и здесь важно выбрать не только подходящий по составу установленных пакетов и библиотек. Важно выбрать образ из надежного источника. Не стоит брать образы с каких‑то ресурсов с сомнительной репутацией, только потому, что этот образ содержит нужный набор библиотек.

Следующий важный момент при минимизации поверхности атаки это использование непривилегированного пользователя внутри контейнера. Архитектура разрабатываемого приложения должна основываться на принципе наименьших привилегий, в соответствии с которыми в частности в контейнере не должен использоваться привилегированный аккаунт. Если в Dockerfile не указано значение USER, то по умолчанию контейнер выполняется с использованием пользователя root. Поэтому можно при запуске контейнера указать флаг ‑u:

5140e184c26f34b214626ec604393009.png

А лучше, конечно, в Dockerfile указать необходимого для работы пользователя:

503c4d63b1f6bbee1650211eb9aa11a0.png

В чем опасность работы под root внутри контейнера? По умолчанию Docker запускает контейнеры от имени пользователя root. И когда это пространство имен затем сопоставляется с пользователем root в запущенном контейнере, это означает, что контейнер потенциально имеет root‑доступ на хосте Docker.

Также в продолжение темы, не запускайте контейнер с повышенными привилегиями. То есть не стоит использовать флаг ‑privileged при запуске контейнера:

43dbf5d1bdd37ab4a6eafc3488e7e7e6.png

Нам не надо много

Принцип минимизации поверхности атаки также означает и отсутствие ненужного функционала внутри контейнера. Необходимо ограничивать функции ядра для контейнера с помощью флагов cap‑drop и cap‑add. При этом принцип работы этих команд довольно прост: сначала отключаем все, а потом подключаем только то, что нам действительно нужно:

docker run --cap-drop=ALL --cap-add=MKNOD …

Также в рамках ограничения ненужного функционала не лишним будет отключение межконтейнерного взаимодействия. По умолчанию все контейнеры могут общаться посредством сети docker0. Для отключения этой нежелательной возможности воспользуемся флагом icc со значением false в файле Dockerfile:

DOCKER_OPTS=”--icc=false”

Ну и не надо быть слишком жадными и требовать неограниченного числа ресурсов. Ограничить ресурсы контейнеров можно с помощью флагов --memory и --memory-swap для памяти и cpus, cpuset‑cpus для процессора.

Например:

docker run -it --memory="1g" ubuntu

docker run -it --cpus="1.0" ubuntu

И заканчивая тему ограничений для контейнеров, не лишним будет (если позволяет архитектура контейнеризируемого приложения) использовать read only файловую систему и Volume:

docker run --read-only alpine

docker run -v volume-name:/path/in/container:ro alpine

Сканер контейнеров

Кроме вышеперечисленных мер, обязательно необходимо проверить образ на наличие пропатченных уязвимостей. Не должно быть незакрытых дыр, то есть уязвимых сервисов и библиотек. Для проверки можно использовать различные сканеры уязвимостей (trivy, clair…).

Мы воспользуемся наиболее распространенным решением — плагином docker scan. Для сканирования необходимо сначала установить плагин, а затем запустить процесс сканирования:

$ apt-get update && apt-get install docker-scan-plugin

$ docker scan имя_образа

В результате получаем отчет следующего вида:

f0729a59d12a7a26a981ac54e9f39d6b.png

Подпись

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

export DOCKER_CONTENT_TRUST=1

Тогда если вы попытаетесь запустить неподписанный образ, ваш запрос будет отклонен.

На этом тему безопасности Docker контейнеров предлагаю считать исчерпанной, основные моменты мы рассмотрели, а для более тонкой настройки контейнеров предлагаю обратиться к документации вендора. И перейдем рассмотрению механизмов безопасности самого kubernetes.

Admission Controllers

Admission controllers — это специальные плагины в Kubernetes, предназначенные для перехвата запросов к серверу API Kubernetes до того момента, когда объект будет сохранен, с целью проверки подлинности и авторизации запроса.

8fbc5c07485af7ca54ec8e2c3448c836.png

В целом, концепция Admission controllers состоит из следующих основных этапов:

  • API HTTP handler

  • Authentification/Authorization

  • Mutating admission (mutating webhooks)

  • Schema validation

  • Validating admission (validating webhooks)

  • Persisted to etcd

По сути, когда на API приходит запрос, затем уже на сервере KubeAPI происходит аутентификация и авторизация, далее происходит проверка данных и схемы с помощью веб хуков, и только потом запись идет в хранилище etcd. Таким образом, мы прежде всего проверяем какие данные нам передали, нет ли в них чего‑то не слишком безопасного в плане создания контейнера, а также мы можем изменить потенциально небезопасные значения параметров если они не соответствуют правилам безопасности.

Включение admission controller«ов может происходить посредством отправки флага напрямую kubeapi‑server:

--enable-admission-plugins=ValidatingAdmission Webhook,MutatingAdmissionWebhook

В текущих версиях Kubernetes несколько Admission Controller уже включены по умолчанию и обеспечивают базовый уровень безопасности. Kubernetes советует использовать следующие admission controller по умолчанию:

--enable-admission-plugins=NamespaceLifecycle,imitRanger,ServiceAccount,

DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,Priority,ResourceQuota,PodSecurityPolicy

Отдельно хотелось бы обратить внимание использование mutating/validating webhooks. С помощью валидирующих и модифицирующих вебхуков, можно внедрить запрет запуска пода под повышенными привилегиями. При попытке развернуть под с повышенными привилегиями мы получим ошибку, и такой под не будет развернут.

Например, мы можем при развертывании указать runAsNonRoot, но далее установить runAsUser = 0. В результате предполагается запуск под root. Однако, вебхук может обнаружить подобную попытку обхода запрета и не дать запустить контейнер.

Таким образом, Admission controllers могут повысить безопасность, установив разумный базовый уровень безопасности для namespace или всего кластера.

Встроенный PodSecurityPolicy admission controller можно использовать, например для запрета запуска контейнеров под root или обеспечения того, чтобы корневая файловая система контейнера всегда монтировалась только для чтения. То есть, реализовать тот самый функционал, о котором мы говорили в разделе, посвященном безопасности контейнеров. Также можно разрешить извлечение образов только из определенных реестров, известных разработчиков. Не позволять загружать контейнеры откуда попало.

С помощью PodSecurityPolicy можно отклонять развертывания, не соответствующие стандартам безопасности. Например, контейнеры, использующие privileged флаг, могут обойти множество проверок безопасности

Помимо выполнения задач связанных с безопасностью, Admission controllers позволяют проверять конфигурацию объектов, работающих в кластере, и предотвращают попадание каких‑либо очевидных неправильных конфигураций в ваш кластер.

Admission controllers могут также быть полезны для обнаружения и исправления образов, развернутых без семантических тегов, например, при автоматическом добавлении или проверке лимитов ресурсов, автоматическом добавление меток и аннотаций, а также для проверки того, чтобы ссылки на образы, используемые в продакшен развертываниях, не использовали latest‑теги или теги с ‑dev‑суффиксом. Использование таких тегов чревато тем, что у нас окажется нестабильная версия контейнера со всеми вытекающими последствиями в части безопасности и надежности.

Безопасность в Run-Time

На смену устаревшей PodSecurityPolicy в версии 1.22 пришел контроллер доступа Pod Security Admission controller (PSA). Данный контроллер определяет стандарты безопасности pod на уровне пространства имен (namespace). После активации PSA (или установки вебхука) вы можете настроить пространства имен, чтобы определить режим контроля доступа, который вы хотите использовать.

Kubernetes определяет набор режимов, которые вы можете установить, чтобы определить, какой из предопределенных уровней безопасности Pod Standard вы хотите использовать для неймспейсов. Выбранная вами метка определяет, какое действие предпринимает k8s при обнаружении потенциального нарушения. Набор режимов, которые можно установить в PSA:

  • Enforce — Нарушения правил приведут к отклонению развертывания.

  • Audit — Нарушения политики вызывают добавление аннотации аудита к событию, записанному в журнале аудита (audit log), но в остальном они разрешены.

  • Warn — Нарушения политики вызовут предупреждение для пользователя, но в остальном разрешены.

Для подов имеются три политики от наименее запретительной к наиболее.

  • Privileged — все открыто и неограниченно.

  • Baseline — охватывает наиболее распространенные известные повышения привилегий и обеспечивает более легкую адаптацию.

  • Restricted — строго ограниченно, в соответствии с лучшими практиками. Может вызвать проблемы с совместимостью.

Политики применяются к пространству имен с помощью меток. Эти метки следующие:

REQUIRED: pod-security.kubernetes.io/:

OPTIONAL: pod-security.kubernetes.io/-version: (defaults to latest)

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

kubectl label --dry-run=server --overwrite ns --all \

    pod-security.kubernetes.io/enforce=baseline

34b8aa48147b3b7111071aad10277096.png

Далее в зависимости от развернутых контейнеров мы можем получить различные результаты.

Заключение

В этой статье мы рассмотрели основные моменты обеспечения безопасности Kubernetes, начиная от безопасной настройки контейнеров и заканчивая настройками политик. За рамками статьи остались темы связанные с безопасной настройкой сети и приложений, а также etcd и многое другое. В следующих статьях мы еще вернемся к этой теме.

А прямо сейчас приглашаю вас на бесплатный урок, где мы изучим тонкости архитектурного устройства kubernetes, поговорим о концепциях data и control plane и подробнее остановимся на ключевом компоненте — etcd и алгоритме консенсуса raft.

© Habrahabr.ru