はじめに
GAE、便利ですよね。
ちゃちゃっとデプロイして簡単に公開できる。
アプリのデプロイバージョン管理や、トラフィック管理、ABテストなんかもできちゃう。
1つのGCPプロジェクト内のGAEで、 サービス
を複数作成することで、マイクロサービスのような環境設定も可能です。
今回は、そんな感じで複数サービスにまたがるGAEサービス間での認証の話です。
やりたかったこと
- 複数のGAEサービス間で、サービスAはオープンなWebアプリ、サービスBはクローズドなAPIとし、サービスBはサービスAからしかアクセスされない。
- セキュリティ面を考慮して、サービスBはサービスAのみからアクセスを可能としたい。
- できればアプリ内での認証ではなく、より上位層(GCP)で認証したい
どうやったか
GAEにデフォルトで設定されるサービスアカウントで認証できないか
- GAEに1つでもサービスをデプロイすると、自動的に
[PROJECT-ID]@appspot.gserviceaccount.com
というサービスアカウントが作成されます。 - そのアカウントを使って認証できたら楽だなぁと思い、調べてみました。
Identity-Aware Proxy(IAP) を使えばいいのでは?
- いろいろ調べていると、Identity-Aware Proxy(IAP) という機能により、GAEのアプリよりも上位層で認証ステップを提供してくれるとのこと。
- 早速やってみよう!
- IAPを有効にしたらサービスAもサービスBも両方に適用されて、GAE全体が非公開になってしまった…
やりたいことは、サービスAはパブリックに公開、サービスBだけにIAPでアクセス制限をしたい
- どうしよう。
- この設定行うと、少し反映完了までラグがありますが、サービスAにアクセスする際は認証リダイレクトが行われずに、無事にサービスBだけを非公開にできます。
WebブラウザからのアクセスはGoogleの認証画面にリダイレクトされるけど、サーバーサイドでAPIアクセスするときにどう認証しよう
- さて、これでアクセス制限周りのGCP設定はできましたが、次はサービスAからサービスBのAPIを叩く際に認証が必要になります。
- このままの状態で普通にリクエストを投げると403エラーで認証が失敗すると思います。
- そこで、GAEのサービスアカウントの出番です。
プログラム側でTokenの取得
- GCP上でのアクセス権限を設定したので、実際にHTTPリクエストを送りましょう。
- HTTPリクエスト送信の際、GAEのサービスアカウントのそのリクエストに対してOICDトークンを付加してリクエストするとサービスアカウントでの認証が可能です。
-
こちらのページを参考に、以下のような処理を行います。
- ここではPythonで記述していますが、公式ドキュメントに他言語でのサンプルもありますので問題ないかと思います。
import requests
from google.auth.transport.requests import Request
from google.oauth2 import id_token
IAP_CLIENT_ID = "IAPの認証情報ページから取得したクライアントID"
def make_open_id_connect_token(client_id=""):
if not client_id:
client_id = IAP_CLIENT_ID
open_id_connect_token = id_token.fetch_id_token(Request(), client_id)
return open_id_connect_token
token = make_open_id_connect_token()
headers = {
"Authorization": f"Bearer {token}",
}
res = requests.get(url="https://サービスB/api/", headers=headers)
print(res.content)
-
make_open_id_connect_token()
でOICDトークンを取得し、それをリクエストヘッダーに含めてアクセスすることで認証されます。
さいごに
- これで当初やりたかった、GAEのサービス間でパブリックなWebサービスとクローズドなAPIとの通信が認証付きでできるようになりました。
- ただ、デメリットとして以下が挙げられます。
- 既に公開・運用されているサービスがある場合、IAPを有効にする際に一時的に全サービスに対してIAPでの認証が追加されてしまうので、ダウンタイムが発生する。
- allUsersを設定して全ユーザーがIAPをスルーしてアクセスできるようにしても、(おそらく)何かしらの認証ステップは有ると思うので、軽微だと思いますがレスポンスなどに影響がありそう。
- そのプロジェクトに新たにGAEでパブリックサービスを追加するたびに、allUsersの権限を追加してあげる必要がある。
- これらのデメリットを考慮して、うまく使い分けていくのが良いのかなと思います。
参考にさせていただいた記事
-
GAE 2nd-gen でのサービス間認証
- こちら非常に参考になりました。ありがとうございます。
- GoogleAppEngineのサービス間通信の認証などについて調べたこと(未解決)