尝试使用Keycloak来进行Kubernetes的OIDC认证

首先

通常情况下,Kubernetes并没有用户管理机制,需要借助外部机制来进行用户认证。如果使用kubeadm或minikube等工具构建原生的Kubernetes集群,通常会使用x509证书进行用户认证(admin证书可用于认证)。

在这里,我们将使用Keycloak作为OIDC(OpenID Connect)ID提供者之一来管理用户信息并进行OIDC认证,然后使用该认证信息(id_token)来进行Kubernetes(API)的认证。

Keycloak是一个网站,其网址是https://www.keycloak.org/。

另外,Kubernetes中的OIDC认证顺序可以参考官方文档中如下说明。

pic0.png

OpenID Connect令牌

https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#openid-connect-tokens

前提意味着一个前面的条件或假设。 (The premise implies a previously stated condition or assumption.)

minikube v1.25.2(Kubernetes v1.23.3)のシングルNode構成で検証した

Keycloakは現時点の最新バージョンであるv18.0.0を用いることとし、Kubernetes(minikube)クラスター上に構築した(一般的なKubernetesクラスター上に構築するケースでもそんなに大差はない想定)
検証用なのでKeycloakは以下の条件で起動した

dev mode(本来であればprod modeで起動すべき)

KeycloakのデータストアはInternal DB(H2)を永続化して利用(本来であればPostgreSQLなど外部DBを利用すべき)
証明書は自己証明書を利用した(本来であれば信頼できるCAで署名されたものを利用すべき)

Keycloakの名前解決にはDNSではなくhostsファイルを使用した

Keycloakにはそれぞれkube-apiserverとクライアントが同じホスト名でアクセスできる必要があるため暫定対処
本来はDNSの名前解決でアクセスできるのが望ましい

Keycloakのアクセスエンドポイント(HTTP/HTTPS)はNodePortとした

Keycloakにはそれぞれkube-apiserverとクライアントが同じポートでアクセスできる必要があるため暫定対処
本来はingressやLBに証明書のインポートを行いTLS終端すべき

基本的にKeycloak公式ドキュメントに沿っているが、必要に応じて設定を変更している
Getting started/Kubernetes
https://www.keycloak.org/getting-started/getting-started-kube

3. 创建Keycloak的证书

首先,创建Keycloak将用于TLS的密钥对。
然后,在Kubernetes集群上,通过hostPath将密钥对挂载到部署Keycloak的Pod上,在minikube虚拟机上执行创建操作。

连接到minikube的SSH。

minikube ssh

ディレクトリ作成

sudo su -
mkdir /srv/keycloak-ssl
cd /srv/keycloak-ssl 

创建证书设置文件

sslcert.conf:ssl证书配置文件

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = keycloak 
[v3_req]      
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth, clientAuth   
subjectAltName = @alt_names                 
[alt_names]
DNS.1 = keycloak                            
DNS.2 = keycloak.example.com

创建密钥对

# openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout tls.key -out tls.crt -config sslcert.conf -extensions 'v3_req'

ls
sslcert.conf  tls.crt  tls.key 

可以确定公钥的签发者和主题都是同一个自签名证书(即所谓的自制证书)。

# openssl x509 -in tls.crt --text --noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            53:e8:c5:1e:e4:6c:dd:13:59:f8:4d:81:0d:6f:56:97:06:e3:4f:f3
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = keycloak
        Validity
            Not Before: May  8 06:27:59 2022 GMT
            Not After : May  7 06:27:59 2024 GMT
        Subject: CN = keycloak
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                ・・・

今回KeycloakのPodはhostPathを用いてキーペア格納フォルダをマウントするため権限を変更しておく。
※厳密にはtls.keyにRead権限を付与しておけば良いはず。

# chmod -R 777 /srv/keycloak-ssl 

将公开密钥配置到主节点上。

# mkdir -p /etc/kubernetes/ssl/
# cp tls.crt /etc/kubernetes/ssl/kc-ca.crt
# ls /etc/kubernetes/ssl/
kc-ca.crt

创建Keycloak持久化目录。

当将Keycloak部署为Pod时,通常情况下信息将保存在Keycloak内部的本地数据库(H2)中。然而,当容器重新启动时,这些数据将会丢失。
理想情况下,应该指定外部数据库(如PostgreSQL)作为保存位置。但是,本次我们将数据保存到minikube虚拟机上的目录,并通过将其作为hostPath的volumeMounts挂载到Pod上来解决这个问题。

在minikube虚拟机上创建存储目录。

# mkdir -p /srv/keycloak
# chmod -R 777 /srv/keycloak
# ls -la /srv
total 0
drwxr-xr-x  4 root root  80 May  8 06:46 .
drwxr-xr-x 19 root root 500 May  8 06:24 ..
drwxrwxrwx  2 root root  40 May  8 06:46 keycloak
drwxrwxrwx  2 root root 100 May  8 06:27 keycloak-ssl

5. Keycloak建立

通过具有minikube操作权限的客户端,在Kubernetes集群上将Keycloak部署为Pod。

Keycloak的部署

我使用了一部分Keycloak官方文档作为参考,并修改了一些manifest。然后使用以下manifest进行部署。

Keycloak启动配置的注意事项。

dev modeで起動

HTTPSポートの指定(デフォルト8443)

NodePortでサービスを公開

TLSキーペアの指定と格納先をhostPathとしてマウント

keycloak.yaml 文件

apiVersion: v1
kind: Namespace
metadata:
  name: keycloak
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak
  namespace: keycloak
  labels:
    app: keycloak
spec:
  ports:
  - name: http
    port: 8080
    targetPort: 8080
    nodePort: 31008 # NodePortを明示
  - name: https # HTTPアクセス用に8443ポートを公開
    port: 8443
    targetPort: 8443
    nodePort: 32084 # NodePortを明示
  selector:
    app: keycloak
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  namespace: keycloak
  labels:
    app: keycloak
spec:
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
      - name: keycloak
        image: quay.io/keycloak/keycloak:18.0.0
        args: ["start-dev"] # dev modeで起動
        env:
        - name: KEYCLOAK_ADMIN
          value: "admin"
        - name: KEYCLOAK_ADMIN_PASSWORD
          value: "admin"
        - name: KC_PROXY
          value: "edge"
        - name: "KC_HTTP_ENABLED" # HTTP接続有効化(ブラウザアクセス用)
          value: "true"
        - name: "KC_HTTPS_PORT" # HTTPSポート
          value: "8443"
        - name: KC_HTTPS_CERTIFICATE_FILE # 公開鍵のパス
          value: "/opt/keycloak/tls/tls.crt"
        - name: KC_HTTPS_CERTIFICATE_KEY_FILE # 秘密鍵のパス
          value: "/opt/keycloak/tls/tls.key"
        ports:
        - name: http
          containerPort: 8080
        - name: https # TLS用に8443ポートを公開
          containerPort: 8443
        readinessProbe:
          httpGet:
            path: /realms/master
            port: 8080
        volumeMounts: # 再起動してもデータが揮発しないよう暫定でhostPathにマウント
        - name: "keycloak-persistent-storage"
          mountPath: "/opt/keycloak/data"
        - name: "keycloak-ssl" # キーペア格納先
          mountPath: "/opt/keycloak/tls"
      volumes:
      - name: keycloak-persistent-storage
        hostPath:
          path: /srv/keycloak
          type: DirectoryOrCreate
      - name: keycloak-ssl # 証明書格納先
        hostPath:
          path: /srv/keycloak-ssl     
          type:  DirectoryOrCreate

部署执行

# kubectl apply -f keycloak.yaml
namespace/keycloak created
service/keycloak created
deployment.apps/keycloak created

# kubectl -n keycloak get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/keycloak-679b66bbd8-l2n9g   1/1     Running   0          5m16s

NAME               TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                         AGE
service/keycloak   NodePort   10.102.40.27   <none>        8080:31008/TCP,8443:32084/TCP   5m16s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/keycloak   1/1     1            1           5m16s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/keycloak-679b66bbd8   1         1         1       5m16s

Keycloak名前解決設定

由于这次是验证,请在/etc/hosts文件中添加记录并进行相应的处理。
*在Keycloak的浏览器访问设备和minikube虚拟机上分别进行添加。

确认 minikube 虚拟机的 IP 地址。

# minikube ip
192.168.59.101

/etc/hosts 请将以下内容用中文本地化:

・・・
<minikube VM IP>	keycloak.example.com
・・・

6. Keycloak访问确认

进行对Keycloak的访问确认。

检查curl访问(HTTPS)

使用curl命令从minikube虚拟机验证是否可以进行HTTPS访问。
由于Keycloak使用自签名证书,因此使用公钥对其进行CA签名。
因此,在进行TLS连接时,客户端需要信任Keycloak的CA证书(即公钥),以实现访问。

# curl -v --cacert /etc/kubernetes/ssl/kc-ca.crt https://keycloak.example.com:32084

浏览器访问确认(HTTP)

使用浏览器访问以下网址。
* 由于直接使用浏览器通过HTTPS访问无法显示页面(ERR_SSL_KEY_USAGE_INCOMPATIBLE),故改为使用HTTP访问。

http://keycloak.example.com:31008

7. Keycloak的配置。

通过浏览器访问Keycloak并进行以下配置。

访问管理界面

使用ID/Password=admin登录到管理控制台。

pic2.png

创建领域

在Keycloak中创建与租户概念相对应的Realm。本次创建的Realm名称为”kubernetes”。

pic5.png

创建Clients和ClientScopes。

创建一个客户端并设置相应的客户端范围,作为Keycloak的身份验证接收端。

制作客户端

Client ID: kubernetes

Access Type: confidential

Valid Redirect URIs: http://* https://*

pic9.png

创建客户范围

Client Scope: groups

Mappers: name groupsを追加

pic17.png

向客户端的客户端范围添加groups。

pic20.png

从客户端凭据中获取密钥

pic21.png

创建用户和组

创建用于认证的用户和组。

administrators、developersというグループを作成。
これらのグループに対して後程RBACによりKubernetesクラスターに対する権限を付与する

administrators: Kubernetesクラスターの管理者権限を持つグループ

developers: Kubernetesクラスターに対して特定の権限のみしか持たないグループ

尽管截图只显示了管理员的创建,但也要同样创建开发人员。

pic24.png

创建名为admin-user和dev-user的用户。

admin-user: administratorsグループに所属

dev-user: developersグループに所属

只显示了创建admin-user,但是需要同样创建dev-user。
密码可以设置为任意值(在此设置为P@ssw0rd)。

pic27.png

確認

确认可以通过以下命令在Keycloak上进行认证,并获取id-token和refresh-token。

# curl -k -d "grant_type=password" -d "scope=openid" -d "client_id=kubernetes" -d "client_secret=<client=kubernetesのSecret>" -d "username=<Keycloakで作成したユーザー名>" -d "password=<Keycloakで作成したユーザーパスワード>" https://keycloak.example.com:32084/realms/kubernetes/protocol/openid-connect/token | jq .

・・・
{
  "access_token": ・・・,
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": ・・・
  "id_token": ・・・,
  "not-before-policy": 0,
  "session_state": ・・・,
  "scope": "openid profile email groups"
}

可以使用以下命令对每个令牌进行验证。

exp: token有効期限(UNIX時間)

iat: token発行時間(UNIX時間)

active: tokenの有効有無

# curl -k --user "kubernetes:<client=kubernetesのsecret>" -d "token=<token>" https://keycloak.example.com:32084/realms/kubernetes/protocol/openid-connect/token/introspect | jq .

・・・
{
  "exp": 1651997178,
  "iat": 1651996878,
  ・・・
  "active": true
}

8. Kubernetes的API服务器配置

kube-apiserverがKeycloakを用いてOIDC認証を行うための設定を行う。
minikube VMにアクセスし、kube-apiserverのmanifestに起動OPおよびKeycloakCA証明書(=公開鍵)格納先をvolumeMountsする。
なお、kube-apiserverはStatic Podとして起動しているためmanifest変更後に自動的に再デプロイされる。

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

・・・
spec:
  containers:
  - command:
    - kube-apiserver
    ・・・
    - --oidc-issuer-url=https://keycloak.example.com:32084/realms/kubernetes
    - --oidc-client-id=kubernetes
    - --oidc-username-claim=name
    - --oidc-groups-claim=groups
    - --oidc-ca-file=/etc/kubernetes/ssl/kc-ca.crt
    ・・・
    volumeMounts:
    ・・・
    - mountPath: /etc/kubernetes/ssl
      name: keycloak-ca-certificates
      readOnly: true
  ・・・
  volumes: 
  ・・・
  - hostPath:
      path: /etc/kubernetes/ssl
      type: DirectoryOrCreate
    name: keycloak-ca-certificates
status: {} 

9. 制定RBAC系统

Keycloakで作成したグループに対してRBACの設定を行う。
今回はそれぞれ以下のような権限設定とする。

administrators: クラスターに対する全ての権限を付与(ビルドインのClusterRole=cluster-adminをバインド)

developers: NamespaceとPodの参照権限のみを付与(独自でClusterRole=developer-roleを作成してバインド)

管理员角色配置文件.yaml

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: administrator-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: Group
  name: "administrators"
  apiGroup: rbac.authorization.k8s.io

devrole.yaml-请将devrole.yaml进行释义。

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: developer-role
rules:
  - apiGroups: [""]
    resources: ["namespaces","pods"]
    verbs: ["get", "watch", "list"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: developer-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: developer-role
subjects:
- kind: Group
  name: "developers"
  apiGroup: rbac.authorization.k8s.io

クラスターに適用

# kubectl apply -f adminrole.yaml
clusterrolebinding.rbac.authorization.k8s.io/administrator-crb created

# kubectl apply -f devrole.yaml
clusterrole.rbac.authorization.k8s.io/developer-role created
clusterrolebinding.rbac.authorization.k8s.io/developer-crb created

10.kubectlの設定

kubectlにはOIDC ID プロバイダーにログインする仕組みが用意されていないため、
手動でログインおよびkubeconfigへのtoken設定を行う。

pic28.png

似乎存在一种可以通过kubectl进行登录的插件。kubelogin 可在 GitHub 上找到:https://github.com/int128/kubelogin。

步骤1:登录Keycloak

可以通过以下命令在Keycloak上进行身份验证,获取id-token和refresh-token。

# curl -k -d "grant_type=password" -d "scope=openid" -d "client_id=kubernetes" -d "client_secret=<client=kubernetesのsecret>" -d "username=<Keycloakで作成したユーザー名>" -d "password=<Keycloakで作成したユーザーパスワード>" https://keycloak.example.com:32084/realms/kubernetes/protocol/openid-connect/token | jq .

步骤2:设置kubeconfig配置文件

使用获得的id-token和refresh-token来配置kubeconfig。

# kubectl config set-credentials <Keycloakで作成したユーザー名> \
    "--auth-provider=oidc" \
    "--auth-provider-arg=idp-issuer-url=https://keycloak.example.com:32084/realms/kubernetes" \
    "--auth-provider-arg=client-id=kubernetes" \
    "--auth-provider-arg=idp-certificate-authority=<keycloak公開鍵のパス>" \
    "--auth-provider-arg=client-secret=<client=kubernetesのsecret>" \
    "--auth-provider-arg=id-token=<id-token>" \
    "--auth-provider-arg=refresh-token=<refresh-token>"
    
# kubectl config set-context <Keycloakで作成したユーザー名>@<kubeconfigで定義されているk8sクラスタ名> --cluster=<kubeconfigで定義されているk8sクラスタ名> --user=<Keycloakで作成したユーザー名>

# kubectl config use-context <Keycloakで作成したユーザー名>@<kubeconfigで定義されているk8sクラスタ名>
Switched to context "<Keycloakで作成したユーザー名>@<kubeconfigで定義されているk8sクラスタ名>".

使用shell脚本一次性执行上述操作

#!/bin/bash

scope=openid
client_id=kubernetes
client_secret=<client=kubernetesのsecret>
username=<Keycloakで作成したユーザー名>
password=<Keycloakで作成したユーザーパスワード>
oidc_url=https://keycloak.example.com:32084/realms/kubernetes/protocol/openid-connect/token
realm_url=https://keycloak.example.com:32084/realms/kubernetes
certificate=<keycloak公開鍵のパス>
cluster=<kubeconfigで定義されているk8sクラスタ名>

### Generate Authentication token

json_data=`curl -k -d "grant_type=password" -d "scope=${scope}" -d "client_id=${client_id}" -d "client_secret=${client_secret}" -d "username=${username}" -d "password=${password}" ${oidc_url}`

id_token=`echo $json_data | jq '.id_token' | tr -d '"'`
refresh_token=`echo $json_data | jq '.refresh_token' | tr -d '"'`
access_token=`echo $json_data | jq '.access_token' | tr -d '"'`

### Print tokens

echo "ID_TOKEN=$id_token"; echo
echo "REFRESH_TOKEN=$refresh_token"; echo
echo "ACCESS_TOKEN=$access_token"; echo

### Introspect the id token

token=`curl -k --user "${client_id}:${client_secret}" -d "token=${id_token}" ${oidc_url}/introspect`
token_details=`echo $token | jq .`
echo $token_details

### Update kubectl config

kubectl config set-credentials ${username} \
    "--auth-provider=oidc" \
    "--auth-provider-arg=idp-issuer-url=${realm_url}" \
    "--auth-provider-arg=client-id=${client_id}" \
    "--auth-provider-arg=client-secret=${client_secret}" \
    "--auth-provider-arg=refresh-token=${refresh_token}" \
    "--auth-provider-arg=idp-certificate-authority=${certificate}" \
    "--auth-provider-arg=id-token=${id_token}"

### Create new context

kubectl config set-context ${username}@${cluster} --cluster=${cluster} --user=${username}

### Set current context
kubectl config use-context ${username}@${cluster} 

### Validate access with new context

kubectl get pods

11. 確認行動

使用在Keycloak上创建的用户来验证对Kubernetes集群的访问权限。

管理员 – 用户

对于集群,可以执行所有操作。

# kubectl -n kube-system get all
NAME                                   READY   STATUS    RESTARTS      AGE
pod/coredns-64897985d-dn9c4            1/1     Running   0             125m
pod/etcd-minikube                      1/1     Running   0             125m
pod/kube-apiserver-minikube            1/1     Running   0             15m
pod/kube-controller-manager-minikube   1/1     Running   0             125m
pod/kube-proxy-gbw5v                   1/1     Running   0             125m
pod/kube-scheduler-minikube            1/1     Running   0             125m
pod/storage-provisioner                1/1     Running   4 (15m ago)   125m

NAME               TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
service/kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   125m

NAME                        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/kube-proxy   1         1         1       1            1           kubernetes.io/os=linux   125m

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/coredns   1/1     1            1           125m

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/coredns-64897985d   1         1         1       125m

亲爱的用户

参照未经许可的资源会导致错误。

# kubectl -n kube-system get all
NAME                               READY   STATUS    RESTARTS      AGE
coredns-64897985d-dn9c4            1/1     Running   0             128m
etcd-minikube                      1/1     Running   0             128m
kube-apiserver-minikube            1/1     Running   0             17m
kube-controller-manager-minikube   1/1     Running   0             128m
kube-proxy-gbw5v                   1/1     Running   0             128m
kube-scheduler-minikube            1/1     Running   0             128m
storage-provisioner                1/1     Running   4 (18m ago)   128m
Error from server (Forbidden): replicationcontrollers is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "replicationcontrollers" in API group "" in the namespace "kube-system"
Error from server (Forbidden): services is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "services" in API group "" in the namespace "kube-system"
Error from server (Forbidden): daemonsets.apps is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "daemonsets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): deployments.apps is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "deployments" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): replicasets.apps is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "replicasets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): statefulsets.apps is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "statefulsets" in API group "apps" in the namespace "kube-system"
Error from server (Forbidden): horizontalpodautoscalers.autoscaling is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "horizontalpodautoscalers" in API group "autoscaling" in the namespace "kube-system"
Error from server (Forbidden): cronjobs.batch is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "cronjobs" in API group "batch" in the namespace "kube-system"
Error from server (Forbidden): jobs.batch is forbidden: User "https://keycloak.example.com:32084/realms/kubernetes#user dev" cannot list resource "jobs" in API group "batch" in the namespace "kube-system"

继续

使用Keycloak的OIDC身份验证在Kubernetes上进行租户控制。

请阅读以下内容。

Keycloak/入门/Kubernetes
https://www.keycloak.org/入门/入门-kube

Kubernetes/身份验证
https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#openid-connect-tokens

如何在Kubernetes中使用Keycloak OIDC提供者对用户进行身份验证

How to authenticate user with Keycloak OIDC Provider in Kubernetes


广告

我在使用Twitter。
如果你能关注我,我会很高兴。

@mochizuki875
我希望成为一名专业的IT工程师,我喜欢Kubernetes。
一只狐狸,而不是狸猫。

广告
将在 10 秒后关闭
bannerAds