
Control plane Kubernetes — как сейф с главными ключами. Он управляет кластером, хранит sensitive-информацию и зачастую представляет собой лакомую цель для злоумышленников.
В этой статье — разбор того, как спрятать control plane в сервисе Managed Kubernetes, предоставляемом в облаке: зачем это нужно, какие варианты существуют и с какими проблемами мы столкнулись на практике. Мы рассмотрим несколько open-source решений, которые протестировали у себя в поисках надёжного способа изолировать control plane-ноды от пользователя и сделать их недоступными для какого-либо внешнего взаимодействия.
Меня зовут Каиржан, я DevOps/Software-инженер в команде разработки MWS Cloud Platform, пишу на Go под Kubernetes и ClusterAPI. Наша команда разрабатывает сервис Managed Kubernetes для публичного облака — от инфраструктуры до собственных провайдеров для ClusterAPI. Поэтому вопрос безопасности control plane (CP) для нас стоит особенно остро.
А для чего вообще прятать control plane?
Поговорим немного о том, зачем мы вообще решили заморочиться со скрытием CP от пользователя. Какие это даёт плюсы?
В ванильной инсталляции Kubernetes, control plane-ноды содержат sensitive-информацию кластера: сертификаты, ключи от сервис-аккаунтов, ключи от etcd, зачастую саму базу etcd. Все эти секреты могут послужить вектором атаки. Если атакующий завладеет корневым сертификатом от кластера и ключом к нему, он фактически завладеет всем кластером и сможет подделывать identity в конфигах подключения к кластеру и других местах, где используется данный кластер.
Атаке также может быть подвержен любой софт, запущенный на мастерах. К примеру, поды контроллеров CNI/CSI, которые зачастую содержат kubeconfig с расширенными правами, секреты от инфраструктуры и т. п.
Помимо этого, точка входа в кластер — kube-api endpoint, если она светится в незащищённой сети в открытом виде, — тоже может послужить предметом атаки. Тут в качестве примера можно вспомнить уязвимость в гошной библиотеке net/http2, позволяющую провести DoS-атаку на сервис.
Ну и никто не отменял того, что сами ноды, если они видны, могут также стать целью атаки. Например, существовала уязвимость в NodeRestriction-плагине для kube-api, которая позволяла путём подмены лейблов от мастеров переместить workload с мастеров на произвольные (читай — скомпрометированные) ноды и получить доступ к sensitive-информации.
Тут приводим несколько конкретных уязвимостей, эксплуатируемых при открытых control plane нодах и на которые мы наткнулись в процессе наших изысканий:
Компонент |
CVE |
Описание |
---|---|---|
kube-apiserver |
CVE-2023-5408 |
Обход NodeRestriction plugin: можно подделать label-ноды, переместить workload c etcd/СР-нод, получив эскалацию привилегий |
kube-apiserver |
CVE-2023-45288 |
Уязвимость НТТР/2: удалённый атакующий может перегрузить API-сервер (DoS). GKE рекомендовал ограничить доступ к АРІ через список доверенных ІР-адресов (authorized networks) |
etcd |
CVE-2023-32082 |
Утечка ключей lease API: можно узнать имена ключей, |
secrets-store CSI driver |
CVE-2023-32082 |
Утечка токенов: токены service-account попадали в открытом виде в логи |
А помимо устранения уязвимостей, что ещё это может дать?
Если в кластере не будет мастеров, то пользователь не сможет разместить на них свою нагрузку. Это позволит снизить количество аварий, когда пользователь прибивает мастера своей нагрузкой, тем самым приводя к отказу весь кластер. С другой стороны, и облачному провайдеру становится проще планировать свои ресурсы под control plane.
Упрощаются процессы поддержки кластера, его регулярного обслуживания, периодического обновления: как версии Kubernetes, так и версий системного софта. Можно проводить обновления по своему расписанию, выделять сервисные окна или дать возможность автоматического обновления пользователем, как это сделано в GKE Autopilot.
Изоляция control plane и kube-api endpoint в принципе является хорошей практикой в построении Kubernetes-кластеров. Google и Amazon пишут об этом в своих рекомендациях по безопасности кластеров.
Кроме того, за последние несколько лет у облачных провайдеров в принципе появился тренд на хостинг control plane на своей стороне и сокрытие его от клиента (в дальнейшем будем такую инсталляцию называть hosted-CP). Вспомнить хотя бы GKE Autopilot или AWS EKS.
Постановка задачи
В настоящее время наша команда занимается построением сервиса Managed Kubernetes в MWS Cloud Platform — новом публичном облаке MWS. В этом разделе попытаемся описать State of the Art инсталляцию mk8s. Ниже кратко приводим ключевые пункты, которые мы считаем критичными для нашего сервиса:
На фундаментальном уровне используем технологию ClusterAPI для управления полным жизненным циклом клиентского Kubernetes. Исходя из этого, любые наши решения должны быть бесшовно совместимы и нативно интегрированы с ClusterAPI и его библиотеками;
control plane ноды предоставляемых кластеров должны быть скрыты и недоступны для воздействия со стороны клиента. Иными словами, клиенту предоставляется kube-api endpoint и воркера, доступные для размещения нагрузок;
control plane должен быть изолирован как инфраструктурно — ноды не видны и недоступны из кластера, так и по сети — ноды находятся в отдельном сетевом сегменте, отличном от клиентского VPC. В идеале сделать две точки входа в кластер (два endpoint-а на kube-api): сервисную, по которой будет ходить служебный трафик со стороны ClusterAPI, и пользовательскую, которая будет доступна клиенту;
весь процесс бутстраппинга control plane и worker-нод должен быть максимально приближен к ванильному. Управление и все процессы в кластере должны быть неотличимы от Vanilla Kubernetes со стороны пользователя.

Также для нас важны:
Возможность работы в High Availability-режиме, возможность иметь несколько инстансов мастер-нод и инстансов etcd.
Создание CP-провайдером изолированных друг от друга пользовательских control plane. Иными словами, чтобы пользовательские кластеры были разграниченными (например, по неймспейсам на управляющем кластере) друг от друга.
Немаловажным является и лёгкость в установке, обслуживании и управлении выбранным control plane-провайдером CAPI. Например, хотелось бы, чтобы его было легко обновлять, он нативно цеплялся к capi-контроллерам и не требовал больших доработок.
Из предыдущего пункта также вытекает гибкость к различной инфраструктуре. Не хотелось бы иметь провайдера, который можно развернуть, например, только на Vsphere или только в Openstack.
Пару слов о Hosted-CP: наша мотивация и тренды
Опишем архитектуру hosted-control-plane в целом: что это и чем отличается от ванильного провижининга Kubernetes тем же ClusterAPI?
Пару слов о том «что такое этот ваш Hosted-CP?»:
Первое и самое важное отличие — собственно, скрытый control plane кластера. Мастера в таком кластере не находятся во владении пользователя, не видны ему и не доступны для взаимодействия. Они даже не находятся в пользовательском сетевом сегменте, а расположены либо в провайдерской сети рядом с управляющим management-кластером, либо даже в отдельном сервисном сегменте.
В примерах, которые мы рассмотрим, control plane представляет собой либо поды, внутри которых крутятся бинари k8s: kube-api, scheduler, controller-manager, либо полноценные виртуалки, созданные инфраструктурным провайдером capi, внутри которых также запущены бинари k8s без регистрации такой инсталляции в качестве ноды кластера.
Воркер-ноды при этом могут быть расположены где угодно, бутстрапиться как угодно нативным процессом через kubeadm.
Зачем это нужно?
Масштабируемость. При традиционном подходе каждый полноценный кластер съедает много ресурсов — etcd, kube-apiserver, controller-manager, scheduler и т. д. Hosted сontrol planes при определённых конфигурациях позволяют держать тысячи кластеров на одних и тех же физических мощностях.
Стоимость. Снижение затрат на инфраструктуру — не нужно поднимать отдельные виртуалки или bare metal для каждого control plane.
Изоляция и безопасность. Каждый control plane работает изолированно на уровне сети и прав доступа.

Схожие решения, используемые другими облачными провайдерами
Red Hat/OKD — проект Hypershift: OpenShift на базе hosted control plane. Под капотом — операторы, управляющие отдельными control plane'ами внутри выделенных namespace. ([Doc], [Doc])
Amazon EKS by-design прячет мастера в приватной сети и предоставляет только kube-api endpoint. [Doc]
Google GKE Autopilot — концептуально очень близкий подход: полностью управляемый control plane без необходимости заботиться о его инфраструктуре. ([Doc])
Полезная ссылка про тренд на Hosted-CP от Clastix (Kamaji)
Обзор Open-Source решений
Мы рассмотрели и попробовали три open-source проекта, которые позволяют создать нужную нам инсталляцию Kubernetes. В рамках данной статьи представляем обзор на них.
k0smotron
Первое Hosted-CP решение, рассмотренное нами.
По сути, это логическое продолжение проекта k0s, который запускает control plane Kubernetes не так, как это делается нативно — через kubelet и контейнеры с apiserver, scheduler, controller-manager и etcd, а как простые исполняемые файлы и процессы на linux-машине. При этом, т. к. у вас отсутствует kubelet и все компоненты запускаются просто как исполняемые файлы, у вас фактически отсутствует понятие мастер-ноды — виртуальная машина, где запущен control plane в кластере не регистрируется в кластере и не видна клиенту.
Что делает k0smotron — по сути он связывает k0s с ClusterAPI. Это оператор, который берёт на себя bootstrap и управление k0s control plane. Причём k0smotron может создавать control plane двух видов: как поды внутри management-кластера, где установлены все компоненты CAPI, и как отдельные виртуальные машины, которые создаются инфрапровайдером в вашем облаке, например в VSphere или Openstack-е, и в которых запускается k0s. В рамках статьи рассматривается инсталляция k0smotron с интеграцией с ClusterAPI и созданием control plane вне контура управляющего кластера.

В первом случае CP представляет собой просто деплоймент из подов в namespace управляющего кластера. В подах запущены apiserver, scheduler, controller manager, etcd при этом может располагаться там же в виде StatefulSet или быть внешним кластером. Сам деплоймент вывешивается наружу через кубовый сервис — ClusterAPI или Loadbalancer. Этот эндпоинт также можно дополнительно запроксировать и передать пользователю. Соответственно, каждый клиентский кластер — это отдельный deploy в отдельном namespace.
Worker-ноды в свою очередь располагаются в пользовательском сегменте и соединены с apiserver-ом с помощью специальной утилиты — Konnectivity proxy, по сути, это backchannel-прокси, который позволяет выстроить коннект между нодами и apiserver-ом в разных сегментах.
Во втором же случае kosmotron выступает в роли bootstrap провайдера и бутстрапит нам CP на отдельной виртуальной машине. Процесс бутстрапа достаточно прост — он поднимает k0s, который уже запускает нам бинарники Kubernetes в виде linux-процессов. Worker-ноды в обоих случаях ничем не отличаются, находятся в пользовательском сегменте, цепляются через backchannel-прокси.
Собственно, так работает k0smotron. Небольшой спойлер: все решения hosted-CP, которые мы нашли и которые рассматриваем в статье, будут построены примерно схожим образом. Это либо поды в namespace управляющего кластера, либо отдельно стоящие виртуальные машины с кастомным бутстрапом кубера.

Так как провайдер совместим с CAPI, он поддерживает стандартные манифесты для Cluster/InfrastructureCluster и K0sControlPlane. На примере показаны манифесты для развёртки k0s control plane на виртуальных машинах Vsphere. Эта инсталляция, которую мы использовали в наших исследованиях.
Тут достаточно применить манифест Cluster, в его ownerRef-ах указать ссылки на control plane провайдера — K0sControlPlane и инфраструктурного — в нашем случае это VsphereCluster и VsphereMachineTemplate.
Манифесты vsphere обрабатываются capi-vsphere-провайдером, на инфраструктуре создаются виртуалки, которые затем подхватывает k0smotron и бутстрапит мастера Kubernetes согласно конфигурации, которую мы описали в соответствующем манифесте. Таким образом получаем мастера на виртуалках, которые не видны пользователю и которые можно создавать либо в провайдерской сети, рядом с capi-кластером, либо в любом произвольном сегменте. Главное — обеспечить связность с capi-кластером.
И всё было бы так радужно и работало бы хорошо, если бы не нюансы…
У k0smotron есть несколько достаточно критичных вещей, которые требуют доработки.
С точки зрения etcd отсутствует полноценный менеджмент над etcd-кластером. В одномастерной конфигурации кластера проблем нет — k0smotron создаст вам под или vm под мастер. В случае пода etcd будет в виде StatefulSet рядом с деплойментом, в случае VM etcd будет располагаться на той же виртуалке, где и крутится apiserver.
Сложности начинаются при скейлинге или роллауте. У k0smotron нет механизма управления etcd-кластером. Другими словами, он не умеет добавлять или удалять member'ов etcd, а это очень критично, т. к. etcd использует кворум и, если у вас периодически будут отваливаться члены этого кворума, существует неиллюзорная вероятность положить весь кластер и получить аварию. В документации k0smotron-a об этом даже написано, что в HA-инсталляциях манипуляции с etcd нужно производить самостоятельно ручками.
Также есть некоторые вопросы к совместимости провайдера с нативным flow ClusterAPI, а именно со статусной моделью контроллера машин. Этот контроллер работает следующим образом: он дожидается того, что машины под мастера и под воркеры будут созданы инфрапровайдером и на них забутстрапится кубер. После этого контроллер capi соотнесёт ноду в кубере, providerID и объект capi'шной Machine. Тут-то и возникают трудности. Так как на наших мастерах запускается k0s, нет kubelet-а и ноды не регистрируются, capi не с чем соотносить объекты Machine и они не выходят в конечный статус Ready. Флоу capi нарушается, и статусная модель не соблюдается.

Таким образом, у k0smotron-а, по крайней мере на момент, когда мы его исследовали, существовали перечисленные проблемы.

Openshift

Тут одновременно всё просто и всё сложно. Простота в том, что Openshift уже достаточно давно разрабатывает собственное решение для HostedCP, проект называется Hypershift или OKD Hosted Control Plane. Это набор операторов, которые управляют развёрткой CP и etcd.
Принцип работы схож с k0smotron in-cluster: CP — это деплоймент в namespace management-кластера, etcd — это StatefulSet.
Сложность же в том, что это всё-таки не кубер в чистом виде, а операторы Openshift/OKD, сущности Openshift/OKD и кластер Openshift/OKD. Процесс развёртки похож на ClusterAPI, но это не ClusterAPI. Вместо Cluster тут объекты HostedCluster, вместо MachineDeployment тут NodePool. И на выходе вы, собственно, тоже получите кластер Openshift.
Из приятностей — менеджмент etcd тут есть, кластер также разворачивается оператором и поддерживает управление всем жизненным циклом: созданием, скейлингом, обновлением и удалением. Все процедуры достаточно подробно описаны в документации, и такой анархии, как в k0smotron, в Openshift всё же нет.
Очень подробно на том, как это работает и как это установить, останавливаться не будем, т.к. и у Openshift, и у OKD есть замечательная и подробная документация, Step-By-Step guide о том, как сконфигурировать все операторы и как развернуть Openshift в режиме Hosted-CP.
Pros & Cons OKD Hosted-CP
Из плюсов — всё делается операторами и, соответственно, имеем полный менеджмент над клиентским кластером. Управляется такой кластер централизованно через API и объекты Openshift. Плюс есть достаточно хорошая и подробная документация о том, как это всё развернуть и обслуживать.
Из минусов — это всё же завязка на вендора, неванильный кубер. Другими словами, немного не то, что мы бы хотели предоставлять у себя. Достаточно непростой процесс первичной установки и поддержки всех операторов, по сути, это уже не ClusterAPI и все процессы придётся настраивать и изучать заново.
И всё-таки некоторые ограничения по инфраструктуре есть, по крайней мере, когда мы смотрели Hypershift, в доке был перечень поддерживаемых провайдеров, и пререквизиты, которые тоже необходимо изучить.

Kamaji
Теперь перейдём к ещё одному интересному CP-провайдеру — Kamaji. Kamaji — это проект, который очень похож на то, что мы уже рассмотрели. Это оператор, который разворачивает deployment с подами, в которых крутятся бинари Kubernetes.
Отличие заключается в том, что Kamaji более тесно интегрирован с CAPI, написан capi-provider-kamaji, который полностью берёт на себя управление над Kamaji-сущностью (Tenant Control Plane) и Capi-сущностью (Control Plane).
Таким образом, в отличие от k0smotron, со статусами здесь всё в порядке, CP в своей основе использует не стандартный CAPI ресурс Machine, а управляется через custom resource KamajiControlPlane.

Etcd в этом проекте всегда external cluster. Это может быть как StatefulSet (команда kamaji заботливо написала helm-чарт, который разворачивает нам такой etcd), так и внешний кластер, собранный на ваше усмотрение.

Чтобы развернуть KamajiCP достаточно поднять кластер etcd, например через helm, и применить стандартный capi-манифест Cluster с рефом на KamajiControlPlane.
Следует обратить внимание, что секция infrastructureRef необходима только для worker-нод. Другими словами, Kamaji предоставляет нам только control plane, а как, где и с помощью чего разворачивать воркеры — уже наша забота. К примеру, воркеры можно разворачивать на инфре Vsphere, бутстрапить с помощью ванильного kubeadm-провайдера и без проблем таким образом джойнить к подам мастеры, созданные Kamaji.

После применения манифестов получим деплой из подов, содержащих бинари куба и endpoint kube-api, который можно также запроксировать и отдать пользователю как точку входа в кластер и использовать для join-а воркеров. Весь процесс бутстрапа Kubernetes нативный, на kubeadm-процессе, соответственно, у нас создаются все необходимые сервисные RBAC, токены и сервис-аккаунты для join-а воркеров.
Плюсы и минусы Kamaji
Kamaji — это провайдер, в котором частично решены недостатки k0smotron и который, в отличие от Openshift, предоставляет ванильный Kubernetes. Его достаточно просто поставить, и он практически не зависит от инфраструктуры, так как CP разворачивается в единственном варианте — в виде деплоймента в management-кластере.
Из видимых проблем можно выделить:
отсутствующий менеджмент над etcd, хоть это частично сглаживается тем, что установку StatefulSet при развёртке кластера можно делать по хуку или использовать GitOps-подход. Но всё же полноценное решение отсутствует.
На наш взгляд, проект Kamaji относительно мал по сравнению с тем же k0s илиOpenshift. Провайдер для capi содержит баги и иногда может крашиться. Баги регулярно чинят, регулярно появляются новые — в целом при условиях серьёзного продакшена следует отнестись к кодовой базе весьма настороженно.
Подведём итоги

На наш взгляд:
k0smotron — интересный провайдер, самый гибкий из всех, предлагающий построить Kubernetes на собственной инфре либо как поды.
Openshift/OKD — полностью вендорское решение, достаточно зрелое и задокументированное, но достаточно сложное в имплементации. Подойдёт тем, кто и так работает с Openshift, предоставляет данный сервис и хочет захарденить control plane.
Kamaji — что-то среднее. Прост в инсталляции, быстр в установке и развёртке, митигирует некоторые шероховатости k0smotron'а. Независим от инфраструктуры, достаточно иметь кластер Kubernetes с установленными контроллерами capi. Хороший вариант, чтобы потестить hosted-CP с минимальными затратами на разбор документации и подготовку пререквизитов.
А что в итоге взяли мы?
Вкратце: в чистом виде — ничего из перечисленного. Конечно же, серебряной пули нет и какого-либо идеального провайдера, отвечающего всем нашим внутренним нуждам, мы не нашли. Мы решили собрать всё лучшее из представленных вариантов — бутстрап кубера на ВМ на инфре из k0smotron, нативный процесс бутстрапа из kamaji и флоу оператора из Openshift и написать свой собственный kubeadm-провайдер для ClusterAPI.

Под капотом — бутстрап на определённых kubeadm-стейджах, используем kubelet как инструмент для запуска статических подов, в конфиге того же kubelet не регистрируем ноду. Наш провайдер сейчас находится в активной разработке, возможно, о нём я или мои коллеги ещё расскажем в будущих статьях. Stay tuned.
Интересные статьи про MWS Cloud Platform: