1.はじめに
一般的にKubernetes
はユーザー管理の仕組みを有しておらず何かしら外部の仕組みを用いてユーザーの認証を行うことになり、kubeadm
やminikube
などによりバニラなKubernetes
クラスターを構築した場合、通常はx509証明書
(adminの証明書が払い出される)によりユーザー認証を行うことになります。
ここではOIDC(OpenID Connect)IDプロバイダー
の1つであるKeycloak
でユーザー情報の管理およびOIDC認証
を行い、その認証情報(id_token
)を用いてKubernetes(API)
での認証を行うことを目指します。
Keycloak
https://www.keycloak.org/
なお、Kubernetes
におけるOIDC
認証のシーケンスは公式ドキュメントで示されている以下が参考になります。
OpenID Connect Tokens
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens
2.前提
-
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
クラスター上にデプロイするPod
にhostPath
でマウントするためminikube VM
上で作成を行う。
minikube
にSSH
接続する。
minikube ssh
ディレクトリ作成
sudo su -
mkdir /srv/keycloak-ssl
cd /srv/keycloak-ssl
証明書設定ファイルの作成
sslcert.conf
[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
公開鍵の内容確認
Issuer: CN
とSubject: CN
が同じ自己証明書
(所謂オレオレ証明書)になっていることがわかる。
# 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
Master Node
への公開鍵配置
# mkdir -p /etc/kubernetes/ssl/
# cp tls.crt /etc/kubernetes/ssl/kc-ca.crt
# ls /etc/kubernetes/ssl/
kc-ca.crt
4.Keycloak永続化用ディレクトリの作成
Keycloak
をPod
としてデプロイする際、通常はKeycloak
内部のローカルDB(H2
)に情報が保存され、コンテナが再起動するとそれらのデータは揮発してしまう。
本来は外部DB(PostgreSQL
など)を保存先として指定すべきだが、今回はデータ保存先ディレクトリをminikube VM
上のディレクトリとし、Pod
にhostPath
としてvolumeMounts
することで対応した。
minikube VM
上で保存先ディレクトリを作成する。
# 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 VM
上それぞれで追加
minikube VM
のIPアドレス確認
# minikube ip
192.168.59.101
/etc/hosts
・・・
<minikube VM IP> keycloak.example.com
・・・
6.Keycloakアクセス確認
Keycloak
へのアクセス確認を行う。
curlアクセス確認(HTTPS)
minikube VM
からcurl
コマンドで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
でAdministration Console
にログイン。
Realmの作成
Keycloak
においてテナントの概念に該当するRealm
を作成する。
今回はkubernetes
という名前で作成した。
ClientsとClientScopesの作成
Keycloak
の認証受口になるClient
とその設定に該当するClient Scope
を作成する。
Client
の作成
-
Client ID
:kubernetes
-
Access Type
:confidential
-
Valid Redirect URIs
:http://*
https://*
Client Scope
の作成
-
Client Scope
:groups
-
Mappers
:name
groups
を追加
-
Client
のClient Scopes
にgroups
を追加
Client
のCredentials
からSecret
取得
ユーザーとグループの作成
認証に用いるユーザーとグループを作成する。
administrators
、developers
というグループを作成。
これらのグループに対して後程RBAC
によりKubernetes
クラスターに対する権限を付与する
-
administrators
:Kubernetes
クラスターの管理者権限を持つグループ -
developers
:Kubernetes
クラスターに対して特定の権限のみしか持たないグループ
※キャプチャではadministrators
の作成しか示していないがdevelopers
についても同様に作成する。
admin-user
、dev-user
というユーザーをそれぞれ作成。
-
admin-user
:administrators
グループに所属 -
dev-user
:developers
グループに所属
※キャプチャではadmin-user
の作成しか示していないがdev-user
についても同様に作成する。
※パスワードは任意の値を設定(ここではP@ssw0rd
という値を設定した)
確認
以下コマンドにより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"
}
以下コマンドにより各token
の検証が行える。
-
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.kube-apiserverの設定
kube-apiserver
がKeycloak
を用いてOIDC認証
を行うための設定を行う。
minikube VM
にアクセスし、kube-apiserver
のmanifest
に起動OPおよびKeycloak
CA証明書(=公開鍵)格納先を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
を作成してバインド)
adminrole.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
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
設定を行う。
kubectl
からログインを行うためのプラグインも存在する模様
kubelogin
https://github.com/int128/kubelogin
手順①: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 .
手順②: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
クラスターへのアクセスを確認する。
admin-user
クラスターに対して全ての操作が可能
# 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
dev-user
許可されていないリソースの参照はエラーになる。
# 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/Getting started/Kubernetes
https://www.keycloak.org/getting-started/getting-started-kube
Kubernetes/Authenticating
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens
How to authenticate user with Keycloak OIDC Provider in Kubernetes
https://middlewaretechnologies.in/2022/01/how-to-authenticate-user-with-keycloak-oidc-provider-in-kubernetes.html
宣伝
Twitterやってます。
よかったらフォローいただけると嬉しいです。
@mochizuki875
プロになりたいITエンジニア Kubernetesが好き
a fox, not a raccoon dog.
https://twitter.com/mochizuki875