はじめに
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起動時に利用可能な環境変数の値が、そのままコンテナに引き渡される動きになる。
詳細は以下を参照。
docs.docker.com
Makefileは必須ではないが、今回はKeycloakのレルムのデータのインポートとエクスポートを行うためのヘルパとして利用する。
こちらも、細かい説明は後回しにして、まずは内容全体を示す。
REALM_NAME := demo
REALM_FILE_TO_EXPORT := $(REALM_NAME)-realm-$(shell date +%s).json
.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
.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」を押下する。
(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」を押下する。
(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 |
Email |
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のインポート/エクスポートに関するマニュアルはこちら。
www.keycloak.org
上記のとおりに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に基づく認証サーバとして利用している。
www.keycloak.org
OAuth2 Proxyとは
アプリケーションサーバ等の前段にリバースプロキシとして立って、OAuth2/OpenID Connectのための処理を請け負ってくれるサーバソフトウェア。
アプリケーション内にOAuth2/OpenID Connectの処理を組み込まなくてもよいので、レガシーなサーバに簡易的に認証/認可の機能を付加する場合に便利。
oauth2-proxy.github.io