Forbidden в Kubernetes: проверяем ServiceAccount, Role и RoleBinding

Контроллер внутри Pod пытается прочитать ConfigMap и получает Forbidden. Сеть работает, API отвечает, однако операция запрещена. Выдача cluster-admin быстро уберёт симптом, но вместе с ним исчезнет полезная граница полномочий. Начнём с того, кому, над каким ресурсом и какое действие отказались разрешить.

Читаем отказ как описание запроса

В сообщении API обычно есть identity, verb, resource, API group и namespace. Для ServiceAccount identity имеет вид system:serviceaccount:demo:config-reader. Сохраните эти поля без токена. Ошибку list нельзя исправить правом только get: клиент, использующий informer, обычно запрашивает список и watch. Аналогично pods и pods/log — разные ресурсы в правилах доступа.

kubectl -n demo get pod controller-example -o jsonpath="{.spec.serviceAccountName}"
kubectl -n demo get serviceaccount config-reader
kubectl -n demo get role,rolebinding -o yaml

Не полагайтесь на имя Deployment или права человека, который его установил. Контейнер работает от ServiceAccount из spec Pod, а CLI оператора может использовать личный kubeconfig. Если приложение передаёт собственные credentials, дополнительно проверьте их источник. Forbidden подтверждает отказ авторизации конкретной identity; ошибка получения токена или TLS относится к другому этапу.

Выдаём минимальный набор чтения

Пусть контроллер должен читать ConfigMap только в demo. Следующий Role разрешает get, list и watch для core API group. RoleBinding связывает его с config-reader; ServiceAccount уже должен существовать:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: config-reader
  namespace: demo
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: config-reader
  namespace: demo
subjects:
  - kind: ServiceAccount
    name: config-reader
    namespace: demo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: config-reader

Пустая строка apiGroups обозначает core API, куда входят ConfigMap. Для Deployment нужен group apps, без версии v1. Namespace RoleBinding определяет область предоставленного доступа. Namespace в subjects определяет, какой ServiceAccount получает право; это не одно и то же поле. Ошибка в любом из них оставит требуемую identity без нужного разрешения.

RoleBinding может ссылаться и на ClusterRole, предоставляя его применимые namespaced-права в своём namespace. ClusterRoleBinding распространяет привязку на кластер и нужен для другого масштаба доступа. Для чтения cluster-scoped nodes одним Role в demo обойтись нельзя. Выбирайте область по ресурсу и задаче, а не по желанию унифицировать все YAML.

Проверяем ровно ту операцию, которая падала

kubectl auth can-i list configmaps -n demo --as=system:serviceaccount:demo:config-reader
kubectl auth can-i watch configmaps -n demo --as=system:serviceaccount:demo:config-reader
kubectl auth can-i delete configmaps -n demo --as=system:serviceaccount:demo:config-reader

Для примера ожидаются yes, yes и no. Эти команды требуют права impersonate у проверяющего пользователя; ошибка impersonation не доказывает отсутствие прав у ServiceAccount. Если таких полномочий нет, выполняйте проверку из разрешённого контекста этой identity или попросите администратора провести её. Не создавайте бессрочный токен только ради удобного can-i.

При использовании resourceNames помните, что ограничение по имени не делает любой list-запрос разрешённым автоматически. Клиенту может потребоваться подходящий fieldSelector, и его поведение нужно проверить. Если задача — прочитать один заранее известный ConfigMap, простой get часто позволяет выдать более узкое право, чем informer всего namespace.

Подтверждаем работу и сохранение границы

После изменения повторите штатное действие приложения и проверьте журнал нового запроса. Отдельно оставьте отрицательную проверку операции, которую приложение выполнять не должно. Права RBAC складываются: дополнительный широкий binding способен сделать delete разрешённым, хотя приведённый Role его не содержит. Поэтому неожиданный yes требует чтения остальных привязок.

Сохраните вместе identity, ресурс, verb и изменённый binding. При следующем Forbidden это позволит сравнить фактический запрос с выданным контрактом доступа. Повторите отрицательную проверку после изменения общих ролей: широкая новая привязка может незаметно расширить права этого же ServiceAccount.

Ещё по теме

Продолжить изучение

Хотите увереннее разворачивать приложения и разбирать сбои в кластере? Сети, конфигурация, хранилища и обновления входят в программу «Kubernetes с нуля до PRO».

Продолжить на практике

  • Kubernetes с нуля до PRO ↗

    Разворачивайте и сопровождайте приложения в Kubernetes: от первого Pod до релизов, сетей, хранилищ, масштабирования и диагностики. Итоговый проект — сервис с базой данных, мониторингом и проверенным откатом.