はじめに
自前のKubernetes(以下、K8s)クラスターではDex(github.com/dexidp/dex)を、OIDCのIdPとして利用しています。
現状はLDAPをバックエンドとして、ユーザー本人と所属グループを識別することで細かな権限管理を実現しています。
これまで設定は静的に設定ファイル中で実施してきましたが、gRPCを利用してAPIによる動的な管理を行うことにしました。
gRPCを本格的に使うのは始めてなので、少し不安はありますが、とりあえずPostgreSQLをバックエンドとしてDexを構成できるように準備を進めていきます。
参考資料
資料はそれほど多くはりません。特にgRPCについてはapi.protoファイルを直接確認する必要がありました。
gRPCを利用してAPIにアクセスするサンプルコードは"The Dex API"のドキュメント以外にも、githubのリポジトリにも含まれています。
テスト環境の構築
まず空いているk8sクラスターを利用して、PostgreSQLとdexを稼動させていきます。
K8sクラスターはあるものとして、PostgreSQL-Operatorはzalandoのoperatorを利用しています。
PostgreSQLは次のような設定で構成しています。
---
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
labels:
team: dex-team
name: pgcluster
namespace: dex
spec:
allowedSourceRanges: []
databases:
dexdb: pguser
numberOfInstances: 2
postgresql:
version: '16'
resources:
limits:
cpu: 500m
memory: 1500Mi
requests:
cpu: 100m
memory: 100Mi
teamId: dex-team
users:
pguser:
- superuser
- createdb
volume:
size: 50Gi
storageClass: rook-ceph-block
パスワードは動的に構成されるのでSecretオブジェクトから自動的に表示するようshell関数を定義しています。
$ type passwd-pguser
passwd-pguser is a function
passwd-pguser ()
{
sudo kubectl -n dex get secret pguser.pgcluster.credentials.postgresql.acid.zalan.do -o jsonpath='{.data.password}' | base64 -d;
echo ""
}
次にDexの設定をConfigMapに準備します。
これまではClientIDなどを登録するのにも利用していましたが、今回はSQLite3に代えてPostgreSQLへの変更とgRPC関連の設定を新たに追加しています。
なおPostgreSQLへの接続はOperatorが自動的にTLSを有効化しているので、mode: require
である必要があります。
LDAPはPosixAccountを利用しているオーソドックスな構成ですが、ADなどとは違うので環境によって変更してください。
apiVersion: v1
kind: ConfigMap
metadata:
name: dex-conf
namespace: dex
data:
config-ldap.yaml: |
issuer: "https://example.com/dex"
storage:
type: postgres
config:
host: pgcluster
port: 5432
database: dexdb
user: pguser
password: BKL9RfMpCZFDC27GX63PnQ34ZL0gctBWUj3q5CpgYfvX6NHsecqx5FRFO7uDTuvD
ssl:
mode: require
web:
http: 0.0.0.0:8080
grpc:
addr: 0.0.0.0:8088
tlsCert: /tls/tls.crt
tlsKey: /tls/tls.key
# tlsClientCA: /tls-ca/ca.crt
reflection: true
logger:
level: "debug"
format: "text"
connectors:
- type: ldap
name: OpenLDAP
id: ldap
config:
host: ldap.example.com:636
# No TLS for this setup.
insecureNoSSL: false
usernamePrompt: LDAP-ID
userSearch:
baseDN: ou=People,ou=Proxy,dc=example,dc=com
filter: "(objectClass=posixAccount)"
username: uid
# "DN" (case sensitive) is a special attribute name. It indicates that
# this value should be taken from the entity's DN not an attribute on
# the entity.
idAttr: DN
emailAttr: mail
nameAttr: gecos
preferredUsernameAttr: uid
groupSearch:
baseDN: ou=MailGroup,ou=Proxy,dc=example,dc=com
filter: "(objectClass=groupOfNames)"
# A user is a member of a group when their DN matches
# the value of a "member" attribute on the group entity.
userAttr: DN
groupAttr: member
# The group name should be the "cn" value.
nameAttr: cn
ここで設定しているgRPCのTLS接続用の証明書ファイルなどはコマンドラインから設定しています。
TLS証明書の準備については別の記事を参照してください。
上記記事中に記載していますが、easyrsaコマンドの実行時には--san="DNS:..."
オプションが必須です。
必要なファイルをconf/ディレクトリに配置してから次のようにkubectlコマンドを実行しました。
$ sudo kubectl -n dex create secret tls grpc-tls --cert=./conf/tls.crt --key=./conf/tls.nopass.key
# $ sudo kubectl -n dex create secret generic grpc-tls-ca --from-file=./conf/ca.crt
ca.crtはクライアント認証を行う場合に必要(# tlsClientCA: /tls-ca/ca.crt
のコメントを外す事)です。
今回のテーマはPostgreSQLをバックエンドにしたdexの設定方法にあるので、この02.cm-dexconf.yaml
の内容までで、説明は終っています。
この他の設定について
この他の準備としてIngressから接続するためのHTTP接続用のServiceオブジェクトも定義しておきます。
---
apiVersion: v1
kind: Service
metadata:
namespace: dex
name: issuer
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: issuer
いよいよDeploymentオブジェクトを配置していきますが、まだdex-serverコンテナは公開設定になっていないので、bin/dex serve /config/config-ldap.yaml
を実行するようなrun.shを配置すれば動作します。
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: dex
name: issuer
labels:
app: issuer
spec:
replicas: 2
selector:
matchLabels:
app: issuer
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: issuer
spec:
imagePullSecrets:
- name: regcred
containers:
- name: webapi
image: docker.io/yasuhiroabe/dex-server:latest
ports:
- containerPort: 8080
name: issuer
volumeMounts:
- name: config
readOnly: false
mountPath: /config
- name: grpc-certs
readOnly: true
mountPath: /tls
- name: grpc-cacert
readOnly: true
mountPath: /tls-ca
volumes:
- name: config
configMap:
name: dex-conf
items:
- key: config-ldap.yaml
path: config-ldap.yaml
- name: grpc-certs
secret:
secretName: grpc-tls
- name: grpc-cacert
secret:
secretName: grpc-tls-ca
最後にgRPC用のYAMLファイルを展開します。
---
apiVersion: v1
kind: Service
metadata:
namespace: dex
name: grpc
spec:
type: LoadBalancer
ports:
- port: 80
protocol: TCP
targetPort: 8088
selector:
app: issuer
LoadBalancerを指定していますが、最終的には設定用のアプリケーションを作成してnamespace:dex内部で動作させるつもりなので、将来的にはClusterIPになります。
稼動確認
問題はgRPCを経由して設定するための実施例がそれほど多くないという点です。
公式ガイドに掲載されているコードは例だとしても、注意が必要です。
- 一度クライアントID等を登録をすると自動的には上書きされないので、変更するにはまず消すための機能を実装する必要がある
- TLS証明書を準備する必要がある (Common Nameは無視されるので、subjectAltNameを適切に指定しなければいけない)
- gPRCのdex側設定例では
tlsClientCA
を有効化しているので、クライアントコードではTLSの設定が省略されている
例えば既存のdex-client
と設定されたClientSecretやRedirectURLsを削除するには次のようなコードを追加しています。
delete_req := &api.DeleteClientReq{
Id: "dex-client",
}
if resp, err := client.DeleteClient(context.TODO(), delete_req); err != nil {
log.Warnf("failed deleting existing dex-client setting: %v", err)
} else {
log.Printf("client deleted successfully: %v", resp)
}
ClientTLSに対応するためのコード例はdexのソースコードのexamplesディレクトリ内では記載があるので、そちらを参照すれば解決すると思います。
テスト環境を作ろとした時に、比較的本番に近い構成の環境ではないとホスト名の有無やIPアドレスの直指定の有無などによって本番でいきなりエラーになるようなことが想定されるので注意が必要かもしれません。
結局は慣れと経験値の差でスムーズに利用できるか分かれるのでしょうけれど、ちょっと難しいかもと感じました。
サンプルコードの実行手順
公式ガイドに掲載されているコードはpackage main
が宣言されているので、適当な作業ディレクトリでmain.go
というファイル名で保存しておきます。
このままではgo build main.go
を実行してもエラーになってしまうので、go.modファイルを準備します。
## go.modファイルがない初回だけinitを実行
$ go mod init example.com/m
## 新しいmoduleをimportした場合には次のコマンドを都度実行
$ go mod tidy
## コードを実行する場合には以下の手順
$ go build main.go
$ ./main
example.com/m
の部分はダミーですが、このまま実行できます。
自分のGitHubリポジトリがあれば、自分が管理できる適当なプロジェクトに変更してください。
gRPC API経由のサーバー設定について
クライアント設定のCRUD操作(Create, Read, Update, and Delete)が出来れば良いので、protoファイルもあるのでRubyを使って簡単にサービスを追加しようかなと思います。
ここから先はまた別の記事にまとめようと思います。
ただ不特定多数が利用するとなると、どうやって操作の許可を出すのか決めておく必要がありそうです。
単純なネーミングルールでは少し弱いと思われるので、管理台帳を別にしてdexへの直接の操作は行わないような仕組みの方がいいのかもしれません。
さいごに
やってみればそれほど難しくはないのですが、これまでバックエンドを準備するのが面倒でsqlite3を使って1つのPodだけでサービスを提供してきました。
規模的にはそれでも困らないのですが、自分以外の利用者も自由に登録できるようにするには静的な設定では限界があるので良い機会でした。
Dexの汎用的な管理ツールはあっても良いと思うのですが、dexctlのようなCLIツールの構想はあっても、少なくとも公開されているものはなさそうです。
gRPCで自由に準備してください、ということだとは思うのですが、Webアプリにしたいものの当面は基本機能をCLIで実装するような形式になりそうです。