Как стать автором
Обновить
VK
Технологии, которые объединяют

Как предоставить доступ к кластеру Kubernetes с помощью клиентского сертификата: простое руководство

Блог компании VK Облачные вычисления *DevOps *Kubernetes *
Перевод
Автор оригинала: Luc Juggery

Источник

Предположим, мы создали кластер 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 — я проиллюстрирую это ниже.

Некоторые соображения и допущения

  1. Кластер используют несколько команд или клиентов (подход с несколькими пользователями), так что нужно изолировать рабочую нагрузку для каждого клиента. Мы создадим пространство имен для команды разработчиков, в которую входит разработчик, которому надо дать доступ (пусть его зовут Дейв). Это пространство имен мы назовем development.

  2. Дейву предстоит развернуть стандартные ресурсы Kubernetes. Затем он получит право создавать, просматривать, обновлять, получать список и удалять ресурсы Deployment и Service. Дополнительные права можно предоставить при необходимости, но они ограничены пространством имен development.

  3. Скорее всего, членам команды Дейва потребуется такой же уровень доступа. Мы заведем группу dev и предоставим права на уровне группы.

  4. Дейву потребуется kubectl, а также openssl — он сгенерирует закрытый ключ и запрос на вход с сертификатом.

Создание закрытого ключа и запроса на подпись сертификата (CSR)

Сначала Дейв генерирует закрытый ключ RSA и CSR. Закрытый ключ можно создать с помощью команды:

$ openssl genrsa -out dave.key 4096

С CSR немного сложнее, поскольку Дейву нужно убедиться, что он:

  1. использует свое имя в поле Common Name (CN) — оно требуется для идентификации на сервере API;

  2. использует имя группы в поле Organisation (O) — это имя нужно для идентификации группы на сервере API.

Ниже файл конфигурации, который Дейв использует для создания CSR:

[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]
CN = dave
O = dev

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=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/v1beta1
kind: CertificateSigningRequest
metadata:
name: mycsr
spec:
groups:
- system:authenticated
request: ${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 csr
NAME        AGE   REQUESTOR            CONDITION
mycsr       9s    28b93...d73801ee46   Pending

Подтверждаем CSR с помощью команды:

$ kubectl certificate approve mycsr

Еще раз проверяем статус CSR — теперь он одобрен:

$ kubectl get csr
NAME        AGE   REQUESTOR            CONDITION
mycsr       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 -text
Certificate:
Data:
    Version: 3 (0x2)
    Serial Number:
        48:29:cf:ae:d6:...:09:33:ef:14:58
Signature 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: v1
kind: Namespace
metadata:
name: development

Применяем dev-ns.yaml с помощью команды:

$ kubectl apply -f dev-ns.yaml

Примечание рекомендую создать ресурс ResourceQuota и связать его с пространством имен. Это позволит ограничить объем CPU и ОЗУ, которые можно использовать в пространстве имен.

Настройка правил RBAC

С помощью сертификата Дейв может пройти аутентификацию на сервере API. Но пока у него нет прав, так что он не может делать многие вещи. Давайте дадим ему права создавать, получать список, обновлять, просматривать и удалять ресурсы Deployment и Service в пространстве имен dev.

Ресурсы, задействованные в управлении доступом к базе ролей Kubernetes (RBAC)
Ресурсы, задействованные в управлении доступом к базе ролей Kubernetes (RBAC)

Коротко: роль (то же самое справедливо и для ClusterRole) содержит список правил. Каждое правило определяет действия, которые могут быть выполнены (например: list, get, watch) со списком ресурсов (например: Pod, Service, Secret) в apiGroups (например: core, apps/v1). Роль определяет права для конкретного пространства имен, область ClusterRole — весь кластер.

Создание роли

Создадим ресурс Role со следующей спецификацией:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: development
name: dev
rules:
- 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: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dev
namespace: development
subjects:
- kind: User
name: dave
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: dev
apiGroup: rbac.authorization.k8s.io

Эта RoleBinding связывает:

  • субъект — пользователь Дейв;

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

Примечание: поскольку Дейв входит в группу разработчиков, то можно использовать следующую привязку RoleBinding для связи роли с группой, а не отдельным пользователем. Помните: информация о группе указывается в поле Organisation (O) сертификата, его отправляют с каждым запросом.

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dev
namespace: development
subjects:
- kind: Group
name: dev
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: dev
apiGroup: rbac.authorization.k8s.io

Мы сохранили спецификацию ресурса RoleBinding в файле role-binding.yaml и создаем его с помощью команды:

$ kubectl apply -f role-binding.yaml

Создание файла конфигурации KubeConfig

Все настроено. Теперь отправляем Дейву информацию, которая необходима для настройки его локального клиента kubectl для связи с нашим кластером. Сначала создаем файл kubeconfig.tpl со следующим содержанием, его мы будем использовать в качестве шаблона:

apiVersion: v1
kind: Config
clusters:
- 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: dave
name: ${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 version
Client 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 nodes
Error 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.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: www
namespace: development
spec:
replicas: 3
selector:
matchLabels:
    app: www
template:
metadata:
    labels:
    app: www
spec:
    containers:
    - name: nginx
    image: nginx:1.14-alpine
    ports:
    - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: www
namespace: development
spec:
selector:
app: vote
type: ClusterIP
ports:
- port: 80
targetPort: 80

Из результата следующей команды видно, что Дейв может создавать эти ресурсы в кластере:

$ kubectl apply -f www.yaml
deployment.apps/www created
service/www created

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

$ kubectl get pods
Error from server (Forbidden): pods is forbidden: User "dave" cannot list resource "pods" in API group "" in the namespace "default"

Еще он не может создавать другие ресурсы, кроме тех, к которым ему предоставили доступ. Например, мы можем попробовать следующую спецификацию ресурса типа Secret:

# credentials.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
namespace: development
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm

Давайте посмотрим, как Дейв попытается его создать:

$ kubectl apply -f credentials.yaml
Error 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.

Что еще почитать:

  1. 90+ инструментов, без которых не обойтись при использовании Kubernetes.

  2. Основы безопасности внутри Kubernetes: 10 простых советов.

  3. Как устроен Kubernetes as a Service на платформе Mail.ru Cloud Solutions.

Теги:
Хабы:
Всего голосов 23: ↑23 и ↓0 +23
Просмотры 3.5K
Комментарии Комментарии 3

Информация

Дата основания
Местоположение
Россия
Сайт
vk.com
Численность
5 001–10 000 человек
Дата регистрации
Представитель
Миша Буданов