4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

同一GCPプロジェクト内でGAEサービス間をIAPで認証してHTTP通信する

Posted at

はじめに

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のアプリよりも上位層で認証ステップを提供してくれるとのこと。
  • 早速やってみよう!
    image.png
    • IAPを有効にしたらサービスAもサービスBも両方に適用されて、GAE全体が非公開になってしまった…

やりたいことは、サービスAはパブリックに公開、サービスBだけにIAPでアクセス制限をしたい

  • どうしよう。
    • もしかして、サービス単位に権限を制御できるのでは?
    • サービス単位で権限が割り当てられるなら、片方のサービスだけを公開に出来るはず。
      image.png
    • できた!
  • この設定行うと、少し反映完了までラグがありますが、サービスAにアクセスする際は認証リダイレクトが行われずに、無事にサービスBだけを非公開にできます。

WebブラウザからのアクセスはGoogleの認証画面にリダイレクトされるけど、サーバーサイドでAPIアクセスするときにどう認証しよう

  • さて、これでアクセス制限周りのGCP設定はできましたが、次はサービスAからサービスBのAPIを叩く際に認証が必要になります。
  • このままの状態で普通にリクエストを投げると403エラーで認証が失敗すると思います。
  • そこで、GAEのサービスアカウントの出番です。
    • サービスアカウントをサービスBの認証済みユーザーとして登録します。
      image.png
    • これで、このサービスアカウントを使うとサービスBに対してアクセス権が与えられます。

プログラム側で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の権限を追加してあげる必要がある。
  • これらのデメリットを考慮して、うまく使い分けていくのが良いのかなと思います。

参考にさせていただいた記事

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?