Kubernetes RBAC на практике: как выдать минимальные права сервис-аккаунту и не открыть дыру в кластере

Коротко:

  • По умолчанию каждый под получает 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 (минимально необходимые права) - это не про то, чтобы начать с нуля и медленно добавлять. Это про то, чтобы задать правильный вопрос перед тем, как писать манифест.

Три вопроса, которые стоит задать для каждого сервиса:

  1. К каким 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-доступ к каким-либо ресурсам инструменту мониторинга не нужен никогда, и если такое обнаружится в аудите, это красный флаг.

Чеклист перед выдачей прав новому сервису

Собрать всё вышесказанное в один практический список удобно для код-ревью манифестов или онбординга новых сервисов в кластер.

  1. Создан отдельный ServiceAccount с именем, которое отражает назначение сервиса, а не общий default.
  2. На ServiceAccount выставлен automountServiceAccountToken: false, если токен не нужен постоянно.
  3. Используется Role (не ClusterRole), если сервис работает только в одном namespace.
  4. В правилах Role нет wildcard ни в verbs, ни в resources.
  5. Для доступа к конкретным секретам или ConfigMap добавлено поле resourceNames.
  6. Verbs ограничены реально используемыми: если нужен только get, нет watch и list.
  7. RoleBinding привязывает Role только к нужному ServiceAccount, без лишних subjects.
  8. После применения манифестов выполнена проверка: kubectl auth can-i --list показывает ожидаемый минимум.
  9. Права задокументированы в README или комментарии к манифесту: зачем они нужны и какой компонент их использует.
  10. В планах команды есть регулярный аудит 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. Это предотвращает сценарий, когда один пайплайн случайно накатывает изменения в чужое окружение.
СценарийРекомендованный подходПочему
Один сервис в одном namespaceRole + RoleBindingМинимальная область действия, легко аудировать
Переиспользование набора прав в нескольких namespaceClusterRole + 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 нельзя - придется удалять и создавать заново, что неудобно в продакшене.