Задача
Приложение в поде читает список сервисов через Kubernetes API. Создана роль:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: service-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch"]Приложение стабильно получает 403 Forbidden.
Вопрос: чего не хватает и от чьего имени вообще ходит под?
Что кажется очевидным
«Роль создана, права выданы — должно работать». Здесь и кроется путаница: создать роль ≠ выдать права.
Как это работает на самом деле
RBAC в Kubernetes состоит из двух половин, и обе обязательны:
- Role / ClusterRole — что можно делать. Это просто описание набора разрешений; сама по себе роль никому ничего не даёт.
- RoleBinding / ClusterRoleBinding — кому это можно. Связывает роль с субъектом.
Без второй половины роль — мёртвый объект. Именно этого и не хватает в задаче.
От чьего имени ходит под
У каждого пода есть ServiceAccount. Если не указан явно — это ServiceAccount default того namespace, где под запущен. Его токен монтируется в под:
/var/run/secrets/kubernetes.io/serviceaccount/tokenУ default по умолчанию почти нет прав — это правильно. Поэтому первый вопрос при 403: под каким аккаунтом идёт запрос?
Role против ClusterRole
| Область действия | |
|---|---|
| Role | Ресурсы в одном namespace |
| ClusterRole | Весь кластер, плюс cluster-scoped ресурсы (ноды, PV, namespace’ы, CRD) |
И для привязок:
| Что делает | |
|---|---|
| RoleBinding | Даёт права в своём namespace — может ссылаться и на Role, и на ClusterRole |
| ClusterRoleBinding | Даёт права во всём кластере |
Полезный приём: RoleBinding, ссылающийся на ClusterRole, даёт права этой роли только в пределах своего namespace. Так одну общую роль («читатель») переиспользуют во многих namespace’ах, не давая доступа ко всему кластеру.
Ответ
Не хватает RoleBinding, связывающего роль с ServiceAccount пода:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-service-reader
namespace: production
subjects:
- kind: ServiceAccount
name: my-app
namespace: production
roleRef:
kind: Role
name: service-reader
apiGroup: rbac.authorization.k8s.ioИ под должен использовать именно этот аккаунт:
spec:
serviceAccountName: my-appБез последней строки под продолжит ходить от default — и права так и не применятся.
Что с этим делать
Проверка прав
Самый быстрый инструмент — auth can-i с подстановкой субъекта:
# Может ли конкретный ServiceAccount читать сервисы
kubectl auth can-i list services \
--as=system:serviceaccount:production:my-app \
-n production
# Полный список того, что ему разрешено
kubectl auth can-i --list \
--as=system:serviceaccount:production:my-app \
-n productionЭто избавляет от гаданий: команда отвечает ровно то же, что ответит API server приложению.
Опасные разрешения
Некоторые права выглядят безобидно, но фактически означают полный контроль:
create pods— можно запустить под с примонтированным ServiceAccount администратора, сhostPath: /или в host-namespace. Это эквивалент прав администратора ноды, а часто и кластера.create pods/exec— зайти в любой под и действовать от его имени.get secrets— прочитать все учётные данные namespace, включая токены других аккаунтов.escalate/bind— выдать себе любые права в обход обычных ограничений.impersonate— действовать от имени другого пользователя.- Право на создание/правку workload-объектов (Deployment, Job, CronJob, DaemonSet) косвенно даёт
create pods.
Отсюда практическое правило: раздавая доступ «только на деплой», вы фактически раздаёте очень много. Отделяйте namespace’ы и используйте Pod Security Admission.
Гигиена
- Свой ServiceAccount на каждое приложение, не
default. - Отключайте монтирование токена, если приложение не ходит в API:Это снимает целый класс рисков: украденный токен из скомпрометированного контейнера.YAML
spec: automountServiceAccountToken: false - Начинайте с минимума и добавляйте по факту
403, а не наоборот. - Не используйте
cluster-adminдля приложений. Никогда для CI-агентов «чтобы работало». - Избегайте wildcards (
resources: ["*"],verbs: ["*"]) — они автоматически включают и будущие ресурсы. - Аудит существующего:BASH
# Кто имеет cluster-admin kubectl get clusterrolebindings -o json | jq -r ' .items[] | select(.roleRef.name=="cluster-admin") | "\(.metadata.name): \(.subjects // [] | map(.kind+"/"+.name) | join(", "))"'
Токены
Современные токены ServiceAccount — короткоживущие и привязанные к поду: они выдаются через projected volume, автоматически ротируются и становятся недействительными при удалении пода. Старая модель с «вечным» токеном в Secret устарела; если в кластере остались такие Secret’ы — это кандидаты на удаление.
Дальше: Задача 19. Нода под давлением: кого вытеснят первым.