CorBlogкейсы · ops

Статья

Вход в Kubernetes через Keycloak (OIDC)

Нужен был доступ в Kubernetes для инженеров без раздачи клиентских сертификатов — в тесте поднял Kubespray и OIDC через уже живой Keycloak.

Нужно было дать инженерам и разработчикам доступ в Kubernetes. Постоянно выпускать клиентские сертификаты под каждого — неудобно: ротация, отзыв, раздача файлов. В тестовой среде поднял кластер через Kubespray и повесил вход на уже используемый Keycloak по OIDC: человек логинится в realm, kubectl получает короткий ID token, права режет RBAC.

Что где стоит

Что Где
Keycloak 26.x keycloak.example.com
Realm k8s
Клиент kubernetes, public, Standard flow, PKCE S256
Пользователи подключённый LDAP/AD к realm
Группа в токене developers
Control plane cp-01.example.com

Как это связано

Keycloak только называет пользователя и группы. Что можно делать в кластере, решает RBAC:

Поле ID token Кто это в API
preferred_username пользователь oidc:dev.user
groups: ["developers"] группа oidc:developers
aud клиент kubernetes
iss https://keycloak.example.com/realms/k8s

Группа developers — группа Keycloak, в неё добавлены пользователи из LDAP/AD.

Keycloak

В Realm settings → General → Frontend URL должен быть указан — keycloak.example.com, пример проверки issuer:

curl -s https://keycloak.example.com/realms/k8s/.well-known/openid-configuration | jq .issuer
"https://keycloak.example.com/realms/k8s"

Клиент kubernetes:

  • Client type: OpenID Connect
  • Client authentication: Off
  • Standard flow: On
  • Direct access grants: Off
  • PKCE method: S256
  • Valid redirect URIs:
http://localhost:8000
http://localhost:8000/*
http://127.0.0.1:8000
http://127.0.0.1:8000/*
  • Web origins: http://localhost:8000

Mapper групп: Clients → kubernetes → Client scopes → kubernetes-dedicated → Mappers → Group Membership.

  • Name: groups
  • Token Claim Name: groups
  • Full group path: Off
  • Add to ID token: On
  • Add to access token: On

Проверка до изменений в Kubernetes: Clients → kubernetes → Client scopes → Evaluate. Пользователь из федерации, scope openid. Смотреть Generated ID token.

Ожидаемый токен:

{
  "iss": "https://keycloak.example.com/realms/k8s",
  "aud": "kubernetes",
  "preferred_username": "dev.user",
  "groups": ["developers"]
}

В discovery-документе в claims_supported нет groups. Это нормально: список стандартный, mapper всё равно кладёт claim в токен.

Сертификат

В --oidc-ca-file нужен корневой публичный CA сертификат. На control plane положить сертификат в виде .crt в:

/etc/kubernetes/ssl/keycloak-ca.crt

Тот же CA в base64 лежит в inventory как kube_oidc_ca_cert, чтобы следующий прогон Kubespray положил файл сам. Проверка с control plane:

curl --cacert /etc/kubernetes/ssl/keycloak-ca.crt \
  https://keycloak.example.com/realms/k8s/.well-known/openid-configuration

Kubespray

В — inventory/lab/group_vars/k8s_cluster/k8s-cluster.yml:

kube_oidc_auth: true
kube_oidc_url: https://keycloak.example.com/realms/k8s
kube_oidc_client_id: kubernetes
kube_oidc_ca_file: "{{ kube_cert_dir }}/keycloak-ca.crt"
kube_oidc_ca_cert: base64_root_ca_cert
kube_oidc_username_claim: preferred_username
kube_oidc_username_prefix: "oidc:"
kube_oidc_groups_claim: groups
kube_oidc_groups_prefix: "oidc:"

Запустить — ansible-playbook для применения изменений в кластере:

ansible-playbook -i inventory/lab/hosts.yaml cluster.yml

После смены OIDC-переменных на рабочем кластере нужно выполнить:

kubeadm init phase upload-config all --config /etc/kubernetes/kubeadm-config.yaml
kubeadm init phase control-plane apiserver --config /etc/kubernetes/kubeadm-config.yaml

Kubelet подхватывает манифест и перезапускает static pod. Для проверки можно выполнить:

grep oidc- /etc/kubernetes/manifests/kube-apiserver.yaml

Ожидаемые флаги:

--oidc-issuer-url=https://keycloak.example.com/realms/k8s
--oidc-client-id=kubernetes
--oidc-ca-file=/etc/kubernetes/ssl/keycloak-ca.crt
--oidc-username-claim=preferred_username
--oidc-groups-claim=groups
--oidc-username-prefix=oidc:
--oidc-groups-prefix=oidc:

RBAC

Подготовить пример binding-а:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developers-edit
  namespace: dev
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: oidc:developers

Создать .yaml файл, dev пространство (namespace) и применить:

kubectl apply -f .yaml

Проверка, без логина пользователя:

kubectl auth can-i create deployments -n dev --as=oidc:dev.user --as-group=oidc:developers
kubectl auth can-i get pods -n kube-system --as=oidc:dev.user --as-group=oidc:developers

Первая команда — yes, вторая — no.

Kubeconfig пользователя

Нужен плагин int128/kubelogin: бинарник kubectl-oidc_login, в kubeconfig команда — kubectl, аргумент — oidc-login.

macOS (Homebrew):

brew install int128/kubelogin/kubelogin

Linux (бинарник с Releases):

# amd64, для arm64 нужно использовать kubelogin_linux_arm64.zip
curl -fsSL -o kubelogin.zip \
  https://github.com/int128/kubelogin/releases/latest/download/kubelogin_linux_amd64.zip
unzip -o kubelogin.zip kubelogin
sudo install -m 0755 kubelogin /usr/local/bin/kubectl-oidc_login
rm -f kubelogin.zip kubelogin

Windows (бинарник с Releases):

Скачать kubelogin_windows_amd64.zip с GitHub, из архива взять/переименовать в kubectl-oidc_login.exe и положить в каталог из PATH.

Проверка на любой ОС:

kubectl oidc-login --help

Пример oidc.kubeconfig файла в ~/.kube/:

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: CERT
    server: https://cp-01.example.com:6443
  name: lab
contexts:
- context:
    cluster: lab
    user: oidc
  name: oidc@lab
current-context: oidc@lab
kind: Config
users:
- name: oidc
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - oidc-login
      - get-token
      - --oidc-issuer-url=https://keycloak.example.com/realms/k8s
      - --oidc-client-id=kubernetes
      command: kubectl
      env: null
      interactiveMode: IfAvailable
      provideClusterInfo: false

Проверка:

kubectl --kubeconfig ~/.kube/oidc.kubeconfig auth whoami

Первый запуск открывает браузер для аутентификации в Keycloak.

Успешный — whoami:

ATTRIBUTE                                           VALUE
Username                                            oidc:user
Groups                                              [oidc:developers system:authenticated]
Extra: authentication.kubernetes.io/credential-id   [JTI=e464595d-9250-1213-c46b-b45d69b6fbc7]

Итог

Новый человек в той же группе developers получает тот же oidc.kubeconfig. Отдельный файл для каждого не нужен: личность берётся из логина Keycloak. Keycloak называет человека и группу, RBAC решает, что можно в Kubernetes-кластере.