Kong Gateway の OpenID Connect プラグインを使ってみる

# はじめに

この記事では、[OpenID Connect プラグイン](https://docs.konghq.com/hub/kong-inc/openid-connect/)を使い Kong Gateway で OpenID Connect による認証・認可を実現するための設定例について、[認可コードフロー(Authorization code flow)の使用方法のドキュメント](https://docs.konghq.com/hub/kong-inc/openid-connect/how-to/authentication/authorization-code-flow/)に沿って説明します。 


::: note warn
OpenID Connect プラグインは Kong Gateway Enterprise / Konnect で利用可能です。
Kong Gateway OSS では利用できません。
:::

# OpenID Connect とは
OpenID Connect (以後 OIDC) とはざっくり、 OAuth 2.0 での認可のフローでは十分ではなかった認証について補った仕様です。

OpenID Connect の詳細については手に余りますので Qiita 内で "OpenID Connect" または "OIDC" で検索するか、仕様書がありますのでそちらにあたってみてください。

- 日本語訳仕様書

https://www.openid.or.jp/document/

- 仕様書
以下のサイトの "OpenID Connect specifications" をクリックすると各仕様へのリンクが表示されます。

https://openid.net/developers/specs/

# この記事でやってみること

後続の図は OpenID Connect プラグイン(以後OIDCプラグイン)を使った認可コードフローのシーケンス図です。
(https://docs.konghq.com/hub/kong-inc/openid-connect/how-to/authentication/authorization-code-flow/#authorization-code-flow より引用。リンク先ではもっときれいに表示されます)
この記事では KeyCloak を OIDC の IdP に、OpenID Connect を有効化した Kong Konnect + httpbin を RP として認可コードフローの動作を確認します。

:::note
OpenID では、認可・認証ホストを IdP (ID Provider)、サービスを提供するホストを RP (Relying Party) と呼びます。
:::

![docs.konghq.com_hub_kong-inc_openid-connect_how-to_authentication_authorization-code-flow_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/f6d0a2cb-72b4-72f1-e546-2e12add4bae2.png)

❶ クライアントが Kong Gateway に設定したルートを通じてアップストリームの httpbin にアクセスしようとすると、
❷❸ Kong Gateway の OIDCプラグインが Keycloak の URL にリダイレクトさせます。
❹ Keycloak がログイン画面を出すので、
❺ クライアントからクレデンシャルを提示します。
❻❼ 認証できたら、Keycloak は認可コードをつけて Kong Gateway にリダイレクトさせます。
❽ Kong Gateway の OIDC プラグインは認可コードを検証し、OKだったら、
❾ Keycloak に対してIDトークン・アクセストークン・リフレッシュトークンを要求します。
❿ Keycloak は Kong Gateway が提示した認可コードを検証し、OKだったら、
⓫ IDトークン・アクセストークン・リフレッシュトークンを返却します。
⓬ Kong Gateway の OIDC プラグインは、受け取ったトークンを検証します。トークンの署名・有効期限を確認して、OKだったら、(セッション変数にKeycloakから受け取ったトークンを保存した後、)
⓭⓮ セッションID (SID) の入ったクッキーをクライアントに食わせてから、認証済みリクエストとして❶のアクセス先にリダイレクトさせます。
⓯⓰ Kong Gateway の OIDC プラグインは SID を検証し、OKだったら SID に紐づくIDトークンとアクセストークンをセッション変数から取り出しアップストリームの httpbin への要求電文に追加して呼び出します。
⓱ アップストリームの httpbin は受け取った情報を応答電文に設定して Kong Gateway に返します。
⓲ Kong Gateway は httpbin から受け取った応答電文をクライアントに返します。

:::note
httpbin は単に呼び出し内容を返すだけですが、実際のアップストリームのWebサービスでは IdP から受け取った ID トークンによりアカウントを特定し、該当アカウント向けのサービスを実行します(⓰⓱)。
:::

# 実行環境の構築

Docker Compose を使って実行環境を構築します。

```yaml: compose.yaml
---
services:
  kong:
    image: "kong/kong-gateway:${KONG_VERSION:-3.8.0.0}"
    environment:
      KONG_ROLE: "data_plane"
      KONG_DATABASE: "off"
      KONG_PROXY_LISTEN: "${KONG_PROXY_LISTEN:-0.0.0.0:48000}"
      KONG_VITALS: "off"
      KONG_CLUSTER_MTLS: "pki"
      KONG_CLUSTER_CONTROL_PLANE: "${CONTROL_PLANE}"
      KONG_CLUSTER_SERVER_NAME: "${SERVER_NAME}"
      KONG_CLUSTER_CERT: "${CERT}"
      KONG_CLUSTER_CERT_KEY: "${CERT_KEY}"
      KONG_LUA_SSL_TRUSTED_CERTIFICATE: "system"
      KONG_KONNECT_MODE: "on"
    ports:
      - "48000:48000"

  keycloak:
    image: "keycloak/keycloak:23.0"
    ports:
      - 48080:8080
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    command:
      - start-dev

  httpbin:
    image: kennethreitz/httpbin
    ports:
      - '40080:80'

```

:::note warn
実行ホストの都合によりポート番号はデフォルト+40000に変更しています。
バッティングがなければデフォルトのポート番号でOKです。
:::

### 起動

```shell-session: 起動
$ cd oidc-test
$ ls -la
-rw-r--r--. 1 ec2-user ec2-user     1021 Dec  4 09:27 compose.yaml
-rw-r--r--. 1 ec2-user ec2-user     1132 Nov 29 10:55 .env
$ docker compose up -d
[+] Running 3/3
 ✔ Container oidc-test-httpbin-1   Started                        0.5s 
 ✔ Container oidc-test-kong-1      Started                        0.5s 
 ✔ Container oidc-test-keycloak-1  Started                        0.5s
$ 
```

起動後、それぞれ以下のホスト名でアクセスできます。

- Keycloak: http://IPアドレス:48080/
- httpbin: http://IPアドレス:40080/
- Kong Gateway: http://IPアドレス:48000/

### 各コンテナの設定内容

#### Kong Gateway コンテナ
Kong Konnect を Hybrid モードで起動する設定にしています。

- compose.yaml での設定内容

| キー | 値 | 内容 |
| --- | --- | --- |
| image | kong-gateway:3.8.0 | Kong Gateway コンテナを使用 |
| environment | KONG_ROLE: "data_plane" | Kong Konnect データプレーンとして使用 |
| | KONG_DATABASE: "off" | DB-lessモードで使用 |
| | KONG_VITALS: "off" | 監視機能をOFFにします |
| | KONG_CLUSTER_CONTROL_PLANE: "${CONTROL_PLANE}" | Kong Konnect コントロールプレーンのホスト名:ポート番号 |
| | KONG_CLUSTER_SERVER_NAME: "${SERVER_NAME}" | Kong Konnect サーバ名 |
| | KONG_CLUSTER_CERT: "${CERT}" | Kong Konnect で使用するために必要 |
| | KONG_CLUSTER_CERT_KEY: "${CERT_KEY}" | Kong Konnect で使用するために必要 |
| | KONG_LUA_SSL_TRUSTED_CERTIFICATE: "system" | Kong (Lua) ではOSのルート証明書を使用 |
| | KONG_KONNECT_MODE: "on" | Kong Konnect で使用するために必要 |
| ports | "48000:8000" | Kong の API Gateway のポート 8000 を 48000 で公開 |

`${CERT_KEY}` のような部分には環境変数が展開されます(この例では `CERT_KEY`)。
`docker compose up -d` 実行時にそれぞれの環境変数を指定するか、または、 compose.yaml ファイルと同じフォルダに .env ファイルを以下の内容で配置すると、設定した値が展開されます。

```
# Kong Konnect コントロールプレーン
CONTROL_PLANE="クラスタにつけられるID.usまたはeu.cp0.konghq.com:443"
# Kong Konnect ベースホスト名 
SERVER_NAME="クラスタにつけられるID.usまたはeu.cp0.konghq.com"

# Kong Cluster で使う証明書
CERT="-----BEGIN CERTIFICATE-----
(省略)
-----END CERTIFICATE-----"

# Kong Cluster で使う秘密鍵
CERT_KEY="-----BEGIN PRIVATE KEY-----
(省略)
-----END PRIVATE KEY-----"
```

:::note
Kong Konnect データプレーンの実行については[次の記事](https://qiita.com/m5takeda/items/162c690b9dbf393246af#%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%A8%E7%A7%98%E5%AF%86%E9%8D%B5%E3%81%AE%E7%94%9F%E6%88%90)が参考になると思います。
:::

https://qiita.com/m5takeda/items/162c690b9dbf393246af

#### Keycloack コンテナ
Keycloak を開発モードで実行します。
データストアを外部に用意せず、 Keycloak に追加したユーザでログイン動作を確認します。

- compose.yaml での設定内容

| キー | 値 | 内容 |
| --- | --- | --- |
| image | quay.io/keycloak/keycloak:23 | Keycloak 23 の Docker イメージを使用 |
| ports | "48080:8080" | Keycloak の実行ポート 8080 を 48080 で公開 |
| environment | KEYCLOAK_ADMIN: admin | 初期管理ユーザのログインID |
| | KEYCLOAK_ADMIN_PASSWORD: admin | 初期管理者の初期パスワード |
| command | start-dev | 実行コマンド (Dockerfile での CMD 上書き)<br/>開発モードでの実行 |

:::note
Keycloak も Kong Gateway も HTTP で動作させるため、 HTTP での Cookie の取り扱いで問題が出にくい Keycloak 23 を使っています。
:::

:::note warn
[最新のドキュメントの説明](https://www.keycloak.org/getting-started/getting-started-docker#_start_keycloak)とは環境変数名が異なりますが、Keycloak 23 ではこの設定で動作します。
:::

#### httpbin コンテナ
ユーザのWEBサービスに相当するモックアップです。

- compose.yaml での設定内容

| キー | 値 | 内容 |
| --- | --- | --- |
| image | kennethreitz/httpbin | httpbin コンテナイメージを使用 |
| ports | "40080:80" | httpbin の実行ポート 80 を 40080 で公開 |

:::note
実行環境から http://httpbin.konghq.com/ にアクセスできる場合は、このコンテナは実行せず、 Service として http://httpbin.konghq.com/ をアップストリームに指定することができます。
:::

### 設定

Kong Konnect と OIDC プラグイン / Keycloak それぞれに対して設定をします。

:::note
[Kong のサイト](https://docs.konghq.com/hub/kong-inc/openid-connect/how-to/authentication/authorization-code-flow/#authorization-code-flo)では private_key_jwt によるクライアント認証で説明されていますが、ここでは client_secret_basic で動作を確認します。
:::


#### Kong Konnect の設定
Konnect の管理画面から以下を設定します。
1. httpbin をアップストリームのアプリケーションに指定して Service を追加
2. テスト用の Route を作成し、対象の Service に割り付ける
3. テスト用の Route に対して OpenID Connect プラグインを設定する

##### 1. Service の追加
local-httpbin という名称で、ローカル実行の httpbin をアップストリームに指定します。

![service-local-httpbin-masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/073c19ad-ea1a-e4e2-ce60-4aeb008f4405.png)

##### 2. Route の作成

mock-route という名称で、 local-httpbin Service に Route を追加します。
/mock-api (http://IPアドレス/mock-api) へのアクセスを local-httpbin ( http://IPアドレス:40080/ で起動しているダミーホスト) に割り付けます。

![route-mock-api-masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/4aaeb761-41d5-9d86-8bb8-acb129230541.png)

##### 3. OIDC プラグインの設定
左のメニューから Plugin を選択し、 OpenID Connect プラグインの Enable のリンクをクリックします。

![choose-plugin-masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/d2fb8c2f-1924-a757-8428-28feb9a93f1f.png)

設定画面が現れます。最初に現れた画面で基本項目と共通 (Common) 項目を設定します。

- プラグインを特定の Route にのみ割り付けるため、`Scoped` を選択します。
- Route は直近で追加した `mock-api` を使用します。
- OIDCプラグインは同一サービスでもそれぞれ別の設定で使われがちなため、必須ではありませんが、名前を付けます (`oidc-test`)。
- Client ID をここでは `kong` にします。Keycloak で設定するクライアント名がこれになります。
- `Authorization Code Flow` にチェックを入れ、`Enable Session Management` を ON にします。

![oidc-plugin-common-masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/56107884-ab0a-0aed-af4e-68740fad5dc6.png)

:::note
クライアントシークレットは Keycloak で発行したものを設定します。
:::

#### Keycloak の設定

Keycloak のバージョン/方式が異なるため、[公式サイトでの説明](https://docs.konghq.com/hub/kong-inc/openid-connect/how-to/authentication/authorization-code-flow/#prerequisites)とは見た目や画面遷移が少々異なります。

##### 1. Client ID の追加
Keycloak に Client ID として kong を追加します。

Clients メニューより Create client ボタンを押して新規にクライアントを追加します。 
![keycloak-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/8682450a-8c0d-40df-ba85-54277a2a672d.png)
Client ID として kong を設定します。
![keycloak-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/99cf3b59-49c5-a58a-a81f-1c3f0e73e595.png)

以下の項目を ON にします。
Client authentication: on
Authorizaton: on
Authentication flow: ✓
Direct access grants: ✓

![keycloak-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/76c3e0f5-d2cc-6ab3-e79f-9d27a7286653.png)

有効なリダイレクトURIを設定します。
(Kong の Route (mock-api) に対応する URL です) 
![keycloak-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/9ff1d4c4-a131-e20b-02d1-88c89f1d737e.png)

ここで保存(Save)します。

##### 2. シークレットの確認と設定
Credentials タブを開いて、シークレットを取得し、Kong Konnect の OIDC プラグインに設定します。
- KeyCloack からシークレットを取得
Client Authentication が Client Secret でなければ Client Secret に変更してください。
画面から取得できます。Client Secret が📋ボタンでコピーできない場合は、👁をクリックすると表示することができます(範囲選択でテキストをコピーできます)。
![client-secret-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/79ae7f44-157a-7760-e961-229f99048825.png)

- Kong へのクライアントシークレットの設定
Kong Konnect の管理画面でシークレットを設定します。
シークレットは Common タブで設定できます。
![client-secret-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/06352008-dd65-cfb4-b72e-899f31407764.png)

##### 3. ログインユーザの追加
Users メニューからテスト用のユーザを追加(Add user)します。
![keycloak-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/b5bab9ae-97f3-70f1-0765-91469ebe071e.png)

ログイン名は oidc-user と設定しています。ログインテスト用なので、 email によるユーザ確認はスキップします (Email verified を ON に設定)。
![keycloak-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/f30ce119-67ce-09e4-9e59-c026f87fb77c.png)

Save ボタンで保存したら、 Credentials タブに移動します。
![keycloak-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/f8c55263-7660-548d-d16d-68a924f1d7a0.png)

Set Password ボタンからパスワードを設定してください。
Temporary のスイッチは切っておいてください(初回ログインにパスワードの変更を求められません)。

![keycloak-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/9e6cb0b2-65e1-c7ab-7c44-9f296758b3a9.png)

これで、Keycloak に oidc-user というアカウントが作成されました。
以後、KeyCloak を OIDC の IdP として使用できます。

:::note
ここでは master レルムにユーザを追加していますが、開発・テストなどある程度の期間使う場合はレルムを追加し、そのレルムに対してクライアントおよびユーザを追加するようにしてください。
:::

:::note

Keycloak の起動直後は localhost 以外から http で接続できません。
管理画面表示には https が必要ですが、証明書のエラーを無視できない場合には CLI で http でアクセスできるように設定することができます。

<details><summary>http でも管理画面を出すようにする手順</summary>

Keycloak の Docker コンテナが起動したら、Keycloak の管理用シェルスクリプトを使って設定を変更します。

1\. 実行中のコンテナから Keycloak を探す
```shell-session
$ docker ps
CONTAINER ID   IMAGE                              COMMAND                  CREATED              STATUS              PORTS                                                                                                NAMES
7ed1039dc8c3   quay.io/keycloak/keycloak:26.0.6   "/opt/keycloak/bin/k…"   About a minute ago   Up About a minute   0.0.0.0:8080->48080/tcp, [::]:48080->8080/tcp, 9000/tcp, 0.0.0.0:48443->8443/tcp, [::]:48443->8443/tcp   oidc-test-keycloak-1
$ 
```
2\. 見つけたコンテナで shell を実行する
```shell-session
docker exec -it 7ed1039dc8c3 /bin/bash
```
3\. CLIツールにより管理ユーザーでログインし、realm (master) の設定を変更する
```shell-session
bash-5.1$ cd /opt/keycloak/
bash-5.1$ ./bin/kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin
Logging into http://localhost:8080 as user admin of realm master
Enter password:
bash-5.1$ ./bin/kcadm.sh update realms/master -s sslRequired=NONE
bash-5.1$ 
```


**admin でログインできれば**、管理画面からも設定できます。
デフォルト値は All requests (常に HTTPS 必要)です。

![keycloak-require-ssl-none.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/2f1e0580-12aa-8f93-3e08-de10a525798e.png)

</details>

参考記事: https://www.mtioutput.com/entry/keycloak-admin-httpserror

:::

:::note alert

http での管理画面表示は開発時・動作確認時のみにとどめてください。

:::

# 実行確認

実行確認はあっさりしたものです。

`http://データプレーンの動作しているホストのIPアドレス:48000/mock-api/anything`

に対してアクセスすると Keycloak のログイン画面が出ます。
最初に提示したシーケンス図の❶～❹にあたります。

![login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/755fd58e-9f69-f07f-e8e2-b8eb689fac97.png)


ID/パスワードを入力した後は、通常通りの応答電文が得られます。
最初に提示したシーケンス図の❺～⓲にあたります。

![mock-api-anything.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/5021c545-053f-f111-4372-402286b78877.png)


要求ヘッダには、ブラウザからのものに加えて、Kong と OIDC プラグインが付けたトークンが追加されており、Web サービスでユーザー情報などを取得できます。

- トークンの内容の確認
    ピリオドで区切られたトークンの1番目と2番目のJSONの内容を確認してみます。一部バイナリ値があるので jq がエラーになりますが、表示はできます。
![oidc-decode-token.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935352/542028f3-6076-d529-cec4-83d90a2e0f5e.png)

:::note
スクリーンキャプチャには一部塗りつぶした部分があります。
:::

# おわりに

OpenID Connect ではさまざまな認証方法が選択できますが、その分設定項目が多く煩雑になりがちで、 Web サービス自体に組み込むとテスト・保守がしにくくなります。Webサービス側で本来の目的に直接関係しない設定項目の面倒もみなければなりません。

Kong Gateway に OpenID Connect プラグインを追加することで、Web サービスへのログインを Web サービスから切り離しやすくなり、テスト計画や保守をしやすくすることができます。

Kong Gateway をうまく使って、実装・運用・保守の負荷を下げて行きましょう。