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, причина одна из четырёх:
- У Pod нет своего SA, и используется
defaultбез прав. - SA есть, но нет RoleBinding или ClusterRoleBinding.
- Есть всё, но в Role не хватает нужного verb или resource.
- 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
Таблица покажет все разрешённые действия. Смотреть её полезно до правок, чтобы понять, чего именно не хватает.
Расшифровка ошибки
Текст ошибки структурирован, и в нём есть всё для починки:
- subject:
system:serviceaccount:demo:app-sa - verb:
list - resource:
configmaps - apiGroup:
"", то есть core API, поэтому без префикса - namespace:
demo
Значит, в 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, у которого прав нет, и симптом будет тот же
самый.
Коротко
- Ошибка Forbidden содержит subject, verb, resource, apiGroup и namespace - всё, что нужно для правки Role.
kubectl auth can-i --as=system:serviceaccount:<ns>:<sa>отвечает однозначно, без догадок.--listпоказывает все права subject разом.- Правила Role работают как декартово произведение apiGroups, resources и verbs; запрещающих правил в RBAC нет.
- Изменения RBAC действуют сразу, рестарт Pod не требуется.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс