IP制限のALB配下アプリケーションをJWTオーソライザーでラップするレシピ 〜Cognitoを添えて〜

2025/01/12に公開

こんにちは。ダイの大冒険ガチ勢のbun913と申します。

皆さんは社内ネットワークからしかアクセスできないテスト環境に対して、「自動テストをCIから実行するときどうしよう?」と思ったことはありませんか?私はあります。

それを回避する方法はいくつか考えられますよね。

  • Staging環境のアプリケーション側で良い感じにしてあげる(ウフフ)
  • Basic認証などでアクセスに制限をかけた上で、IP制限を解除する
  • テストできる環境を用意する

それぞれのアプリケーションや業界の事情によって実践できない方法はあるものの、今回はできるだけ既存のアプリケーションやインフラに(あまり)手を加えずにテスト用の環境を用意する方法を考え、一つのTerraformサンプルをご用意しました。

前提条件

  • 以下のように External な ALBの配下にEC2 や ECS などで稼動するアプリケーションがある
  • ALB には Security Group などでIP制限がかかっている
    • 例えば社内の規定や業界の規定などにより、制限自体を無くしたり、不用意に広い範囲のIPからのアクセスを許可することができない

Base図

先にまとめ

  • 以下の2案を考えました
    • 案1: VPC内に自動テストを実施するECSタスク + Step Functionsなどを立てる
    • 案2: Cognito + API Gateway でアプリに手を加えずにJWTオーソライザーで認証する
  • Terraform で案2 をサクッと作るサンプルを作成してみました

idea2

案1 VPC内に自動テスト用の基盤を作る

非常に簡易的な図ですが、真っ先に以下のような基盤を思いつきました。

idea1

テストを実行するアプリケーションをECSなどで動作させ、Step Functions などで適宜実行していくような想定です。

想定するメリット・デメリット

  • メリット
    • ネットワーク的に外部からの接点を増やさないので、そのような要件のある環境に良さそう
    • 作っていて楽しそう
    • 時間課金などによるコスト要素が強いため、あまり頻繁に呼び出さない環境であればコストは案2より安そう
  • デメリット
    • テスト自動化のアプリケーションなどを管理するコスト(コンテナイメージ管理など)が発生する
    • 構築するコストも案2よりは高そう

真っ先にこちらの案が思いついたのですが、同僚に軽く相談した際に「ネットワーク的な要件にこだわるよりも、IdPなどを活用したらもっと楽できるのでは?」とアドバイスをもらいました。

個人的にも構築と運用のコストを考えると、案2の方が良さそうなので案2を考えることにしました。

案2 API Gateway などのマネージドサービスだけで認証付きのエンドポイントを作る

idea2

そこまでネットワーク的に厳しい要件がなく、「ちゃんと許可された人がアクセスできるStaging環境で良い」というような要件には手軽でマッチするかもしれません。

流れとしては以下のような形です。

  • 社内ネットワークからアクセスする場合
    • 特に意識することなく、これまでと同様にALBのエンドポイントにアクセスする (IPアドレスによる制限)
  • それ以外の場合
    • まず Cognito の トークン発行者エンドポイントからトークンを取得する
      • 今回用意しているサンプルではクライアントIDとクライアントシークレットの文字列を使用します
    • 次に取得したトークンを API Gateway (HTTP API) を含めて送信
    • API Gateway の JWT オーソライザーを使用して、自動でトークンの検証をしてもらう
    • 検証が成功した場合は、リクエストを VPC リンク経由でインターナルのNLBに転送
    • NLB は ALB に転送する
      • NLBのターゲットにALBを登録できるため、あまり難しいことは考えずにALBに転送するようにしました
      • 一見このNLBが余計に見えるかもしれませんが、VPCリンクを直接ExternalなALBに設定しても、うまく動作しませんでした(503エラーが返る。VPCフローログでも NODATA が返ってきており、調査が難しかったです)
      • もっと良い方法があればぜひ教えてください(曖昧な記述で申し訳ありません)

このようにすることで、既存のアプリケーション側には手を加えず、ALBのインバンウドルールを一つ追加するだけでテスト用のエンドポイントを用意できます。

想定するメリット・デメリット

  • メリット
    • 既存のアプリケーションに手を加えずに、テスト用のエンドポイントを用意できる
    • JWTの検証も含めて API Gateway がやってくれるので、新たにコンテナイメージなどを管理する必要がない
    • APIテストを実装するときにも、CIで自動実行するときも同じようにテストできる
  • デメリット
    • NLB の固定コストが必要

ソースコード

詳しく知りたい方は以下のリポジトリを参照ください。

https://github.com/bun913/api-gw-jwt-ip-limitation-alb-sample/tree/main/environments/dev

コードが長いので折りたたんでいます

既存の ALB とアプリケーションの設定

今すでに動いている ALB (IP制限がかかっている) と、その配下にあるアプリケーションを想定しています。
今はLambdaを使っていますが、ECSなどでも同じように設定できると思います。

https://github.com/bun913/api-gw-jwt-ip-limitation-alb-sample/blob/main/environments/dev/application.tf

この際 var.ip という形でIPアドレスを設定していますが、これは terraform.tfvars で設定しています。

Gitで管理していない情報であるため、 terraform apply する際には terraform.tfvars にIPアドレスを設定してください。

Cognito と API Gateway の設定

Cognito の User Pool や Client を作成して、クライアントIDとクライアントシークレットによる認証を行えるようにします。

https://github.com/bun913/api-gw-jwt-ip-limitation-alb-sample/blob/main/environments/dev/auth.tf

API Gateway のリソースを以下の点に留意しながら作成します。

  • JWT オーソライザーを設定する
    • 今回は HTTP Header の PreAuthorization にトークンを設定することを想定しています
      • Authorization ヘッダーを使うこともできますが、Authorization ヘッダーは普通にアプリケーションを使う際に認証で使うことが多いので、別のヘッダーを設定しました
    • これを設定することで Cognito から発行されたトークンを検証してもらうことができます
      • 自分で検証ロジックを書く必要がなくて本当に楽です
  • VPC リンクを設定する
    • API Gateway でトークンの検証が成功した場合、VPC リンクを経由して NLB に転送します
    • このとき、NLB は ALB に転送するように設定します

https://github.com/bun913/api-gw-jwt-ip-limitation-alb-sample/blob/main/environments/dev/exgw.tf

Outputs

最後に outputs.tf で API Gateway のエンドポイントなどを出力します。

https://github.com/bun913/api-gw-jwt-ip-limitation-alb-sample/blob/main/environments/dev/outputs.tf

リクエスト実演

まずは Cognito の認証情報を知りたいので、マネジメントコンソールから今回作成した Cognito のユーザープールのクライアントIDとクライアントシークレットを取得します。

clientInfo

次に、curl で以下のようにトークンリクエストを送信します。

curl --location '${terraform の output で出力されたoauth_urlの値を入力してください}' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=${取得したクライアントIDを入力してください}' \
--data-urlencode 'client_secret=${取得したクライアントシークレットの値を入力してください}'

以下のようにレスポンスが返ってくるので、 access_token の値をコピーします。

{
    "access_token": "アクセストークン",
    "expires_in": 86400,
    "token_type": "Bearer"
}

次に取得した access_token を利用して、以下のように API Gateway にリクエストを送信します。

curl --location '${terraform outputの base_url の値を入力してください}' \
--header 'PreAuthorization: ${先ほど入手したaccess_tokenの値を入れてください}' \
--data ''

これで 以下のようにレスポンスが返ってくれば、JWTオーソライザーを通過して、元のアプリケーションからのレスポンスが返ってきていることがわかります。

{"message":"Congrats! Your can reach the function!"}

なお、この時点で PreAuthorization ヘッダーに access_token 以外の値を入れると以下のように Unauthorized が返ってきます。

{"message":"Unauthorized"}

また、ついでに 特定のIPアドレスから直接ALBのエンドポイントにリクエストを送信しても、以下のようにリクエストが成功していることがわかります。

curl --location 'http://${terraform output のexternal_ip_limited_endpointの値を入力してください}'
{"message":"Congrats! Your can reach the function!"}

また、このエンドポイントに対してIPアドレスの制限がかかっているため、他のIPアドレスからリクエストを送信するとレスポンスは上のように帰りません。

まとめ

  • API Gateway の JWT オーソライザーや VPC リンクを使うことで、既存のアプリケーションに手を加えずに、テスト用のエンドポイントを用意できました
  • このようにAWS基盤側で入り口を用意しておくことで、テストを作る時もCIで実行するときも同じようにテストできるので良いですね
  • 今回はCognitoを使いましたが、他のIdPによる認証も可能です

以上、TerraformでAPI GatewayのJWTオーソライザーを使って、IP制限のかかったALBのエンドポイントをテスト用に使う方法をご紹介しました。

長文にも関わらず最後まで読んでいただき、ありがとうございました。

GitHubで編集を提案
Money Forward Developers

Discussion