Ошибка авторизации для kubectl exec часто выглядит нелогично:

cannot create resource "pods/exec"

Pod уже существует. Команда не создаёт новый Pod и не изменяет его спецификацию. Поэтому ожидаемым разрешением кажется get или update, но Kubernetes проверяет create.

Причина в том, что RBAC описывает операции Kubernetes API, а не буквальный смысл команд kubectl.

exec — отдельный подресурс

kubectl exec обращается не к ресурсу pods напрямую. Команда вызывает подресурс pods/exec:

/api/v1/namespaces/<namespace>/pods/<pod>/exec

В RBAC ресурс и его подресурсы указываются отдельно:

resources:
  - pods
  - pods/log
  - pods/exec

Разрешение get на pods не даёт доступ к pods/exec. Это разные точки API с разными операциями и рисками.

get pods позволяет получить описание Pod. get pods/log позволяет прочитать его логи. create pods/exec позволяет запустить новый сеанс выполнения команды внутри контейнера.

Откуда взялся create

Kubernetes преобразует методы HTTP в собственные глаголы авторизации:

Метод HTTPVerb Kubernetes
GETget, list или watch
POSTcreate
PUTupdate
PATCHpatch
DELETEdelete или deletecollection

Исторически kubectl exec открывал потоковое соединение через SPDY. Начальный запрос к pods/exec использовал HTTP POST, поэтому API server преобразовывал его в проверку:

create pods/exec

create здесь не означает создание нового Pod или другого сохраняемого ресурса Kubernetes. Он разрешает начать новую операцию через подресурс.

Это важное различие. Глагол RBAC описывает способ обращения к API. Он не гарантирует, что результатом станет новый объект в etcd.

Что изменил переход на WebSocket

С Kubernetes 1.31 kubectl по умолчанию использует WebSocket вместо SPDY для exec, attach, cp и port-forward.

Рукопожатие WebSocket начинается с HTTP GET:

GET /api/v1/namespaces/<namespace>/pods/<pod>/exec
Connection: Upgrade
Upgrade: websocket

Из-за этого прежнее объяснение через соответствие POST и create перестало описывать всю картину. Во время перехода WebSocket-запрос к pods/exec мог проверяться как get, хотя тот же вызов через SPDY требовал create.

Одинаковая операция получала разные требования RBAC в зависимости от транспортного протокола.

В Kubernetes 1.35 появилась включённая по умолчанию feature gate AuthorizePodWebsocketUpgradeCreatePermission. При WebSocket-подключении API server выполняет дополнительную проверку create для следующих подресурсов:

  • pods/exec
  • pods/attach
  • pods/portforward

Так Kubernetes вернул единое правило: смена потокового протокола не должна превращать активную операцию в разрешение на чтение.

Как выглядит Role

Типичная Role для оператора, который просматривает Pod и при необходимости запускает в нём команды:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-debugger
  namespace: example
rules:
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
      - list
      - watch

  - apiGroups:
      - ""
    resources:
      - pods/log
    verbs:
      - get

  - apiGroups:
      - ""
    resources:
      - pods/exec
    verbs:
      - create

Разрешения разделены намеренно.

Чтение Pod и логов не даёт возможности выполнять команды в контейнере. Доступ к exec можно выдать отдельно только тем пользователям и ServiceAccount, которым он действительно нужен.

Это важно ещё и потому, что Kubernetes RBAC не ограничивает команды внутри открытой exec-сессии. Получив доступ к pods/exec, пользователь может выполнять любые команды, доступные процессу контейнера.

Как проверить разрешение

Проверить доступ к подресурсу можно без запуска команды:

kubectl auth can-i create pods \
  --subresource=exec \
  --namespace=example

Отдельно следует проверить доступ к самому Pod:

kubectl auth can-i get pods \
  --namespace=example

Для проверки другого пользователя или ServiceAccount можно использовать --as:

kubectl auth can-i create pods \
  --subresource=exec \
  --namespace=example \
  --as=system:serviceaccount:example:debugger

Результат yes для get pods не говорит ничего о доступе к pods/exec. Проверять нужно именно сочетание ресурса, подресурса и глагола.

Вывод

kubectl exec требует create не потому, что создаёт новый Pod. Команда обращается к активному подресурсу pods/exec и начинает новый сеанс выполнения команды.

Исторически create следовал из HTTP POST, который использовал SPDY. После перехода на WebSocket с GET-рукопожатием Kubernetes сохранил ту же модель с помощью отдельной проверки авторизации.

Поэтому глаголы RBAC не стоит выводить из названия команды или предположения о том, изменяется ли объект. Надёжнее определить ресурс, подресурс и операцию, которую API server авторизует для конкретного запроса.

Ссылки