社内Proxy下でKong Gateway(Konnect)を利用する

以前「[Kong GatewayをProxy経由で利用する](https://qiita.com/ipppppei/items/5749cb7f3db534651b51)」というタイトルでForward Proxy Advanced Pluginを使っての接続方法を紹介した。
今回は逆方向でKongのコンポーネントを中に置き、外部のサービスに繋ぐようなケースを検証する。
社内Proxyが用意されていて、インターネットにアクセスする際にはそのProxy経由でないとアクセスできない会社は結構な数あると思う。
そういった会社の人がKong Gatewayを使う際に参考にしてもらえれば幸いである。
なお、今回はKonnectを使い、以下のような構成を検証する。
![20241203085943.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/fb2e8a70-b8b6-b3af-b925-29e7bbcaa085.png)

KonnectもKong Gatewayも基本的にはデプロイの仕方はそれほど変わらないので、Kong Gatewayで試す方はパラメータ設定部分などを上手く取り込んでもらえればと思う。

## 前提条件
以下の条件で検証する。
- ProxyはSquidを利用
- Data Plane、SquidはそれぞれAmazon Linux上に構築
- Data PlaneはDockerで構築

なお、以下は既に実施済みのものとする。
- EC2の払い出しやネットワーク設定
- KonnectのControl Plane作成
- EntraIDでのアプリケーション作成

## 構築・検証
以下の手順で構築・検証する。

1. Proxyの構築
2. Data Planeの構築
3. 外部サービスとの接続
4. OIDCとの連携確認

なお1の手順は前回記事の[こちら](https://qiita.com/ipppppei/items/5749cb7f3db534651b51#proxy%E3%81%AE%E7%94%A8%E6%84%8F)と一緒なので割愛する。

### Data Planeの構築
#### Dockerの有効化
Proxyのアドレスを環境変数に設定する。
```sh
export PROXY=http://172.31.66.0:3128
```
次に念の為Proxy経由で外部のサービスにアクセス出来ることを確認する。
```sh
$ curl -x http://172.31.66.0:3128 http://httpbin.org/user-agent
{
  "user-agent": "curl/8.5.0"
}
```
問題なければ`.bashrc`にProxyの環境変数を設定し、sourceコマンドで読み込む。
```sh
cat <<EOF >> ~/.bashrc
export HTTP_PROXY=$PROXY
export HTTPS_PROXY=$PROXY
export http_proxy=$PROXY
export https_proxy=$PROXY
export no_proxy="127.0.0.1,localhost"
export NO_PROXY="127.0.0.1,localhost"
EOF
.  ~/.bashrc
```
これで先程のcurlなどの環境変数を参照するコマンドはProxyの指定なしで外部にアクセスできるようになる。
```sh
$ curl http://httpbin.org/user-agent
{
  "user-agent": "curl/8.5.0"
}
```
次にDockerをインストールする。
今回はAmazon Linux 2023を使っているので[こちら](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/install-docker.html)の手順に従う。
```sh
sudo yum update -y
sudo yum install -y docker
sudo service docker start
sudo usermod -a -G docker ec2-user
```
ログインしなおせば`docker ps`が正常に動作する。
```sh
$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
```
[こちら](https://docs.docker.com/engine/daemon/proxy/#environment-variables)の手順に従い、docker daemonがProxyを使えるようDocker側に設定を追加する。
```sh
sudo mkdir -p /etc/systemd/system/docker.service.d
cat <<EOF | sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=$HTTP_PROXY"
Environment="HTTPS_PROXY=$HTTPS_PROXY"
EOF
```
変更を反映しdaemonを再起動する。
```sh
sudo systemctl daemon-reload
sudo service docker restart
```
Kong Gatewayのイメージがpull出来ることを確認する。
```sh
docker pull kong/kong-gateway
```
#### Data Planeの構築
KonnectのWebUIにアクセスし、`Gateway Manager`からControl Planeを選択後、`Data Plane Nodes`->`New Data Plane Node`からData Planeのデプロイ画面に遷移する。
Platformで`Linux (Docker)`を選択し、証明書を生成するとデプロイのためのdockerコマンドが表示されるのでこれをコピーする。
ここでProxyの設定を追加する。
ここでは以下のkong.confのパラメータを設定する。
- `proxy_server`：Proxyを利用する際のURLを指定
- `proxy_server_ssl_verify`：TLSの検証をするかどうか。ここではスキップするため`off`を指定。
- `cluster_use_proxy`：HybridモードでProxyを使う場合に指定。KonnectはHybridモード相当なので`on`を指定

Dockerでデプロイする場合、kong.confの設定は`KONG_`を頭に付けて大文字にした環境変数を渡してあげることで設定できる。
そのため、以下のパラメータを追加してdocker runを実行すればよい
```sh
-e "KONG_PROXY_SERVER=$HTTPS_PROXY" \
-e "KONG_PROXY_SERVER_TLS_VERIFY=off" \
-e "KONG_CLUSTER_USE_PROXY=on" \
```
追加して起動すると無事にData Planeとして認識される。
![20241202190802.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/253611af-87cb-8d35-92f8-f9ae3172e1ca.png)

### 外部サービスとの接続
Konnect上にService、Routeを作成し、Routeに対して[Forward Proxy Advanced Plugin](https://docs.konghq.com/hub/kong-inc/forward-proxy/)を設定して外部サービスと通信できるか確認する。
ここではそれぞれのエンティティの作成はAPIを利用して行う。
API利用のため、最初にKonnectのトークン、ControlPlaneのID、KonnectのAPIエンドポイントをそれぞれ環境変数に設定する。
```sh
KONNECT_TOKEN=kpat_ER1WQxxxx
CP_ID=788397a0-b6f4-4dc8-a2f8-63cc6f0c62bb
KONNECT_API=https://us.api.konghq.com/v2
```
最初に外部サービスの[httpbin.org](https://httpbin.org/)と繋ぐためのエンティティServiceを作成する。
```sh
curl -s -X POST \
  "${KONNECT_API}/control-planes/${CP_ID}/core-entities/services" \
  -H "Authorization: Bearer $KONNECT_TOKEN" \
  -d name=httpbin-service \
  -d url=https://httpbin.org
```
作成時に表示されるServiceのIDを環境変数に設定する。
```sh
SERVICE_ID=75e92f55-5516-4173-8ad3-3491d6c51578
```
Proxy経由でService(httpbin.org)にアクセスするためのエンティティRouteを作成し、`<ProxyのIP:Port>/httpbin`でアクセスできるよう設定する。
```sh
curl -s -X POST \
  "${KONNECT_API}/control-planes/${CP_ID}/core-entities/routes" \
  -H "Authorization: Bearer ${KONNECT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "httpbin-route",
    "paths": ["/httpbin"],
    "service": {
      "id": "'"${SERVICE_ID}"'"
    }
  }'
```
作成したRouteのIDは後で使うので環境変数に設定しておく。
```sh
ROUTE_ID=0a8ca643-a2b2-4f1e-af17-fb39ce813b3c
```

Data Planeを構築したマシンからProxy経由で接続してみる。
```sh
$ curl -X GET -m 2 localhost:8000/httpbin
curl: (28) Operation timed out after 2002 milliseconds with 0 bytes received
```
今はForward Proxy Advanced Pluginの設定がないのでタイムアウトとなる。
Routeに対してForward Proxy Advanced Pluginを設定する。
```sh
curl -X POST \
  "${KONNECT_API}/control-planes/${CP_ID}/core-entities/routes/${ROUTE_ID}/plugins" \
  -H "Authorization: Bearer $KONNECT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "forward-proxy",
    "config": {
      "http_proxy_host": "172.31.66.0",
      "http_proxy_port": 3128,
      "https_proxy_host": "172.31.66.0",
      "https_proxy_port": 3128,
      "https_verify": false
     }
   }'
```
設定すると自動でプラグインが有効化されるので、再度curlを試してみる。
```sh
$ curl -X GET -m 2 localhost:8000/httpbin
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>httpbin.org</title>
    <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700"
        rel="stylesheet">
：（省略）
```
無事通信出来た。

### OIDCとの連携確認
最後におまけ的にOIDCとの連携も確認してみる。
OpenID Connect Pluginの設定には[Proxyの設定](https://docs.konghq.com/hub/kong-inc/openid-connect/configuration/#config-http_proxy)があるので、これが使えるかの確認となる。
今回はEntraIDとの接続確認を行う。
EntraIDを使ったPlugin設定は過去に「[APIの認証をEntra ID(旧Azure AD)で行う](https://qiita.com/ipppppei/items/a975dccf56d5f89a1ccf)」で説明しているので今回は説明は大幅に省略して進める。
分からない部分は適宜そちらの記事を参照して欲しい。

Issuer、クライアントID、クライアントSecret、テナントIDを環境変数に設定する。
```sh
AZURE_ISSUER=https://login.microsoftonline.com/f177c1d6-50cf-49e0-818a-xxxxxx/v2.0
AZURE_CLIENT_ID=36cd0496-ab59-4ffd-8925-xxxxxx
AZURE_CLIENT_SECRET=U_u8Q~Jq3RRbYixxxxx
AZURE_TENANT_ID=$(sed "s@.*.com/\(.*\)/v2.0@\1@g" <<< "$AZURE_ISSUER")
```
次にリダイレクトURIを設定する。リダイレクト先は`<ProxyのIP:Port>/<Routeのパス>`になる。
```sh
ENDPOINT=http://localhost:8000/httpbin
```
OIDC PluginをRouteに設定する。
```sh
curl -X POST \
  "${KONNECT_API}/control-planes/${CP_ID}/core-entities/routes/${ROUTE_ID}/plugins" \
  -H "Authorization: Bearer $KONNECT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "openid-connect",
    "config": {
      "issuer": "'"${AZURE_ISSUER}"'/.well-known/openid-configuration",
      "issuers_allowed": [
        "https://sts.windows.net/'"${AZURE_TENANT_ID}"'/",
        "https://login.microsoftonline.com/'"${AZURE_TENANT_ID}"'/v2.0",
        "https://login.windows.net/'"${AZURE_TENANT_ID}"'/v2.0"
      ],
      "client_id": ["'"${AZURE_CLIENT_ID}"'"],
      "client_secret": ["'"${AZURE_CLIENT_SECRET}"'"],
      "redirect_uri": ["'"${ENDPOINT}"'"],
      "display_errors": true,
      "scopes": [
        "openid",
        "email",
        "profile",
        "'"${AZURE_CLIENT_ID}"'/.default"
      ],
      "verify_parameters": false,
      "http_proxy": "http://172.31.66.0:3128",
      "https_proxy": "http://172.31.66.0:3128",
      "authorization_endpoint": "https://login.microsoftonline.com/'"${AZURE_TENANT_ID}"'/oauth2/v2.0/authorize",
      "token_endpoint": "https://login.microsoftonline.com/'"${AZURE_TENANT_ID}"'/oauth2/v2.0/token",
    }
  }'
```
前回記事と違う点として、`http_proxy`と`https_proxy`でProxyを指定しており、`authorization_endpoint`と`token_endpoint`でEntraIDの認可のエンドポイントとトークンのエンドポイントを指定している点である。
これ、試行方法によってエラーが出たり出なかったりして、必須なのかがよく分かっていないが、付けておけばとりあえず問題ないと思う。
なお、指定しなかった場合は以下のようなエラーが出る。
```json
{"message":"An unexpected error occurred (authorization endpoint was not specified)"}
```
以上でOIDC認証をProxy経由で利用できる。

設定後、curlで動作確認する。
curlで確認する場合はトークンが必要なので以下で発行する。
```sh
BEARER_TOKEN=$(curl -X POST "https://login.microsoftonline.com/${AZURE_TENANT_ID}/oauth2/v2.0/token" \
 --data scope="${AZURE_CLIENT_ID}/.default" \
 --data grant_type="client_credentials" \
 --data client_id="${AZURE_CLIENT_ID}" \
 --data client_secret="${AZURE_CLIENT_SECRET}"　\
 | jq -r '.access_token')
```
発行したトークンを使ってアクセスする。
```sh
$ curl -s $ENDPOINT/user-agent -H "Authorization: Bearer $BEARER_TOKEN"
{
  "user-agent": "curl/8.5.0"
}
```
問題なくアクセスできた。
またトークンがないとアクセスできないので、OIDC Plugin連携も適切に設定されていることが分かる。

## おわりに
今回は閉鎖環境でData Planeを構築し、Forward Proxy Advanced PluginやOpenID Connect Pluginを使って外部のサービスにアクセスしてみた。
Proxyの設定箇所がコンポーネントごとに出てきて手間ではあるが、設定すればProxy経由でも問題なく動作することが確認できた。
自社のProxyのせいでなかなかKong Gatewayの検証が出来なかった人はこちらの記事を参考に試していただければ幸いである。

## 補足
KongのServiceではなく[Upstream](https://docs.konghq.com/gateway/latest/get-started/load-balancing/)を使ってアクセスする場合、Forward Proxy Pluginを使ってUpstreamのTargetにアクセスすることが出来ない。
以下はSquidのログだが、Serviceのホスト名に指定したUpstream名を使ってProxyがアクセスしようとする。
```
2025/01/14 11:58:41.284      0 172.31.73.60 TCP_TUNNEL/503 0 CONNECT myupstream:443 - HIER_NONE/- -
```
Forward Proxyの[ドキュメント](https://docs.konghq.com/hub/kong-inc/forward-proxy/)をよくよく読むと以下の記述があり、バグではなくて仕様としてサポートしていないようである。
> Note that this plugin can’t be used with an upstream. As a workaround for load balancing, configure the host field in service to a domain name so that you can use a DNS-based load balancing technique.

Proxyが必要な環境でトラフィックを分散したい場合はDNSなどKong Gateway以外を使って分散する必要がある点に注意。