Kong GatewayのSAML PluginでリクエストにSAML認証を挟む

Kongではリクエストの認証にSAMLを使うプラグインが用意されており、これはサービスプロバイダ(SP)として機能し、リクエストにSAML認証を挟みたい場合、IdPへのログインを手助けしてくれる。
ここではEntra IDを使って簡単な使い方を検証する。

プラグインのドキュメントは[こちら](https://docs.konghq.com/hub/kong-inc/saml/)。
ちなみにSAMLプラグインに関する詳細な公式ブログが以下用意されている。
- [How to Configure SAML Plugin using Kong and Azure AD](https://konghq.com/blog/engineering/kong-saml-plugin-examples-and-uses)
- [Configure SAML 2.0 Single Sign-on with Kong Enterprise](https://konghq.com/blog/engineering/configure-saml-2-0-single-sign-on-with-kong-enterprise)

こちらも併せてチェックしておくと良いと思う。

## SAMLプラグインについて
SAML PluginはSPとして動作し、実際に動作する際は以下のようなフローとなる。
```mermaid
sequenceDiagram
    participant B as エンドユーザ（ブラウザ）
    participant K as サービスプロバイダ（SAML Plugin）
    participant I as IdP（Entra ID）

    B->>K: ① リソースへのアクセスを要求
    K-->>B: ② SAMLリクエストをブラウザにリダイレクト
    B-->>I: ③ SAMLリクエストをIdPへ中継
    I-->>B: ④ SAMLアサーションを生成
    B-->>K: ⑤ SAMLアサーションをKongに中継
    K-->>B: ⑥ 認証に基づきリソースへのアクセスを許可または拒否
```

このフローに関連するプラグインの設定項目はおそらく以下のような感じとなる。
|設定項目|意味|
|---|---|
|`idp_sso_url`|Entra IDのサインオンURLで③④で使用|
|`issuer`|Entra IDのアプリケーションの識別子で④で使用|
|`assertion_consumer_path`|SAMLからの応答のためのエンドポイントで`https://Proxy/<Route>/指定値`がフルパスとなる。⑤で使用|

その他必須の設定項目として、`session_secret`がありセッションデータを暗号化するための秘密鍵として利用される。
また、アサーションの検証のために`idp_certificate`も指定が必要だが、アサーションの検証を省略する場合は省略可能で、その際は`validate_assertion_signature`に`false`を指定する。


## 前提条件の準備
始めるにあたり、Kong Gatewayは起動済みで、HTTPSで利用するものとする。
### Kong Gateway側の用意
最初にConsumerを用意する。
SAMLで使うユーザとKong GatewayのConsumerをマッピングして使う方法があるのだが、今回は簡略化してanonymousで進める。
ということで以下でanonymousのConsumerを作成する。
```sh:consumer
curl -i -k -X PUT https://localhost:8444/consumers/anonymous
```
次にServiceとRouteを作成する。
ここでは[https://httpbin.konghq.com](https://httpbin.konghq.com)へのアクセスを`/httpbin`で転送する設定とする。
```sh:service
curl -i -k -X POST https://localhost:8444/services \
  -d name=httpbin_svc \
  -d url='https://httpbin.konghq.com'
```
```sh:route
curl -i -k -X POST https://localhost:8444/services/httpbin_svc/routes \
  -d 'paths[]=/httpbin' \
  -d name=httpbin_rt
```

### Entra ID側の用意
Entra IDでSAML認証用にエンタープライズアプリケーションを作成する。
作成方法については「[Konnectのログイン認証をEntra IDのSAMLで行う](https://qiita.com/ipppppei/items/04241a6d48051cb40f03)」で紹介しているのでここでは割愛する。

作成後、`管理`->`シングルサインオン`からSAMLを選択する。
`基本的な SAML 構成`の編集ボタンを押し、ここでは以下のように設定した。
- 識別子 (エンティティ ID)：`https://hoge.fuga/mysaml`
- 応答 URL (Assertion Consumer Service URL)：`https://localhost:8443/httpbin/saml-acs`

![20250409172442.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/b6e7e072-b73f-4be6-aef2-7a3c85fc5ac9.png)


エンティティIDはテナント内で一意であればいいので、適当に設定した。
応答URLは
- Kong Gatewayを介してのアクセス先URL＋`assertion_consumer_path` = `https://localhost:8443/httpbin/saml-acs`

ということで、後でPlugin設定時に`assertion_consumer_path`に`saml-acs`を指定するようにする。


設定後、アサーションの検証で利用するために③のSAML証明書の`証明書 (Base64)`の横の`ダウンロード`をクリックして証明書をダウンロードしておく。


また、後で設定値は環境変数に入れてPluginを登録するので、以下で設定値を環境変数に保存しておく。
`ENTRAID_IDENTITY_ID`には先程設定したエンティティIDを、`ENTRAID_SIGN_ON_URL`には④で取得できるログインURLを設定する。
```sh
ENTRAID_IDENTITY_ID=https://hoge.fuga/mysaml
ENTRAID_SIGN_ON_URL=https://login.microsoftonline.com/f177c1d6-50cf-49e0-818a-xxxxxxx/saml2
```

最後にこのアプリケーションにユーザも追加する。
`管理`->`ユーザーとグループ`から`ユーザーまたはグループの追加`をクリックしSAML認証で使うユーザを追加する。


## SAML Pluginの利用
最初にアサーションの検証をスキップ(`config.validate_assertion_signature=false`)にして試してみる。
以下でプラグインをService`httpbin_svc`に対して有効化する。
```sh
curl -i -k -XPOST https://localhost:8444/services/httpbin_svc/plugins \
 -H 'Content-Type: multipart/form-data' \
 -F name=saml \
 -F config.anonymous=anonymous \
 -F config.issuer=$ENTRAID_IDENTITY_ID \
 -F config.idp_sso_url=$ENTRAID_SIGN_ON_URL \
 -F config.assertion_consumer_path=/saml-acs \
 -F config.validate_assertion_signature=false \
 -F config.session_secret=12345678901234567890123456789012
```
上記のプラグインを設定した上で設定したService/Routeにブラウザでアクセスする。ここでは[https://httpbin.konghq.com/anything](https://httpbin.konghq.com/anything)にKong Gateway経由でアクセスしてみる。
```sh
open https://localhost:8443/httpbin/anything
```
アクセスするとMicrosoftに飛ばされる。
![20250409173426.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/0c4025d5-4157-4ba3-b21d-b97abf731a8b.png)

認証情報を入力して認証を突破すると正常にアクセスできたことが確認できる。
![20250409173653.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/579b2761-e88f-4469-8381-d1f0274a902b.png)

なお、Kongが付与するヘッダからanonymousでログインしていることも確認できる。

次にアサーションの検証をするよう証明書を設定し直す。
設定したプラグイン設定を一度削除し、プラグインを再度設定する。
まず、証明書を環境変数に設定する。[Certificate is valid, but isn’t being accepted](https://docs.konghq.com/hub/kong-inc/saml/#certificate-is-valid-but-isnt-being-accepted)によると証明書内のヘッダとフッタは削除した方がよいようなので、以下のような感じで環境変数に設定する。
```sh
IDP_CERT=$(grep -v CERTIFICATE saml-plugin-test.cer | tr -d '\r\n')
```
プラグインを設定する。
```sh
curl -i -k -XPOST https://localhost:8444/services/httpbin_svc/plugins \
 -H 'Content-Type: multipart/form-data' \
 -F name=saml \
 -F config.anonymous=anonymous \
 -F config.issuer=$ENTRAID_IDENTITY_ID \
 -F config.idp_sso_url=$ENTRAID_SIGN_ON_URL \
 -F config.assertion_consumer_path=/saml-acs \
 -F config.validate_assertion_signature=true \
 -F config.session_secret=12345678901234567890123456789012 \
 -F config.idp_certificate=$IDP_CERT
```
こちらもブラウザからアクセスして、問題なくアクセスできることを確認する。
```sh
open https://localhost:8443/httpbin/anything
```
設定ミスがなければ、先程と同じようにhttpbinの内容が表示されるはずだ。

せっかくなので、本当に証明書の内容を見ているか確認してみる。
プラグインを削除し、証明書を以下のように細工してプラグインを再適用する。
```s
IDP_CERT=$(echo -n $IDP_CERT | sed s/y/z/g)
```
この状態でアクセスすると以下のように401が返ってくる。
![20250409180541.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/60942d43-a723-4710-b5cc-873e40041299.png)

これにより、アサーションの検証も適切に行われていることが確認できる。