Предположим, мы создали кластер Kubernetes. И кто-то из команды разработчиков хочет развернуть и протестировать на нем новое приложение. Как нам предоставить ему доступ в кластер?
Команда Kubernetes aaS Mail.ru Cloud Solutions перевела простое руководство по предоставлению доступа к новому кластеру Kubernetes, включая настройку аутентификации и привязку ролей. Автор показывает процесс, используя клиентский сертификат x509.
Управление пользователями в Kubernetes
Для управления кластером Kubernetes и запущенными в нем приложениями обычно используют утилиту kubectl или веб-интерфейс. Под капотом эти инструменты вызывают API Server: HTTP Rest API, открывающий конечные точки управления кластером. Этот HTTP API хорошо документирован посмотрите сами.
После отправки запроса на сервер API он проходит сначала аутентификацию, а затем авторизацию. Аутентификация позволяет убедиться, что запрашивающий известен системе, авторизация что отправителю запроса разрешено выполнить конкретное действие.
Аутентификацию выполняют с помощью плагинов, есть плагины с разными механизмами:
-
сертификаты клиентов о них в этой статье;
-
Bearer tokens (персональные токены);
-
аутентифицирующий прокси;
-
базовая аутентификация HTTP.
В зависимости от механизма аутентификации плагин ищет информацию о пользователе в определенных местах. Например, для аутентификации по сертификату клиента идентификацию пользователя (идентификатор, имя, адрес электронной почты и так далее) указывают в поле Common Name (CN) сертификата. Информацию о группе, если она есть, добавляют в поле Organisation (O).
Внутри кластера Kubernetes нет ни ресурсов пользователей, ни ресурсов групп. Их обрабатывают вне кластера и предоставляют с каждым запросом, который направляют на сервер API я проиллюстрирую это ниже.
Некоторые соображения и допущения
-
Кластер используют несколько команд или клиентов (подход с несколькими пользователями), так что нужно изолировать рабочую нагрузку для каждого клиента. Мы создадим пространство имен для команды разработчиков, в которую входит разработчик, которому надо дать доступ (пусть его зовут Дейв). Это пространство имен мы назовем development.
-
Дейву предстоит развернуть стандартные ресурсы Kubernetes. Затем он получит право создавать, просматривать, обновлять, получать список и удалять ресурсы Deployment и Service. Дополнительные права можно предоставить при необходимости, но они ограничены пространством имен development.
-
Скорее всего, членам команды Дейва потребуется такой же уровень доступа. Мы заведем группу dev и предоставим права на уровне группы.
-
Дейву потребуется kubectl, а также openssl он сгенерирует закрытый ключ и запрос на вход с сертификатом.
Создание закрытого ключа и запроса на подпись сертификата (CSR)
Сначала Дейв генерирует закрытый ключ RSA и CSR. Закрытый ключ можно создать с помощью команды:
$ openssl genrsa -out dave.key 4096
С CSR немного сложнее, поскольку Дейву нужно убедиться, что он:
-
использует свое имя в поле Common Name (CN) оно требуется для идентификации на сервере API;
-
использует имя группы в поле Organisation (O) это имя нужно для идентификации группы на сервере API.
Ниже файл конфигурации, который Дейв использует для создания CSR:
[ req ]default_bits = 2048prompt = nodefault_md = sha256distinguished_name = dn[ dn ]CN = daveO = dev[ v3_ext ]authorityKeyIdentifier=keyid,issuer:alwaysbasicConstraints=CA:FALSEkeyUsage=keyEncipherment,dataEnciphermentextendedKeyUsage=serverAuth,clientAuth
Примечание: запись clientAuth в поле extendedKeyUsage нужна, поскольку сертификат будут использовать для идентификации клиента.
С помощью указанного файла конфигурации, сохраненного в csr.cnf, CSR можно создать одной командой:
$ openssl req -config ./csr.cnf -new -key dave.key -nodes -out dave.csr
Создав файл .csr, Дейв отправляет его администраторам, чтобы они подписали его с помощью центра сертификации кластера.
Подписание CSR
После подписания файла .csr выпускается сертификат. Он будет использоваться для аутентификации запросов, который Дейв отправит на сервер API.
Начнем с создания ресурса Kubernetes Certificate Signing Request.
Примечание мы могли создать управляемый кластер (например, в DigitalOcean, Google GKE, Microsoft Azure, Mail.ru Cloud Solutions или другой платформе) или собственный (допустим, kubeadm или kubespray). Процесс подписи везде устроен одинаково.
Мы используем следующую спецификацию и сохраняем ее в csr.yaml:
apiVersion: certificates.k8s.io/v1beta1kind: CertificateSigningRequestmetadata:name: mycsrspec:groups:- system:authenticatedrequest: ${BASE64_CSR}usages:- digital signature- key encipherment- server auth- client auth
Значение ключа request содержимое переменной окружения BASE64_CSR. Первый шаг получить кодированный в base64 файл .csr, созданный Дейвом. Затем использовать envsubst, чтобы заменить значения этой переменной перед созданием ресурса.
# Кодируем файл .csr в base64$ export BASE64_CSR=$(cat ./dave.csr | base64 | tr -d '\n')# Подставляем переменную env BASE64_CSR и создаем ресурс CertificateSigninRequest$ cat csr.yaml | envsubst | kubectl apply -f -
Проверяем статус созданного CSR мы видим, что он находится в состоянии ожидания:
# Проверяем статус созданного CSR$ kubectl get csrNAME AGE REQUESTOR CONDITIONmycsr 9s 28b93...d73801ee46 Pending
Подтверждаем CSR с помощью команды:
$ kubectl certificate approve mycsr
Еще раз проверяем статус CSR теперь он одобрен:
$ kubectl get csrNAME AGE REQUESTOR CONDITIONmycsr 9s 28b93...d73801ee46 Approved,Issued
Сертификат создан, теперь извлечем его из ресурса CSR, сохраним в файле с именем dave.crt и проверим, что внутри:
$ kubectl get csr mycsr -o jsonpath='{.status.certificate}' \| base64 --decode > dave.crt
Следующая команда openssl показывает: сертификат подписан центром сертификации кластера DigitalOcean (часть Issuer). Subject содержит dave в полях CN (CommonName) и O (Organisation), как указал Дейв при создании файла .csr:
$ openssl x509 -in ./dave.crt -noout -textCertificate:Data: Version: 3 (0x2) Serial Number: 48:29:cf:ae:d6:...:09:33:ef:14:58Signature Algorithm: sha256WithRSAEncryption Issuer: O=DigitalOcean, CN=k8saas Cluster CA Validity Not Before: Jun 3 07:56:00 2019 GMT Not After : Jun 2 07:56:00 2020 GMT Subject: O=dev, CN=dave Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (4096 bit) Modulus:...
Примечание в примере используем управляемый кластер Kubernetes, созданный в DigitalOcean мы видим это в Issuer кластера. На другой платформе будет похоже.
Создание пространства имен
Начинаем с создания пространства имен development благодаря этому ресурсы, которые развернут Дейв и его команда, будут изолированы от остальной рабочей нагрузки кластера.
Его можно создать с помощью простой команды:
$ kubectl create ns development
или с помощью файла dev-ns.yaml:
apiVersion: v1kind: Namespacemetadata:name: development
Применяем dev-ns.yaml с помощью команды:
$ kubectl apply -f dev-ns.yaml
Примечание рекомендую создать ресурс ResourceQuota и связать его с пространством имен. Это позволит ограничить объем CPU и ОЗУ, которые можно использовать в пространстве имен.
Настройка правил RBAC
С помощью сертификата Дейв может пройти аутентификацию на сервере API. Но пока у него нет прав, так что он не может делать многие вещи. Давайте дадим ему права создавать, получать список, обновлять, просматривать и удалять ресурсы Deployment и Service в пространстве имен dev.
Ресурсы, задействованные в управлении доступом к базе ролей Kubernetes (RBAC)Коротко: роль (то же самое справедливо и для ClusterRole) содержит список правил. Каждое правило определяет действия, которые могут быть выполнены (например: list, get, watch) со списком ресурсов (например: Pod, Service, Secret) в apiGroups (например: core, apps/v1). Роль определяет права для конкретного пространства имен, область ClusterRole весь кластер.
Создание роли
Создадим ресурс Role со следующей спецификацией:
kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata:namespace: developmentname: devrules:- apiGroups: [""]resources: ["pods", "services"]verbs: ["create", "get", "update", "list", "delete"]- apiGroups: ["apps"]resources: ["deployments"]verbs: ["create", "get", "update", "list", "delete"]
Ресурсы подов и служб принадлежат основной группе API (значение ключа apiGroups пустая строка), а ресурсы развертывания группе API приложений. Для этих двух групп apiGroup мы определили список ресурсов и действия, которые нужно авторизовать на этих ресурсах.
Строки сохраняем в файл role.yaml, для создания роли используем команду:
$ kubectl apply -f role.yaml
Создание RoleBinding
Назначение RoleBinding связать роль, то есть список разрешенных действий, с пользователем или группой. Чтобы у Дейва были права, указанные в созданной выше роли, мы привязываем его к этой роли. Для этого используем ресурс RoleBinding:
kind: RoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: devnamespace: developmentsubjects:- kind: Username: daveapiGroup: rbac.authorization.k8s.ioroleRef:kind: Rolename: devapiGroup: rbac.authorization.k8s.io
Эта RoleBinding связывает:
-
субъект пользователь Дейв;
-
роль: с именем dev, которая позволяет создавать, просматривать, обновлять, получать список, удалять ресурсы Deployment и Service.
Примечание: поскольку Дейв входит в группу разработчиков, то можно использовать следующую привязку RoleBinding для связи роли с группой, а не отдельным пользователем. Помните: информация о группе указывается в поле Organisation (O) сертификата, его отправляют с каждым запросом.
kind: RoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: devnamespace: developmentsubjects:- kind: Groupname: devapiGroup: rbac.authorization.k8s.ioroleRef:kind: Rolename: devapiGroup: rbac.authorization.k8s.io
Мы сохранили спецификацию ресурса RoleBinding в файле role-binding.yaml и создаем его с помощью команды:
$ kubectl apply -f role-binding.yaml
Создание файла конфигурации KubeConfig
Все настроено. Теперь отправляем Дейву информацию, которая необходима для настройки его локального клиента kubectl для связи с нашим кластером. Сначала создаем файл kubeconfig.tpl со следующим содержанием, его мы будем использовать в качестве шаблона:
apiVersion: v1kind: Configclusters:- cluster:certificate-authority-data: ${CLUSTER_CA}server: ${CLUSTER_ENDPOINT}name: ${CLUSTER_NAME}users:- name: ${USER}user:client-certificate-data: ${CLIENT_CERTIFICATE_DATA}contexts:- context:cluster: ${CLUSTER_NAME}user: davename: ${USER}-${CLUSTER_NAME}current-context: ${USER}-${CLUSTER_NAME}
Чтобы создать kubeconfig из этого шаблона, нужно сначала установить переменные среды:
# Имя пользователя$ export USER="dave"# Имя кластера (полученное из текущего контекста)$ export CLUSTER_NAME=$(kubectl config view --minify -o jsonpath={.current-context})# Сертификат клиента$ export CLIENT_CERTIFICATE_DATA=$(kubectl get csr mycsr -o jsonpath='{.status.certificate}')# Данные центра сертификации кластера$ export CLUSTER_CA=$(kubectl config view --raw -o json | jq -r '.clusters[] | select(.name == "'$(kubectl config current-context)'") | .cluster."certificate-authority-data"')# Точка входа API$ export CLUSTER_ENDPOINT=$(kubectl config view --raw -o json | jq -r '.clusters[] | select(.name == "'$(kubectl config current-context)'") | .cluster."server"')
Подставляем их, используя удобную утилиту envsubst:
$ cat kubeconfig.tpl | envsubst > kubeconfig
Отправляем Дейву файл kubeconfig. Чтобы взаимодействовать с кластером, ему достаточно добавить в файл свой закрытый ключ.
Использование контекста
Чтобы использовать kubeconfig, Дейв устанавливает переменную среды KUBECONFIG, указав путь к файлу.
$ export KUBECONFIG=$PWD/kubeconfig
Примечание: есть разные способы использовать конфигурации Kubernetes. Можно установить переменную среду KUBECONFIG, добавить новую запись в файл $ HOME/.kube/config по умолчанию или использовать флаг --kubeconfig для каждой команды kubectl.
Чтобы добавить закрытый ключ dave.key, Дейв использует команду:
$ kubectl config set-credentials dave \--client-key=$PWD/dave.key \--embed-certs=true
Команда создает ключ client-key-data в записи пользователя файла kubeconfig и устанавливает dave.key в кодировку base64 в качестве значения.
Если все успешно, Дейв может проверить версию сервера (и клиента) с помощью команды:
$ kubectl versionClient Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.2", GitCommit:"66049e3b21efe110454d67df4fa62b08ea79a19b", GitTreeState:"clean", BuildDate:"2019-05-16T16:23:09Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"darwin/amd64"}Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.2", GitCommit:"66049e3b21efe110454d67df4fa62b08ea79a19b", GitTreeState:"clean", BuildDate:"2019-05-16T16:14:56Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}
Теперь проверим, позволяет ли связанная с Дейвом текущая роль отображать узлы кластера:
$ kubectl get nodesError from server (Forbidden): nodes is forbidden: User "dave" cannot list resource "nodes" in API group "" at the cluster scope
Конечно, нет! Но Дейв может что-то развертывать в кластере по крайней мере, в пространстве имен development. Давайте проверим это с помощью YAML-файла, который определяет Deployment на основе образа nginx и Service для его предоставления:
# www.yamlapiVersion: apps/v1kind: Deploymentmetadata:name: wwwnamespace: developmentspec:replicas: 3selector:matchLabels: app: wwwtemplate:metadata: labels: app: wwwspec: containers: - name: nginx image: nginx:1.14-alpine ports: - containerPort: 80---apiVersion: v1kind: Servicemetadata:name: wwwnamespace: developmentspec:selector:app: votetype: ClusterIPports:- port: 80targetPort: 80
Из результата следующей команды видно, что Дейв может создавать эти ресурсы в кластере:
$ kubectl apply -f www.yamldeployment.apps/www createdservice/www created
Дейв ограничен пространством имен development. Если он попытается получить список всех подов в пространстве имен по умолчанию, то получит сообщение об ошибке:
$ kubectl get podsError from server (Forbidden): pods is forbidden: User "dave" cannot list resource "pods" in API group "" in the namespace "default"
Еще он не может создавать другие ресурсы, кроме тех, к которым ему предоставили доступ. Например, мы можем попробовать следующую спецификацию ресурса типа Secret:
# credentials.yamlapiVersion: v1kind: Secretmetadata:name: mysecretnamespace: developmentdata:username: YWRtaW4=password: MWYyZDFlMmU2N2Rm
Давайте посмотрим, как Дейв попытается его создать:
$ kubectl apply -f credentials.yamlError from server (Forbidden): error when retrieving current configuration of:Resource: "/v1, Resource=secrets", GroupVersionKind: "/v1, Kind=Secret"Name: "mysecret", Namespace: "development"Object: &{map["apiVersion":"v1" "data":map["password":"MWYyZDFlMmU2N2Rm" "username":"YWRtaW4="] "kind":"Secret" "metadata":map["annotations":map["kubectl.kubernetes.io/last-applied-configuration":""] "name":"mysecret" "namespace":"development"]]}from server for: "credentials.yaml": secrets "mysecret" is forbidden: User "dave" cannot get resource "secrets" in API group "" in the namespace "development"
Заключение
Мы показали, как использовать сертификат клиента для авторизации пользователей в кластере Kubernetes. Можно настраивать аутентификацию другим способом, но этот довольно прост.
После настройки аутентификации мы использовали роль, чтобы определить некоторые права, ограниченные пространством имен, и привязать их к пользователю с помощью RoleBinding. Если нам нужно будет предоставить права для всего кластера, мы сможем использовать ресурсы ClusterRole и ClusterRoleBinding.
Что еще почитать: