← Kubernetes: разбор поломок
7 мин · средне · Урок 5 из 12

Forbidden у ServiceAccount

Как разобрать ошибку 403 от apiserver по словам и проверить права через kubectl auth can-i, не гадая.

Приложение внутри кластера ходит в Kubernetes API и получает отказ:

Error from server (Forbidden): configmaps is forbidden:
  User "system:serviceaccount:demo:app-sa" cannot list resource
  "configmaps" in API group "" in the namespace "demo"

Снаружи всё выглядит настроенным: ServiceAccount есть, Role есть, RoleBinding есть. А запрос всё равно не проходит. Разбираем, как читать эту ошибку и как проверить права, не перебирая варианты наугад.

Четыре причины Forbidden

ServiceAccount (SA) - учётная запись для процессов внутри кластера. Когда SA получает 403 от kube-apiserver, причина одна из четырёх:

  1. У Pod нет своего SA, и используется default без прав.
  2. SA есть, но нет RoleBinding или ClusterRoleBinding.
  3. Есть всё, но в Role не хватает нужного verb или resource.
  4. Subject в RoleBinding указан с другим namespace.

Subject в RoleBinding несёт свой namespace, и он не обязан совпадать с namespace самого RoleBinding. Права выдали в один namespace, SA живёт в другом: объект создался без ошибок и не работает.

Чаще всего третий пункт. Кто-то написал Role на pods и забыл про configmaps. Или выдал get, но не list. Симптом одинаковый.

Главный инструмент: kubectl auth can-i

Команда отвечает на вопрос «может ли этот subject выполнить это действие». Без неё RBAC приходится дебажить перебором.

kubectl auth can-i list configmaps \
  --as=system:serviceaccount:demo:app-sa -n demo

Ответ будет yes или no. Гадать не нужно: kubectl отправляет запрос на авторизацию от имени указанного subject, и apiserver отвечает так же, как ответил бы реальному клиенту.

Формат subject важен: system:serviceaccount:<namespace>:<sa-name>. Без префикса kubectl поймёт строку как имя пользователя, а не SA, и ответ будет про другого субъекта.

Флаг -n тоже важен. Права на namespaced-ресурсы зависят от namespace. Если флаг опустить, kubectl возьмёт namespace из текущего контекста, а это может быть не тот, где живёт SA.

Полная картина по subject выдаётся одной командой:

kubectl auth can-i --list \
  --as=system:serviceaccount:demo:app-sa -n demo

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

Расшифровка ошибки

Forbidden · текст ошибки cannot list resource "configmaps" Role · rules verbs: ["list"] resources: ["configmaps"] совпадение строк
Resource и verb из ошибки переписываются в rules без перевода.

Текст ошибки структурирован, и в нём есть всё для починки:

Значит, в Role внутри namespace demo нужно правило, где есть apiGroups: [""], resources: ["configmaps"] и verbs: ["list"]. На практике к list сразу добавляют get и watch, потому что клиентские библиотеки почти всегда используют их вместе.

Wildcard resources: ["*"] тоже уберёт ошибку, но выдаст SA права на всё в namespace. Так делать не стоит.

Где смотреть существующие правила

# Все Role в namespace
kubectl get role -n demo

# Полный YAML конкретного Role
kubectl get role app-reader -n demo -o yaml

# Только правила
kubectl get role app-reader -n demo -o jsonpath='{.rules}'

rules это массив. В каждом элементе три обязательных поля: apiGroups, resources, verbs. Работают они как декартово произведение: каждый verb разрешён на каждом resource из каждого apiGroup внутри одного правила.

Пример Role с двумя правилами:

rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get"]

С такой Role можно делать get pods, list pods и get configmaps. Нельзя: list configmaps (нет verb), delete pods (нет verb), get secrets (нет resource). Права в Kubernetes только разрешающие, запрещающих правил нет: чего нет в правилах, того нельзя.

Role против ClusterRole

Четыре объекта, которые легко перепутать:

ОбъектОбласть действия
Roleправа внутри одного namespace
ClusterRoleправа на уровне кластера или шаблон для нескольких namespace
RoleBindingвыдаёт права (из Role или ClusterRole) внутри одного namespace
ClusterRoleBindingвыдаёт права (только из ClusterRole) во всём кластере

Комбинация RoleBinding плюс ClusterRole рабочая и распространённая: общий шаблон прав применяется точечно в одном namespace.

Как чинят

Недостающий verb добавляют в существующий Role:

kubectl edit role app-reader -n demo

или патчем без редактора:

kubectl patch role app-reader -n demo --type=json \
  -p='[{"op":"add","path":"/rules/1/verbs/-","value":"list"}]'

Проверка та же команда, что и в начале разбора: kubectl auth can-i должна ответить yes. Изменения в RBAC применяются сразу, перезапускать Pod не нужно.

Если не хватает не verb, а RoleBinding целиком:

kubectl create rolebinding app-reader-binding \
  --role=app-reader \
  --serviceaccount=demo:app-sa \
  -n demo

Как не допустить

Права выдают по факту, от конкретной ошибки Forbidden, а не заранее и «с запасом». kubectl auth can-i --list перед выкаткой показывает, что на самом деле получил SA.

Пропись serviceAccountName в spec Pod обязательна. Без неё Pod работает от default, у которого прав нет, и симптом будет тот же самый.

Коротко

Прошли материал до конца? Отметьте урок пройденным.