Коротко:
- По умолчанию каждый под получает ServiceAccount с токеном, который может обращаться к API кластера - это нужно явно ограничивать.
- Для изолированного доступа внутри одного namespace используй Role + RoleBinding; ClusterRole нужна только тогда, когда ресурс не привязан к namespace.
- Самые опасные антипаттерны - wildcard в verbs и resources, а также привязка к встроенной роли cluster-admin.
- Перед тем как писать манифест, проверь, что уже разрешено:
kubectl auth can-i --list --as=system:serviceaccount:NAMESPACE:SA_NAME. - Аудит прав стоит запускать регулярно - rakkess и rbac-tool показывают реальную картину за минуты.
Почему права в кластере важнее, чем кажется
Представь: один из микросервисов скомпрометирован через уязвимость в зависимости. Злоумышленник получает исполнение кода внутри пода. Что произойдет дальше - зависит от того, какой ServiceAccount у этого пода. Если у него стандартный токен без ограничений или, хуже, cluster-admin, атакующий за несколько команд прочитает все секреты, создаст новые поды и получит доступ к любому namespace.
Именно это делает Kubernetes RBAC настройку темой не для галочки, а для ежедневной практики. Большинство реальных постэксплойт-сценариев в кластерах строятся не на уязвимостях в ядре Kubernetes, а на избыточных правах, которые никто не убрал.
В этой статье разберем всю цепочку: от создания изолированного ServiceAccount до аудита того, что уже выдано. Все примеры - рабочие манифесты, которые можно сразу адаптировать под свой кластер.
Как устроена модель авторизации в Kubernetes
Каждый запрос к Kubernetes API проходит три этапа: аутентификация (кто ты?), авторизация (что тебе разрешено?) и admission control (соответствует ли запрос политикам?). RBAC работает на втором этапе.
Модель состоит из четырех объектов:
- ServiceAccount - идентичность для процессов внутри пода.
- Role / ClusterRole - набор разрешений: что можно делать с какими ресурсами.
- RoleBinding / ClusterRoleBinding - связь между идентичностью и набором прав.
Сами по себе Role и ClusterRole ничего не делают. Они начинают работать только после привязки через RoleBinding. Это важно: можно создать очень мощную ClusterRole, но если она ни к чему не привязана, она безвредна.
ClusterRole vs Role: когда что использовать
Один из частых вопросов - нужна ли ClusterRole или хватит обычной Role. Короткий ответ: начинай с Role, ClusterRole бери только под конкретные нужды.
| Объект | Область действия | Когда использовать |
|---|---|---|
| Role | Один namespace | Сервис читает поды или секреты только в своем namespace |
| ClusterRole | Весь кластер | Нужен доступ к non-namespaced ресурсам: nodes, PV, namespaces |
| RoleBinding | Один namespace | Привязка любой роли (включая ClusterRole) к namespace |
| ClusterRoleBinding | Весь кластер | Оператор или инструмент мониторинга с кластерным scope |
Важный нюанс: ClusterRole можно привязать через обычный RoleBinding. Это полезно, если хочешь переиспользовать один набор разрешений в нескольких namespace без создания дублирующих Role.
Антипаттерн: ClusterRoleBinding к cluster-admin для сервисного аккаунта приложения. Это эквивалент root-доступа ко всему кластеру. Если такой аккаунт скомпрометирован, игра окончена.
Шаг 1: создаем изолированный ServiceAccount
По умолчанию каждый под получает аккаунт default в своем namespace. У него обычно нет явных привилегий, но токен всё равно монтируется в файловую систему пода и может использоваться для базовых запросов к API. Первое, что стоит сделать, - отключить автомонтирование там, где оно не нужно.
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-processor
namespace: billing
automountServiceAccountToken: false
Флаг automountServiceAccountToken: false запрещает монтировать токен в поды, которые используют этот аккаунт. Если токен все-таки нужен - явно включи его на уровне конкретного пода:
spec:
serviceAccountName: payment-processor
automountServiceAccountToken: true
Таким образом, только те поды, которым это действительно нужно, получат доступ к API.
Шаг 2: Role с минимальным набором разрешений
Допустим, сервис payment-processor должен читать ConfigMap и секреты в своем namespace и ничего больше. Манифест Role выглядит так:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: payment-processor-role
namespace: billing
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["payment-api-key"]
verbs: ["get"]
Обрати внимание на поле resourceNames: оно ограничивает доступ конкретным ресурсом по имени. Без него сервис сможет читать любой секрет в namespace, что уже избыточно.
Что здесь принципиально:
- Нет wildcard (
*) ни в verbs, ни в resources. - Нет
watchтам, где достаточноget. - Нет
updateилиdelete, которые сервис никогда не вызывает.
Шаг 3: RoleBinding - связываем аккаунт с ролью
Role без привязки не работает. Создаем RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payment-processor-binding
namespace: billing
subjects:
- kind: ServiceAccount
name: payment-processor
namespace: billing
roleRef:
kind: Role
name: payment-processor-role
apiGroup: rbac.authorization.k8s.io
Поле subjects может содержать несколько записей - например, если одну роль нужно выдать нескольким аккаунтам. Но лучше избегать этого: чем уже привязка, тем проще аудировать и отзывать права точечно.
Принцип least privilege: как думать при проектировании прав
Least privilege (минимально необходимые права) - это не про то, чтобы начать с нуля и медленно добавлять. Это про то, чтобы задать правильный вопрос перед тем, как писать манифест.
Три вопроса, которые стоит задать для каждого сервиса:
- К каким Kubernetes-ресурсам приложение обращается на практике? (Не
Как проверить, что уже выдано: аудит прав в кластере
Прежде чем выдавать новые права или менять существующие, стоит понять, что уже есть. Без этого шага легко накопить избыточные привязки, о которых никто не помнит.
Самый быстрый способ проверить, что может делать конкретный аккаунт:
kubectl auth can-i --list
--as=system:serviceaccount:billing:payment-processor
--namespace=billing
Эта команда возвращает полный список разрешённых действий от имени указанного аккаунта в заданном namespace. Если вывод длиннее, чем вы ожидали, это повод пересмотреть привязки.
Для более детального анализа используют сторонние инструменты:
- rakkess - показывает матрицу доступа: кто что может делать с каждым типом ресурса. Удобно для быстрого сравнения аккаунтов между собой.
- rbac-tool - умеет строить граф зависимостей между ролями, биндингами и аккаунтами. Хорошо подходит для кластеров, где RBAC настраивали несколько команд и вручную отследить цепочки сложно.
- kubectl-who-can - отвечает на вопрос «кто может делать X с ресурсом Y». Полезно при расследовании инцидента: сразу видно, у каких аккаунтов есть право удалять поды или читать секреты.
Запускать аудит стоит не только при создании новых ролей, но и после обновления операторов и Helm-чартов. Многие инструменты создают ClusterRole при установке и не всегда документируют это поведение явно.
Пример из практики: команда установила оператор для управления сертификатами. После установки rakkess показал, что созданный аккаунт оператора имеет право читать секреты во всех namespace - хотя для работы ему нужен доступ только к namespace cert-manager. Достаточно было заменить ClusterRoleBinding на RoleBinding в нужном namespace, и поверхность атаки сократилась в несколько раз.
Типичные ошибки при настройке прав и как их избежать
Большинство проблем с авторизацией в кластерах не возникают из-за злого умысла. Их оставляют в спешке, при дебаггинге или когда копируют манифесты из примеров в интернете, не убирая лишнее.
| Ошибка | Чем опасна | Как исправить |
|---|---|---|
Wildcard в verbs: verbs: ["*"] | Разрешает всё: create, delete, update, patch - даже если нужен только get | Перечислить только нужные действия явно |
Wildcard в resources: resources: ["*"] | Даёт доступ ко всем типам ресурсов, включая секреты и токены | Указать конкретный тип ресурса |
| Привязка к встроенной роли edit или admin | Эти роли включают права, которые приложению не нужны никогда | Создать собственную Role с минимальным набором |
| ClusterRoleBinding вместо RoleBinding | Права действуют на весь кластер, хотя нужен только один namespace | Использовать RoleBinding с явным указанием namespace |
| Отсутствие поля resourceNames | Сервис может читать все секреты в namespace, хотя нужен один конкретный | Добавить resourceNames с именем нужного ресурса |
| Забытый токен в поде (automountServiceAccountToken не отключён) | Токен смонтирован в файловую систему и может быть прочитан при компрометации | Выставить automountServiceAccountToken: false на уровне ServiceAccount |
Отдельная история - роли, которые создавались для отладки. Инженер выдал широкие права, чтобы быстро проверить гипотезу, и забыл убрать после. Через несколько месяцев никто уже не помнит, кто и зачем это сделал. Регулярный аудит - единственный способ находить такие хвосты.
Права для операторов и инструментов мониторинга: отдельный случай
Сервисные аккаунты бывают двух типов по происхождению: те, которые вы создаёте для своих приложений, и те, которые создают инструменты при установке. Со вторыми сложнее, потому что у вас меньше контроля над тем, что запрашивается по умолчанию.
Операторы - Prometheus Operator, cert-manager, ArgoCD, различные контроллеры - часто запрашивают широкие права при установке. Это не всегда необходимость: иногда это просто наиболее универсальная конфигурация, которую авторы выбрали для простоты установки.
Что стоит делать при установке подобных инструментов:
- Изучить, какие роли и биндинги создаёт чарт или оператор:
helm templateпокажет все создаваемые объекты до применения. - Проверить, нужны ли ClusterRoleBinding там, где создаются кластерные привязки, или можно ограничить namespace.
- После установки прогнать
kubectl auth can-i --listот имени созданного аккаунта и сравнить с документацией инструмента. - Если инструмент позволяет выбрать режим установки (namespace-scoped vs cluster-scoped), предпочесть namespace-scoped там, где достаточно.
ArgoCD, например, поддерживает режим, при котором Application Controller работает только в рамках конкретных namespace. Это удобно для мультитенантных кластеров, где разные команды используют разные namespace и не должны видеть приложения друг друга.
Prometheus в большинстве конфигураций требует доступа к nodes и endpoints на уровне кластера - это обоснованная необходимость для сбора метрик. Но write-доступ к каким-либо ресурсам инструменту мониторинга не нужен никогда, и если такое обнаружится в аудите, это красный флаг.
Чеклист перед выдачей прав новому сервису
Собрать всё вышесказанное в один практический список удобно для код-ревью манифестов или онбординга новых сервисов в кластер.
- Создан отдельный ServiceAccount с именем, которое отражает назначение сервиса, а не общий default.
- На ServiceAccount выставлен
automountServiceAccountToken: false, если токен не нужен постоянно. - Используется Role (не ClusterRole), если сервис работает только в одном namespace.
- В правилах Role нет wildcard ни в verbs, ни в resources.
- Для доступа к конкретным секретам или ConfigMap добавлено поле resourceNames.
- Verbs ограничены реально используемыми: если нужен только get, нет watch и list.
- RoleBinding привязывает Role только к нужному ServiceAccount, без лишних subjects.
- После применения манифестов выполнена проверка:
kubectl auth can-i --listпоказывает ожидаемый минимум. - Права задокументированы в README или комментарии к манифесту: зачем они нужны и какой компонент их использует.
- В планах команды есть регулярный аудит RBAC (хотя бы раз в квартал или после каждого крупного обновления кластера).
Последний пункт часто пропускают, считая его необязательным. Но кластеры живут годами, команды меняются, инструменты обновляются - и без периодической проверки реального состояния прав накопленный технический долг становится дырой в безопасности.
Как тестировать правила перед применением в продакшене
Выдать права легко. Убедиться, что они работают именно так, как задумано, уже сложнее. Перед тем как применять новые манифесты в боевом кластере, стоит проверить их поведение заранее.
Первый инструмент - уже знакомый kubectl auth can-i, но в режиме проверки конкретного действия:
kubectl auth can-i get secrets/payment-api-key
--as=system:serviceaccount:billing:payment-processor
--namespace=billingКоманда вернет yes или no. Это можно встроить в CI-пайплайн как автоматическую проверку после применения манифестов: если права вышли за ожидаемые границы, пайплайн падает и сигнализирует команде.
Второй подход - dry-run при применении манифестов. Он не проверяет логику прав, но ловит синтаксические ошибки и конфликты имен до того, как объект попадет в кластер:
kubectl apply -f role.yaml --dry-run=serverФлаг --dry-run=server отправляет запрос на сервер, но не сохраняет объект. Это важнее, чем --dry-run=client: серверная проверка учитывает admission webhooks и существующие объекты в кластере.
Третий инструмент для более глубокой проверки - kube-score или kubeaudit. Они статически анализируют манифесты и предупреждают о потенциально опасных конфигурациях: wildcard-права, отсутствие ограничений на automount, использование встроенных ролей с широкими полномочиями.
Для команд, которые используют GitOps, имеет смысл добавить такую проверку прямо в пайплайн при открытии пулл-реквеста. Ошибка, пойманная при ревью кода, дешевле ошибки, найденной в продакшене через три месяца.
Хорошая практика: заведи отдельный namespace для тестирования новых RBAC-конфигураций. Применяй там манифест, прогоняй проверки через kubectl auth can-i --list и только после подтверждения переноси в основной namespace. Это занимает несколько минут, но дает уверенность, что конфигурация работает именно так, как ожидается.
Работа с несколькими namespace и мультитенантные кластеры
В небольших кластерах с одним продуктом и одной командой управлять правами относительно просто. Ситуация меняется, когда в кластере появляется несколько команд, продуктов или окружений в разных namespace.
Главная проблема мультитенантных кластеров - случайный или намеренный переход прав между tenant-ами. Если один сервис получил ClusterRole вместо Role, его токен потенциально позволяет читать данные из чужого namespace. Это происходит незаметно и не фиксируется без аудита.
Несколько принципов, которые помогают удержать контроль при многих командах:
- Для каждого namespace назначь владельца - команду или сервис, отвечающий за содержимое. Это помогает при аудите: сразу понятно, кого спрашивать про конкретный аккаунт или роль.
- Ограничь право создавать RoleBinding внутри namespace только CI/CD-системе или платформенной команде. Если разработчики могут сами выдавать права внутри namespace, контроль теряется быстро.
- Используй LimitRange и ResourceQuota в паре с RBAC: они ограничивают не только права на API, но и потребление ресурсов, что важно при изоляции tenant-ов.
- При использовании ArgoCD или Flux явно ограничи, в каких namespace может создавать объекты каждый Application. Это предотвращает сценарий, когда один пайплайн случайно накатывает изменения в чужое окружение.
| Сценарий | Рекомендованный подход | Почему |
|---|---|---|
| Один сервис в одном namespace | Role + RoleBinding | Минимальная область действия, легко аудировать |
| Переиспользование набора прав в нескольких namespace | ClusterRole + RoleBinding (не ClusterRoleBinding) | Один набор правил, но действует только там, где привязан |
| Оператор, работающий со всем кластером | ClusterRole + ClusterRoleBinding с ограничением по verbs | Кластерный scope обоснован, но wildcard всё равно недопустим |
| Мультитенантный кластер с несколькими командами | Отдельные namespace, RoleBinding без ClusterRoleBinding для tenant-ов | Изоляция между командами, утечка прав не затрагивает соседей |
Еще один нюанс мультитенантных кластеров - агрегированные роли. Kubernetes позволяет создавать ClusterRole, которые автоматически объединяют права из нескольких других ClusterRole через label-селекторы. Это удобно для расширения встроенных ролей, но может стать неожиданностью при аудите: добавив новую ClusterRole с нужным лейблом, можно расширить права агрегированной роли незаметно для остальных. Стоит держать это в голове при проектировании структуры ролей.
Когда RBAC недостаточно: дополнительные инструменты контроля
RBAC отвечает за то, кто и что может делать через Kubernetes API. Но безопасность кластера не ограничивается только API-доступом. Несколько сценариев, где RBAC не поможет в одиночку.
Первый сценарий: контроль того, какие образы могут запускаться в кластере. Даже если у аккаунта минимальные права и он не может менять другие объекты, образ с вредоносным кодом внутри пода уже является угрозой. Для контроля образов используют admission webhooks и инструменты вроде Kyverno или OPA Gatekeeper - они могут запретить запуск образов без подписи или из недоверенных реестров.
Второй сценарий: сетевая изоляция между подами. RBAC не ограничивает сетевой трафик внутри кластера. Скомпрометированный под с минимальными API-правами всё равно может попытаться подключиться к базе данных другого сервиса по сети, если нет NetworkPolicy. RBAC и NetworkPolicy работают в паре, а не заменяют друг друга.
Третий сценарий: аудит-лог. Kubernetes ведет audit log всех запросов к API, но по умолчанию он не включен или сохраняется только в минимальном объеме. Для полноценного расследования инцидента нужно настроить audit policy с сохранением событий по RBAC-чувствительным ресурсам: secrets, serviceaccounts, rolebindings. Без этого даже если утечка произошла, восстановить хронологию будет трудно.
Четвертый сценарий: ротация токенов. Классические ServiceAccount-токены не имеют срока жизни и не ротируются автоматически. Начиная с Kubernetes 1.22 рекомендуется использовать Bound Service Account Tokens - они привязаны к поду, имеют ограниченный срок жизни и автоматически отзываются при удалении пода. Включить это можно через поле serviceAccountToken в spec пода с указанием expirationSeconds.
Вместе эти инструменты формируют многоуровневую защиту: RBAC ограничивает API-доступ, NetworkPolicy закрывает сетевые маршруты, admission webhooks контролируют, что запускается, а audit log позволяет разобраться постфактум, если что-то пошло не так.
Жизненный цикл прав: как обновлять и отзывать доступ
Создать минимальные права один раз недостаточно. Сервисы развиваются, требования меняются, и вместе с ними меняется то, что нужно аккаунту. Без явного процесса обновления права со временем только накапливаются: старые разрешения никто не убирает, новые добавляются поверх. Через год в кластере появляется аккаунт с набором прав, который никто не может объяснить.
Несколько принципов, которые помогают держать жизненный цикл под контролем:
- При добавлении нового разрешения одновременно проверяй, не стало ли какое-то из старых лишним. Это удобно делать в рамках того же пулл-реквеста.
- Если сервис выводится из эксплуатации, явно удаляй его ServiceAccount, связанные Role и RoleBinding. Удалённый под не означает удалённый аккаунт: объект остаётся в кластере и его токен остаётся действующим, пока аккаунт существует.
- При передаче сервиса между командами пересматривай права заново: то, что было нужно прошлой команде для отладки, не обязательно нужно новой для работы в продакшене.
- Для временного доступа, который давался на период инцидента или миграции, устанавливай напоминание на удаление. В Kubernetes нет встроенного TTL для RBAC-объектов, поэтому это нужно делать вручную или через внешний процесс.
Как это выглядит на практике: несколько команд договариваются, что любые изменения в RBAC-манифестах проходят через код-ревью с участием security-роли или платформенной команды. Это не замедляет работу - большинство изменений ревьюируются за день. Зато через полгода при аудите видно, что каждое право обосновано и задокументировано в истории коммитов.
Разграничение прав между окружениями
Отдельный вопрос, который редко обсуждается явно: должны ли права в dev, staging и prod совпадать? Интуитивно кажется, что да - удобно держать одни манифесты для всех окружений. На практике это почти всегда избыточно для боевой среды и недостаточно строго для разработки.
| Окружение | Типичная ситуация | Рекомендация |
|---|---|---|
| dev | Разработчик запускает поды вручную, хочет быстро читать логи и секреты | Широкие права внутри dev-namespace допустимы, но изолированы от prod |
| staging | Близко к prod по конфигурации, но данные не боевые | Права должны совпадать с prod, чтобы проверять реальное поведение |
| prod | Минимальные права, каждое разрешение обосновано | Строгий least privilege, аудит после каждого обновления |
Главная ошибка здесь - скопировать конфигурацию из dev в prod потому что она уже написана и проверена. В dev могут быть широкие права, добавленные для удобства отладки, которые в бою станут дырой. Лучше держать манифесты для prod отдельно и ревьюить их независимо от dev-конфигурации.
При использовании Helm это решается через разные values-файлы для каждого окружения: права, указанные в values-dev.yaml, не попадают в values-prod.yaml автоматически. Это небольшое неудобство при создании, которое сильно упрощает аудит позже.
Именование объектов: почему это важнее, чем кажется
Когда в кластере десятки namespace и сотни ролей, найти нужный объект и понять его назначение без контекста становится нетривиальной задачей. Плохое именование превращает аудит в расследование.
Несколько правил, которые работают на практике:
- Имя ServiceAccount должно отражать сервис или компонент, а не окружение или команду:
payment-processor, а неteam-billing-sa. - Имя Role повторяет имя аккаунта с суффиксом, описывающим область:
payment-processor-secrets-reader. Сразу понятно, что делает эта роль и для кого. - Имя RoleBinding соответствует связи, которую оно создает:
payment-processor-to-secrets-reader. Когда видишь объект в списке, не нужно открывать его содержимое, чтобы понять суть. - Используй labels для группировки:
app: payment-processor,managed-by: platform-team. Это позволяет находить все RBAC-объекты конкретного сервиса одной командой:kubectl get roles,rolebindings -l app=payment-processor.
Соглашение об именовании стоит зафиксировать в документации команды до того, как кластер вырастет. Переименовывать объекты в Kubernetes нельзя - придется удалять и создавать заново, что неудобно в продакшене.