Стадия жизненного цикла модуля: General Availability

Как создать пользователя?

Создание пользователя.

Как ограничить права пользователю конкретными неймспейсами?

Чтобы ограничить права пользователя конкретными неймспейсами в гранулярной ролевой модели, используйте в RoleBinding namespace-роль с соответствующим уровнем доступа. Пример.

В упрощённой ролевой модели используйте параметры namespaceSelector или limitNamespaces (устарел) в кастомном ресурсе ClusterAuthorizationRule.

Что, если два ClusterAuthorizationRules подходят для одного пользователя?

В примере пользователь jane.doe@example.com состоит в группе administrators. Созданы два ClusterAuthorizationRules:

apiVersion: deckhouse.io/v1
kind: ClusterAuthorizationRule
metadata:
  name: jane
spec:
  subjects:
    - kind: User
      name: jane.doe@example.com
  accessLevel: User
  namespaceSelector:
    labelSelector:
      matchLabels:
        env: review
---
apiVersion: deckhouse.io/v1
kind: ClusterAuthorizationRule
metadata:
  name: admin
spec:
  subjects:
  - kind: Group
    name: administrators
  accessLevel: ClusterAdmin
  namespaceSelector:
    labelSelector:
      matchExpressions:
      - key: env
        operator: In
        values:
        - prod
        - stage
  1. jane.doe@example.com имеет право запрашивать и просматривать объекты среди всех пространств имён, помеченных env=review.
  2. Administrators могут запрашивать, редактировать, получать и удалять объекты на уровне кластера и из пространств имён, помеченных env=prod и env=stage.

Так как для Jane Doe подходят два правила, итоговые права определяются следующим образом:

  • Jane Doe будет иметь максимальный accessLevel среди всех подходящих правил — ClusterAdmin.
  • Опции namespaceSelector будут объединены так, что Jane Doe будет иметь доступ в пространства имён, помеченные лейблом env со значением review, stage или prod.

Если есть правило без опции namespaceSelector и без опции limitNamespaces (устаревшая), это значит, что доступ разрешён во все пространства имён, кроме системных, что повлияет на результат вычисления доступных пространств имён для пользователя.

Можно ли использовать упрощённую и гранулярную ролевые модели одновременно?

Да. Обе модели в итоге сводятся к стандартному механизму RBAC Kubernetes, а RBAC — разрешающая модель: права из всех источников суммируются. Если действие разрешено хотя бы одним источником — ClusterAuthorizationRule, AuthorizationRule, RoleBinding на роль гранулярной модели или ProjectRoleBinding, — оно будет разрешено. Ничего специально «переключать» не нужно: можно оставить существующие ClusterAuthorizationRule и постепенно добавлять привязки ролей гранулярной модели.

Это верно и в режиме мультитенантности (enableMultiTenancy), с одним правилом об уровнях. Ограничение неймспейсов в ClusterAuthorizationRule (limitNamespaces или namespaceSelector) ограничивает права только этого правила: в неймспейсе вне списка пользователь сохраняет то, что ему дают RoleBinding, AuthorizationRule или ProjectRoleBinding, на уровне этой привязки — accessLevel из ClusterAuthorizationRule туда не распространяется. Неймспейс, в котором у пользователя нет ни одного источника прав, остаётся недоступным. Полный список источников и пример — в описании модуля.

Как обновление на релиз с контроллером влияет на биндинги?

Ничего не создаётся заново, доступ не прерывается. Компонент user-authz-controller принимает под управление биндинги, которые раньше создавал Helm-чарт: те же объекты с теми же именами, к ним добавляется ownerReference на правило.

Спланируйте релиз, в котором выполняется миграция: он один раз обрабатывает все существующие биндинги, поэтому идёт дольше обычного. На последующие релизы это не влияет.

Биндинги в релизе Чего ожидать
До 5000 Релиз идёт дольше обычного и завершается сам
Больше 5000 Релиз может не уложиться в 20-минутный таймаут модуля и завершиться с ошибкой

Посчитайте биндинги перед обновлением. Для получения количества кластерных биндингов используйте команду:

d8 k get clusterrolebindings -l heritage=deckhouse,module=user-authz --no-headers | wc -l

Для получения количества namespaced-биндингов используйте команду:

d8 k get rolebindings -A -l heritage=deckhouse,module=user-authz --no-headers | wc -l

Если их больше 5000, на время обновления увеличьте таймаут релиза или переключитесь на предыдущий движок раскатки, а после обновления верните настройку:

Чтобы увеличить таймаут релиза, используйте команду:

d8 k -n d8-system set env deploy/deckhouse HELM_TIMEOUT=60m

Чтобы переключиться на предыдущий движок, используйте команду:

d8 k -n d8-system set env deploy/deckhouse USE_NELM=false

Если миграция не смогла подготовить все биндинги, релиз не стартует, а биндинги остаются в прежнем состоянии.

Как проверить, что контроллер поддерживает биндинги в актуальном состоянии?

Компонент user-authz-controller синхронизирует ClusterRoleBinding и RoleBinding каждого ClusterAuthorizationRule и AuthorizationRule, выдачу d8:use:dict в экспериментальной ролевой модели и проекции manage-ролей в use-RoleBinding’и неймспейсов. О его состоянии говорят три источника (статус объекта, метрики и алерты).

Статус объекта. У каждого правила есть условие Ready и число созданных для него ClusterRoleBinding и RoleBinding:

Чтобы получить список всех ClusterAuthorizationRule в кластере, выполните команду:

d8 k get clusterauthorizationrules

Пример вывода:

NAME        ACCESS LEVEL   READY   BINDINGS   AGE
my-rule     Admin          True    3          5d

Чтобы получить список всех AuthorizationRule во всех неймспейсах кластера, выполните команду:

d8 k get authorizationrules -A

Пример вывода:

NAMESPACE   NAME       ACCESS LEVEL   READY   BINDINGS   AGE
default     app-rule   Editor         True    2          3d
team-a      dev-rule   User           False   0          1h

Чтобы получить только массив conditions указанного ClusterAuthorizationRule, выполните команду:

d8 k get clusterauthorizationrule <name> -o jsonpath='{.status.conditions}'

Пример вывода:

[
  {
    "lastTransitionTime": "2026-09-11T10:00:00Z",
    "message": "3 bindings applied",
    "observedGeneration": 1,
    "reason": "BindingsApplied",
    "status": "True",
    "type": "Ready"
  }
]

Ready=True с причиной BindingsApplied означает, что биндинги соответствуют правилу. Ready=False указывает на проблему: InvalidSpec (правило нельзя превратить в биндинги, в сообщении сказано почему) или ApplyError (API-сервер отклонил запись; в сообщении указаны биндинг и ошибка). На правиле записываются события с теми же причинами.

Метрики. Контроллер отдаёт их на порту metrics своего пода, собирает их PodMonitor user-authz-controller (нужен включённый модуль operator-prometheus). Лейбл kind принимает значения ClusterAuthorizationRule, AuthorizationRule, dict и manage.

Метрика Описание
d8_user_authz_authorization_rules{kind} Известные контроллеру объекты данного вида
d8_user_authz_bindings_desired{kind} Биндинги, которые должны быть у объектов данного вида
d8_user_authz_bindings_actual{kind} Существующие биндинги данного вида
d8_user_authz_bindings_drift{kind,reason} Биндинги, не приведённые к желаемому состоянию на последнем reconcile; reason — missing, extra или changed. Больше нуля, пока контроллер не привёл биндинги к желаемому состоянию
d8_user_authz_bindings_apply_total{kind,op,result} Операции записи контроллера (op: create, update, delete; result: success, error)
d8_user_authz_authorization_rules_invalid{kind,reason} Правила с условием Ready=False по виду и причине
d8_user_authz_authorization_rule_invalid{kind,name,rule_namespace,reason} 1 для каждого такого правила (по имени экспортируется не более 50 правил; агрегат выше всегда полный)
d8_user_authz_custom_cluster_roles{level} Кастомные ClusterRole (с аннотацией user-authz.deckhouse.io/access-level) по уровням доступа
d8_user_authz_custom_aggregation_missing{level} 1, если у агрегированной ClusterRole user-authz:<level>:custom нет правил, хотя кастомные роли для неё существуют
d8_user_authz_bindings_keep_stamped_total, d8_user_authz_keep_stamp_duration_seconds Работа миграционного хука, защищающего созданные чартом биндинги от удаления релизом: сколько объектов помечено и длительность последнего прогона
controller_runtime_reconcile_total, controller_runtime_reconcile_errors_total, controller_runtime_reconcile_time_seconds Стандартные метрики controller-runtime по каждому контроллеру (лейбл controller: clusterauthorizationrule-bindings, authorizationrule-bindings, dict-bindings, manage-bindings)

Алерты (в правилах Prometheus d8_user_authz):

  • D8UserAuthzControllerUnavailable и D8UserAuthzControllerTargetDown — у контроллера недоступные реплики или его метрики не собираются 5 минут;
  • D8UserAuthzControllerReconcileErrorsHigh — устойчивый поток ошибок reconcile;
  • D8UserAuthzBindingsDrift — биндинги какого-то вида 15 минут не приводятся к желаемому состоянию;
  • D8UserAuthzAuthorizationRulesInvalid и D8UserAuthzAuthorizationRuleInvalid — число и имена правил, биндинги которых не применяются 10 минут;
  • D8UserAuthzCustomRolesNotAggregated — агрегированная ClusterRole уровня доступа остаётся пустой, хотя кастомные роли для неё есть.

Если правило остаётся в Ready=False с ApplyError из-за того, что у биндинга вручную изменили roleRef, удалите этот биндинг: roleRef неизменяем, контроллер пересоздаст биндинг с правильным.

Как проверить, что вебхук авторизации знает актуальные правила?

Вебхук авторизации и Permission Browser читают параметры мультитенантности из ClusterAuthorizationRules (limitNamespaces, namespaceSelector, allowAccessToSystemNamespaces) напрямую из API, поэтому изменение доходит до них за несколько секунд.

Чтобы проверить, что поды вебхука запущены, выполните команду:

d8 k -n d8-user-authz get pods -l app=user-authz-webhook -o wide

Пример вывода (по одному поду на каждый master, в сети узла; готовы должны быть оба контейнера):

NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
user-authz-webhook-qrm2n   2/2     Running   0          2d    10.0.0.11    master-0   <none>           <none>
user-authz-webhook-h6jbh   2/2     Running   0          2d    10.0.0.12    master-1   <none>           <none>
user-authz-webhook-97fvs   2/2     Running   0          2d    10.0.0.13    master-2   <none>           <none>

Чтобы вывести последние 100 строк логов контейнера вебхука из всех таких подов, выполните команду:

d8 k -n d8-user-authz logs -l app=user-authz-webhook -c webhook --tail=100

Пример вывода:

2026/09/11 10:00:00 server is starting to listen on  127.0.0.1:40443 ...
2026/09/11 10:00:01 rules source: directory rebuilt from 12 rules (18 subjects, 0 quarantined) in 4.2ms

Строка rules source: directory rebuilt from N rules показывает, что informer получил список правил, и сколько их в каталоге; quarantined — число правил, у которых не скомпилировались шаблоны limitNamespaces.

Экземпляр, который ещё не прочитал правила, не готов и отвечает на запросы авторизации ошибкой, а не запретом. В те секунды, пока созданное правило не дошло до вебхука, его субъектам временно запрещён доступ ко всем неймспейсам.

Метрики вебхук отдаёт через сайдкар-контейнер kube-rbac-proxy; их собирает PodMonitor user-authz-webhook, для этого должен быть включён модуль operator-prometheus. На каждый master-узел приходится своя серия.

Метрика Описание
user_authz_webhook_rules_informer_synced 1, если экземпляр хотя бы раз прочитал ClusterAuthorizationRules
user_authz_webhook_rules_observed Правила, которые экземпляр использует сейчас
user_authz_webhook_rules_subjects Число различных субъектов в этих правилах
user_authz_webhook_rules_max_resource_version Наибольший resourceVersion среди прочитанных правил. Сравните его с кластером, чтобы измерить отставание
user_authz_webhook_rules_quarantined Правила, которые экземпляр не смог использовать полностью: не компилируется паттерн limitNamespaces или namespaceSelector либо правило не удалось прочитать
user_authz_webhook_rules_directory_updated_timestamp_seconds Время последнего обновления правил
user_authz_webhook_rules_directory_rebuilds_total, user_authz_webhook_rules_directory_rebuild_duration_seconds Число обновлений правил и их длительность
user_authz_webhook_rules_watch_errors_total Ошибки list и watch при чтении правил

Permission Browser отдаёт те же метрики с префиксом user_authz_permission_browser; их собирает PodMonitor permission-browser-apiserver.

Для обоих компонентов модуль поставляет следующие алерты:

Алерт Когда срабатывает
D8UserAuthzWebhookTargetDown Prometheus 5 минут не может собрать метрики хотя бы с одного экземпляра вебхука
D8UserAuthzWebhookRulesQuarantined Правило 10 минут не компилируется
D8UserAuthzWebhookRulesWatchErrors Вебхук 10 минут не может следить за изменениями правил
D8UserAuthzWebhookDirectoryDiverged Экземпляры 10 минут используют разные наборы правил, поэтому на один и тот же запрос приходит разный ответ в зависимости от master-узла, на который он попал
D8UserAuthzRulePropagationLag Один экземпляр час не обновлял правила, а другой обновлял
D8UserAuthzPermissionBrowserUnavailable У Permission Browser есть недоступные реплики

Почему изменение ClusterAuthorizationRule применяется не сразу?

API-сервер кеширует ответы вебхука авторизации. В ресурсе AuthorizationConfiguration, который создаёт control-plane-manager, для вебхука заданы authorizedTTL: 5m, unauthorizedTTL: 30s и timeout: 3s. Ключ кеша — весь SubjectAccessReview, поэтому ответ на повторный идентичный запрос приходит из кеша.

Вебхук никогда не разрешает запрос: он либо запрещает его, либо не имеет мнения, и оба ответа API-сервер кеширует на unauthorizedTTL, то есть на 30 секунд. Поэтому создание правила или его сужение применяется к конкретному запросу в течение 30 секунд с момента последнего идентичного запроса:

d8 k auth can-i --as=user@example.com get pods -n other-namespace
sleep 30
d8 k auth can-i --as=user@example.com get pods -n other-namespace

Сбросить кеш без перезапуска kube-apiserver нельзя.

Для ресурса только что установленного CRD, задержка достигает 40 секунд. Вебхук узнаёт из discovery, принадлежит ли ресурс неймспейсу, и запрашивает discovery не чаще раза в 10 секунд, после чего API-сервер кеширует ответ ещё на 30 секунд.

В эти секунды ограничения по неймспейсам к такому ресурсу не применяются: вебхук о нём ещё не знает, мнения не имеет, и отвечает один только RBAC — поэтому субъект, которого правило ограничивает несколькими неймспейсами, может запросить новый ресурс по всему кластеру. Установка CRD требует значительно более широких прав, чем может предоставить это окно, поэтому discovery запрашивается с ограниченной частотой, а не на каждый запрос. У Permission Browser есть такое же по порядку окно для его отчёта — до 30 секунд.

Почему правило требует мультитенантности?

Параметры limitNamespaces, namespaceSelector и allowAccessToSystemNamespaces применяет вебхук авторизации, а он разворачивается, только если включён параметр enableMultiTenancy. Правило, в котором эти параметры заданы при выключенной мультитенантности, применяется без них: субъекты правила получают его уровень доступа во всех неймспейсах кластера, включая системные.

В метриках учитываются только параметры, которые задают ограничение. Правило с allowAccessToSystemNamespaces: false или с пустым списком limitNamespaces в метрики не попадает.

О ситуации сообщают две метрики:

Метрика Описание
d8_user_authz_rule_needs_multitenancy{name,options} Одна серия на каждое затронутое правило, не более 50, с параметрами, которые не применятся
d8_user_authz_rules_needing_multitenancy Общее число затронутых правил, включая те, что не попали в первые 50

Алерт D8UserAuthzRuleNeedsMultiTenancy называет отдельное правило, а D8UserAuthzRulesNeedMultiTenancy срабатывает, когда затронутых правил больше, чем называет первый алерт. Оба входят в группу D8UserAuthzMisconfigured.

Чтобы устранить ситуацию, включите мультитенантность или уберите параметры из правил.

Чтобы посмотреть, включена ли мультитенантность, используйте команду:

d8 k get moduleconfig user-authz -o jsonpath='{.spec.settings.enableMultiTenancy}'

Чтобы получить имена ClusterAuthorizationRule, которые ограничивают доступ по неймспейсу или разрешают доступ к системным неймспейсам, используйте команду:

d8 k get clusterauthorizationrule -o json | jq -r '.items[] | select((.spec.limitNamespaces // [] | length > 0) or (.spec.namespaceSelector != null) or (.spec.allowAccessToSystemNamespaces == true)) | .metadata.name'

Как получить аналог ролей ClusterAdmin и SuperAdmin в гранулярной модели?

В гранулярной модели нет ролей, объединяющих права в одну сущность, как ClusterAdmin и SuperAdmin в упрощённой модели. В ней разделяется управление платформой (системные роли) и доступ к приложениям (namespace- и проектные роли). Эквивалент собирается из двух привязок: ClusterRoleBinding на системную роль и ClusterProjectRoleBinding на проектную роль (она действует во всех проектах, включая создаваемые позже).

Приблизительное соответствие уровней:

Роль упрощённой модели Эквивалент в гранулярной модели
User d8:namespace:viewer (через RoleBinding или ProjectRoleBinding).
PrivilegedUser d8:namespace:user.
Editor d8:namespace:manager — тот же уровень; отличается область: роль гранулярной модели выдаётся на неймспейс (RoleBinding) или на проект (ProjectRoleBinding), а не на весь кластер с фильтром по неймспейсам.
Admin d8:namespace:admin.
ClusterEditor Прямого аналога нет. ClusterEditor — уровень Editor во всех неймспейсах, включая системные, плюс cluster-scoped-объекты. Собирается из ClusterProjectRoleBinding на d8:project:manager (все проекты) и системной роли для платформенной части; d8:system:manager сам по себе эквивалентом не является: доступа к пользовательским неймспейсам он не даёт.
ClusterAdmin d8:system:manager + ClusterProjectRoleBinding на d8:project:admin.
SuperAdmin d8:system:superadmin + ClusterProjectRoleBinding на d8:project:superadmin.

Пример для ClusterAdmin (группа k8s-admins):

# Платформа: конфигурация модулей DP, cluster-wide-ресурсы, системные неймспейсы.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: k8s-admins-platform
subjects:
  - kind: Group
    name: k8s-admins
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: d8:system:manager
  apiGroup: rbac.authorization.k8s.io
---
# Приложения: администратор во всех неймспейсах всех проектов (включая будущие).
apiVersion: deckhouse.io/v1alpha3
kind: ClusterProjectRoleBinding
metadata:
  name: k8s-admins-projects
spec:
  subjects:
    - kind: Group
      name: k8s-admins
  roleRef:
    kind: ClusterRole
    name: d8:project:admin

Для SuperAdmin замените роли на d8:system:superadmin и d8:project:superadmin.

Особенности:

  • При включённом модуле multitenancy-manager каждый пользовательский неймспейс является проектом, поэтому пара «системная роль + ClusterProjectRoleBinding» покрывает и платформу, и все пользовательские неймспейсы. Не покрывается только неймспейс default — он не относится ни к проектам, ни к системным.
  • Создать собственную роль «со всеми правами» (apiGroups: ["*"], resources: ["*"], verbs: ["*"]) не получится: такая роль даёт, в том числе права на управление проектами и будет отклонена встроенной защитой. Если нужен именно неограниченный доступ ко всему API (вне ролевой модели платформы), используйте ClusterRoleBinding на встроенную роль Kubernetes cluster-admin — назначить её может только тот, у кого такие права уже есть.

Как дать пользователю доступ только к ресурсам одного модуля?

Типовой запрос: пользователь в неймспейсе должен работать только с ресурсами одного модуля (например, только с виртуальными машинами), не видя остальных ресурсов (Pod, Deployment и т. п.).

Каждый модуль DP поставляет отдельные capabilities на свои ресурсы, поэтому такой доступ выдаётся без написания RBAC-правил. Соберите собственную роль, агрегирующую только capabilities нужного модуля (селектор по лейблу module):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: d8:custom:namespace:virtualization-only
  labels:
    rbac.deckhouse.io/kind: custom-role
    rbac.deckhouse.io/scope: namespace
    rbac.deckhouse.io/delegatable: "true"   # Разрешает использовать роль в RoleBinding внутри проектов.
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.deckhouse.io/kind: capability
        rbac.deckhouse.io/scope: namespace
        module: virtualization
rules: []

Выдайте роль через RoleBinding в нужном неймспейсе или через ProjectRoleBinding на весь проект. Пользователь получит доступ только к ресурсам модуля — стандартные ресурсы Kubernetes ему видны не будут.

Вне проектов то же самое можно сделать, привязав capability модуля напрямую, без создания роли:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: virtualization-view
  namespace: my-namespace
subjects:
  - kind: User
    name: user@example.com
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: d8:namespace-capability:virtualization:view
  apiGroup: rbac.authorization.k8s.io

Внутри неймспейсов проектов обычный RoleBinding может ссылаться только на роли, доступные проекту, — capabilities туда по умолчанию не входят, поэтому для проектов используйте вариант с собственной ролью (лейбл rbac.deckhouse.io/delegatable: "true" в примере выше как раз делает её доступной) либо ProjectRoleBinding.

Как расширить роль или создать новую?

Гранулярная ролевая модель построена на принципе агрегации, она собирает более мелкие роли в более обширные, тем самым предоставляя лёгкие способы расширения модели собственными ролями.

Создание новой роли подсистемы

Предположим, что текущие подсистемы не подходят под ролевое распределение в компании и требуется создать новую подсистему, которая будет включать в себя роли из подсистемы deckhouse, подсистемы kubernetes и модуля user-authn.

Для решения этой задачи создайте следующую роль:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: d8:custom:mycustom:manager
  labels:
    rbac.deckhouse.io/use-role: admin
    rbac.deckhouse.io/kind: custom-role
    rbac.deckhouse.io/scope: subsystem
    rbac.deckhouse.io/subsystem: mycustom
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.deckhouse.io/aggregate-to-deckhouse-as: manager
    - matchLabels:
        rbac.deckhouse.io/aggregate-to-kubernetes-as: manager
    - matchLabels:
        rbac.deckhouse.io/scope: system
        module: user-authn
rules: []

В начале указаны лейблы для новой роли:

  • показывает, какую namespace-роль хук должен использовать при создании RoleBinding в неймспейсах модулей:

    rbac.deckhouse.io/use-role: admin
    
  • показывает, что роль является кастомной (кастомные роли не определяют собственных правил, а только агрегируют capabilities):

    rbac.deckhouse.io/kind: custom-role
    

    Этот лейбл обязателен.

  • показывает, что роль является ролью подсистемы, и обрабатываться будет соответственно:

    rbac.deckhouse.io/scope: subsystem
    
  • указывает подсистему, за которую отвечает роль:

    rbac.deckhouse.io/subsystem: mycustom
    

Далее указаны селекторы, именно они реализуют агрегацию:

  • агрегирует роль менеджера из подсистемы deckhouse:

    rbac.deckhouse.io/aggregate-to-deckhouse-as: manager
    
  • агрегирует все системные (scope system) capabilities модуля user-authn:

     rbac.deckhouse.io/scope: system
     module: user-authn
    

Таким образом роль получает права от подсистем deckhouse, kubernetes и от модуля user-authn.

Особенности:

  • кастомные роли и capabilities должны иметь префикс имени d8:custom: (остальное пространство имён d8: зарезервировано за встроенными объектами DP). Имя должно согласовываться с объявленной областью: подсистемная роль — d8:custom:<подсистема>:<имя> (сегмент — сама подсистема, как в примере выше), namespace- или проектная роль — d8:custom:namespace:<имя> и d8:custom:project:<имя>, capability — d8:custom:<область>-capability:<имя>. Имя, расходящееся с лейблом rbac.deckhouse.io/scope, будет отклонено;
  • RoleBinding с namespace-ролью (d8:namespace:<уровень>) будут созданы в неймспейсах модулей агрегированных подсистем, уровень задаётся лейблом rbac.deckhouse.io/use-role;
  • кастомную роль нельзя агрегировать в d8:system:<уровень>, потому что лейбл rbac.deckhouse.io/aggregate-to-system-as может нести только кастомная capability. Чтобы выдать роль обладателям d8:system:manager, привяжите её к ним через ClusterRoleBinding.

Расширение пользовательской роли

Например, в кластере появился новый кластерный (пример для manage-роли) CRD-объект — MySuperResource, и нужно дополнить собственную роль из примера выше правами на взаимодействие с этим ресурсом.

Первым делом нужно дополнить роль новым селектором:

rbac.deckhouse.io/aggregate-to-mycustom-as: manager

Этот селектор позволит агрегировать capabilities к новой подсистеме через указание этого лейбла. После добавления нового селектора роль будет выглядеть так:

 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   name: d8:custom:mycustom:manager
   labels:
     rbac.deckhouse.io/use-role: admin
     rbac.deckhouse.io/kind: custom-role
     rbac.deckhouse.io/scope: subsystem
     rbac.deckhouse.io/subsystem: mycustom
 aggregationRule:
   clusterRoleSelectors:
     - matchLabels:
         rbac.deckhouse.io/aggregate-to-deckhouse-as: manager
     - matchLabels:
         rbac.deckhouse.io/aggregate-to-kubernetes-as: manager
     - matchLabels:
         rbac.deckhouse.io/scope: system
         module: user-authn
     - matchLabels:
         rbac.deckhouse.io/aggregate-to-mycustom-as: manager
 rules: []

Далее нужно создать новую capability, в которой следует определить права для нового ресурса. Например, только чтение:

 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
     rbac.deckhouse.io/aggregate-to-mycustom-as: manager
     rbac.deckhouse.io/kind: custom-capability
     rbac.deckhouse.io/scope: subsystem
     rbac.deckhouse.io/subsystem: mycustom
     rbac.deckhouse.io/capability: "custom.subsystem-capability.mycustom.superresource_view"
   name: d8:custom:subsystem-capability:mycustom:superresource:view
 rules:
 - apiGroups:
   - mygroup.io
   resources:
   - mysuperresources
   verbs:
   - get
   - list
   - watch

Capability добавит свои права в роль подсистемы, дав права на просмотр нового объекта.

Особенности:

  • кастомные capabilities должны иметь префикс имени d8:custom:; остальная часть имени не ограничена, но для читаемости лучше использовать этот стиль.

Расширение существующих подсистемных ролей

Если необходимо расширить существующую роль, нужно выполнить те же шаги, что и в пункте выше, но изменив лейблы и название роли.

Пример для расширения роли менеджера из подсистемы deckhouse (d8:subsystem:deckhouse:manager):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    rbac.deckhouse.io/aggregate-to-deckhouse-as: manager
    rbac.deckhouse.io/kind: custom-capability
    rbac.deckhouse.io/scope: subsystem
    rbac.deckhouse.io/subsystem: deckhouse
    rbac.deckhouse.io/capability: "custom.subsystem-capability.deckhouse.superresource_view"
  name: d8:custom:subsystem-capability:deckhouse:superresource:view
rules:
- apiGroups:
  - mygroup.io
  resources:
  - mysuperresources
  verbs:
  - get
  - list
  - watch

Таким образом новая capability расширит роль d8:subsystem:deckhouse:manager.

Расширение подсистемных ролей с добавлением нового неймспейса

Если необходимо добавить новый неймспейс (для создания в нём хуком RoleBinding с namespace-ролью), потребуется добавить лишь один лейбл:

"rbac.deckhouse.io/namespace": namespace

Этот лейбл сообщает хуку, что в этом неймспейсе нужно создать RoleBinding с namespace-ролью:

 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
     rbac.deckhouse.io/aggregate-to-deckhouse-as: manager
     rbac.deckhouse.io/kind: custom-capability
     rbac.deckhouse.io/scope: subsystem
     rbac.deckhouse.io/subsystem: deckhouse
     rbac.deckhouse.io/namespace: namespace
   name: d8:custom:subsystem-capability:deckhouse:superresource:view
 rules:
 - apiGroups:
   - mygroup.io
   resources:
   - mysuperresources
   verbs:
   - get
   - list
   - watch

Хук отслеживает ClusterRoleBinding и при создании привязки анализирует все системные и подсистемные роли, чтобы найти все объединенные в них capabilities с помощью проверки правила агрегации. Затем он берёт неймспейс из лейбла rbac.deckhouse.io/namespace и создает RoleBinding с namespace-ролью в этом неймспейсе.

Хук отслеживает только объекты с лейблом rbac.deckhouse.io/scope: system или subsystem. Capability без этого лейбла всё так же отдаёт свои правила роли через агрегацию, но её лейбл rbac.deckhouse.io/namespace не будет прочитан, и RoleBinding в неймспейсе не появится.

Расширение существующих namespace-ролей

Если ресурс принадлежит неймспейсу, необходимо расширить namespace-роль вместо системной/подсистемной. Разница лишь в лейблах и имени:

 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
     rbac.deckhouse.io/aggregate-to-namespace-as: user
     rbac.deckhouse.io/kind: custom-capability
     rbac.deckhouse.io/scope: namespace
     rbac.deckhouse.io/capability: "custom.namespace-capability.mycustom.superresource_view"
   name: d8:custom:namespace-capability:mycustom:superresource:view
 rules:
 - apiGroups:
   - mygroup.io
   resources:
   - mysuperresources
   verbs:
   - get
   - list
   - watch

Эта capability дополнит роль d8:namespace:user.

Создание собственной namespace- или проектной роли

Иногда встроенная иерархия уровней не подходит: например, нужна роль «разработчик» — просмотр всего неймспейса плюс чтение логов, но без права менять квоты или RBAC. Такая роль собирается из готовых capabilities, без написания RBAC-правил вручную.

Правила для собственных ролей:

  • имя должно начинаться с d8:custom: (например, d8:custom:namespace:developer);
  • роль должна иметь лейбл rbac.deckhouse.io/kind: custom-role;
  • namespace- или проектная роль, которую будут выдавать через RoleBinding, должна также иметь rbac.deckhouse.io/delegatable: "true". Каждый пользовательский неймспейс — это проект, и RoleBinding в нём принимают только роли с этим лейблом. На системные и подсистемные роли лейбл ставить нельзя — вебхук отклонит такую роль;
  • роль не может содержать собственных правил (rules) — только агрегировать capabilities через aggregationRule. Права описываются в отдельных capabilities — так состав роли всегда прозрачен;
  • нельзя в одной роли агрегировать capabilities пользовательских областей (namespace, project) вместе с административными (system, подсистемы) — такая роль будет отклонена.

Пример: роль, включающая всё, что разрешено d8:namespace:viewer, плюс одну конкретную capability (подключение к подам), выбранную адресно по её уникальному лейблу rbac.deckhouse.io/capability:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: d8:custom:namespace:developer
  labels:
    rbac.deckhouse.io/kind: custom-role
    rbac.deckhouse.io/scope: namespace
    rbac.deckhouse.io/delegatable: "true"   # Нужен, чтобы роль можно было указать в RoleBinding внутри проекта.
  annotations:
    custom.meta.deckhouse.io/title: "Разработчик"
    custom.meta.deckhouse.io/description: "Просмотр ресурсов и подключение к подам, без управления квотами и RBAC"
aggregationRule:
  clusterRoleSelectors:
    # Всё, что входит в уровень viewer namespace-линейки.
    - matchLabels:
        rbac.deckhouse.io/aggregate-to-namespace-as: viewer
    # Плюс одна конкретная capability, выбранная по её уникальному имени.
    - matchLabels:
        rbac.deckhouse.io/capability: "namespace-capability.kubernetes.access_terminal"
rules: []

Если готовой capability с нужными правами нет, создайте собственную (custom-capability может содержать правила) и добавьте в aggregationRule роли селектор по её лейблу rbac.deckhouse.io/capability (в примере ниже — matchLabels: {rbac.deckhouse.io/capability: "custom.logs-reader"}):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: d8:custom:namespace-capability:logs-reader
  labels:
    rbac.deckhouse.io/kind: custom-capability
    rbac.deckhouse.io/capability: "custom.logs-reader"
rules:
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get", "list"]

Чтобы получить список всех доступных capabilities и их уникальных имён, используйте команду:

d8 k get clusterroles -l rbac.deckhouse.io/kind=capability \
  -o custom-columns='NAME:.metadata.name,CAPABILITY:.metadata.labels.rbac\.deckhouse\.io/capability'

Созданная роль назначается так же, как и встроенная: через RoleBinding в неймспейсе или через ProjectRoleBinding на весь проект (для проектных ролей используйте rbac.deckhouse.io/scope: project и агрегируйте aggregate-to-project-as). Для RoleBinding в неймспейсе проекта на роли обязателен лейбл delegatable; ProjectRoleBinding его не требует (у него свой список допустимых префиксов). Назначить роль через ClusterRoleBinding нельзя — как и встроенные роли этих областей.

Собрать такую роль можно и без YAML — мастером выдачи доступа в веб-интерфейсе Deckhouse Console: он показывает доступные capabilities, собирает из них роль и сразу создаёт нужную привязку.

Как перевести кастомные роли на новую схему?

Вместе с переименованием ролей изменилась и схема лейблов, по которым агрегируются права.

У встроенных capabilities больше нет лейблов rbac.deckhouse.io/kind: manage и rbac.deckhouse.io/kind: use. Кастомная роль старой схемы, которая выбирает capabilities по этим лейблам, потеряла их права, а вместе с ней и её субъекты. Псевдонимы совместимости для кастомных ролей не создаются, поэтому почините такие роли, как описано ниже.

Селектор агрегации кастомной роли, который требует один из этих лейблов (например, rbac.deckhouse.io/kind: manage + rbac.deckhouse.io/aggregate-to-<SUBSYSTEM>-as), не находит ни одной встроенной capability. По такому селектору контроллер агрегации Kubernetes берёт только правила кастомных capabilities, которые ещё несут старые лейблы. Deckhouse создаёт объекты RoleBinding для субъектов роли с лейблом rbac.deckhouse.io/use-role в неймспейсах модулей, только если у роли есть лейбл rbac.deckhouse.io/scope: system или subsystem. Кастомная роль старой схемы с use-role и без такого лейбла потеряла эти объекты, даже если её селекторы уже переписаны. Селектор, который требует лейбл агрегации линейки, которую не собирает ни одна встроенная роль, тоже не находит ни одной встроенной capability, потому что модули ставят на свои capabilities только лейблы линеек, которые собирают встроенные роли. Это, например, лейбл rbac.deckhouse.io/aggregate-to-all-as старой схемы, лейбл подсистемы, которой больше нет в ролевой модели, или лейбл подсистемы, из которой ушёл модуль. Исключение составляет линейка, которую несёт кастомная capability, потому что селектор по-прежнему получает правила этой capability. Кастомная capability здесь означает объект ClusterRole без лейбла heritage: deckhouse и без поля aggregationRule. Исключение составляет и линейка собственной подсистемы роли из её лейбла rbac.deckhouse.io/subsystem, потому что её несут кастомные capabilities этой подсистемы, см. Расширение пользовательской роли. Ни одно из исключений не относится к линейкам all, deckhouse, infrastructure, kubernetes и networking, которые не считаются кастомными. Устаревшая роль-псевдоним, то есть ClusterRole с лейблом rbac.deckhouse.io/deprecated: "true", не считается встроенной ролью, которая собирает линейку, потому что линейку старой подсистемы она собирает только для привязок к старому имени. Кастомная capability старой схемы тоже могла перейти в другие роли, см. Замена capability.

Такие роли перечисляет алерт D8UserAuthzLegacyRBACv2CustomRoleFound. Он срабатывает на каждый объект ClusterRole без лейбла heritage: deckhouse и с полем aggregationRule, у которого есть селектор, требующий лейбл rbac.deckhouse.io/kind: manage или use, есть селектор, требующий такой лейбл агрегации, или есть лейбл rbac.deckhouse.io/use-role без rbac.deckhouse.io/scope: system или subsystem, независимо от имени роли. Роль без поля aggregationRule, например замороженная ранее, в него не попадает. Чтобы получить список таких ролей, используйте команду:

d8 k get clusterroles -o json | jq -r '
  def lineages: [((.matchLabels // {}) | keys[]), (.matchExpressions[]? | select(.operator == "In" or .operator == "Exists") | .key)]
    | map(capture("^rbac\\.deckhouse\\.io/aggregate-to-(?<lineage>.+)-as$").lineage);
  ([.items[] | select(.metadata.labels.heritage == "deckhouse" and .metadata.labels["rbac.deckhouse.io/deprecated"] != "true")
        | .aggregationRule.clusterRoleSelectors[]? | lineages[]] + ["system", "namespace", "project"]) as $collected
  | ([.items[] | select(.metadata.labels.heritage != "deckhouse" and .aggregationRule == null) | (.metadata.labels // {}) | keys[]
        | capture("^rbac\\.deckhouse\\.io/aggregate-to-(?<lineage>.+)-as$").lineage]) as $carried
  | .items[]
  | select(.metadata.labels.heritage != "deckhouse" and .aggregationRule != null)
  | (.metadata.labels["rbac.deckhouse.io/subsystem"] // "") as $own
  | select(
      (
        ((.metadata.labels["rbac.deckhouse.io/use-role"] // "") != "")
        and (((.metadata.labels["rbac.deckhouse.io/scope"] // "") | IN("system", "subsystem")) | not)
      )
      or (
        [
          .aggregationRule.clusterRoleSelectors[]?
          | (.matchLabels["rbac.deckhouse.io/kind"] // empty),
            (.matchExpressions[]? | select(.key == "rbac.deckhouse.io/kind" and .operator == "In") | .values[])
        ]
        | any(IN("manage", "use"))
      )
      or (
        [
          .aggregationRule.clusterRoleSelectors[]? | lineages[]
          | select((IN($collected[]) | not)
              and (IN("all", "deckhouse", "infrastructure", "kubernetes", "networking") or (. != $own and (IN($carried[]) | not))))
        ]
        | length > 0
      )
    )
  | .metadata.name'

Соответствие старой и новой схем:

Было (старая схема) Стало (новая схема)
Произвольное имя роли (например, custom:manage:mycustom:manager) Обязательный префикс d8:custom: (например, d8:custom:mycustom:manager)
rbac.deckhouse.io/kind: manage или use на кастомной роли rbac.deckhouse.io/kind: custom-role
rbac.deckhouse.io/kind: manage или use на кастомной capability rbac.deckhouse.io/kind: custom-capability, имя с префиксом d8:custom:
rbac.deckhouse.io/level: all \| subsystem \| module rbac.deckhouse.io/scope: system \| subsystem \| namespace
rbac.deckhouse.io/kind: manage + rbac.deckhouse.io/aggregate-to-all-as: <уровень> на кастомной capability rbac.deckhouse.io/aggregate-to-system-as: <уровень>
rbac.deckhouse.io/kind: use + rbac.deckhouse.io/aggregate-to-all-as: <уровень> на кастомной capability rbac.deckhouse.io/kind: custom-capability + rbac.deckhouse.io/scope: namespace + rbac.deckhouse.io/aggregate-to-namespace-as: <уровень>, имя d8:custom:namespace-capability:<имя>
rbac.deckhouse.io/kind: manage + rbac.deckhouse.io/aggregate-to-all-as: <уровень> на кастомной роли ClusterRoleBinding роли на субъектов d8:manage:all:<уровень> и d8:system:<уровень>, потому что кастомная роль не может нести rbac.deckhouse.io/aggregate-to-system-as
rbac.deckhouse.io/kind: use + rbac.deckhouse.io/aggregate-to-kubernetes-as: <уровень> на кастомной capability rbac.deckhouse.io/kind: custom-capability + rbac.deckhouse.io/scope: namespace + rbac.deckhouse.io/aggregate-to-namespace-as: <уровень>, имя d8:custom:namespace-capability:<имя>
Селектор агрегации: rbac.deckhouse.io/kind: manage + rbac.deckhouse.io/aggregate-to-<подсистема>-as: <уровень> Только rbac.deckhouse.io/aggregate-to-<подсистема>-as: <уровень>
Селектор для use-прав: rbac.deckhouse.io/kind: use + rbac.deckhouse.io/aggregate-to-kubernetes-as: <уровень> rbac.deckhouse.io/aggregate-to-namespace-as: <уровень>
Селектор по модулю: rbac.deckhouse.io/kind: manage + module: <модуль> rbac.deckhouse.io/scope: system + module: <модуль>

Имена встроенных capabilities также изменились (без псевдонимов):

  • d8:manage:permission:module:<модуль>:view|edit → d8:system-capability:<модуль>:view|edit;
  • d8:use:capability:module:<модуль>:view|edit → d8:namespace-capability:<модуль>:view|edit.

Селекторы агрегации работают по лейблам, а не по именам. Прямые привязки к capabilities использовать не следует.

Починка роли

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

  1. Создайте рядом со старой ролью роль новой схемы с префиксом имени d8:custom:, лейблом rbac.deckhouse.io/kind: custom-role и селекторами агрегации новой схемы, без rbac.deckhouse.io/kind. См. пример «до и после». Селектор по лейблу агрегации линейки, которую не собирает ни одна встроенная роль, замените, как описано в разделе Селекторы по линейке, которую не собирает ни одна встроенная роль. Выберите область действия роли и назовите её соответственно:

    • Роль с лейблом rbac.deckhouse.io/use-role или с лейблом rbac.deckhouse.io/level: subsystem или all получает лейблы rbac.deckhouse.io/scope: subsystem и rbac.deckhouse.io/subsystem: <SUBSYSTEM> с именем d8:custom:<SUBSYSTEM>:<NAME> или лейбл rbac.deckhouse.io/scope: system с именем d8:custom:system:<NAME>. Сохраните на ней лейбл rbac.deckhouse.io/use-role. Тогда для субъектов её объектов ClusterRoleBinding Deckhouse создаёт объекты RoleBinding на роль d8:namespace:*, которую называет этот лейбл, в неймспейсах модулей, системные capabilities которых агрегирует роль. Вебхук rbacv2-cluster-roles.deckhouse.io отклоняет кастомную роль без лейбла rbac.deckhouse.io/scope, селекторы которой называют лейбл агрегации подсистемы или системной области, например rbac.deckhouse.io/aggregate-to-deckhouse-as.
    • Namespace- или проектная роль, которую вы выдаёте через RoleBinding, получает лейблы rbac.deckhouse.io/scope: namespace и rbac.deckhouse.io/delegatable: "true" с именем d8:custom:namespace:<NAME>. См. Создание собственной namespace- или проектной роли.
  2. Создайте capabilities новой схемы вместо кастомных capabilities старой схемы, которые агрегирует роль, как описано в разделе Замена capability. Старые capabilities пока не удаляйте.
  3. Для каждого объекта RoleBinding и ClusterRoleBinding, указывающего на старую роль, создайте объект с теми же субъектами, указывающий на новую роль. Поле roleRef неизменяемо, поэтому создайте новый объект, а не редактируйте старый. Если у старой роли есть лейблы rbac.deckhouse.io/kind: manage и rbac.deckhouse.io/aggregate-to-all-as, привяжите новую роль ещё и так, как описано в разделе Роли в составе системных ролей. С rbac.deckhouse.io/kind: use она расширяла роли неймспейса, см. тот же раздел.
  4. Проверьте права субъектов:

    d8 k auth can-i <VERB> <RESOURCE> --as=<USER_NAME>
    d8 k auth can-i <VERB> <RESOURCE> -n <NAMESPACE> --as=<USER_NAME>
    

    Чтобы проверить права группы, добавьте к --as флаг --as-group=<GROUP_NAME>.

  5. Удалите старые объекты RoleBinding и ClusterRoleBinding, старую роль и старые capabilities.

Если объекты применяются из Git-репозитория (например, с помощью Argo CD или Flux), внесите те же изменения в репозиторий. Иначе следующее применение манифестов снова создаст старые объекты.

Роли в составе системных ролей

В старой схеме лейбл rbac.deckhouse.io/aggregate-to-all-as: <LEVEL> означал одно из двух, в зависимости от лейбла вида рядом с ним. С rbac.deckhouse.io/kind: use он расширял роли d8:use:role:<LEVEL> во всех неймспейсах. Им соответствует d8:namespace:<LEVEL>, которая выбирает rbac.deckhouse.io/aggregate-to-namespace-as: <LEVEL>, поэтому перенесите такие права в capability неймспейса, как описано в разделе Замена capability. С rbac.deckhouse.io/kind: manage роль с этим лейблом расширяла d8:manage:all:<LEVEL>, и дальше этот раздел о ней. Ни одна встроенная роль этот лейбл больше не выбирает. Псевдоним d8:manage:all:<LEVEL> и роль d8:system:<LEVEL> выбирают лейбл rbac.deckhouse.io/aggregate-to-system-as: <LEVEL>, а вебхук rbacv2-cluster-roles.deckhouse.io допускает этот лейбл только на кастомной capability. Поэтому кастомная роль не может расширить эти роли, и субъекты, которые получали старую роль через них, получают новую роль только через собственный объект ClusterRoleBinding.

Следующие команды создают такой объект ClusterRoleBinding для каждого объекта ClusterRoleBinding на d8:manage:all:<LEVEL> и d8:system:<LEVEL>:

NEW_ROLE=<NEW_ROLE_NAME>
LEVEL=<LEVEL>
d8 k get clusterrolebindings -o json > clusterrolebindings.json
jq --arg role "$NEW_ROLE" --arg level "$LEVEL" '
  {apiVersion: "v1", kind: "List", items: [.items[]
    | select(.roleRef.kind == "ClusterRole" and (.roleRef.name | IN("d8:manage:all:" + $level, "d8:system:" + $level)))
    | {apiVersion, kind, metadata: {name: ($role + ":" + .metadata.name)},
       roleRef: {apiGroup: "rbac.authorization.k8s.io", kind: "ClusterRole", name: $role}, subjects}]}' \
  clusterrolebindings.json > new-clusterrolebindings.json
d8 k apply -f new-clusterrolebindings.json

Перед созданием объектов просмотрите файл new-clusterrolebindings.json и удалите из него субъектов, которые не должны получить роль. Deckhouse не синхронизирует эти объекты с объектами ClusterRoleBinding, из которых они скопированы. Когда вы отзываете доступ субъекта, удалите оба. Имя копии заканчивается именем исходного объекта.

Селекторы по линейке, которую не собирает ни одна встроенная роль

Селектор по лейблу агрегации линейки выбирал capabilities, которые несли этот лейбл. Например, rbac.deckhouse.io/aggregate-to-virtualization-as: <LEVEL> выбирал capabilities модуля virtualization. Когда модуль ставит на них лейбл линейки подсистемы, в которой он теперь находится, селектор по этой линейке выбирает capabilities всех модулей подсистемы, а это может быть намного больше, чем давала роль раньше. Чтобы новая роль осталась такой же узкой, как старая, выбирайте сами capabilities по лейблу rbac.deckhouse.io/capability. Его значение не зависит от подсистемы. Для системной capability это system-capability.<MODULE_NAME>.<ACTION>, для capability неймспейса это namespace-capability.<MODULE_NAME>.<ACTION>.

  1. Получите список capabilities модуля с уровнями ролей, которые их собирают:

    d8 k get clusterroles -l rbac.deckhouse.io/kind=capability,module=<MODULE_NAME> -o json | jq -r '
      .items[]
      | [.metadata.labels["rbac.deckhouse.io/capability"],
         (.metadata.labels | to_entries
           | map(select(.key | test("^rbac\\.deckhouse\\.io/aggregate-to-.+-as$")) | (.key | ltrimstr("rbac.deckhouse.io/aggregate-to-") | rtrimstr("-as")) + "=" + .value)
           | join(","))]
      | @tsv'
    
  2. Оставьте capabilities, которые находил старый селектор. Селектор со значением <LEVEL> находил capabilities этого уровня и, через встроенные роли нижних уровней, capabilities этих уровней.
  3. В новой роли укажите по одному селектору по лейблу rbac.deckhouse.io/capability для каждой из них. Чтобы выдать все системные capabilities модуля независимо от уровня, достаточно одного селектора по rbac.deckhouse.io/scope: system и module: <MODULE_NAME>.

Например, следующая роль неймспейса даёт в неймспейсе то же, что давал селектор по rbac.deckhouse.io/aggregate-to-virtualization-as: user, без других модулей подсистемы. Замените <ACTION> значениями из списка:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: d8:custom:namespace:virtualization-user
  labels:
    rbac.deckhouse.io/kind: custom-role
    rbac.deckhouse.io/scope: namespace
    rbac.deckhouse.io/delegatable: "true"
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.deckhouse.io/capability: namespace-capability.virtualization.<ACTION>
    - matchLabels:
        rbac.deckhouse.io/capability: namespace-capability.virtualization.<ACTION>
rules: []

Роль системной области или подсистемы выбирает значения system-capability.<MODULE_NAME>.<ACTION> так же.

Замена capability

Кастомная capability старой схемы несёт лейбл rbac.deckhouse.io/kind: manage или use и лейбл агрегации, и её называет алерт D8UserAuthzForeignAggregationLabel. Пока на ней есть лейбл агрегации системной области, области неймспейса или проекта либо встроенной подсистемы, вебхук rbacv2-cluster-roles.deckhouse.io отклоняет любое её изменение. Вебхук допускает такой лейбл только вместе с rbac.deckhouse.io/kind: custom-capability, а этот лейбл требует префикса имени d8:custom:, который существующий объект получить не может. Создайте на её месте capability новой схемы с теми же правилами, а старую удалите, когда новая будет давать доступ.

Лейблы старой capability определяют новую:

Старая capability Новая capability
rbac.deckhouse.io/kind: use + rbac.deckhouse.io/aggregate-to-kubernetes-as: <LEVEL> d8:custom:namespace-capability:<NAME> с лейблами rbac.deckhouse.io/scope: namespace и rbac.deckhouse.io/aggregate-to-namespace-as: <LEVEL>
rbac.deckhouse.io/kind: manage + rbac.deckhouse.io/aggregate-to-<SUBSYSTEM>-as: <LEVEL> для встроенной или собственной подсистемы d8:custom:subsystem-capability:<NAME> с лейблами rbac.deckhouse.io/scope: subsystem, rbac.deckhouse.io/subsystem: <SUBSYSTEM> и тем же лейблом агрегации
rbac.deckhouse.io/kind: use + rbac.deckhouse.io/aggregate-to-all-as: <LEVEL> d8:custom:namespace-capability:<NAME> с лейблами rbac.deckhouse.io/scope: namespace и rbac.deckhouse.io/aggregate-to-namespace-as: <LEVEL>
rbac.deckhouse.io/kind: manage + rbac.deckhouse.io/aggregate-to-all-as: <LEVEL> d8:custom:system-capability:<NAME> с лейблами rbac.deckhouse.io/scope: system и rbac.deckhouse.io/aggregate-to-system-as: <LEVEL>

Каждая новая capability также получает лейбл rbac.deckhouse.io/kind: custom-capability.

Старая capability с лейблом rbac.deckhouse.io/namespace: <NAMESPACE> давала субъектам агрегирующих её ролей объекты RoleBinding в этом неймспейсе, неймспейсе модуля. Deckhouse читает этот лейбл только на объектах ClusterRole с rbac.deckhouse.io/scope: system или subsystem, поэтому старая capability таких объектов RoleBinding больше не даёт. Перенесите лейбл на её замену, системную capability или capability подсистемы, и Deckhouse создаст их снова.

Capability с лейблами rbac.deckhouse.io/kind: use и rbac.deckhouse.io/aggregate-to-kubernetes-as: <LEVEL> расширяла роли d8:use:role:<LEVEL>. Теперь она ничего к ним не добавляет, потому что d8:namespace:<LEVEL> и псевдонимы d8:use:role:<LEVEL> выбирают лейбл rbac.deckhouse.io/aggregate-to-namespace-as. На уровнях viewer и manager её лейбл вместо этого выбирают роли d8:subsystem:kubernetes:<LEVEL>, какое бы значение ни было у её лейбла rbac.deckhouse.io/kind. Через них её правила попадают в d8:system:<LEVEL> и в роли более высоких уровней, и их субъекты получают права этой capability во всех неймспейсах. Замените такую capability в первую очередь. Например, следующие команды создают её аналог для namespace-ролей:

OLD_CAPABILITY=<OLD_CAPABILITY_NAME>
NEW_CAPABILITY=d8:custom:namespace-capability:<NAME>
LEVEL=<LEVEL>
d8 k get clusterrole "$OLD_CAPABILITY" -o json | jq --arg name "$NEW_CAPABILITY" --arg level "$LEVEL" '
  {apiVersion, kind, metadata: {name: $name, labels: {
     "rbac.deckhouse.io/kind": "custom-capability",
     "rbac.deckhouse.io/scope": "namespace",
     "rbac.deckhouse.io/aggregate-to-namespace-as": $level}},
   rules}' > new-capability.json
d8 k apply -f new-capability.json

Проверьте права субъектов namespace-ролей, а затем удалите старую capability:

d8 k delete clusterrole "$OLD_CAPABILITY"

После удаления её права пропадают и из ролей d8:subsystem:kubernetes:<LEVEL>.

Замороженные роли

Роль, которую заморозили до того, как встроенные capabilities получили новые лейблы (удалили поле aggregationRule и сохранили правила), сохраняет свои права, и алерт D8UserAuthzLegacyRBACv2CustomRoleFound её не перечисляет.

  • Явные объекты RoleBinding, которые заменили её автоматические, указывают на d8:use:role:<LEVEL>. Новая схема сохраняет эти роли только как устаревшие псевдонимы d8:namespace:<LEVEL>. Такие объекты называет алерт D8UserAuthzDeprecatedRBACv2RoleInUse. Переведите их на d8:namespace:<LEVEL>, потому что привязка к псевдониму перестаёт давать доступ, когда псевдоним удаляется. Следующие команды делают это для объектов RoleBinding с префиксом имени explicit::

    d8 k get rolebindings -A -o json | jq '{apiVersion: "v1", kind: "List", items: [.items[]
      | select(.roleRef.kind == "ClusterRole" and (.roleRef.name | startswith("d8:use:role:")) and (.metadata.name | startswith("explicit:")))]}' \
      > use-role-rolebindings.json
    jq '.items |= map({apiVersion, kind,
        metadata: {name: ("namespace:" + .metadata.name), namespace: .metadata.namespace,
          annotations: ((.metadata.annotations // {}) | with_entries(select(.key == "rbac.deckhouse.io/related-with")))},
        roleRef: (.roleRef | .name |= (sub("^d8:use:role:"; "d8:namespace:") | sub(":kubernetes$"; ""))), subjects})' \
      use-role-rolebindings.json > namespace-rolebindings.json
    d8 k apply -f namespace-rolebindings.json && d8 k get -f namespace-rolebindings.json > /dev/null && d8 k delete -f use-role-rolebindings.json
    

    Старые объекты удаляются, только если все новые уже есть, и после сбоя команды можно запустить снова.

  • Если на роли остался лейбл rbac.deckhouse.io/aggregate-to-all-as, её называет алерт D8UserAuthzForeignAggregationLabel. Ни одна встроенная роль этот лейбл больше не выбирает, поэтому удалите его:

    d8 k label clusterrole <ROLE_NAME> rbac.deckhouse.io/aggregate-to-all-as-
    
  • Замороженная роль не получает права на ресурсы, добавленные позже, например на ресурсы нового модуля. Чтобы они появились, замените её ролью новой схемы, как описано в разделе Починка роли.

Примеры

Кастомная роль до и после

Пример конфигурации роли, объединяющей права подсистем deckhouse и kubernetes и модуля user-authn.

  • Было (старая схема):

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: custom:manage:mycustom:manager
      labels:
        rbac.deckhouse.io/use-role: admin
        rbac.deckhouse.io/kind: manage
        rbac.deckhouse.io/level: subsystem
        rbac.deckhouse.io/subsystem: custom
        rbac.deckhouse.io/aggregate-to-all-as: manager
    aggregationRule:
      clusterRoleSelectors:
        - matchLabels:
            rbac.deckhouse.io/kind: manage
            rbac.deckhouse.io/aggregate-to-deckhouse-as: manager
        - matchLabels:
            rbac.deckhouse.io/kind: manage
            rbac.deckhouse.io/aggregate-to-kubernetes-as: manager
        - matchLabels:
            rbac.deckhouse.io/kind: manage
            module: user-authn
    rules: []
    
  • Стало (новая схема):

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: d8:custom:mycustom:manager
      labels:
        rbac.deckhouse.io/use-role: admin
        rbac.deckhouse.io/kind: custom-role
        rbac.deckhouse.io/scope: subsystem
        rbac.deckhouse.io/subsystem: mycustom
    aggregationRule:
      clusterRoleSelectors:
        - matchLabels:
            rbac.deckhouse.io/aggregate-to-deckhouse-as: manager
        - matchLabels:
            rbac.deckhouse.io/aggregate-to-kubernetes-as: manager
        - matchLabels:
            rbac.deckhouse.io/scope: system
            module: user-authn
    rules: []
    

Что изменилось:

  • имя получило обязательный префикс d8:custom: и называет подсистему роли;
  • rbac.deckhouse.io/kind: manage → rbac.deckhouse.io/kind: custom-role;
  • rbac.deckhouse.io/level: subsystem → rbac.deckhouse.io/scope: subsystem;
  • лейбл rbac.deckhouse.io/aggregate-to-all-as удалён, субъекты d8:system:manager получают роль через ClusterRoleBinding, см. Роли в составе системных ролей;
  • из селекторов агрегации убран лейбл rbac.deckhouse.io/kind: manage;
  • выборка всех системных прав модуля теперь выполняется по rbac.deckhouse.io/scope: system + module: <модуль>.

Кастомная capability до и после

Пример конфигурации capability, которая даёт права на просмотр ресурса MySuperResource и агрегируется в роль из примера выше (в её поле aggregationRule должен быть селектор rbac.deckhouse.io/aggregate-to-mycustom-as: manager).

  • Было (старая схема):

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: custom:manage:permission:mycustom:superresource:view
      labels:
        rbac.deckhouse.io/kind: manage
        rbac.deckhouse.io/aggregate-to-mycustom-as: manager
    rules:
      - apiGroups:
          - mygroup.io
        resources:
          - mysuperresources
        verbs:
          - get
          - list
          - watch
    
  • Стало (новая схема):

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: d8:custom:subsystem-capability:mycustom:superresource:view
      labels:
        rbac.deckhouse.io/kind: custom-capability
        rbac.deckhouse.io/scope: subsystem
        rbac.deckhouse.io/subsystem: mycustom
        rbac.deckhouse.io/aggregate-to-mycustom-as: manager
    rules:
      - apiGroups:
          - mygroup.io
        resources:
          - mysuperresources
        verbs:
          - get
          - list
          - watch
    

Лейблы и аннотации: было и стало

Лейблы на объектах ClusterRole:

Лейбл Было Стало Назначение
rbac.deckhouse.io/kind manage или use custom-role / custom-capability для кастомных объектов; role / capability у встроенных (зарезервированы) Тип объекта ролевой модели. Обязателен, объекты без него не обрабатываются
rbac.deckhouse.io/level all | subsystem | module Удалён Старый уровень роли; заменён лейблом scope
rbac.deckhouse.io/scope — system | subsystem | namespace Область действия роли или capability
rbac.deckhouse.io/subsystem Имя подсистемы Без изменений Подсистема роли; используется при scope: subsystem
rbac.deckhouse.io/use-role Уровень use-роли Уровень namespace-роли Какая namespace-роль автоматически выдаётся обладателю системной/подсистемной роли в системных неймспейсах её модулей (через автоматически создаваемые объекты RoleBinding)
rbac.deckhouse.io/aggregate-to-all-as <уровень> С rbac.deckhouse.io/kind: manage заменён лейблом rbac.deckhouse.io/aggregate-to-system-as на кастомной capability, который кастомная роль нести не может. С rbac.deckhouse.io/kind: use заменён лейблом rbac.deckhouse.io/aggregate-to-namespace-as на кастомной capability Агрегация объекта в общесистемную роль (d8:system:<уровень>)
rbac.deckhouse.io/aggregate-to-<подсистема>-as Использовался в селекторах вместе с rbac.deckhouse.io/kind: manage Используется в селекторах сам по себе Агрегация объекта в подсистемную роль указанного уровня
rbac.deckhouse.io/aggregate-to-kubernetes-as <уровень> (для use-прав) По-прежнему для подсистемы kubernetes (d8:subsystem:kubernetes:*). Старый use-смысл переехал на aggregate-to-namespace-as Агрегация в d8:subsystem:kubernetes:<уровень>
rbac.deckhouse.io/namespace Неймспейс Без изменений Дополнительный неймспейс, в котором обладателям роли автоматически создаётся RoleBinding
rbac.deckhouse.io/capability — Уникальное имя capability (например, system-capability.deckhouse.view) Машиночитаемый идентификатор встроенной capability
rbac.deckhouse.io/deprecated — "true" на ролях-псевдонимах Роль устарела и будет удалена; переведите привязки на новую роль
module Имя модуля Без изменений Принадлежность встроенного объекта модулю DP; удобен в селекторах агрегации вместе со scope
heritage: deckhouse Признак объекта платформы Без изменений Устанавливать на кастомные объекты нельзя

Аннотации на объектах ClusterRole (в старой схеме аннотации не использовались):

Аннотация Назначение
ru.meta.deckhouse.io/title, ru.meta.deckhouse.io/description Отображаемые название и описание роли или capability на русском языке (платформа ставит их на встроенные объекты; на кастомных можно указать свои)
en.meta.deckhouse.io/title, en.meta.deckhouse.io/description То же на английском языке
rbac.deckhouse.io/deprecated-replaced-by Стоит на ролях-псевдонимах прежних имён и называет новую роль, на которую нужно перевести привязки. Псевдоним агрегирует capabilities новой роли, поэтому существующие привязки к прежнему имени дают доступ, пока псевдонимы не удалены. Это не те же права, что прежде, потому что d8:use:role:admin больше не даёт выпуск токена ServiceAccount и impersonate

Добавление кастомной capability (в новой схеме)

Capability — это обычный объект ClusterRole с правилами, который через лейбл агрегации автоматически включается в выбранную роль. В новой схеме кастомная capability создаётся следующим образом:

  1. Определите, какую роль нужно расширить: namespace-роль, подсистемную, системную или кастомную.
  2. Создайте ClusterRole с префиксом имени d8:custom: (для читаемости — d8:custom:capability:<имя>:<ресурс>:<действие>), лейблом rbac.deckhouse.io/kind: custom-capability и лейблом агрегации целевой роли:
    • rbac.deckhouse.io/aggregate-to-namespace-as: <viewer|user|manager|admin|superadmin> — в namespace-роль d8:namespace:<уровень>;
    • rbac.deckhouse.io/aggregate-to-<подсистема>-as: <viewer|manager|superadmin> — в подсистемную роль d8:subsystem:<подсистема>:<уровень>;
    • rbac.deckhouse.io/aggregate-to-system-as: <viewer|manager|superadmin> — в системную роль d8:system:<уровень>;
    • rbac.deckhouse.io/aggregate-to-<имя своей подсистемы>-as: <уровень> — в кастомную роль (такой селектор должен присутствовать в её поле aggregationRule).
  3. Опишите права в rules.

Kubernetes агрегирует правила автоматически: сразу после создания capability её права появятся у всех обладателей целевой роли. Проверить результат можно командой d8 k auth can-i --as <пользователь> или посмотрев итоговые правила роли: d8 k get clusterrole <роль> -o yaml.

Примеры конфигурации доступны выше в подразделах «Кастомная роль до и после» и «Кастомная capability до и после».

Как переименовать встроенную роль?

Изменять права встроенных ролей нельзя, но можно изменить их отображаемое название и описание — например, чтобы в интерфейсе они назывались в принятых в компании терминах. Для этого добавьте на роль аннотации custom.meta.deckhouse.io/title и custom.meta.deckhouse.io/description:

d8 k annotate clusterrole d8:namespace:admin \
  custom.meta.deckhouse.io/title='Администратор команды' \
  custom.meta.deckhouse.io/description='Управление ресурсами и доступом в пространстве имён команды'

Это единственное изменение, которое разрешено вносить в объекты с префиксом d8: (кроме d8:custom:*): попытка изменить правила, агрегацию или лейблы встроенной роли будет отклонена.

Как узнать, у кого есть доступ к ресурсу?

При включённом режиме мультитенантности (enableMultiTenancy) доступен обратный запрос к авторизации — ресурс WhoCan. Он отвечает на вопрос «кто может выполнить действие X над ресурсом Y?» и возвращает список пользователей, групп и ServiceAccount’ов:

d8 k create -o yaml -f - <<EOF
apiVersion: authorization.deckhouse.io/v1alpha1
kind: WhoCan
metadata:
  name: who-can-create-networkpolicies
spec:
  resourceAttributes:
    namespace: my-namespace
    verb: create
    group: networking.k8s.io
    resource: networkpolicies
EOF

Ответ возвращается в поле status (users, groups, serviceAccounts) сразу в выводе команды; объект нигде не сохраняется.

Право создавать WhoCan-запросы даёт кластерная роль d8:user-authz:who-can-checker. Она никому не выдана по умолчанию: результат запроса раскрывает субъекты доступа во всех неймспейсах, поэтому выдавайте её только доверенным администраторам через ClusterRoleBinding.

Как узнать, что разрешено конкретному пользователю, группе или ServiceAccount’у?

При включённом режиме мультитенантности (enableMultiTenancy) доступен ресурс SubjectAccessReport — обратная сторона WhoCan. Он отвечает на вопрос «что разрешено этому субъекту» и сразу возвращает отчёт: какие роли и через какие привязки выданы, какие действия и над какими ресурсами разрешены в кластере и в каждом неймспейсе, и откуда взялось каждое право.

d8 k create -o yaml -f - <<EOF
apiVersion: authorization.deckhouse.io/v1alpha1
kind: SubjectAccessReport
metadata:
  name: what-can-jane-do
spec:
  subject:
    kind: User
    name: jane@example.com
EOF

Поле spec.subject.kind принимает значения User, Group и ServiceAccount (для последнего обязательно укажите spec.subject.namespace). Если spec.subject не указан, отчёт строится для того, кто выполняет запрос.

Особенности отчёта:

  • Группы пользователя определяются автоматически по каталогу Group, включая вложенные: если пользователь состоит в группе B, а B входит в A, права A тоже попадут в отчёт. Учтённые группы возвращаются в status.subject.groups. Группы, которых нет в каталоге (например, приходящие из внешнего провайдера аутентификации), можно передать в spec.groups.
  • В каждом источнике права (status.scopes[].resources[].sources[]) есть поле matchedBy: по нему видно, выдано право лично субъекту или через группу — и можно посмотреть картину прав без учёта групп.
  • Неймспейсы с одинаковым доступом объединяются в одну секцию (status.scopes[].namespaces), поэтому проект с десятком неймспейсов не превращается в десяток одинаковых таблиц.
  • Права, выданные ClusterRoleBinding, показываются один раз в кластерной области (status.scopes[].cluster: true) — они действуют во всех неймспейсах.
  • Ограничить отчёт конкретными неймспейсами можно через spec.namespaces.

Отчёт строится по данным RBAC. Ограничения admission-вебхуков не сворачиваются в строки RBAC — они помечаются в status.scopes[].caveat. Например, изменение и удаление системных ресурсов запрещено ниже уровня superadmin — кроме администраторов кластера (cluster-admin, user-authz:super-admin и bypass-групп). Если права субъекта ограничены ресурсом ClusterAuthorizationRule, об этом сообщается в status.notes.

Право строить отчёт о другом субъекте даёт кластерная роль d8:user-authz:subject-access-checker. Как и who-can-checker, она намеренно никому не выдана по умолчанию: отчёт раскрывает полную карту прав субъекта, включая чужие неймспейсы. Отчёт о самом себе доступен любому аутентифицированному пользователю и не требует дополнительных прав.

Как пользователю увидеть список доступных ему неймспейсов?

При включённом режиме мультитенантности (enableMultiTenancy) список неймспейсов фильтруется автоматически: команда d8 k get namespaces возвращает пользователю только те неймспейсы, к которым у него есть доступ — независимо от механизма предоставления доступа (привязки ролей, ProjectRoleBinding/ClusterProjectRoleBinding, ClusterAuthorizationRule/AuthorizationRule). Пользователь не видит чужих неймспейсов и не может по списку узнать об их существовании.

Тот же список отдаёт read-only ресурс accessiblenamespaces — его может запросить любой аутентифицированный пользователь для самого себя:

d8 k get accessiblenamespaces

Это удобно для скриптов и интерфейсов: не нужно перебирать неймспейсы и проверять доступ к каждому.