← Kubernetes: разбор поломок
7 мин · жёстко · Урок 12 из 12

RBAC закрыл прод: CI получает Forbidden

Как читать Forbidden из пайплайна, воспроизводить права через auth can-i и собирать минимальную Role для деплоя.

Пайплайн падает на шаге деплоя, хотя манифесты не менялись. В логе одна строка:

Error from server (Forbidden): error when creating "deploy.yaml":
deployments.apps is forbidden: User "system:serviceaccount:prod:ci-deployer"
cannot create resource "deployments" in API group "apps" in the namespace "prod"

Сообщение содержит весь диагноз: кто (ci-deployer), что хотел (create deployments), в какой apiGroup (apps), в каком namespace (prod). Дальше остаётся понять, чего не хватает в Role, и дописать ровно это.

Что нужно CI для деплоя

kubectl apply -f deploy.yaml это не одна операция. Под капотом:

  1. get ресурса, чтобы понять, существует ли он;
  2. create, если нет, patch или update, если есть;
  3. часто ещё list и watch для проверки rollout.

Если в манифесте лежат Deployment, Service и ConfigMap, kubectl делает запросы к трём разным apiGroup. Role должна покрыть каждую группу отдельно.

Карта apiGroups

РесурсapiGroupГде
Pod, Service, ConfigMap, Secret, Namespace, ServiceAccount"" (core)базовые
Deployment, ReplicaSet, StatefulSet, DaemonSetappsконтроллеры
Job, CronJobbatchзадачи
Ingress, NetworkPolicynetworking.k8s.ioсеть
Role, RoleBinding, ClusterRole, ClusterRoleBindingrbac.authorization.k8s.ioсам RBAC
HPAautoscalingавтоскейл

Самая частая ошибка свежей Role: apiGroups: [""] на всё, включая Deployment. Деплой упадёт с Forbidden, потому что Deployment живёт в apps, а не в core. Именно этот случай и печатает deployments.apps is forbidden.

Как воспроизвести проблему из своего терминала

kubectl auth can-i понимает флаг --as и эмулирует запрос от любого subject. Ровно то, что делал бы CI:

# Может ли CI создать Deployment?
kubectl auth can-i create deployments \
  --as=system:serviceaccount:prod:ci-deployer

# Полный список разрешённых действий
kubectl auth can-i --list \
  --as=system:serviceaccount:prod:ci-deployer

Пустой вывод --list или один selfsubjectreviews означает, что RoleBinding не существует либо указывает не на тот ServiceAccount.

Проверить сам apply, ничего не создавая:

kubectl apply --as=system:serviceaccount:prod:ci-deployer \
  -f deploy.yaml --dry-run=server

--dry-run=server отправляет запрос на сервер, авторизация проверяется по-настоящему, а ресурс не создаётся. Удобный способ прогнать права до следующего запуска пайплайна.

Минимальные права для деплоя

Для связки Deployment, Service и ConfigMap хватает такого набора:

rules:
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

Почему именно так:

Порядок восстановления

  1. Посмотреть текущую Role: kubectl get role.
  2. Прочитать её правила: kubectl get role ci-deployer -o yaml.
  3. Прогнать can-i по каждой операции, которая нужна пайплайну.
  4. Дописать недостающие правила патчем, не переписывая Role целиком.
  5. Проверить can-i снова.
  6. Прогнать apply --dry-run=server от имени ServiceAccount.

Соблазн на шаге 4 велик: выдать всё и разбираться потом. Этот путь заканчивается инцидентом посерьёзнее.

Антипаттерны

verbs: ["*"]

Дать всё сразу. Через месяц получишь пайплайн, который умеет delete namespace.

resources: ["*"]

То же самое с другой стороны. Открывает доступ к Secret, к RBAC-объектам, ко всему подряд. Ресурсы перечисляют явно.

ClusterRole cluster-admin через RoleBinding

Частая отговорка: «RoleBinding же в namespace, значит ограничено». Нет. cluster-admin через RoleBinding даёт всё внутри этого namespace, включая создание ServiceAccount с правами на другие namespace.

Один Role на всё

Большой Role со звёздочками по всем ресурсам невозможно аудировать. Лучше несколько маленьких Role и отдельные RoleBinding под разные функции: deployer, reader, ротация секретов.

Как не наступать снова

Права CI стоит держать в том же репозитории, что и манифесты, и катить их тем же процессом. Проверку kubectl auth can-i --list --as=... можно повесить отдельным шагом пайплайна: тогда нехватка прав всплывёт до попытки задеплоить, а не посреди релиза.

Полезные команды

kubectl get role,rolebinding
kubectl describe role ci-deployer
kubectl auth can-i --list \
  --as=system:serviceaccount:prod:ci-deployer

Коротко

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