RBAC (Role-based access control) — это система распределения прав доступа к различным объектам в кластере Kubernetes.
Объекты в кластере Kubernetes — это YAML-манифесты, а права доступа определяют, какому пользователю можно только просматривать манифесты, а кто может их создавать, изменять или даже удалять.
Рассказываем, как устроен RBAC.
Материал подготовлен на основе лекции архитектора Southbridge Сергея Бондарева «Продвинутые абстракции Kubernetes» из курса «Kubernetes База».
Пользователи кластера Kubernetes — это все, кто шлёт запросы в API-сервер, не только администраторы и разработчики, но и различные скрипты CI/CD, компоненты control plane, kubelet и kube-proxy на узлах, а также приложения, запущенные в кластере. В рамках модели контроля доступа на основании ролей есть 5 сущностей: Role, RoleBinding, ClusterRole, ClusterRoleBinding, ServceAccount. О них рассказываем в статье.
ServiceAccount
Начнём с конца. В Kubernetes есть самый простой тип пользователей, который называется ServiceAccount. Что же такое сервис аккаунт и чем он отличается от обычных пользователей?
Дело в том, что Kubernetes ничего не знает о пользователях в том виде, в котором мы привыкли их видеть в других системах ограничения доступа, где у пользователей есть логин, пароль, группы и т. д. То есть внутри своих объектов Kubernetes никаких списков логинов, групп и паролей не хранит, но при этом у него есть механизмы вызова внешних сервисов проверки паролей, таких как oidc, есть вариант проверки пользовательского сертификата или даже обычный HTTP Basic Auth с классическим апачевским файликом htpasswd.
ServiceAccount был создан в первую очередь для ограничения прав ПО, которое работает в кластере. Всё общение между компонентами кластера идёт через запросы к API-серверу, и каждый такой запрос как раз авторизуется специальным JWT-токеном. Этот токен автоматически генерируется при создании объекта типа ServiceAccountи кладётся в secret.
В отличие от обычного пользователя, которому мы можем задать произвольный пароль, JWT-токен содержит внутри себя служебную информацию с названием ServiceAccount, Namespace и подписан корневым сертификатом кластера.
Посмотреть содержимое JWT-токена можно, введя его в форму Debugger на сайте https://jwt.io
Далее этот secret монтируется внутрь контейнера, где работает наше приложение, и если приложение знает и умеет в Kubernetes, оно берёт токен из файла /var/run/secrets/kubernetes.io/serviceaccount/token, смонтированного из secret, и с ним делает запросы в API.
Ничего не мешает взять из секрета этот сгенерированный токен и положить его в скрипт CI или отдать разработчику и объяснить, что с этим токеном он может делать запросы в API кластера. Когда разработчиков и CI в кластере немного, такой подход вполне приемлем — он простой и достаточно безопасный.
Перейдём в консоль и посмотрим на манифест ServiceAccount'а:
kubectl get serviceaccount
NAME SECRETS AGE default 1 2y298d Видим ServiceAccount default
Свой сервис аккаунт default есть в каждом неймспейсе, он создаётся автоматически. По умолчанию прав у этого аккаунта на доступ к API нет никаких. Смотрим внутрь:
Видим служебные поля и имя. Имя — это и есть то, что мы указываем при создании ServiceAccount. В поле secret лежит название секрета с токеном. Это поле добавляется контроллером автоматически.
Если посмотреть в secret, увидим там корневой сертификат кластера ca.crt. Он нужен, чтобы клиент был уверен, что он связывается именно с тем кластером, с которым надо. Есть поле namespace — оно показывает, в каком неймспейсе создан сервис аккаунт. И есть токен, с которым собственно и делаются запросы к API. Токен передаётся в заголовках HTTP-запроса.
Вы можете создать свой сервис аккаунт и указать его имя в манифесте пода, тогда при запуске контейнеров пода в них будет смонтирован секрет с токеном от этого сервис аккаунта. Если ServiceAccount не указывать, то будет смонтирован секрет от сервис аккаунта default.
Иногда говорят, что под в кластере Kubernetes работает с правами сервис аккаунта. Это приводит к путанице и проблемам в понимании принципов работы. Под нигде не работает, работает приложение. Приложение запущено в контейнерах пода на воркер-узлах и работает там как обычный Linux-процесс в самоизоляции. Всё!
Единственное, что происходит в кластере Kubernetes — kubelet монтирует токен внутрь контейнеров. И дальше уже приложение решает: если ему нужно обращаться к API кластера, будет ли оно брать токен из этого смонтированного секрета или возьмёт другой токен, например, запросит у пользователя.
Role
С сервис аккаунтами разобрались. Теперь рассмотрим, каким образом сервис аккаунтам выдаются права. Для этого используются ещё две сущности: Role и RoleBinding. Role — это YAML-манифест, который описывает некий набор прав на объекты кластера Kubernetes. Role ничего и никому не разрешает. Это просто список.
Напомню, что объекты кластера — это YAML-манифесты, которые хранятся в etcd. Все права проверяются API-сервером и относятся к запросам, которые принимает API-сервер.
То есть если вы запретили девелоперу выполнять kubectl exec, но у него есть доступ на воркер-ноду, то зайти на воркер-ноду и сделать docker exec в контейнер RBAC запретить никак не сможет.
Идём в кластер и в ns ingress-nginx смотрим на роль: ingess-nginx
kubectl get role -n ingress-nginx ingress-nginx -o yaml
Нас интересует раздел rules — это список правил, описывающих права доступа. В каждом правиле у нас есть три параметра, например:
apiGroups: - extensions - networking.k8s.ioresources: - ingressesverbs: - get - list - watch
apiGroups — описывает API-группу манифеста. Это то, что написано в поле apiVersion: до слеша. Если в apiVersion указана только версия, без группы, например, как в манифесте Pod, то считается, что у этого манифеста так называемая корневая группа (core-group); в роли корневая группа указывается как пустая строка “”.
resourсes — список ресурсов, к которым мы описываем доступ, во множественном числе. Посмотреть список ресурсов в вашем кластере можно командой kubectl api-resources. Также есть подресурсы, описывающие специфические действия, например, подресурс pods/log разрешает просматривать логи контейнеров в поде.
verbs — список действий, которые можно сделать с ресурсами, описанными выше: получить, посмотреть список, следить за изменением, отредактировать, удалить и т.п. О том, что именно хотим сделать с объектом мы, согласно принципам REST, указываем типом HTTP-запроса ( GET, PUT, POST, DELETE) или кодируем в URL, если это какое-нибудь хитрое действие вроде watch или impersonate.
# GET /apis/networking.k8s.io/v1beta1/namespaces/{namespace}/ingresses/{name} - apiGroups: ["extensions", "networking.k8s.io"]resources: ["ingresses"]verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # GET /api/v1/namespaces/{namespace}/pods/{name}/log - apiGroups: [""] # "" indicates the core API groupresources: ["pods", "pods/log"]verbs: ["get", "list"]
Посмотреть на примеры запросов к API-серверу проще всего, запуская команду kubectl get/create/delete с ключом ‘-v 6’.
А ещё есть ResourceNames — c помощью этого поля можно указать название объекта и, например, дать права на изменение не всех configmap, а только одного конкретного configmap с именем ingress-controller-leader.
Повторюсь, это просто список правил, объединённых в одну абстракцию. Никаких прав он никому не выдаёт. Для этих целей есть сущность RoleBinding — своим названием она нам говорит, что занимается назначением роли.
RoleBinding
Сразу смотрим в манифест Rolebinding: kubectl get rolebinding ingress-nginx -n ingress-nginx -o yaml В нём есть два типа полей: roleRef и subjects:
roleRef: apiGroup: rbac.authorization.k8s.iokind: Rolename: ingress-nginx subjects: - kind: ServiceAccountname: ingress-nginxnamespace: ingress-nginx - kind: Username: jane # "name" is case sensitiveapiGroup: rbac.authorization.k8s.io - kind: Groupname: developer # ex: organization in user certificateapiGroup: rbac.authorization.k8s.io
В roleRef указываем права из какой роли будут разрешены. subjects — кому будут разрешены эти права (или назначена эта роль). Если kind: ServiceAccount, то права выдаются сервис аккаунту ingress-nginx в неймспейсе ingress-nginx.
То есть API-сервер кластера при получении HTTP-запроса на действие с объектом кластера увидит в заголовке JWT-токен. Проверит, что токен подписан корневым сертификатом кластера. Возьмет из токена название сервис аккаунта и неймспейс и проверит соответствующие RoleBinding в кластере, чтобы узнать, разрешено ли производить запрошенное действие над объектом из запроса.
Но в списке subjects также есть kind: User и kind: Group. При этом команды `kubectl get user` и `kubectl get group` вернут сообщение, что в кластере нет ресурса такого типа. Что очень странно — ресурса нет, а kind: есть.
Эти kind нужны для того, чтобы описать разрешения для тех запросов, которые были аутентифицированы не через токен от сервис аккаунта, а другим способом. Например, через oidc, когда внешний сервис аутентификации прислал в ответе user и group, соответствующие oidc-токену. Или поля CommonName (CN) и Organization (O) из клиентского сертификата, который был предъявлен при отправке запроса по протоколу HTTPS/TLS.
ClusterRole
Role описывает права в неймспейсе, сущность Role неймспейс-зависимая, и мы можем создавать роли с одинаковым именем в разных неймспейсах.
А Cluster Role — это кластерный объект, эта сущность описывает права на объекты во всём кластере.
В k8s есть много преднастроенных кластерных ролей. В том числе роли admin, edit и view — описывающие права, разрешающие администрирование, редактирование или только просмотр сущностей. Посмотреть роль можно в своём кластере, если у вас есть права администратора, командой
kubectl get clusterrole edit -o yaml
И есть ёще роль кластерного администратора, которая даёт все права на все сущности кластера.
kubectl get clusterrole cluster-admin -o yaml
ClusterRoleBinding
RoleBinding даёт доступ только к тем сущностям, которые находятся в том же неймспейсе, что и манифест Rolebinding. ClusterRoleBinding позволяет выдать доступ к сущностям во всех неймспесах кластера сразу.
К сожалению, механизма, позволяющего выдать доступ к неймспейсам по label или регекспу имени не существует. Или RoleBinding в одном конкретном неймспейсе или ClusterRoleBinding сразу на все неймспейсы кластера.
Подведём итоги
Role в неймспесах привязываем через RoleBinding в неймспейсе — юзер получает права в неймспейсе.
ClusterRole привязываем через ClusterRoleBinding — юзер получает права на весь кластер. Пока логично? А теперь внимательно следим за движением мысли.
ClusterRole можно привязать через RoleBinding в неймспейсе — и юзер получит права в неймспейсе.
Что нам это даёт? Нам нет нужды создавать в каждом неймспейсе одинаковые роли edit, мы можем использовать стандартную кластерную роль edit и назначать ее юзерам, раздавая им доступы в нужные неймспейсы.
На первое время вполне достаточно стандартных ролей edit и view. Чтобы разобраться в том, какие бывают ресурсы и действия с ними, посмотрите встроенные роли и роли в готовых наборах манифестов, например, в Helm Charts.
Много всего описано в документации, но иногда нужно и погуглить. Как правило, все нужные ресурсы/подресурсы уже расписаны в каких-либо специализированных статьях.