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. Ниже кратко приводим ключевые пункты, которые мы считаем критичными для нашего сервиса:

  1. На фундаментальном уровне используем технологию ClusterAPI для управления полным жизненным циклом клиентского Kubernetes. Исходя из этого, любые наши решения должны быть бесшовно совместимы и нативно интегрированы с ClusterAPI и его библиотеками;

  2. control plane ноды предоставляемых кластеров должны быть скрыты и недоступны для воздействия со стороны клиента. Иными словами, клиенту предоставляется kube-api endpoint и воркера, доступные для размещения нагрузок;

  3. control plane должен быть изолирован как инфраструктурно — ноды не видны и недоступны из кластера, так и по сети — ноды находятся в отдельном сетевом сегменте, отличном от клиентского VPC. В идеале сделать две точки входа в кластер (два endpoint-а на kube-api): сервисную, по которой будет ходить служебный трафик со стороны ClusterAPI, и пользовательскую, которая будет доступна клиенту;

  4. весь процесс бутстраппинга control plane и worker-нод должен быть максимально приближен к ванильному. Управление и все процессы в кластере должны быть неотличимы от Vanilla Kubernetes со стороны пользователя.

Схема архитектуры MK8S в облаке
Схема архитектуры MK8S в облаке

Также для нас важны: 

  • Возможность работы в 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.

Зачем это нужно?

  1. Масштабируемость.  При традиционном подходе каждый полноценный кластер съедает много ресурсов — etcd, kube-apiserver, controller-manager, scheduler и т. д. Hosted сontrol planes при определённых конфигурациях позволяют держать тысячи кластеров на одних и тех же физических мощностях.

  2. Стоимость.  Снижение затрат на инфраструктуру — не нужно поднимать отдельные виртуалки или bare metal для каждого control plane.

  3. Изоляция и безопасность. Каждый control plane работает изолированно на уровне сети и прав доступа.

Схема инсталляции Hosted-CP: control plane кластера отделён от workload и находится в провайдерском сегменте
Схема инсталляции Hosted-CP: control plane кластера отделён от workload и находится в провайдерском сегменте

Схожие решения, используемые другими облачными провайдерами

  • 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 вне контура управляющего кластера.

На рисунке можно увидеть описанную архитектуру k0smotron-а в формате In- и Out- of Cluster
На рисунке можно увидеть описанную архитектуру k0smotron-а в формате In- и Out- of Cluster

В первом случае 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 управляющего кластера, либо отдельно стоящие виртуальные машины с кастомным бутстрапом кубера.

Пример манифестов k0smotron control plane
Пример манифестов k0smotron control plane

Так как провайдер совместим с 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 нарушается, и статусная модель не соблюдается.

Статусная модель контроллера Machine нарушена
Статусная модель контроллера Machine нарушена

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

Openshift

Архитектура Openshift схожа с k0smotron in-cluster: сontrol plane в виде подов на управляющем кластере, worker-ы в клиентском сегменте
Архитектура Openshift схожа с k0smotron in-cluster: сontrol plane в виде подов на управляющем кластере, worker-ы в клиентском сегменте

Тут одновременно всё просто и всё сложно. Простота в том, что 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.

Схема инсталляции Kamaji
Схема инсталляции Kamaji

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

Пример манифеста KamajiControlPlane
Пример манифеста KamajiControlPlane

Чтобы развернуть 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 содержит баги и иногда может крашиться. Баги регулярно чинят, регулярно появляются новые — в целом при условиях серьёзного продакшена следует отнестись к кодовой базе весьма настороженно.

Подведём итоги

Общая сравнительная таблица провайдеров CAPI
Общая сравнительная таблица провайдеров CAPI

На наш взгляд:

k0smotron — интересный провайдер, самый гибкий из всех, предлагающий построить Kubernetes на собственной инфре либо как поды.

Openshift/OKD — полностью вендорское решение, достаточно зрелое и задокументированное, но достаточно сложное в имплементации. Подойдёт тем, кто и так работает с Openshift, предоставляет данный сервис и хочет захарденить control plane.

Kamaji — что-то среднее. Прост в инсталляции, быстр в установке и развёртке, митигирует некоторые шероховатости k0smotron'а. Независим от инфраструктуры, достаточно иметь кластер Kubernetes с установленными контроллерами capi. Хороший вариант, чтобы потестить hosted-CP с минимальными затратами на разбор документации и подготовку пререквизитов.

А что в итоге взяли мы?

Вкратце: в чистом виде — ничего из перечисленного. Конечно же, серебряной пули нет и какого-либо идеального провайдера, отвечающего всем нашим внутренним нуждам, мы не нашли. Мы решили собрать всё лучшее из представленных вариантов — бутстрап кубера на ВМ на инфре из k0smotron, нативный процесс бутстрапа из kamaji и флоу оператора из Openshift и написать свой собственный kubeadm-провайдер для ClusterAPI.

Пример бутстрапа на kubeadm без регистрации CP-ноды в кластере
Пример бутстрапа на kubeadm без регистрации CP-ноды в кластере

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


Интересные статьи про MWS Cloud Platform:

  1. Как мы строим Development Platform в новом облаке MWS и развиваем inner source-культуру

  2. Как построили собственный Object Storage

  3. DHCP-сервер облачной сети MWS Cloud Platform: раздаём одинаковые IP на разные виртуалки

Комментарии (0)