Kong GatewayのAdmin APIをKong Gateway自身で保護する

Kong GatewayのAdmin APIはKong GatewayをAPIで操作することが出来る強力な機能であるが、強力が故に保護したいと考えることもあると思う。
前段にWAF/LBを置いて保護することも出来るが、設定次第ではKong Gateway自身でAdmin APIを保護することも出来る。
ここではそのやり方を紹介する。

## 仕組み
まず基礎知識としてKong Gatewayで提供されるProxy機能とAdmin API機能は同一のバイナリで提供され、待受ポートによって役割を変えている。
![20250122095845.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/2a458b14-96dc-2f8c-74f4-458330f08849.png)

また、Admin APIはどのIPで待ち受けるかはkong.confの[`admin_listen`](https://docs.konghq.com/gateway/latest/reference/configuration/#admin_listen)で指定する。

これを組み合わせると以下のような感じでProxy経由の通信のみ受け付け、その他の呼び出しは受け付けないようにすることが出来る。
![20250122100533.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/a42390e7-d30e-a3b5-e1e8-94a1edafe381.png)

更にはProxy経由なので、PluginなどのKong Gatewayの機能も付与することが出来る。
これによってAdmin APIに認証を追加したり、レート制限を掛けたり、様々な設定を追加することが可能となる。

## 検証
ここではdockerを使って起動し、動作確認してみる。
以下の手順でKong Gatewayを立ち上げる。
1. DockerのNetworkを作成
2. Postgresの起動
3. DBの初期化
4. Admin APIのためのService/Route設定をDBに書き込み
5. Kong Gatewayを起動

Kong Gatewayの起動前にService/Route設定をDBに書き込んでいるのは、起動したタイミングで既にAdmin API用のServiceとRouteがないとAPIを叩こうにも叩きようがなくなるためである。

PostgresとKong Gatewayが接続できるようNetworkを作成する。
```sh
docker network create kong-net
```
Postgresを起動する。
```sh
docker run -d --name kong-database \
  --network=kong-net \
  -p 5432:5432 \
  -e "POSTGRES_USER=kong" \
  -e "POSTGRES_DB=kong" \
  -e "POSTGRES_PASSWORD=kongpass" \
  postgres:16
```
利用するKongのバージョンを環境変数に設定後、DBを初期化する。
```sh
VERSION=3.9.0.0
docker run --rm --network=kong-net \
 -e "KONG_DATABASE=postgres" \
 -e "KONG_PG_HOST=kong-database" \
 -e "KONG_PG_PASSWORD=kongpass" \
kong/kong-gateway:$VERSION kong migrations bootstrap
```
次にAdmin APIのService/Routeを定義する。
DB lessモードで起動する時に使う[フォーマット](https://docs.konghq.com/gateway/3.9.x/production/deployment-topologies/db-less-and-declarative-config/)で定義ファイルを作成する。
```yaml
cat <<EOF > ./admin-api.yaml
_format_version: "3.0"
_transform: true

services:
- name: admin-api-service
  url: http://localhost:8001
  routes:
  - name: admin-api-route
    paths:
    - /admin-api
EOF
```
ここでは以下の仕様とした。
- Service:
  - ホスト：`http://localhost:8001`
- Routes:
  - パス：`/admin-api`

これにより、Proxyの`/admin-api`にアクセスするとAdmin APIが利用できるようになる。
作成したYAMLを使ってDBに設定を反映する。
これには`kong config db_import`コマンドを使う。
```sh
docker run --rm --network=kong-net \
 -v ./admin-api.yaml:/tmp/admin-api.yaml \
 -e "KONG_DATABASE=postgres" \
 -e "KONG_PG_HOST=kong-database" \
 -e "KONG_PG_PASSWORD=kongpass" \
 kong/kong-gateway:$VERSION kong config db_import /tmp/admin-api.yaml
```
なお、KongのService/Route設定を読ますのに[declarative_config](https://docs.konghq.com/gateway/latest/reference/configuration/#declarative_config)が使えそうと思ってしまうが、これは[database=off](https://docs.konghq.com/gateway/latest/reference/configuration/#database)を指定した時のみ動作し、今回はDBありで検証しているため利用できない。

設定を反映したらKong Gatewayを起動する。
```sh
docker run -d --name kong-gateway \
 --network=kong-net \
 -e "KONG_DATABASE=postgres" \
 -e "KONG_PG_HOST=kong-database" \
 -e "KONG_PG_USER=kong" \
 -e "KONG_PG_PASSWORD=kongpass" \
 -e "KONG_ADMIN_LISTEN=localhost:8001" \
 -p 8000:8000 \
 -p 8001:8001 \
 kong/kong-gateway:$VERSION
```
`KONG_ADMIN_LISTEN=localhost:8001`を指定し、Admin APIのアクセス元を同一コンテナ内からのアクセスに限定している。
なお、ポート8001はProxy経由で使うので公開する必要はないが、外部からアクセスできないことを検証するために空けている。
以上で起動手順は終了となる。

## 確認
dockerの仕組みの話なので説明は割愛するが、dockerでポートをマッピングしてlocalhostにアクセスした時、ネットワークをブリッジしてアクセスするためコンテナ側から見るとlocalhostにはならない。
そのため`localhost:8001`にアクセスしても`admin_listen`で指定したアドレスと一致せずAdmin APIにはアクセスできない。
```sh
$ curl localhost:8001
curl: (52) Empty reply from server
```
Proxy経由でアクセスしてみる。
```sh
$ curl localhost:8000/admin-api
{"plugins":{"enabled_in_cluster":[],"available_on_server":{"redirect":true,"standard-webhooks":true,"ai-response-transformer":true,"ai-request-transformer":true,"ai-prompt-guard":true,
：（省略）
```
Proxy経由でのみアクセスできた。
追加で、Admin APIにレート制限を掛けてみる。
```sh
ADMIN_API=http://localhost:8000/admin-api
curl -X POST $ADMIN_API/services/admin-api-service/plugins \
    --data "name=rate-limiting" \
    --data "config.minute=3"
```
`config.minute=3`を指定し、1分間のアクセスの上限を3回まで
この状態でAdmin APIにcurlで5回連続でアクセスし、-wオプションを使ってステータスコードだけ表示する。
```
for((i=0;i<5;i++)); do curl -o /dev/null -s -w "%{http_code}\n" localhost:8000/admin-api; done
```
結果は以下となった。
```sh
200
200
200
429
429
```
４回目以降は429となってアクセスが禁止され、Admin APIにレート制限によって保護されたことが確認できた。

以上よりAdmin APIをKong Gatewayを使って保護できることが確認できた。