はじめに
Keycloak 16を使う必要に迫られたので、Docker Composeでお試し環境を作ることにした。 OpenID Connect用のリバースプロキシとしてOAuth2 Proxyがお手軽に使えそうなので、これも試用してみた。
なお、現時点でのKeycloakの最新版はKeycloak 17で、一部非互換がありそう。 特にレルムのインポート/エクスポート等の管理系が大幅に変わっているように見受けられる。
環境
$ cat /etc/os-release | grep PRETTY_NAME PRETTY_NAME="Ubuntu 20.04.4 LTS" $ uname -srvm Linux 5.13.0-35-generic #40~20.04.1-Ubuntu SMP Mon Mar 7 09:18:32 UTC 2022 x86_64 $ docker version Client: Docker Engine - Community Version: 20.10.12 (略) Server: Docker Engine - Community Engine: Version: 20.10.12 (略) $ docker compose version Docker Compose version v2.2.3
今回の構成
Docker Composeを利用して、Keycloak 16とOAuth2 ProxyとNginxを立ち上げる。 以下が構成の概要図で、図中の水色の箱はDockerのネットワークを示している。
ここで、auth-demo:80
がブラウザから見た場合のWebアプリになる。
実際のWebアプリはOAuth2 Proxyの後ろに控えているwebapp:80
で、今回は素のNginxである。
また、KeycloakについてはDockerのネットワークの内と外で同じ名前とポート番号keycloak:8080
でアクセスできるようにしている。
その理由は、認証画面やOpenID ConnectのエンドポイントのURLが内と外で変わると、うまく動作しなかった*1からである。
これらを踏まえて、ホストマシンの/etc/hosts
のローカルホストのエントリを以下のように修正している。
127.0.0.1 localhost keycloak auth-demo
もし、他のマシンでWebブラウザを起動するなら、そのマシンの/etc/hosts
を適切に編集する必要がある。
docker-compose.ymlの作成
以下の内容のdocker-compose.ymlを作成する。 細かい説明は後回しにして、まずは内容全体を示す。
version: '3.9' services: keycloak: image: jboss/keycloak:16.1.1 ports: - 8080:8080 - 9990:9990 environment: KEYCLOAK_USER: kc KEYCLOAK_PASSWORD: kc KEYCLOAK_IMPORT: webapp: image: nginx:1.21.6 oauth2-proxy: image: quay.io/oauth2-proxy/oauth2-proxy:v7.2.1-amd64 ports: - 80:4180 environment: OAUTH2_PROXY_PROVIDER: oidc OAUTH2_PROXY_CLIENT_ID: auth-demo OAUTH2_PROXY_CLIENT_SECRET: OAUTH2_PROXY_REDIRECT_URL: http://auth-demo/oauth2/callback OAUTH2_PROXY_OIDC_ISSUER_URL: http://keycloak:8080/auth/realms/demo OAUTH2_PROXY_COOKIE_SECRET: "01234567890123456789012345678901" OAUTH2_PROXY_COOKIE_SECURE: "false" OAUTH2_PROXY_COOKIE_NAME: "auth_demo" OAUTH2_PROXY_EMAIL_DOMAINS: "*" OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180 OAUTH2_PROXY_UPSTREAMS: http://webapp/ OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER: "true"
一部の環境変数で値が未指定のものがあるが、その場合はDocker Compose起動時に利用可能な環境変数の値が、そのままコンテナに引き渡される動きになる。 詳細は以下を参照。
Makefileの作成
Makefileは必須ではないが、今回はKeycloakのレルムのデータのインポートとエクスポートを行うためのヘルパとして利用する。 こちらも、細かい説明は後回しにして、まずは内容全体を示す。
############################################################################## # Variables REALM_NAME := demo REALM_FILE_TO_EXPORT := $(REALM_NAME)-realm-$(shell date +%s).json ############################################################################## # Targets .PHONY: up-keycloak up-keycloak: if [ "$(REALM_FILE_TO_IMPORT)" ]; then \ echo "up keycloak with realm file to import"; \ KEYCLOAK_IMPORT="/$(REALM_NAME)-realm.json" docker compose up --no-start keycloak; \ docker compose cp "$(REALM_FILE_TO_IMPORT)" "keycloak:/$(REALM_NAME)-realm.json"; \ docker compose start keycloak; \ else \ echo "up keycloak without realm file to import"; \ docker compose up --detach keycloak; \ fi .PHONY: down down: @docker compose down .PHONY: up up: up-keycloak sleep 30 docker compose up --detach webapp oauth2-proxy # see # https://hub.docker.com/r/jboss/keycloak/ # https://www.keycloak.org/docs/16.1/server_admin/#assembly-exporting-importing_server_administration_guide .PHONY: export-realm export-realm: docker compose exec keycloak /opt/jboss/keycloak/bin/standalone.sh \ -Djboss.socket.binding.port-offset=100 \ -Dkeycloak.migration.action=export \ -Dkeycloak.migration.provider=singleFile \ -Dkeycloak.migration.realmName=$(REALM_NAME) \ -Dkeycloak.migration.usersExportStrategy=REALM_FILE \ -Dkeycloak.migration.file=/tmp/$(REALM_FILE_TO_EXPORT) docker compose cp keycloak:/tmp/$(REALM_FILE_TO_EXPORT) $(REALM_FILE_TO_EXPORT)
Keycloakでレルムを設定
(1) Keycloakを立ち上げる
$ make up-keycloak
この時、まだ設定は何も実施していないので、以下の部分のelse
側が実行される。
処理内容的には、単にdocker compose up
を実行してKeycloakだけを起動している。
up-keycloak: if [ "$(REALM_FILE_TO_IMPORT)" ]; then \ (略) else \ echo "up keycloak without realm file to import"; \ docker compose up --detach keycloak; \ fi
(2) WebブラウザでKeycloakにログインする
Webブラウザでhttp://keycloak:8080/auth/
にアクセスする。
表示されたログイン画面で以下を入力して、ボタン「Sign in」を押下する。
項目 | 値 |
---|---|
Username or email | kc |
Password | kc |
ここで入力するユーザ名とパスワードは、docker-compose.yml
の以下の部分で指定している。
services: keycloak: environment: KEYCLOAK_USER: kc KEYCLOAK_PASSWORD: kc
(3) レルムを作成する
画面左上の「Master」というレルム名が表示されている部分にマウスカーソルを合わせると、「Add realm」というボタンが現れるので、そのボタンを押下する。
以下のようなレルムの作成画面が表示されるので、
以下を入力して、ボタン「Create」を押下する。
項目 | 値 |
---|---|
Name | demo |
(4) issuerのURLを確認する
画面左のメニューから「Realm Settings」を選択する。 タブ「General」の中にある項目「Endpoints」で、「OpenID Endpoint Configuration」がリンクになっているので、それをクリックする。
以下の画面のようにJSON形式の構成情報が表示されるので、その中のキーissuer
の値を確認する。
この値はOAuth2 Proxyに設定する必要があり、docker-compose.yml
の以下の場所で設定している。
services: oauth2-proxy: environment: OAUTH2_PROXY_OIDC_ISSUER_URL: http://keycloak:8080/auth/realms/demo
(5) クライアントを作成する
画面左のメニューから「Clients」を選択すると、以下のような画面が表示される。
この画面のタブ「Lookup」の画面の右上にあるボタン「Create」を押下すると、以下のようなクライアントを追加する画面が表示される。
この画面で以下を入力して、ボタン「Save」を押下する。
項目 | 値 |
---|---|
Clinet ID | auth-demo |
Client Protocol | openid-connect |
ここで入力したClient IDはOAuth2 Proxyに設定する必要があり、docker-compose.yml
の以下の場所で設定している。
services: oauth2-proxy: environment: OAUTH2_PROXY_CLIENT_ID: auth-demo
(6) クライアントの設定を行う
クライアントを作成すると画面左のメニューの「Clients」のタブ「Settings」が選択された状態になっている。 画面は以下のようになっており、
以下を入力して、ボタン「Save」を押下する。
項目 | 値 |
---|---|
Access Type | confidential |
Valid Redirect URIs | http://auth-demo/* |
(7) クライアントシークレットを確認する
前の手順のクライアントの設定で「Access Type」を「confidential」にしたことにより、タブ「Credentials」が増えているので、これを選択する。 画面は以下のようになっている。
ここで、項目「Secret」の値を控えておく。
これはクライアントアプリケーション、すなわちコンテナoauth-proxy
がKeycloakにアクセスする際に必要になるシークレットであり、docker-compose.yml
の以下の場所で設定している。
services: oauth2-proxy: environment: OAUTH2_PROXY_CLIENT_SECRET:
「設定している」と書きつつも値を書いていないが、これはクライアントシークレットが機微情報であるため、docker-compose.ymlに直書きはせずに、Docker Composeの起動時に環境変数で指定するようにしているため。 具体的な指定方法は後述する。
(8) ユーザを追加する
画面左のメニューから「Users」を選択すると、以下のような画面になる。
この画面のタブ「Lookup」の中にあるボタン「Add user」を押下すると、以下のようなユーザを追加する画面が表示される。
この画面で以下を入力して、ボタン「Save」を押下する。
項目 | 値 |
---|---|
Username | demo-user |
demo-user@example.com | |
Email Verified | ON |
次に、追加したユーザのパスワードを設定する。 タブ「Credentials」を選択すると以下のような画面が表示される。
この画面で以下を入力して、ボタン「Set Password」を押下する。
項目 | 値 |
---|---|
Password | (任意のパスワード) |
Password Confirmation | (任意のパスワード) |
Temporary | OFF |
(9) レルムのデータをエクスポートする
ここまでで設定したレルムを別環境でも復元できるように、JSONファイル形式でエクスポートしておく。 以下のコマンドを実行する。
$ make export-realm
上記コマンドについて、Makefile
の当該部分を以下に抜粋する。
.PHONY: export-realm export-realm: docker compose exec keycloak /opt/jboss/keycloak/bin/standalone.sh \ -Djboss.socket.binding.port-offset=100 \ -Dkeycloak.migration.action=export \ -Dkeycloak.migration.provider=singleFile \ -Dkeycloak.migration.realmName=$(REALM_NAME) \ -Dkeycloak.migration.usersExportStrategy=REALM_FILE \ -Dkeycloak.migration.file=/tmp/$(REALM_FILE_TO_EXPORT) docker compose cp keycloak:/tmp/$(REALM_FILE_TO_EXPORT) $(REALM_FILE_TO_EXPORT)
Keycloakのコンテナに入って特別なオプションをつけてKeycloakを立ち上げると、レルムのデータをJSON形式でエクスポートできる。 データをエクスポートするためにKeycloakのインスタンスがもう一つ立ち上がるのはどうかと思うが、想像するに管理系の機能を後付けで入れたために、こうなってしまったのだろうと思う*2。 Keycloak 16のインポート/エクスポートに関するマニュアルはこちら。
上記のとおりにmake
を実行すると、コンソールにKeycloakの起動時のログが出力される。
(略) 22:27:54,767 INFO [org.jboss.as.server] (ServerService Thread Pool -- 42) WFLYSRV0010: Deployed "keycloak-server.war" (runtime-name : "keycloak-server.war") 22:27:54,820 INFO [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server 22:27:54,823 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 16.1.1 (WildFly Core 18.0.4.Final) started in 13501ms - Started 573 of 851 services (576 services are lazy, passive or on-demand) 22:27:54,825 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:10090/management 22:27:54,825 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:10090
最後のWFLYSRV0051: Admin console listening on http://127.0.0.1:10090
で起動完了と思えばよい。
ここで[CTRL] + [C]でエクスポート用のKeycloakを停止すると、Makefile
の中のdocker compose cp
に処理が進みホスト側にJSONファイルがコピーされる*3。
エクスポートされるJSONファイルは、Makefile
内で以下のように定義しているので、
REALM_FILE_TO_EXPORT := $(REALM_NAME)-realm-$(shell date +%s).json
具体的にはdemo-realm-1647642460.json
といったようなファイルがホスト側に生成される。
(10) Keycloakのコンテナの破棄
ここまでで設定してきたKeycloakのコンテナは、一旦破棄する。 動作確認では、エクスポートされたレルム(JSONファイル)をロードすることで初期データをセットアップする。
$ make down
Makefile
では、以下のように単にdocker compose down
を実行している。
.PHONY: down down: @docker compose down
動作確認
(1) デモアプリを起動する
クライアントシークレットはdocker-compose.yml
の中で値を指定していない。
そのため、コンテナにクライアントシークレットを引き渡せるように、ターミナルで環境変数を設定しておく。
$ export OAUTH2_PROXY_CLIENT_SECRET=[クライアントシークレット]
次に、インポートするレルムのJSONファイルを環境変数で指定しておく。
このJSONファイルはmake export-realm
を実行して作成されたファイルであり、具体的には以下のような感じで指定する。
$ export REALM_FILE_TO_IMPORT=./demo-realm-1647642460.json
そして、以下のコマンドを実行してKeycloak、OAuth2 Proxy、Nginxを起動する。
$ make up
この上記コマンドについて、Makefile
の当該部分を抜粋しながら説明する。
まずはターゲットup
についてだが、Makefile
では以下のようになっている。
.PHONY: up up: up-keycloak sleep 30 docker compose up --detach webapp oauth2-proxy
ターゲットup
の実行の前に、ターゲットup-keycloak
が実行される。
そしてKeycloakの起動完了の待ち合わせのために30秒スリープした後、OAuth2 ProxyとNginxを立ち上げている。
OAuth2 Proxyは起動処理の過程でKeycloakにアクセスしているため、起動完了の待ち合わせがないとOAuth2 Proxyは起動に失敗する*4。
そして、ターゲットup-keycloak
は以下のようになっており、
.PHONY: up-keycloak up-keycloak: if [ "$(REALM_FILE_TO_IMPORT)" ]; then \ echo "up keycloak with realm file to import"; \ KEYCLOAK_IMPORT="/$(REALM_NAME)-realm.json" docker compose up --no-start keycloak; \ docker compose cp "$(REALM_FILE_TO_IMPORT)" "keycloak:/$(REALM_NAME)-realm.json"; \ docker compose start keycloak; \ else \ (略) fi
今回は変数(環境変数)REALM_FILE_TO_IMPORT
が定義されているためthenの方が実行される。
ここでは、コンテナの作成(docker compose up --no-start
)、コンテナ内にレルムのJSONファイルをコピー(docker compose cp
)、コンテナの起動(docker compose start
)を実施している。
インポートするレルムのJSONファイルを引き渡す方法としては、ボリュームを利用するやり方もあるが、これだとdocker-compose.yml
がベタに環境依存になるので避けた。
(2) デモアプリにWebブラウザでアクセスする
Webブラウザでhttp://auth-demo/
にアクセスすると、以下のような画面が表示される。
この画面でボタン「Sign in with OpenID Connect」を押下すると、以下のログイン画面が表示される。
この画面で以下を入力して、ボタン「Sign in」を押下する。
項目 | 値 |
---|---|
Username or email | demo-user |
Password | (demo-userのパスワード) |
ログインに成功すると、以下のようなNginxのデフォルトのページが表示される。
これにより、OAuth2 ProxyをリバースプロキシとしてKeycloakと連携し、KeyCloakによる認証を通過した後、OAuth2 Proxy経由でバックエンドのWebアプリケーション(Nginx)にアクセスできることを確認できた。
参考
Keycloakとは
Red HatのSSO (Single Sign On)のサーバソフトウェア。 今回はSSOというよりは、OpenID Connectに基づく認証サーバとして利用している。
OAuth2 Proxyとは
アプリケーションサーバ等の前段にリバースプロキシとして立って、OAuth2/OpenID Connectのための処理を請け負ってくれるサーバソフトウェア。 アプリケーション内にOAuth2/OpenID Connectの処理を組み込まなくてもよいので、レガシーなサーバに簡易的に認証/認可の機能を付加する場合に便利。
*1:これはKeycloakの設定次第かもしれないが、調べきれなかった。
*2:Keycloak 17から基盤がWildflyからQuarkusに変わったのでこの方法は使えない。管理系のコマンドが整備されているように見えるので、改善されているかもしれない
*3:[CTRL] + [C]でファイルがコピーされるとか不格好過ぎるが、Keycloak 17では改善しているかもしれないし、そもそもデモなのでプロダクションレベルの品質は不要だしで、このままでいいやという結論になった
*4:スリープによる待ち合わせは超簡易的な方法なので、プロダクション環境であればhttpを利用したアライブチェック等にすべき。