Control Plane Hardening
Last updated
Last updated
https://habr.com/ru/companies/slurm/articles/712736/
В кластере Kubernetes control plane управляет нодами, ноды — pod’ами, pod'ы — контейнерами, контейнеры — приложениями. А кто управляет control plane?
Kubernetes предоставляет API для комплексного управления всем кластером Kubernetes. Получается, что, в первую очередь, мы должны защитить доступ к Kubernetes API. Даже в свежих рекомендациях агентства нацбезопасности США по защите Kubernetes нам велят использовать надёжную аутентификацию и авторизацию, чтобы ограничить пользовательский и административный доступ и сократить поверхность атаки.
В этой статье мы поговорим о том, как защитить доступ к API в кластере Kubernetes. Если вы хотите реализовать надёжную аутентификацию и авторизацию в кластере Kubernetes, вам сюда. Даже если вы используете управляемые сервисы Kubernetes, например AWS EKS или GCP Kubernetes Engine, будет полезно узнать, как устроен контроль доступа, чтобы планировать общую безопасность в Kubernetes .
Сначала давайте в общих чертах вспомним, как работает Kubernetes. Kubernetes — это не один продукт, а набор программ, работающих вместе. У многих других решений единая поверхность атаки, а для защиты Kubernetes нужно учитывать целый ряд инструментов и компонентов.
Иллюстрация: https://kubernetes.io/docs/concepts/security/overview/
В классической модели 4C для защиты облака мы должны обеспечить безопасность четырёх компонентов: code, container, cluster и cloud (код, контейнер, кластер и облако), и Kubernetes в этой парадигме соответствует кластеру. Уязвимости и ошибки конфигурации на одном уровне увеличивают риски для другого. Получается, что безопасность Kubernetes зависит от безопасности других облачных компонентов.
Контроль доступа к API в Kubernetes выполняется в несколько этапов: сначала запрос проходит аутентификацию, потом — авторизацию, затем контроль допуска и наконец ему предоставляется доступ. Ещё до начала аутентификации нужно обязательно убедиться, что контроль сетевого доступа и TLS-соединение правильно настроены.
Рекомендации по защите прямого сетевого доступа к Kubernetes API (control plane):
Весь трафик к серверу API, внутри control plane, между control plane и kubelet должен проходить по TLS-соединениям. Настроить TLS на сервере API не трудно — просто используем флаги --tls-cert-file=[file]
и --tls-private-key-file=[file]
для kube-apiserver. Куда сложнее будет наладить управление сертификатами TLS в кластере, учитывая как легко Kubernetes меняет масштаб. Чтобы решить эту проблему, в Kubernetes появилась новая возможность — TLS bootstrapping для автоматического подписания сертификатов и конфигурации TLS внутри кластера Kubernetes.
Настраиваем флаги, связанные с TLS и поддерживаемые kube-apiserver
.
-secure-port
Сетевой порт для HTTPS-соединения с kube-apiserver. Порт по умолчанию: 6443. Можно изменить значение по умолчанию, указав желаемый номер порта с помощью флага --secure-port
.
-tls-cert-file, -tls-private-key-file
Эти флаги настраивают сертификаты x509 и частный ключ для HTTPS-соединений.
-cert-dir
Настройка каталога для файлов TLS-сертификатов и ключей. Флаги --tls-cert-file
и --tls-private-key-file
получают приоритет над сертификатами в этом каталоге. По умолчанию --cert-dir
находится в /var/run/kubernetes.
-tls-cipher-suites
Настраиваем наборы шифров для TLS. Без этого флага kube-apiserver использует наборы шифров по умолчанию, предложенные Golang.
-tls-min-version
Настраиваем минимальную поддерживаемую версию TLS. Возможные значения: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13.
-tls-sni-cert-key
Настраиваем Server Name Indication (SNI) в виде пары --tls-sni-cert-key=testdomain.crt,testdomain.key.
-strict-transport-security-directives
Настраиваем HTTP Strict Transport Security (HSTS).
-requestheader-client-ca-file, -proxy-client-cert-file, -proxy-client-key-file
Kubernetes позволяет расширить kube-apiserver кастомными API на уровне агрегации. Благодаря уровню агрегации можно создать собственный сервер API (расширение сервера kube-apiserver). Чтобы защитить взаимодействие между kube-apiserve и нашим сервером API, можно с помощью этих флагов настроить сертификаты x509.
-kubelet-certificate-authority, -kubelet-client-certificate, -kubelet-client-key
Это флаги для настройки центра сертификации (certificate authority, CA), сертификата клиента и закрытого ключа клиента для TLS-соединения между сервером Kubernetes API и kubelet.
-etcd-cafile, -etcd-certfile, -etcd-keyfile
Флаги для настройки TLS-соединений между API и etcd.
Не используем порт localhost в продакшене.
По умолчанию Kubernetes API обслуживает HTTP-трафик по двум портам: localhost и защищённый порт. Порт localhost не требует TLS, и запросы к нему могут обходить аутентификацию и авторизацию. За пределами тестового окружения этот порт использовать нельзя.
Используем kubectl proxy для управления защищённым доступом клиентов
Безопасные коммуникации требуют строгого управления секретами как на клиенте, так и на сервере. Если у вас распределённая команда, скорее всего, несколько пользователей используют kubectl из разных мест, что повышает риск компрометации учётных данных (файлов сертификата, токенов). Можно настроить сервер Bastion, где kubectl proxy настроен таким образом, что пользователи отправляют HTTPS-запросы к серверу Bastion, а kubectl proxy перенаправляет их (с необходимыми учётными данными) на сервер API. Таким образом конфиденциальные учётные данные не покидают защищённый сервер Bastion. С помощью этого метода также можно запретить пользователям обращаться к серверу API напрямую через сетевой доступ.
Изучаем и защищаем прокси сервера API
У Kubernetes есть встроенный сервер Bastion (внутри сервера API), который выступает как прокси для доступа к сервисам, запущенным на кластере. Для доступа к сервисам используется следующая схема: http://kubernetes_master_address/api/v1/namespaces/namespace-name/services/service-name[:port_name]/proxy
. Например, если в кластере выполняется сервис elasticsearch-logging, он будет доступен по URL https://ClusterIPOrDomain/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy
Прокси предоставляет доступ к внутренним сервисам, которые напрямую недоступны из внешней сети. Это удобно для административных целей, но злоумышленник может получить доступ к внутренним сервисам, хотя в другом случае не прошёл бы авторизацию с назначенной ролью. Если мы хотим разрешить административный доступ к серверу API, но заблокировать доступ к внутренним сервисам, можно использовать прокси HTTP или WAF, чтобы заблокировать запросы к этим конечным точкам.
Используем прокси HTTP, балансировщики нагрузки и межсетевые экраны
Если добавить простой прокси-сервер HTTP (например, с Nginx), можно быстро применять правила безопасности и URL перед Kubernetes API. Для дополнительной безопасности можно инвестировать в балансировщик нагрузки (например, AWS ELB, Google Cloud Load Balancer) и межсетевые экраны перед сервером Kubernetes API, чтобы контролировать прямой доступ к серверу.
Control plane, главный уровень оркестрации контейнеров в Kubernetes, предоставляет несколько API и интерфейсов, чтобы определять и развёртывать контейнеры, а также управлять их жизненным циклом. API предоставляется как HTTP REST API и может использоваться через любую совместимую клиентскую библиотеку HTTP. Лучше всего обращаться к этим API через kubectl, CLI по умолчанию. Кто будет обращаться к этим API? Обычные пользователи и сервисные аккаунты.
Ниже приводятся рекомендации по управлению аккаунтами обычных пользователей и сервисов в Kubernetes.
Лучше использовать для подготовки и администрирования таких аккаунтов корпоративные решения IAM (AD, Okta, G Suite OneLogin и т. д.), чтобы соблюдать корпоративные политики IAM. Заодно можно изолировать от Kubernetes риски, связанные с аккаунтами обычных пользователей.
Сервисные аккаунты привязаны к определённым пространствам имён и используются для определённых задач по управлению, поэтому нужно тщательно проверять их безопасность.
Доступные сервисные аккаунты можно проверять так:
Можно просмотреть детали объекта сервисного аккаунта:
Чтобы посмотреть секреты, связанные с сервисным аккаунтом (если мы хотим проверить время создания секрета для смены старого секрета), можно использовать команду get secret:
Совет. В экстренном случае или если мы хотим заблокировать доступ для сервисного аккаунта, не удаляя его, можно аннулировать учётные данные аккаунта командой delete secret:
$ kubectl delete secret testserviceaccount-token-mtkv7
Вместо сервисных аккаунтов по умолчанию лучше использовать выделенный сервисный аккаунт для одного приложения, чтобы можно было применять принцип минимальных привилегий. Если у двух приложений одинаковый набор привилегий, можно использовать тот же сервисный аккаунт и не создавать новый, чтобы не усложнять. Все мы знаем, что сложность подрывает безопасность.
Не всем pod’ам в Kubernetes нужен доступ к Kubernetes API. Но поскольку токен сервисного аккаунта по умолчанию автоматически монтируется в новый pod, если не указан конкретный сервисный аккаунт, поверхность атаки будет неоправданно увеличена. Мы можем отключить сервисный аккаунт:
Совет. Поставьте флаг --service-account-key-file для kube-apiserver и флаг --service-account-private-key-file для kube-controller-manager, чтобы для подписания и верификации токенов ServiceAccount использовались специальные сертификаты x509 или пары ключей. В противном случае Kubernetes будет подписывать и верифицировать токены ServiceAccount, используя закрытый ключ TLS — тот же, который использовался для настройки TLS-соединения с сервером API (--tls-private-key-file). Если токены скомпрометированы и нужно их сменить, придётся сменить и основные сертификаты TLS, а это проблемно с операционной точки зрения.
По возможности используем внешний сервис для аутентификации. Например, если организация уже управляет пользовательскими аккаунтами с помощью корпоративного сервиса IAM, самым безопасным вариантом будет использовать его же для аутентификации пользователей и их подключения к Kubernetes с помощью таких методов, как OIDC (см. руководство по настройке OpenID Connect).
Если пользователь проходит аутентификацию с помощью одного метода, Kubernetes не требует дальнейшей аутентификации, а перенаправляет запрос. Поскольку Kubernetes позволяет настроить и включить несколько аутентификаторов сразу, убедитесь, что каждый аккаунт пользователя привязан только к одному методу аутентификации.
Например, если злоумышленник может пройти аутентификацию одним из двух методов и находит способ обойти слабый метод, ему не придётся проходить через более строгую аутентификацию. Сервер Kubernetes API не гарантирует порядок выполнения аутентификаторов.
Время от времени проверяйте неиспользуемые методы и токены аутентификации и удаляйте или отключайте их. Администраторы часто используют одни инструменты, чтобы упростить настройку кластера Kubernetes, а потом переходят на другие методы управления кластерами. В этом случае важно тщательно проверить старые методы и токены аутентификации и удалить их, если они больше не нужны.
Статические токены загружаются бесконечно, пока сервер онлайн. Если токен скомпрометирован и его нужно сменить, нужно будет перезапустить сервер, чтобы очистить его от скомпрометированных токенов. Настроить аутентификацию со статическими токенами проще, но лучше избегать этого метода. Убедитесь, что kube-apiserver не запускается с флагом --token-auth-file=STATIC_TOKEN_FILE
.
Прокси аутентификации велит серверу Kubernetes API проверять подлинность пользователей по имени пользователя, которое указано в заголовке HTTP, например X-Remote-User: [username]. Это очень небезопасный метод аутентификации, ведь клиенты могут легко перехватить и изменить заголовок HTTP, потому что так работает HTTP.
Например, в следующем входящем запросе указан прошедший аутентификацию пользователь dev1:
Ничто не мешает злоумышленнику перехватить или скорректировать запрос:
Можно подделать заголовок HTTP, и единственная защита от спуфинга — доверие на основе mTLS, то есть kube-apiserver доверяет клиентскому сертификату, подписанному доверенным CA.
Если вам действительно очень нужен прокси аутентификации, тщательно продумайте риски, связанные с сертификатами.
По умолчанию HTTP-запросы, которые не прошли аутентификацию, но не были отклонены, обрабатываются как анонимный доступ и считаются пользователями system:anonymous в группе system:unauthenticated. Если у нас нет веских причин разрешить анонимный доступ (включённый по умолчанию), лучше отключить его при запуске сервера API: $ kube-apiserver --anonymous-auth=false.
После аутентификации Kubernetes проверяет, следует разрешить или отклонить этот запрос. Решение зависит от доступного режима авторизации (включается при запуске kube-apiserver), а также от таких атрибутов, как пользователь, группа, пользовательские метки, ресурс API, конечная точка API, команда в запросе API (get, list, update, patch и т. д.), команде в HTTP-запросе (get, pull, post, delete), ресурс, подресурс, пространство имён и группа API.
Ниже приводятся рекомендации по авторизации доступа к Kubernetes API.
RBAC в Kubernetes заменил предыдущий метод ABAC и стал предпочитаемым способом авторизации доступа к API. Чтобы включить RBAC, запускаем сервер API, указав $ kube-apiserver --authorization-mode=RBAC
. Важно помнить, что разрешения RBAC в Kubernetes складываются — запрещающих правил нет.
В Kubernetes два типа ролей: Role и ClusterRole,— и эти роли можно предоставлять с помощью методов RoleBinding и ClusterRoleBinding. Поскольку ClusterRole действует по всему кластеру Kubernetes, лучше использовать Role и RoleBinding вместо ClusterRole и ClusterRoleBinding.
Ниже приводятся встроенные роли по умолчанию:
Cluster-admin. Это пользователь уровня root или суперпользователя, который может выполнять любое действие с любым ресурсом в кластере. Нужно трижды подумать, назначая эту роль пользователю. Здесь мы видим, что роль cluster-admin есть у группы masters:
Admin. Эта роль даёт неограниченный доступ на чтение и запись для ресурсов в определённом пространстве имён. Она похожа на cluster-admin и позволяет создавать роли и привязки ролей в пространстве имён. Будьте очень осторожны, назначая эту роль.
Edit. Эта роль позволяет читать и записывать данные в пространстве имён, но не просматривать или изменять роли и привязки ролей. Однако у этой роли есть доступ к секретам сервисного аккаунта, с помощью которого путём эскалации привилегий можно получить доступ к действиям API, которые не должны быть доступны.
View. Это самая ограниченная встроенная роль, которая разрешает только доступ на чтение объектов в пространстве имён. Нельзя просматривать секреты, роли и привязки ролей.
Всегда предоставляйте для ServiceAccount ту роль, которая нужна этому аккаунту в зависимости от приложения. В духе подхода с минимальными привилегиями Kubernetes по умолчанию ограничивает область действия разрешений RBAC, выданных ServiceAccounts, пространством имён kube-system. Чтобы расширить область разрешения, нужно создать привязку роли. Ниже, например, мы предоставляем разрешение только на чтение в пространстве имён devteam сервисному аккаунту devteamsa.
Не предоставляйте команды escalate и bind для ролей Role и ClusterRole. Kubernetes позволяет предотвратить эскалацию привилегий через изменение ролей и привязок ролей. Пользователи не смогут создавать или обновлять роли, к которым у них нет доступа. Если мы разрешаем escalate или bind, мы обходим защиту от эскалации привилегий, и злоумышленник может воспользоваться этой возможностью.
Например, в следующем коде ClusterRole и RoleBinding позволят пользователю non_priv_user предоставить другим пользователям роли admin, edit, view в пространстве имён non_priv_user_namespace:
Убедитесь, что для kube-apiserver не включён небезопасный порт (--insecure-port
), потому что Kubernetes разрешает вызовы API через небезопасный порт (если он включён) без аутентификации и авторизации.
С помощью команды $ kubectl auth can-i
можно быстро проверить статус авторизации API.
Команда возвращает yes или no, в зависимости от роли авторизации. С помощью этой команды можно проверить и свой статус авторизации, но больше пользы она принесёт для перекрёстной проверки разрешений других пользователей вместе с командой имперсонации:
Совет. Если следующая команда возвращает yes, пользователю разрешено всё.
Проверьте, что для kube-apiserver не установлен флаг --authorization-mode=AlwaysAllow
, потому что он велит серверу не требовать авторизацию входящих запросов API.
Старайтесь как можно реже назначать пользователям роль с возможностью создавать pod’ы в пространстве имён, потому что злоумышленник может эскалировать привилегии в этом пространстве имён, чтобы читать любые секреты и схемы конфигурации или олицетворять любые сервисные аккаунты.
Аутентификация проверяет допустимость учётных данных для входа, а авторизация контролирует наличие у пользователя разрешений на ту или иную задачу. Но как проследить, что пользователи, которые прошли аутентификацию и авторизацию, ведут себя хорошо? Для этого Kubernetes поддерживает контроль допуска, который позволяет изменять или валидировать запросы после успешной аутентификации и авторизации.
Для Kubernetes есть много модулей контроля допуска. NodeRestriction, например, контролирует запросы на доступ от kubelet, а PodSecurityPolicy, который активируется во время событий API, связанных с созданием или изменением pod’а, применяет к pod’у минимальные стандарты безопасности. Контроллеры ValidatingAdmissionWebhooks и MutatingAdmissionWebhooks позволяют на лету применять политику, инициируя HTTP-запросы к внешним сервисам (через вебхук) и выполняя действия в зависимости от ответа. При таком подходе мы централизованно управляем политиками и применяем их на множестве кластеров и развёртываний Kubernetes. См. документацию, чтобы узнать, как запустить кастомный контроллер допуска с помощью этих двух контроллеров.
Включаем/отключаем контроллер допуска
Доступные встроенные контроллеры доступа по умолчанию зависят от того, как вы управляете кластером Kubernetes (кастомный дистрибутив Kubernetes, AWS EKS, GCP Kubernetes Engine и т. д.). Мы можем проверить, какие контроллеры допуска нам доступны:
Чтобы включить или отключить определённый контроллер допуска:
Проверяем доступность и настраиваем таймауты
Kubernetes поддерживает динамический контроль допуска с помощью интеграции вебхуков допуска. Используя динамические контроллеры, можно легко реализовывать кастомные контроллеры допуска, но не забывайте проверять доступность сервиса вебхуков, потому что сторонние запросы могут привести к задержке, которая повлияет на доступность всего кластера Kubernetes.
Kubelet — это агент, который работает на каждой ноде Kubernetes и следит, чтобы в ней выполнялись спецификации PodSpec. Kubelet также предоставляет HTTP API, с помощью которого можно контролировать и настраивать pod’ы и ноды.
Ниже приводится три рекомендации по защите доступа к kubelet:
Как мы контролируем прямой сетевой доступ к серверам API, так следует ограничивать доступ к kubelet и нодам. Для этого есть межсетевые экраны, прокси HTTP и конфигурации.
Объекты NetworkPolicy помогают изолировать pod’ы и контролировать трафик между pod’ами и пространствами имён. С их помощью мы можем настраивать сетевой доступ на L3 и L4 в модели OSI. Надёжно настроенные объекты NetworkPolicy защищают кластер в ситуациях, когда скомпрометирована одна нода, pod или пространство имён.
В этом примере политики мы запрещаем весь входящий трафик.
Есть готовые шаблоны сетевых политик.
Аутентификация с сертификатами x509
Чтобы включить аутентификацию через клиентские сертификаты X509 между kubelet и kube-apiserver, устанавливаем для сервера kubelet флаг --client-ca-file
, а для kube-apiserver — флаги --kubelet-client-certificate
и --kubelet-client-key
.
Аутентификация с токенами на предъявителя
Kubelet поддерживает аутентификацию через токены на предъявителя, делегируя аутентификацию внешнему серверу. Чтобы включить эту возможность, запускаем kubelet с флагом --authentication-token-webhook
. Затем kubelet запросит внешний сервер вебхуков по конечной точке TokenReview https://WebhookServerURL/<TokenReview>
, чтобы валидировать токен.
Отключаем анонимный доступ
Как и kube-apiserver, kubelet поддерживает анонимный доступ. Чтобы его отключить, запускаем kubelet с флагом $ kublet --anonymous-auth=false
.
Включаем авторизацию для kubelet API
Режим авторизации по умолчанию для kubelet API — AlwaysAllow, то есть авторизуется каждый входящий запрос. Kubelet поддерживает делегирование авторизации на сервер вебхуков (похоже на аутентификацию через токен на предъявителя) в режиме webhook. Для этого запускаем kubelet с флагами --authorization-mode=Webhook
и --kubeconfig
. Kubelet будет вызывать конечную точку SubjectAccessReview на kube-apiserver https://WebhookServerURL/<SubjectAccessReview>
для проверок авторизации.
П емь лучших практик для защиты API Kubernetes, помимо TLS, управления пользователями, аутентификации и авторизации.
Незащищённый доступ к дашборду Kubernetes может стать огромной брешью в обороне всей системы. Если вы разрешаете доступ к дашборду Kubernetes, настройте надёжную аутентификацию и авторизацию по стандартным правилам безопасности веб-приложений.
С помощью пространств имён в Kubernetes мы объединяем связанные ресурсы, создавая для них виртуальную границу. В сочетании с RoleBinding пространства имён помогают сдержать радиус поражения в случае инцидента. Когда мы используем пространство имён для разделения конфиденциальных рабочих нагрузок, компрометация аккаунта с доступом в одно пространство имён не повлияет на целостность остальных. Объекты NetworkPolicy и разделение на пространства имён — это лучшая комбинация для защиты нод.
Файл kubeconfig может содержать вредоносный код или нарушать безопасность конфиденциальных файлов. Нужно проверять файл kubeconfig так же тщательно, как shell-скрипт.
Вот пример файла kubeconfig, который раскрывает закрытые ключи SSH (Проблема GitHub: использование ненадёжных файлов Kubeconfig в Kubernetes)
Feature gate представляет собой пару ключ-значение и используется для включения и отключения определённого компонента Kubernetes. Компонентов у Kubernetes очень много (они настраиваются через API), но не все они нам нужны. Ненужные лучше отключать, чтобы сократить поверхность атаки.
В Kubernetes компоненты проходят три стадии: альфа (нестабильный, с ошибками), бета (стабильный, включён по умолчанию) и GA (общедоступный компонент, включён по умолчанию). Если мы хотим отключить компонент на стадии альфа, мы передаём для kube-apiserver флаг --feature-gates="...,LegacyNodeRoleBehavior=false, DynamicKubeletConfig=false"
(не действует на компоненты со статусом GA)
Лимиты (ограничения на потребление ресурсов на уровне pod’ов и контейнеров) и квоты на ресурсы (ограничения на потребление ресурсов в пространстве имён) позволяют справедливо распределять доступные ресурсы и защищаться от таких опасностей, как скрытый майнинг.
См. документацию по лимитам и квотам на ресурсы.
Etcd — это хранилище по умолчанию для всех данных кластера. Доступ к etcd приравнивается к разрешениям root в кластере Kubernetes. Кроме того, у etcd есть собственный набор HTTP API, то есть защищать etcd важно на уровне прямого сетевого доступа и HTTP-запросов. См. документацию Kubernetes по управлению кластером etcd и официальную документацию по etcd. Ещё можно установить флаг --encryption-provider-config
, чтобы настроить, как шифровать данные конфигурации API в etcd. См. руководство по шифрованию секретов при хранении, где рассказывается, как эта возможность реализована в Kubernetes.
Про TLS-соединение между etcd и kube-apiserver см. выше.
Для записи и просмотра всех событий API есть инструменты мониторинга, логирования и аудита. Ниже приводится пример файла конфигурации аудита, который можно передать kube-apiserver:
См. это руководство про конфигурацию аудита.
Kubernetes поддерживает гибкие подходы к контролю доступа, но нужно как следует все обдумать, чтобы выбрать самые эффективные и безопасные варианты управлять доступом к API. В этой статье мы рассмотрели различные схемы контроля доступа к API, которые поддерживаются в Kubernetes, а также рекомендации по их использованию.
Эти рекомендации пригодятся вам, даже если вы не управляете кластерами Kubernetes сами, а используете сервисы, вроде AWS EKS или GCP Kubernetes Engine. Всегда полезно понимать, как что устроено, чтобы гарантировать надёжную защиту ресурсов. Кстати, не забывайте про модель облачной безопасности 4C, в которой каждый уровень строится на предыдущем, и от Kubernetes зависит общая безопасность вашей системы в облаке.