GCP からの HTTP リクエストをセキュアに認証する

Yuki Furuyama
google-cloud-jp
Published in
11 min readApr 14, 2019

はじめに

GCP にはあらかじめ HTTP のエンドポイントを登録しておくと、そこに対して HTTP リクエストが送られてくるようなプロダクトがいくつか存在します。

  • Cloud Pub/Sub
  • Cloud Tasks
  • Cloud Scheduler

どれも非同期系の処理を行うプロダクトであり、非同期処理を行う Worker を HTTP の Web サーバとして記述できるのが大きなメリットになっています。

しかしそれらの Web サーバはパプリックなエンドポイントとして用意することも多いことから、送られてきた HTTP リクエストが本当に GCP の特定のプロダクトから送られてきたものなのか?という「認証」をどうやるかが長らく問題になっていました。

既存のやり方としては Web サーバの実装方式によっていくつかありますが、

  1. App Engine (1st gen) を使う場合: “login: admin” という特別な指定を行い Google 側で認証を行う方法
  2. その他を使う場合: HTTP リクエストの URL に特別なシークレット情報を含め、アプリケーション側で認証を行う方法

1の場合は App Engine 2nd gen で採用できないため将来性に乏しく、2の場合は認証とは呼べないお粗末な方式であるので、どちらもイマイチな状態になっていました。

そんな中登場したのが、先週 Cloud Pub/Sub にリリース(*)された Service Account を利用した新しい認証手段です (*4/13時点ではベータ扱い)。 Cloud Tasks と Cloud Scheduler に関してはまだドキュメントや API は見当たりませんでしたが、NEXT 2019 のセッションを見る限り同様の認証手段が今後用意されるようです。

この認証手段は エンドポイント先がどのような Web サーバでも使える汎用的な仕組みになっているので、今後はこちらが主流になるのではないかと思います。

そこで本記事ではその新しい認証手段がどのようなものなのか、Cloud Pub/Sub の公式ドキュメントを元に解説したいと思います。

Service Account を使用した新しい認証手段

新しい方式では Pub/Sub の Subscription (つまり HTTP エンドポイント) に対して任意の Service Account を紐付けることができるようになりました。紐づけを行うと、あたかもその Service Account 自体がリクエストを送ってきたかのように振る舞うので、自分が事前に登録した Service Account と一致するかを検証することで、本当に自身のプロジェクトの Pub/Sub から送られてきたものかどうか検証することができます。

具体的には HTTP リクエストの Authorization ヘッダに Service Account の情報を含んだ OpenID Connect の ID Token が渡ってくるので、その ID Token を検証すれば ok という形です。

ID Token は JWT であり、以下のような情報が含まれています。

Header:
{“alg”:”RS256",”kid”:”7d680d8c70d44e947133cbd499ebc1a61c3d5abc”,”typ”:”JWT”}
Body:
{
“aud”:”https://example.com",
“azp”:”113774264463038321964",
“email”:”{SERVICE_ACCOUNT_NAME}@{PROJECT_ID}.iam.gserviceaccount.com”,
“sub”:”113774264463038321964",
“email_verified”:true,
“exp”:1550185935,
“iat”:1550182335,
“iss”:”https://accounts.google.com"
}

一番大事なのは Service Account の Email アドレスが含まれている email claim ですね。

ID Token の検証方法は色々なサイト(参考)で述べられてるので具体的な方法は述べませんが、基本的には email, aud, exp 辺りが妥当であるか、Google の提供する証明書で署名が正しく検証できるかを見ることになると思います。デバッグ用途で Google の tokeninfo エンドポイントを使って JWT を検証することもできるので、それで一度試してみるのもありですね。

どのように ID Token が作られるか

次に、こちらに記載されている ID Token の生成の仕組みを見てみたいと思います。

ID Token は Cloud Pub/Sub が内部で生成することになりますが、いくら Google 内部のシステムといってもどんな ID Token を作れてもいい訳はないので、あらかじめ「この Service Account の ID Token だったら作れてもいい」という権限を渡すことになります。この権限は roles/iam.serviceAccountTokenCreator という IAM ロールです。

Cloud Pub/Sub は内部で service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.comという Service Account (SA1 とします) をルートの認証手段として使い、目的の Service Account (SA2 とします) の ID Token を作るようになっているため、SA1 に対して roles/iam.serviceAccountTokenCreator ロールを付与すれば、SA2 の ID Token を作ることができるようになります。まるで他人になりすますような感じですね。

権限を付与する際の gcloud コマンドは以下のようになります。

# 個別に付与する場合
$ gcloud iam service-accounts add-iam-policy-binding {SA2}@{PROJECT_ID}.iam.gserviceaccount.com --member='serviceAccount:service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com' --role='roles/iam.serviceAccountTokenCreator'
# Project一括で付与する場合
$ gcloud projects add-iam-policy-binding {PROJECT_ID} --member='serviceAccount:service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com' --role='roles/iam.serviceAccountTokenCreator'

Service Account が2つでてきてややこしいですが、 roles/iam.serviceAccountTokenCreator ロールを付与するのは Pub/Sub の Service Account の方です。もう片方の方は基本的には認証としてしか使わないのでロールは何も付与しない方が安全ですが、後述するように Cloud Run や Cloud Identity-Aware Proxy と組み合わせて使いたい場合は別途ロールを付与する必要があります。

認証・認可機能が既に備わっているプロダクトを使う場合

GCP にはプロダクトとして認証・認可機能が既に備わっているものがいくつかありますが、それらを Cloud Pub/Sub の Subscription のエンドポイントに置くこともできます。

  • Cloud Run
  • Cloud Identity-Aware Proxy

Cloud Run は認証なしでパブリックにエンドポイントを公開することができますが、roles/run.invoker というロールを持つアカウントのみアクセスできるよう IAM で制限することもできます。その制限を課した場合、 roles/run.invoker を持つ Service Account の ID Token を Authorization ヘッダに入れてアクセスしなければいけませんが、これはまさに Cloud Pub/Sub の認証の形式と同じです。つまり、Cloud Pub/Sub の Subscription のエンドポイントとして Cloud Run を使いたい場合は、Subscription に紐付ける Service Account に roles/run.invoker ロールを付けておけば後は勝手に Google 側で認証・認可を行ってくれるということです。

Identity-Aware Proxy (IAP) も Service Account の ID Token を使ったアクセスをサポートしているため同じようなことができます。IAP の方は roles/iap.httpsResourceAccessor というロールを持つ Service Account のみアクセスできるので、そのロールを持つ Service Account を Cloud Pub/Sub の Subscription に設定すれば、IAP 側で自動で認証・認可をしてくれます。

設定の際に注意が必要なのが、IAP 側の Client ID を Pub/Sub の Subscription の Audience に指定する必要があるという点です。詳しくはこちらを参照してください。

HTTP エンドポイントを直接叩きたい場合

開発用途などで Cloud Pub/Sub を経由せずに直接 Subscription 先の HTTP エンドポイントを叩きたいということもあると思います。その場合は ID Token を自分で生成しちゃえば ok です。

Cloud IAM には generateIdToken という API があるので、それを叩くことで所望の Service Account の ID Token を得ることができます。尚この場合も、API を叩くユーザに roles/iam.serviceAccountTokenCreator ロールが付いていることが必須です。

以下は RESTful API を使って Service Account の ID Token を取得する例です。

$ curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud config config-helper --format=json | jq -M -r '.credential.access_token')" --data '{"delegates":[],"audience":"test","includeEmail":true}' 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{SERVICE_ACCOUNT_NAME}@{PROJECT_ID}.iam.gserviceaccount.com:generateIdToken{
“token”: “eyJ…..”
}

まとめ

GCP から送られてくる HTTP リクエストを認証する新しい方式を解説しました。ID Token というオープンなフォーマットに準拠することで、Google のマネージドサービスであれば自動で認証が行われ、それ以外であっても自分で認証できる余地があるのが綺麗ですね。

最後に各プロダクトを HTTP のエンドポイントに使った場合の認証方法をまとめて終わりにします。

--

--

Yuki Furuyama
google-cloud-jp

Technical Solutions Engineer @Google Cloud. Opinions are my own and not the views of my employer.