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 это не одна операция. Под капотом:
getресурса, чтобы понять, существует ли он;create, если нет,patchилиupdate, если есть;- часто ещё
listиwatchдля проверки rollout.
Если в манифесте лежат Deployment, Service и ConfigMap, kubectl делает запросы к трём разным apiGroup. Role должна покрыть каждую группу отдельно.
Карта apiGroups
| Ресурс | apiGroup | Где |
|---|---|---|
| Pod, Service, ConfigMap, Secret, Namespace, ServiceAccount | "" (core) | базовые |
| Deployment, ReplicaSet, StatefulSet, DaemonSet | apps | контроллеры |
| Job, CronJob | batch | задачи |
| Ingress, NetworkPolicy | networking.k8s.io | сеть |
| Role, RoleBinding, ClusterRole, ClusterRoleBinding | rbac.authorization.k8s.io | сам RBAC |
| HPA | autoscaling | автоскейл |
Самая частая ошибка свежей 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"]
Почему именно так:
replicasetsнужен, потому что Deployment-контроллер создаёт RS, а CI иногда сам ходит туда за состоянием раската;podsдостаточноget/list/watch: CI не создаёт Pod напрямую, но смотрит на них дляrollout status;deleteв CI-роли обычно лишний. Удаление ресурсов прода это отдельная и опасная операция, её делают другим workflow.
Порядок восстановления
- Посмотреть текущую Role:
kubectl get role. - Прочитать её правила:
kubectl get role ci-deployer -o yaml. - Прогнать
can-iпо каждой операции, которая нужна пайплайну. - Дописать недостающие правила патчем, не переписывая Role целиком.
- Проверить
can-iснова. - Прогнать
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
Коротко
- Сообщение Forbidden называет subject, verb, ресурс, apiGroup и namespace. Читай его целиком, там весь диагноз.
- Deployment живёт в apiGroup
apps, не в core; Role сapiGroups: [""]на всё не сработает. kubectl auth can-i --as=воспроизводит права CI из своего терминала.apply --dry-run=server --as=проверяет авторизацию, ничего не создавая.verbs: ["*"]иresources: ["*"]в проде запрещены: минимальный набор глаголов и явный список ресурсов.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс