GAE 2nd-gen でのサービス間認証
TL;DR
GAE 2nd-gen では X-Appengine-Inbound-Appid ヘッダの代わりに、ID Token + Identity-Aware Proxy を使った方式をサービス間認証に使えます。
はじめに
GAE でマイクロサービスを構成する場合、各サービス同士を呼び合うときに同一 GAE アプリからのリクエストであるかを確認したい場面があります。シンプルな例だと、サービスがフロントエンドとバックエンドに別れていて、バックエンドはフロントエンドからしか呼び出せないようにしたい場合です。
GAE 1st-gen では X-Appengine-Inbound-Appid ヘッダという魔法のヘッダがありました。このヘッダは URLFetch を使用して別の GAE サービスにアクセスする時に、GCP が自動で呼び出し元の Project ID を入れてくれるヘッダです。そのため呼び出し元の Project ID が自身の Project ID と同一かどうかを調べるだけで認証できました。
しかし GAE 2nd-gen では App Engine API がサポートされていないため、URLFetch API も使えず、X-Appengine-Inbound-Appid ヘッダも付かなくなりました。そのため 2nd-gen に移行するためには代替となる認証方法が求められています。
ID Token を使用した認証
その代替手段の一つが ID Token を使った認証です。
GAE 2nd-gen では Metadata Server にアクセスできるようになり、自身の ID Token を取得することができます。取得した ID Token を呼び出し先のサービスに渡してあげれば、受け取った側でその ID Token を検証し、意図した呼び出し元からのリクエストかどうかを確認することが出来ます。ID Token の検証方法はこのドキュメントに詳しく記載されています。
ID Token を使用すると他の GCP の Compute 系サービスと組み合わせて使えるのも大きなメリットです。
例えば Cloud Run は ID Token をリクエストの認証に使うことが出来ます。この機能は roles/run.invoker という IAM ロールを持った User, Service Account の ID Token を Authorization ヘッダに入れておけば、自動でリクエストを認証してくれるという機能です。Cloud Functions にも同様の機能があります。
つまり ID Token を使用することで、GAE-to-GAE に限らない様々な種類のサービス間通信の認証に使えるのです。
ただし、ID Token を使った認証はいくつかパフォーマンス上の注意点があります。
- サービス間通信をするたびに毎回 Metadata Server から ID Token を取得するとオーバーヘッドが大きいため、アプリ側で一度取得した ID Token を一定期間キャシュするのがいいと思います。但しリクエスト先に応じて ID Token の
aud
の値を変える必要があるので、audience 単位でのキャッシュが必要です。 - ID Token を受け取った側では、検証するために Google が提供しているエンドポイントから公開鍵をダウンロードする必要があります。これに関しても検証するたびにダウンロードするのではオーバーヘッドが大きいため、基本はキャッシュし、未知の鍵(
kid
)で署名されたトークンを受け取った場合のみ再度ダウンロードするのがいいと思います。定期的にダウンロードし直すバッチを動かしておくのもありですね。
Identity-Aware Proxy を認証に使用する
App Engine の場合、ID Token を受け取ったアプリケーションの中で ID Token を検証することもできますが、Identity-Aware Proxy (IAP) を置いてプラットフォーム側で検証させることも出来ます。
IAP にはリソースレベルでアクセスポリシーを制御することができ、GAE の場合各サービス or 各バージョンレベルでポリシーを設定できます。
例として以下の図のように、フロントエンドのサービスはパブリックにアクセス可能に、バックエンドのサービスはフロントエンドからのみアクセス可能なように設定したいとします。
このようなポリシーの設定を gcloud コマンドで行うには、以下のようにフロントエンドサービスには --member=allUsers
を指定し、バックエンドサービスには GAEのデフォルト Service Account である --member=serviceAccount:my-project@appspot.gserviceaccount.com
を指定します。
$ gcloud alpha iap web add-iam-policy-binding --resource-type=app-engine --service=frontend --project=my-project --member=allUsers --role=roles/iap.httpsResourceAccessor$ gcloud alpha iap web add-iam-policy-binding --resource-type=app-engine --service=backend --project=my-project --member=serviceAccount:my-project@appspot.gserviceaccount.com --role=roles/iap.httpsResourceAccessor
これでバックエンドは同一のプロジェクト内の GAE アプリからのみしかアクセスできないように制御できます。
実際に GAE にデプロイして試せるサンプルアプリもあるので良かったら試してみて下さい。
但し IAP で ID Token を検証させる場合にも、いくつか注意点があります。
- 現時点では GAE に対して IAP を有効にするかどうかはプロジェクトレベルです。一度有効にすると上で述べたようにリソースレベルでアクセスポリシーは制御できるものの、そのポリシーを設定するまでは全てのリクエストがシャットダウンされてしまうので、既存の GAE 1st-gen アプリをダウンタイムなしで移行する用途には不向きです。
- IAP を挟むとその分サービス間通信のレイテンシが大きくなることが想定されます。特にパブリックに公開したいサービスへのリクエストに対しても認証そのものは行われるので注意が必要でしょう。
このようにパフォーマンス上の影響もあるため、実際に採用する際にはサービス間通信のレイテンシに問題ないか、負荷試験等を通して確認するのがいいかと思います。
蛇足ですが、IAP を使うと GAE 1st-gen で存在した login: admin や login: required に似たようなことも実現できます。login: admin (管理者のみアクセス可能) を設定したい場合は、管理者のアカウントだけを IAP で許可するようにし、login: required (Google ログインしている場合のみアクセス可能) を設定したい場合は、allAuthenticatedUsers という特別なメンバーを IAP で許可すれば ok です。
まとめ
GAE 2nd-gen でサービス間通信の認証を行う方法の一つとして、ID Token と Identity-Aware Proxy を使う方法を紹介しました。ID Token を使うことで GAE-to-GAE に限らない様々な種類のサービス間通信の認証に使えるので、GAE, Cloud Functions, Cloud Run 等を組み合わせてマイクロサービスを構成している環境で汎用的に使えると思います。