APIで認証を行う場合、アプリケーション側で作り込む方法もなくはないが、アプリケーション側で作り込むとAPIが乱立した際に個々に実装するのは手間でありメンテも大変なので、API Gateway側で認証を行うことが一般的である。
Kong GatewayをAPI Gatewayとして使った場合、OpenID Connectプラグイン(以下OIDCプラグイン)を使えば外部の認証サービスと連携して簡単にGateway側に認証の機能をもたせることが出来る。
今回はOIDCプラグインの設定方法や挙動の確認のために、素のnginxをEntra ID(旧Azure AD)の認証を使ってアクセスするようにしてみる。
なお、こちらのドキュメントを参考に検証した。
前提条件
以下があるものする。
- Kong Gateway
- サンプルアプリ&サービス
- 上記のアプリ&サービスに対応するService・RouteをKong Gateway上で作成済み
今回サンプルアプリとサービスはKong Gatewayの通信の遅延箇所をJaegerで確認するの記事で使ったこちらのnginxを使用する。
なお、サービスはTLSで暗号化されている必要がある。
サンプルアプリのサービスについてブラウザでアクセスし証明書を確認すると、Let's Encryptを使った証明書で接続していることが確認できるのでこれで進める。
また、サンプルアプリのホスト名を後で使うため、ここで環境変数に設定しておく。
MYHOST=nginx.eks.hogehoge.com
※前の記事を読んでいないと分かりづらいが、このアドレスはProxyからアクセスするRoute設定であり、localhostに立てた場合はlocalhost:8000/<Routeのpath>
部分になる。
事前準備
Entra IDの設定
OIDCプラグインを設定するにはEntra IDの以下の項目が必要となる。
- Issuer
- クライアントID(アプリケーションID)
- クライアントSecret(シークレットIDに対応する値)
これらの値を取得するためにEntra ID側でアプリケーションを作成して各値を取得する。
アプリケーションの作成方法についてはここでは割愛する。手順が必要な場合は過去に書いたこちらの記事を参考にすると良い。
環境変数に取得した値をそれぞれ設定する。
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
また、テナントIDが必要になるのでIssuerから引っこ抜く形で以下で設定しておく。
AZURE_TENANT_ID=$(sed "s@.*.com/\(.*\)/v2.0@\1@g" <<< "$AZURE_ISSUER")
OIDCプラグインの設定
Admin APIのエンドポイントを環境変数に設定する。
ADMIN_API=https://kong.hogehoge.com/api
サンプルアプリのServiceMYHOST
に対応するRouteのIDを取得する。
ROUTE_ID=$(curl -k -X GET ${ADMIN_API}/routes | jq -r --arg host "$MYHOST" '.data[] | select(.hosts != null) | select(.hosts[] == $host) | .id')
念の為値が入っているか確認する。
$ echo $ROUTE_ID
da7eea27-3de1-552b-8f18-xxxxx
公式の手順を参考にAdmin API経由でRouteに対してOIDCプラグインを有効化する。
curl -i -k -X POST ${ADMIN_API}/routes/${ROUTE_ID}/plugins \
--data name="openid-connect" \
--data config.issuer="${AZURE_ISSUER}/.well-known/openid-configuration" \
--data config.issuers_allowed="https://sts.windows.net/${AZURE_TENANT_ID}/" \
--data config.client_id="$AZURE_CLIENT_ID" \
--data config.client_secret="$AZURE_CLIENT_SECRET" \
--data config.redirect_uri="https://$MYHOST" \
--data config.scopes="openid" \
--data config.scopes="email" \
--data config.scopes="profile" \
--data config.scopes="${AZURE_CLIENT_ID}/.default" \
--data config.verify_parameters="false"
なお、公式ドキュメントの手順だとconfig.issuers_allowed
は含まれていないが、これを指定しないと以下のようなエラーが出る(log_level
にdebug
を指定しないと出力されないので注意)。
invalid issuer (https://sts.windows.net/xxxxx/) was specified for access token, unknown error (not a table) was expected
これの回避策についてはこちらのナレッジに記載があり、これに従う形でパラメタconfig.issuers_allowed
で追加している。
動作確認
ブラウザでの確認
ブラウザのキャッシュを削除し、先程と同じようにブラウザ経由でアクセスしてみる。
open https://$MYHOST
上記のようにただのnginxのアクセスに簡単にEntra IDの認証を設定することが出来た。
curlでの確認
APIの認証なのでcurl
で出来ないとあまり意味がない。
ということでcurl
でアクセスするためにIntra IDからアクセストークンを取得する。
グラントタイプをclient_credentials
でトークンを取得した。
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')
curlを実行する。最初はトークンなしで試してみる。
$ curl -i https://$MYHOST
HTTP/2 302
:(省略)
MSの認証サイトに飛ばされるので302が返る。
トークンをつけてみる。
$ curl -i https://$MYHOST -H "Authorization: Bearer $BEARER_TOKEN"
HTTP/2 200
:(省略)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
:(省略)
無事アクセスできた。問題なさそうだ。
補足(追記)
この記事では手を抜いてconfig.issuers_allowed
に1つのURLしか指定していないが、本来はKBにあるように3つ書いた方がいい。
この記事を書いてしばらく経った後に同じ手順で設定したところ、認証突破後に以下のエラーが表示されてUpstreamのサービスに接続できなかった。
{"message":"Unauthorized}
config.display_errors="true"
を追加して再度プラグインを有効化してエラーを確認すると、以下のように表示されて記載漏れが確認できた。
{"message":"Unauthorized (required issuers were not found [ https://login.microsoftonline.com/f177c1d6-50cf-49e0-818a-xxxxxx/v2.0 ])"}
なので、issuers_allowed
はちゃんと書こうというのと、config.display_errors="true"
は開発環境とかであればとりあえず付けておくといいと思う。