Background
Keycloak に Duo を連携する ために Keycloak の ver up が必要になりました。その keycloak を導入した前任者は、辞める直前に 「keycloakのversion upできなかったんですよねぇ…」 と意味深にこぼしていました。その理由を思い知った話です。
Keycloak は 2 つある
Keycloak は Java で実装されていますがそのフレームワークが変わるとともに大きな変更が発生しました。古いのが WildFly(ex. JBoss Application Server), 新しいのが Quarkus です。
WildFly: version ? - 20 まで
Quarkus: version 17 - latest
Version 17 - 19 の間は 2 つの distribution が存在していたことになります。
言い換えます。
同じバージョンでも全く別物があるんだよっっ!!!!
Quarkusへの移行は breaking changes を伴うもので、今回はこれを乗り越えたお話です。詳細な移行ガイドはこちら。
Keycloak version history
Docker image の変遷
以前は Docker hub の jboss/keycloak
イメージが主流でしたが、v16.1.1 を最後に更新が止まりました。
現在は Redhat の docker registry quay.io/keycloak/keycloak
で配布されています。
ここには -legacy
と名のつくimagesがあり、これは WildFly版 を示しています。
しかし、 17.0.0-legacy
から 19.0.3-legacy
までしかありません。
つまりdocker imageをサクッと使う場合、利用できるのは ver 19 が最後ということになります。
jboss/keycloak:16.1.1
-> quay.io/keycloak/keycloak:19.0.3-legacy
への移行は簡単でした。imageを差し替えるだけでした。(envもDBもそのままで動きます)
Quarkusに移行しよう
情報が少なくてめっちゃ大変でしたが無事に移行したのでまとめます。
Quarkusへの移行に必要な事
- docker imageを
quay.io
のものを使うようにする - 環境変数を 全て 差し替える
- DBは何もしなくても動いた
- Keycloakに連動してるサービスの設定を変更する
- なんと Keycloak URL が変わるので client id 持ってるサービスは全部変更が必須です。(ただし設定で回避できます)
移行方法
Dockerfile
as-is
FROM jboss/keycloak:16.1.1
to-be
official doc では build stageを分けるように言っているのでその通りに従ってみました。
#################################################
# Builder
#################################################
ARG KEYCLOAK_VERSION=22.0.1-2
FROM quay.io/keycloak/keycloak:$KEYCLOAK_VERSION as builder
# Enable health and metrics support
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
# Configure a database vendor
ENV KC_DB=mysql
WORKDIR /opt/keycloak
COPY DuoUniversalKeycloakAuthenticator-1.0.7_22.0.1wd.jar /opt/keycloak/providers
RUN /opt/keycloak/bin/kc.sh build
#################################################
# Runner
#################################################
ARG KEYCLOAK_VERSION=22.0.1-2
FROM quay.io/keycloak/keycloak:$KEYCLOAK_VERSION
COPY --from=builder /opt/keycloak/ /opt/keycloak/
EXPOSE 8080
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start"]
*Builderの注釈
ここで入れているENVは全て不要ですが、入れておくとRunnerを違うenvでstartしたときにwarningが出るようになります。
*Runnerの注釈
httpしか使わない設定にしてます。imageにENVを埋め込んだり、起動オプションを追加したい場合はここで入れていいです。私はk8s + 環境変数で管理したかったので変更していません。
起動オプションはここにまとめがあります。
Example of docker-compose.yaml
一例です。環境変数は起動オプションのマニュアルで項目一つずつをexpandすると書いてあります。
version: '3.1'
services:
keycloak:
build:
context: .
environments:
- KC_DB=mysql
- KC_DB_URL=jdbc:mysql://mysql.local:3306/keycloak
- KC_DB_USERNAME=root
- KC_DB_PASSWORD=example
- KC_HOSTNAME=my-keycloak-ui.example.com
- KC_HTTP_PORT=8080
- KC_HTTP_ENABLED=true
- KC_HOSTNAME_STRICT=false
- KC_HOSTNAME_STRICT_HTTPS=false
- KC_HEALTH_ENABLED=true
- KC_METRICS_ENABLED=true
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
- PROXY_ADDRESS_FORWARDING="true"
ports:
- 8080:8080
command: /opt/keycloak/bin/kc.sh start-dev
Environments
Database
as-is
# MySQL connection
DB_ADDR: mysql.local
DB_DATABASE: keycloak
DB_USER: root
DB_PASSWORD: example
DB_PORT: 3306
# Keycloak admin
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
to-be
環境変数の名前が全て変わりました。prefixとして KC_
がついています。
この環境変数をまとめた資料はこれです。ぱっと見は起動オプションに見えますが、expandすると環境変数が見えます。消えたものもあるので注意です。
KC_DB: "mysql"
# MySQL connection
KC_DB_URL: "jdbc:mysql://mysql.local:3306/keycloak"
KC_DB_USERNAME: root
KC_DB_PASSWORD: example
# Keycloak admin
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
DB部分を分けて書く形式だと、おそらく以下でも動きます(試してません)。KC_DB_URL_HOST etc と KC_DB_URLの併用はできません。
KC_DB: "mysql"
# MySQL connection
KC_DB_URL_HOST: mysql.local
KC_DB_URL_DATABASE: keycloak
KC_DB_USERNAME: root
KC_DB_PASSWORD: example
KC_DB_URL_PORT: 3306
Environments for network
HTTPを利用する場合、Load balancerでSSL offloadingする場合の設定が変わりました。 起動するけど、アクセスすると Content-Security-Policy
のエラーで永遠に loading gif が流れたりして大ハマりしました。
browser -> (httpS) -> LB -> (http) -> Keycloak の例
### Required for SSL-termination by LB --->
## LB が SSL Termination している場合のモード = edge
KC_PROXY: "edge"
KC_PROXY_ADDRESS_FORWARDING: "true"
# LB で SSL Termination してる場合は HTTPS を無効化
KC_HOSTNAME_STRICT: "false"
KC_HOSTNAME_STRICT_HTTPS: "false"
### <---
# LB で SSL Terminationしてる場合は HTTP 必須
KC_HTTP_ENABLED: "true"
KC_HTTP_PORT: "8080"
KC_PROXY: "edge"
によってfrontendの HTTP Headerにつく Content-Security-Policy が self
だけから keycloak URL も追加されたものになり、admin consoleを開けるようになります。これがわからなくて時間を溶かしました。横山さん、助けてくれてありがとう。
LB や Reverseproxyを使う場合、ここら辺の設定は 必須 です。ver 16 までのように簡単には動きません。詳細はマニュアルを見てください。AWS EC2 の場合など、さまざまなパターンがあります。
https://www.keycloak.org/server/reverseproxy
Environments for redundancy
QuarkusのKeycloakはinfinispanを使って冗長化する仕組みが入っているみたいです。冗長化をしようとしましたがうまく行かず今回は諦めました。
追記:成功しました https://qiita.com/uturned0/items/37d2ce64d04a3f3a5dd2
infinispanは通常UDPを使ってcluster discoveryや通信をしますが、kubernetesではUDPが使えないのでTCPにするなどの対策が必要だそうです。
### infinispan 分散キャッシュを有効にする
KC_CACHE: "ispn"
# k8sはUDP multicastが使えないため、TCPPINGを使う
# https://www.keycloak.org/server/caching
KC_CACHE_STACK: "kubernetes"
# pod内からk8s serviceの名前解決ができるドメインを指定
# `<service-name>.<namespace-name>.svc.cluster.local`
JAVA_OPTS: "-Djgroups.dns.query=keycloak-service.keycloak.svc.cluster.local"
上記の設定で keycloak は走るのですが、infinispanの通信を観測できませんでした。ここで JAVA_OPTS を空にして走らせると infinispanのdns.queryがないというエラーとともに現在の設定内容が表示されます。そこには 7800/tcp とあったのでそこら辺をtcpdumpしたんですが全く観測できず。sessionも共有されていないためか、Duoからのredirectでエラーが起こりました。時間なく諦めました。
追記: k8sのheadless serviceが必要でした、成功しました。
docs
https://www.keycloak.org/server/caching
How to debug
keycloak podの中で kc.sh を使って状態を確認できます。 /opt/keycloak/
が keycloakのroot dirとなっていて、confなどもそこにあります。
# show config
/opt/keycloak/bin/kc.sh show-config
# database import/export
/opt/keycloak/bin/kc.sh tools
...
show-configの例
手元のdev環境の例です。duo pluginが入ったりしてるので初期状態とは違います。
環境変数で上書きされてるものには KcEnvVarConfigSource
と表示されるのが優しい。
bash-5.1$ /opt/keycloak/bin/kc.sh show-config
Current Mode: development
Current Configuration:
kc.cache = local (PersistedConfigSource)
kc.config.built = true (SysPropConfigSource)
kc.db = mysql (KcEnvVarConfigSource)
kc.db-password = ******* (KcEnvVarConfigSource)
kc.db-url = jdbc:mysql://your-mysql.local:3306/keycloak (KcEnvVarConfigSource)
kc.db-username = keycloak (KcEnvVarConfigSource)
kc.db.password = keycloak (KcEnvVarConfigSource)
kc.db.url = jdbc:mysql://your-mysql.local:3306/keycloak (KcEnvVarConfigSource)
kc.db.username = keycloak (KcEnvVarConfigSource)
kc.health-enabled = true (KcEnvVarConfigSource)
kc.health.enabled = true (KcEnvVarConfigSource)
kc.hostname-strict = false (KcEnvVarConfigSource)
kc.hostname-strict-https = false (KcEnvVarConfigSource)
kc.hostname-url = https://your-keycloak.example.com (KcEnvVarConfigSource)
kc.hostname.strict = false (KcEnvVarConfigSource)
kc.hostname.strict.https = false (KcEnvVarConfigSource)
kc.hostname.url = https://your-keycloak.example.com (KcEnvVarConfigSource)
kc.http-enabled = true (KcEnvVarConfigSource)
kc.http-port = 8080 (KcEnvVarConfigSource)
kc.http.enabled = true (KcEnvVarConfigSource)
kc.http.port = 8080 (KcEnvVarConfigSource)
kc.log-console-output = default (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.log-file = ${kc.home.dir:default}${file.separator}data${file.separator}log${file.separator}keycloak.log (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.metrics-enabled = true (KcEnvVarConfigSource)
kc.metrics.enabled = true (KcEnvVarConfigSource)
kc.provider.file.DuoUniversalKeycloakAuthenticator-1.0.7_22.0.1wd.jar.last-modified = 1702558543000 (PersistedConfigSource)
kc.proxy = edge (KcEnvVarConfigSource)
kc.proxy.address.forwarding = true (KcEnvVarConfigSource)
kc.spi-map-storage-concurrenthashmap-dir = ${kc.home.dir:default}${file.separator}data${file.separator}chm (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.spi-map-storage-concurrenthashmap-key-type-authz-resource-servers = string (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.spi-map-storage-concurrenthashmap-key-type-realms = string (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.spi-map-storage-concurrenthashmap-key-type-single-use-objects = string (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.spi-theme-cache-templates = false (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.spi-theme-cache-themes = false (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.spi-theme-static-max-age = -1 (PropertiesConfigSource[source=jar:file:///opt/keycloak/lib/lib/main/org.keycloak.keycloak-quarkus-server-22.0.1.jar!/META-INF/keycloak.conf])
kc.version = 22.0.1 (SysPropConfigSource)
bash-5.1$
Clientの変更
Keycloakのpathが変わるので、OIDC Clientは変更を変えないといけません。
簡単にいうと、 /auth/realms
の部分から /auth
を消して /realms
にします。
as-is
oidc_url = "https://your-keycloak.example.com/auth/realms/master/protocol/openid-connect/auth"
to-be
oidc_url = "https://your-keycloak.example.com/realms/master/protocol/openid-connect/auth"
ただし以前の挙動を残すオプションが用意されてます
bin/kc.[sh|bat] start-dev --http-relative-path /auth
環境変数だとこうです。
KC_HTTP_RELATIVE_PATH=/auth
defaultは KC_HTTP_RELATIVE_PATH=/
です
ずっと古いpathつけるの嫌なので、今回は利用者にupdateしてもらいました。
Reference
special thanks!
終わり
とっても苦労したので、ハート押してね!
Duo を使った記事はこちらです。