はじめに
自宅のkubernetes上にデプロイしているkibanaをインターネットに外部公開しようとしましたが、デフォルトの認証ではなく多要素認証にしてみようと考えました。
どうせならアプリ側で都度認証の実装をするのではなく、OpenShiftのようにkubernetes上で汎用的な認証サービスをリバースプロキシで提供しようと考えました。
実装検証も兼ねているので、keycloak+mariadbとリバースプロキシ用のhttpdのpodを作成します。
前提環境
- kubernetes 1.28.1 (aarch64 : Raspberry Pi 4B)
- Synology NAS (Synology CSI Driver for Kubernetes)
- Elasticsearch / Kibana 8.11.1
keycloak + mariadb (pod) の作成
keycloakをSSL化する際の証明書を作成してconfigmapにしておきます。(※お作法としてはsecretの方が良いです)
# kubectl create ns keycloak
# openssl req -x509 -nodes -newkey rsa:4096 -keyout keycloak.key -out keycloak.crt -days 365
# kubectl create configmap keycloak-crt -n keycloak --from-file=keycloak.crt --from-file=keycloak.key
mariadb用のPVCを用意します。永続ストレージはご自宅の環境に合う内容にしてください。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: iscsi-keycloak-pvc
namespace: keycloak
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: synology-sc
(※AGEは無視してね)
# kubectl apply -f iscsi-pvc.yaml
# kubectl get pvc -n keycloak
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
iscsi-keycloak-pvc Bound pvc-5b2f2957-f26f-4d49-bf11-1f7ca292e7ee 10Gi RWO synology-sc 56d
mariadbとkeycloakのコンテナでpodを作成します。分割してマイクロサービスにした方が良いです。
(※自宅でDBはElasticsearchを使っていて、mariadbはkeycloak専用です)
apiVersion: v1
kind: Pod
metadata:
name: keycloak
namespace: keycloak
labels:
app: keycloak
spec:
containers:
- name: mariadb
image: docker.io/mariadb:10.5
env:
- name: MARIADB_DATABASE
value: "kcdb"
- name: MARIADB_ROOT_PASSWORD
value: "rootpass1105"
ports:
- containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
- name: keycloak
image: quay.io/keycloak/keycloak:22.0.5
env:
- name: KC_DB
value: "mariadb"
- name: KC_DB_URL
value: "jdbc:mariadb://localhost:3306/kcdb?characterEncoding=UTF-8"
- name: KC_DB_USERNAME
value: "root"
- name: KC_DB_PASSWORD
value: "rootpass1105"
- name: KEYCLOAK_ADMIN
value: "kcadmin"
- name: KEYCLOAK_ADMIN_PASSWORD
value: "kcadmin0420!"
- name: KC_PROXY
value: "edge"
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_HTTPS_PORT
value: "8443"
- name: KC_HTTPS_CERTIFICATE_FILE
value: "/opt/keycloak/tls/keycloak.crt"
- name: KC_HTTPS_CERTIFICATE_KEY_FILE
value: "/opt/keycloak/tls/keycloak.key"
args: ["start-dev"]
ports:
- name: http
containerPort: 8080
- name: https
containerPort: 8443
volumeMounts:
- name: crtvol
mountPath: /opt/keycloak/tls
volumes:
- name: crtvol
configMap:
name: keycloak-crt
- name: data
persistentVolumeClaim:
claimName: iscsi-keycloak-pvc
バージョンは個人的な理由があり、mariadbは10.5でkeycloakはquay.ioの22.0.5を使います。
mariadb側の設定はpod内部の事なので割愛しますが、keycloakの管理ユーザーは「kcadmin」パスワードは「kcadmin0420!」にして、httpとhttpsの両方でアクセスできるようにしています。
小さい設定ファイルのためにストレージ切ったりコンテナイメージをビルドするのは嫌なので、configmapを活用しています。
# kubectl apply -f keycloak-pod.yaml
# kubectl get pods -n keycloak
NAME READY STATUS RESTARTS AGE
keycloak 2/2 Running 0 56d
keycloakはテキトーにNodePortで公開しておきます。
apiVersion: v1
kind: Service
metadata:
name: keycloak
namespace: keycloak
labels:
app: keycloak
spec:
type: NodePort
ports:
- name: keycloak
port: 8080
targetPort: 8080
nodePort: 30808
- name: keycloaks
port: 8443
targetPort: 8443
nodePort: 30843
selector:
app: keycloak
# kubectl apply -f keycloak-svc.yaml
# kubectl get svc -n keycloak
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
keycloak NodePort 10.96.55.87 <none> 8080:30808/TCP,8443:30843/TCP 56d
keycloak側での設定
keycloakは元々設定項目が多いのですが、バージョンによってメニューや設定項目名が変わっているので、追従するのは大変です。(元々そんなに理解もしていないのに...)
keycloakのサービスにアクセスして「Administration Console」を開きます。
podで設定した管理ユーザー「kcadmin」でログインします。
Realmの作成
左上の「master」(レルム)がプルダウンメニューになっています。プルダウンから「Create Realm」ボタンを押します。
以下の項目を設定して「Create」ボタンを押します。
Realm name : "MyLabo"
Clientsの作成
左のメニューから「Clients」を選択して、「Create client」ボタンを押します。
以下の項目を設定して「Next」ボタンを押します。
Clinet ID : "kibana"
以下の項目を設定して「Next」ボタンを押します。
Client authentication : "On"
以下の項目を設定(リバースプロキシ予定のKibanaのURL)して「Save」ボタンを押します。
Valid redirect URIs : "http://192.168.1.9:31561/*"
(※とりあえず自宅内からアクセスできるURLにしてます。リバースプロキシを外部公開した場合は変更する個所です。)
「Credentials」タブを開いて、Client secretをメモ帳かどこかにコピーしておきます。
もう一つ、左メニュー「Realm settings」の「Endpoints」から「OpenID Endpoint Configuration」のURLをコピーしておきます。
ユーザーの作成
左のメニューから「Users」を選択して、「Add user」ボタンを押します。
ユーザー名は任意で、初回ログイン時にOTPの設定とパスワード再設定するように設定して「Create」ボタンを押します。
Required user actions : "Configure OTP" , "Update Password"
Username : "haomei"
Credentialsタブを開いて、「Set password」ボタンを押します。
ユーザーの初期パスワードを入力して「Save」ボタンを押します。
2FAの設定
左メニューの「Authentication」を選択して「Flows」タブの「browser」をクリックします。
「Browser - Conditional OTP」を「Required」にします。
リバースプロキシ(pod)の作成
httpdのコンテナイメージからカスタムイメージを作成します。
FROM httpd:latest
RUN apt-get update && apt-get install -y \
libapache2-mod-auth-openidc \
apache2-utils \
&& apt-get clean
RUN sed -i \
-e 's/^#\(Include .*httpd-proxy.conf\)/\1/' \
-e 's/^#\(LoadModule .*mod_proxy.so\)/\1/' \
-e 's/^#\(LoadModule .*mod_proxy_http.so\)/\1/' \
conf/httpd.conf
RUN echo "Include conf/extra/proxy.conf" >> conf/httpd.conf
RUN echo "Include conf/extra/openidc.conf" >> conf/httpd.conf
dockerコマンドかpodmanコマンドでbuildして自宅のNodePortで公開しているコンテナレジストリにpushします。
自宅にコンテナレジストリが無い場合はkubernetesの各ノードにコピーしてください。
# podman build -t 192.168.1.9:30500/rproxy:v1.0 .
# podman push --tls-verify=false 192.168.1.9:30500/rproxy:v1.0
httpdの拡張設定(OIDC連携用)のファイルを作成します。
ほぼテンプレですが、以下の項目は今まで設定した内容や確認した内容で埋める必要があります。
OIDCProviderMetadataURL : keycloakの「OpenID Endpoint Configuration」のURL
OIDCClientID : keycloakの「Clinet ID」
OIDCClientSecret : keycloakの「Client secret」
OIDCRedirectURI : keycloakの認証が成功した後に遷移させたいURL
OIDCCryptoPassphrase : 何でも良かったはず...
LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so
OIDCSSLValidateServer Off
OIDCProviderMetadataURL https://192.168.1.9:30843/realms/MyLabo/.well-known/openid-configuration
OIDCClientID kibana
OIDCClientSecret disSUTwote75wC7Ipho2c6JRik9YvB6a
OIDCRedirectURI http://192.168.1.9:31561/login
OIDCCryptoPassphrase hogehoge
<Location />
AuthType openid-connect
Require valid-user
Define ES_USERNAME elastic
Define ES_PASSWORD "password"
Define CREDENTIALS ${ES_USERNAME}:${ES_PASSWORD}
RequestHeader set Authorization "expr=Basic %{base64:${CREDENTIALS}}"
</Location>
次にリバースプロキシの設定ファイルを作成します。
ServerName localhost
ProxyRequests Off
ProxyPass / http://kibana.elastic.svc.cluster.local:5601/
ProxyPassReverse / http://kibana.elastic.svc.cluster.local:5601/
<Location />
ProxyPreserveHost On
Require all granted
</Location>
設定ファイルをconfigmapにしておきます。
# kubectl create configmap extra-httpd-conf -n default --from-file=openidc.conf --from-file=proxy.conf
リバースプロキシのpodを作成します。imageはhttpdのコンテナイメージから作成したカスタムイメージを指定します。
設定ファイルはconfigmapをpod内のディレクトリにmountしているので、deploymentにすれば動的な設定変更の運用ができると思います。
apiVersion: v1
kind: Pod
metadata:
name: rproxy
labels:
app: rproxy
spec:
containers:
- name: httpd
image: localhost:30500/rproxy:v1.0
ports:
- containerPort: 80
volumeMounts:
- name: exconf
mountPath: /usr/local/apache2/conf/extra
volumes:
- name: exconf
configMap:
name: extra-httpd-conf
# kubectl apply -f rproxy-pod.yaml
# kubectl get pods/rproxy
NAME READY STATUS RESTARTS AGE
rproxy 1/1 Running 0 3h44m
nodePortのポート番号は、openidc.conf の OIDCRedirectURI で指定しているポート番号に合わせてください。
また、MFA対応のkibanaもこのポート番号でのログインをする事になります。
apiVersion: v1
kind: Service
metadata:
name: rproxy
labels:
app: rproxy
spec:
type: NodePort
ports:
- name: rproxy
port: 80
targetPort: 80
nodePort: 31561
selector:
app: rproxy
# kubectl apply -f rproxy-svc.yaml
# kubectl get svc/rproxy
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
rproxy NodePort 10.96.6.229 <none> 80:31561/TCP 63d
リバースプロキシはURLベースにした方が便利だと思うのですが、kibana側の対応が必要になるので今回は見送ります。
kibanaへのログイン
リバースプロキシのサービス http://192.168.1.9:31561/ へアクセスすると、keycloakの認証画面に遷移します。keycloakで追加したユーザー名と初期パスワードでログインします。
OTPの初回認証画面になります。スマホにTOTPのアプリをインストールしてカメラでQRコードを読み取るか、ブラウザにTOTPの拡張機能をインストールして同様にQRコードをキャプチャして設定します。
(ここからはもたもたしているとkeycloak側がタイムアウトしてしまいますのでスムーズに!)
EdgeやChromeの場合は、Authenticator: 2FA Clientをインストールします。
拡張機能からQRコードをスキャン(枠で囲む)して設定します。
OTPのコードが表示されるので、クリックしてコピーします。
「One-time code」はコピーしたOTPコードを貼り付けて「Device Name」はテキトーに入力して「Submit」ボタンを押します。
次にパスワードの再設定も促されるので、設定して「Submit」ボタンを押します。
うまく2要素認証でkibanaにログインできました。
おわりに
長い手順でしたが、自宅サービスに2要素認証を取り入れる事ができそうです。
ユーザー追加の部分はkeycloakのAdmin CLIを使えばもっと簡素化できそうですね。