Keycloakのセットアップ手順メモ (Ubuntu 20.04 LTS on Azure VM)

OSSのIdP用ツールであるKeycloakのセットアップ手順のメモ書きを備忘用にまとめる。

https://www.keycloak.org/

# 1. VMの作成
パブリックDNS名を持つ仮想マシンが立ち上がればどのサービスでも良い。ここではAzureを利用する。以下の設定以外はデフォルトでOK。

- 利用するイメージ: Ubuntu Server 20.04 LTS - x64 Gen2
- パブリック受信ポートはなしにしておく（後から設定を変更）
- OSディスクの種類はStandard SSDで十分
- 仮想ネットワークのサブネットマスクは/27で十分
- 自動シャットダウンを有効にしておく

# 2. NSGの受信ポートルールの設定
以下のルールを追加する。優先度、名前は特に以下の通りでなくても問題ない。8443、8080はKeycloakのデフォルトのポート。

優先度 | 名前 | ポート　| プロトコル | ソース | 宛先　| アクション
--- | --- | --- | --- | --- | --- | ---
100 | AllowMyIpAddressSSHInbound | 22 | TCP | (自身のIPアドレス) | 任意 | Allow
110 | AllowAnyCustom8443Inbound | 8443 | TCP | 任意 | 任意 | Allow
120 | AllowMyIpAddressCustom8080Inbound | 8080 | TCP | (自身のIPアドレス) | 任意 | Allow
130 | AllowAnyHTTPInbound | 80 | TCP | 任意 | 任意 | Allow

# 3. OpenJDK 17のインストール
VMにSSH接続し、Keycloakの実行に必要なOpenJDK 17をインストールする。

```bash:OpenJDK17のインストール
sudo apt update
sudo apt install openjdk-17-jdk
```

バージョン確認のコマンドを実行し、メジャーバージョン17のOpenJDKがインストールされていればOK。マイナーバージョン以降は特に気にする必要なし。

```bash:Javaバージョン確認
$ java -version
openjdk version "17.0.10" 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7-Ubuntu-120.04.1)
OpenJDK 64-Bit Server VM (build 17.0.10+7-Ubuntu-120.04.1, mixed mode, sharing)
```

## パッケージ依存関係のエラーが出る場合
上記を実行した際、OpenJDK 17のパッケージの依存関係が解決できていない旨のエラーが出た。

```bash:OpenJDK17のインストール時のエラー
$ sudo apt install openjdk-17-jdk
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:

The following packages have unmet dependencies:
 openjdk-17-jdk : Depends: openjdk-17-jre (= 17.0.10+7-1~20.04.1) but it is not going to be installed
                  Depends: openjdk-17-jdk-headless (= 17.0.10+7-1~20.04.1) but it is not going to be installed
                  Recommends: libxt-dev but it is not installable
E: Unable to correct problems, you have held broken packages.
```

以下を実行してパッケージの依存関係を修復後、再度OpenJDK17のインストールを実行することで上手くいった。

```bash:パッケージの依存関係の修復
# パッケージリストの更新
sudo apt update
# 依存関係の修復
sudo apt -f install
# システムのパッケージを最新状態にアップグレード
sudo apt upgrade
```

# 4. Keycloakのダウンロード
以下のOpenJDK - Keycloakのページ内のDownload KeycloakというセクションからZipファイルのリンクをコピーする。（本記事執筆時点ではkeycloak-24.0.1.zipだった）

https://www.keycloak.org/getting-started/getting-started-zip

wgetコマンドでZipファイルをダウンロードして解凍する。解凍のためにunzipコマンドが必要なため先にインストールしておく。

```bash:Keycloakのダウンロード
sudo apt install unzip
# 以下のURLは置き換えること
wget https://github.com/keycloak/keycloak/releases/download/24.0.1/keycloak-24.0.1.zip
unzip keycloak-24.0.1.zip
```

カレントディレクトリにkeycloakのディレクトリが作成される。

```bash:Keycloakディレクトリの構成
$ ls -l keycloak-24.0.1
total 44
-rw-r--r-- 1 hinak hinak 11358 Mar  5 08:02 LICENSE.txt
-rw-r--r-- 1 hinak hinak   492 Mar  5 08:02 README.md
drwxr-xr-x 3 hinak hinak  4096 Mar  5 08:21 bin
drwxr-xr-x 3 hinak hinak  4096 Mar  5 08:21 conf
drwxrwxr-x 3 hinak hinak  4096 Mar  7 13:47 data
drwxr-xr-x 5 hinak hinak  4096 Mar  5 08:21 lib
drwxr-xr-x 2 hinak hinak  4096 Mar  5 08:21 providers
drwxr-xr-x 2 hinak hinak  4096 Mar  5 08:21 themes
-rw-r--r-- 1 hinak hinak    26 Mar  5 08:02 version.txt
```

# 5. Keycloakへの接続
ここから試行錯誤が必要だった。以下にその履歴を記録する。

## 5-1. 管理者設定の不足 -> 環境変数で設定
まずはGetting startedのガイド通りに開発モード (development mode) でKeycloakを起動する。

```bash:Keycloakの起動
bin/kc.sh start-dev
```

それから`http://Azure VMのパブリックDNS名:8080`にWebブラウザからアクセスしたところ、localhostでない場合には環境変数で管理者ユーザー情報を設定する必要がある旨、表示された。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/32091011-8720-a075-c43a-086c0a83817a.png)

指示通りに管理者ユーザーのユーザー名とパスワードを環境変数に設定する。以下の値はダミーである（実際の値ではない）ことに留意。

```bash:Keycloakの管理者情報を環境変数に設定
export KEYCLOAK_ADMIN=dummy_admin_user
export KEYCLOAK_ADMIN_PASSWORD=dummy_admin_password
```

## 5-2. HTTPS設定が必要 -> Let's Encryptの証明書でKeycloakのHTTPS設定を実施
再度Webブラウザからアクセスしたところ、今度はHTTPSが必要な旨、エラーメッセージが出た。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/96876275-be9a-a169-ba30-badac15c8254.png)

まずはLet's EncryptのCertbotをインストールし、証明書を取得する。証明書の取得時に、ポート80のインターネットからのアクセスを許可している必要がある点に留意。

```bash:Certbotのインストールと証明書の取得
sudo apt install certbot
# ドメインはAzure VMのパブリックDNS名に置き換え
sudo certbot certonly --standalone -d mydomain.com
```

証明書取得時に以下を聞かれるので適当に回答する。

- Eメールアドレス（緊急時の連絡受信用）: 適当なメールアドレスを入力
- Terms of Serviceへの同意: (A)gree
- Electronic Frontier FoundationへのEメールアドレスの共有: (N)o

以下のようなメッセージが出れば証明書の取得に成功。

```
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for mydomain.com
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/mydomain.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/mydomain.com/privkey.pem
   Your cert will expire on 2024-06-05. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
```

それから上記で取得した証明書のパスに置き換えて以下のコマンドを実行し、Keycloakを起動する。証明書が保存されているパスは既定ではOSのrootユーザーしかアクセスできないため、sudoを付けて実行している。

```bash:KeycloakをHTTPSで起動
sudo bin/kc.sh start-dev --https-certificate-file=/etc/letsencrypt/live/hinak-vm-keycloak-1.japaneast.cloudapp.azure.com/fullchain.pem --https-certificate-key-file=/etc/letsencrypt/live/hinak-vm-keycloak-1.japaneast.cloudapp.azure.com/privkey.pem
```

Webブラウザから`https://VMのパブリックDNS名:8443`でアクセス、しばらく待つと以下のサインイン画面が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/a2e88ca4-63ad-2c9a-a2c1-d2a3ac2bbcd8.png)

管理者ユーザーの名前とパスワードを入力してサインイン、上手く行けば次の画面が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/33c741e1-91ca-78de-4fca-ce57147246d0.png)

## バックグラウンド実行
上記のコマンドではKeycloakがターミナル上で動作しっぱなしになるので、バックグラウンドで実行できるようにする。

```bash:Keycloakのバックグラウンド実行用スクリプト作成
vi ~/start-kc.sh
chmod +x ~/start-kc.sh
```

```bash:start-kc.sh
#!/bin/bash
nohup sudo ~/keycloak-24.0.1/bin/kc.sh start-dev --https-certificate-file=/etc/letsencrypt/live/mydomain.com/fullchain.pem --https-certificate-key-file=/etc/letsencrypt/live/mydomain.com/privkey.pem &
```

```bash:start-kc.shの実行
$ sh start-kc.sh
$ nohup: appending output to 'nohup.out'
(Enterを押す)

$ cat nohup.out 
2024-03-07 15:08:46,188 INFO  [org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: <unset>, Hostname: <request>, Strict HTTPS: false, Path: <request>, Strict BackChannel: false, Admin URL: <unset>, Admin: <request>, Port: -1, Proxied: false
2024-03-07 15:08:46,878 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
2024-03-07 15:08:47,017 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2024-03-07 15:08:50,302 WARN  [io.quarkus.agroal.runtime.DataSources] (JPA Startup Thread) Datasource <default> enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
2024-03-07 15:08:52,173 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: node_629361, Site name: null
2024-03-07 15:08:52,178 INFO  [org.keycloak.broker.provider.AbstractIdentityProviderMapper] (main) Registering class org.keycloak.broker.provider.mappersync.ConfigSyncEventListener
2024-03-07 15:08:54,155 INFO  [io.quarkus] (main) Keycloak 24.0.1 on JVM (powered by Quarkus 3.8.1) started in 10.059s. Listening on: http://0.0.0.0:8080 and https://0.0.0.0:8443
2024-03-07 15:08:54,155 INFO  [io.quarkus] (main) Profile dev activated. 
2024-03-07 15:08:54,155 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, keycloak, logging-gelf, narayana-jta, reactive-routes, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]
2024-03-07 15:08:54,159 WARN  [org.keycloak.quarkus.runtime.KeycloakMain] (main) Running the server in development mode. DO NOT use this configuration in production.
```

## デーモンとして実行
バックグラウンド実行でも良いが、サーバー再起動する度に自分でサービスを起動する必要がある。デーモンとして実行するようにすると楽。

```bash:デーモン実行準備
# 元々のファイルをバックアップ
$ cp start-kc.sh start-kc.sh.org
$ vi start-kc.sh

#!/bin/bash
# ディレクトリを絶対パスにし、sudoとバックグラウンド実行のnohupおよび&を削除
/home/myuser/keycloak-24.0.1/bin/kc.sh start-dev --https-certificate-file=/etc/letsencrypt/live/mydomain.com/fullchain.pem --https-certificate-key-file=/etc/letsencrypt/live/mydomain.com/privkey.pem
```

```bash:systemdサービスファイルの作成
$ sudo vi /etc/systemd/system/keycloak.service

[Unit]
Description=Keycloak Server
After=network.target

[Service]
User=root
ExecStart=/home/myuser/start-kc.sh
Type=simple

[Install]
WantedBy=multi-user.target
```

```bash:systemdサービスの有効化
sudo systemctl enable keycloak.service
sudo systemctl start keycloak.service
sudo systemctl status keycloak.service
```

以下のようなメッセージが出れば起動OK。

```
$ sudo systemctl status keycloak.service
● keycloak.service - Keycloak Server
     Loaded: loaded (/etc/systemd/system/keycloak.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2024-03-08 01:36:33 UTC; 6min ago
   Main PID: 595 (start-kc.sh)
      Tasks: 39 (limit: 9516)
     Memory: 422.2M
     CGroup: /system.slice/keycloak.service
             ├─595 /bin/bash /home/myuser/start-kc.sh
             └─600 java -Dkc.config.built=true -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.err.encoding=UTF-8 -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -XX:+ExitOnOutOfMemoryErro>

Mar 08 01:36:54 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:49,908 INFO  [org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: <unset>, Hostname: <request>, Strict HTTPS: false, Path: <re>
Mar 08 01:36:54 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:50,861 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
Mar 08 01:36:54 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:51,030 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
Mar 08 01:36:54 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:54,754 WARN  [io.quarkus.agroal.runtime.DataSources] (JPA Startup Thread) Datasource <default> enables XA but transaction recovery is not enabled. Please enable transaction >
Mar 08 01:36:56 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:56,618 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: node_79965, Site name: null
Mar 08 01:36:56 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:56,624 INFO  [org.keycloak.broker.provider.AbstractIdentityProviderMapper] (main) Registering class org.keycloak.broker.provider.mappersync.ConfigSyncEventListener
Mar 08 01:36:59 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:59,392 INFO  [io.quarkus] (main) Keycloak 24.0.1 on JVM (powered by Quarkus 3.8.1) started in 15.456s. Listening on: http://0.0.0.0:8080 and https://0.0.0.0:8443
Mar 08 01:36:59 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:59,393 INFO  [io.quarkus] (main) Profile dev activated.
Mar 08 01:36:59 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:59,394 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, keycloak, logging-gelf, narayana-jta, reactive-routes, resteasy-reactive, resteasy>
Mar 08 01:36:59 hinak-vm-keycloak-1 start-kc.sh[600]: 2024-03-08 01:36:59,408 WARN  [org.keycloak.quarkus.runtime.KeycloakMain] (main) Running the server in development mode. DO NOT use this configuration in production.
```

## 本番サービスとして実行できるように設定
```bash:keycloak.conf設定
vi ~/keycloak-24.0.1/conf/keycloak.conf

# 以下を追記
https-certificate-file=/etc/letsencrypt/live/mydomain.com/fullchain.pem
https-certificate-key-file=/etc/letsencrypt/live/mydomain.com/privkey.pem
hostname=xxxxx # パブリックDNS名
```

```bash:build実行
cd /home/ubuntu/keycloak-24.0.1/bin/
./kc.sh --help  #check command options
/kc.sh build
(しばらく待つ)
```

```bash:start-kc.sh
$ vi start-kc.sh

#!/bin/bash
/home/myuser/keycloak-24.0.1/bin/kc.sh start --log="console,file"
```

```bash:デーモン再起動
sudo systemctl restart keycloak.service
sudo systemctl status keycloak.service

# ログ確認
less ~/keycloak-24.0.1/data/log/keycloak.log
```

# 6. 最初のRealmとユーザーの作成
Realmはテナントに相当、最初のRealmとユーザーを以下ドキュメントの以下セクションに従って実施する。

- Create a realm
- Create a user
- Log in to the Account Console

https://www.keycloak.org/getting-started/getting-started-zip

ユーザー作成後のアクセス先は
`https://VMのパブリックDNS名:8443/realms/myrealm/account/`である点に留意。

# 7. NSGの受信ポートルールの整理
最終的にカスタムルールは以下2つがあればOK、8080やHTTP(80)のルールを削除する。

優先度 | 名前 | ポート　| プロトコル | ソース | 宛先　| アクション
--- | --- | --- | --- | --- | --- | ---
100 | AllowMyIpAddressSSHInbound | 22 | TCP | (自身のIPアドレス) | 任意 | Allow
110 | AllowAnyCustom8443Inbound | 8443 | TCP | 任意 | 任意 | Allow

以上です。