【Cloud Run】特定のサービスアカウントからだけ呼べるようにする方法

【Cloud Run】特定のサービスアカウントからだけ呼べるようにする方法 GCP

Cloud Run で受け口のサービスと裏の処理用サービスを分けたとき、裏のサービスはインターネットから呼べないようにして、受け口のサービスからだけ呼べるようにしたいことがあります。この記事では、呼び出される側のサービスに roles/run.invoker を特定のサービスアカウントだけに付け、呼び出す側から ID トークンを付けて呼ぶまでの手順をまとめます。前提は Google Cloud の Cloud Run(サービス)と gcloud CLI で、2026年11月時点の公式ドキュメントに沿っています。

以下では、呼び出す側を frontend、呼び出される側を worker と呼びます。

全体の流れ

  1. 呼び出す側(frontend)専用のサービスアカウントを作る
  2. frontend をそのサービスアカウントで動かす
  3. worker を認証必須にする(allUsers を外す)
  4. worker の roles/run.invoker を、frontend のサービスアカウントにだけ付ける
  5. frontend のコードで ID トークンを取り、Authorization ヘッダーに付けて呼ぶ

Cloud Run のサービスは、何も指定しなければ認証必須(非公開)で作られます。公開にしたのは --allow-unauthenticated を付けたか、allUsers に roles/run.invoker を付けたときです。

呼び出す側のサービスアカウントを用意する

1. サービスアカウントを作る

サービスアカウントを指定しないと、Cloud Run は Compute Engine のデフォルトのサービスアカウント(PROJECT_NUMBER-compute@developer.gserviceaccount.com)で動きます。これに roles/run.invoker を付けると、同じプロジェクトでこのアカウントを使う他のリソースまで worker を呼べてしまいます。公式ドキュメントも、サービスごとに権限を絞ったユーザー管理のサービスアカウントを使うことを勧めています。

gcloud iam service-accounts create frontend-sa \
  --display-name="frontend service"

作られるメールアドレスは frontend-sa@PROJECT_ID.iam.gserviceaccount.com です。

2. 呼び出す側をそのサービスアカウントで動かす

gcloud run deploy の --service-account に指定します。このサービスアカウントが、動いているリビジョンの身元になります。

gcloud run deploy frontend \
  --image=IMAGE_URL \
  --region=asia-northeast1 \
  --service-account=frontend-sa@PROJECT_ID.iam.gserviceaccount.com

呼び出される側を特定のサービスアカウントだけに開ける

3. 認証必須にする

新しく出すときは --no-allow-unauthenticated を付けます。

gcloud run deploy worker \
  --image=IMAGE_URL \
  --region=asia-northeast1 \
  --no-allow-unauthenticated

すでに公開しているサービスは、まず今のポリシーを確かめます。

gcloud run services get-iam-policy worker --region=asia-northeast1

members に allUsers があれば外します。

gcloud run services remove-iam-policy-binding worker \
  --region=asia-northeast1 \
  --member=allUsers \
  --role=roles/run.invoker

あわせて、呼び出し元の IAM チェックそのものを切っていないか(--no-invoker-iam-check で出していないか)も確かめます。切っている場合は、次のコマンドで有効に戻します。

gcloud run services update worker \
  --region=asia-northeast1 \
  --invoker-iam-check

4. 呼び出す側のサービスアカウントに roles/run.invoker を付ける

プロジェクト全体ではなく、worker というサービス1つにだけ付けます。--member は serviceAccount:メールアドレス の形で書きます。

gcloud run services add-iam-policy-binding worker \
  --region=asia-northeast1 \
  --member='serviceAccount:frontend-sa@PROJECT_ID.iam.gserviceaccount.com' \
  --role='roles/run.invoker'

呼べればよいだけなら、roles/run.invoker の代わりにサービスを呼ぶ権限だけを持つ roles/run.servicesInvoker も使えます。roles/run.invoker はジョブの実行やジョブ実行の取り消しもできるロールです。

呼び出す側から ID トークンを付けて呼ぶ

5. ID トークンを取ってヘッダーに付ける

Cloud Run の上で動いているコードは、メタデータサーバーから自分のサービスアカウントの ID トークンを取れます。audience には呼び出す先のサービスの URL(https://worker-xxxxx.a.run.app の形)を渡します。

curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://WORKER_URL" \
  -H "Metadata-Flavor: Google"

取ったトークンを、worker へのリクエストの Authorization ヘッダーに付けます。

Authorization: Bearer ID_TOKEN

Python なら google-auth のライブラリで同じことができます。

import urllib.request

import google.auth.transport.requests
import google.oauth2.id_token

audience = "https://WORKER_URL"
auth_req = google.auth.transport.requests.Request()
id_token = google.oauth2.id_token.fetch_id_token(auth_req, audience)

req = urllib.request.Request(f"{audience}/tasks")
req.add_header("Authorization", f"Bearer {id_token}")
with urllib.request.urlopen(req) as resp:
    print(resp.status)

ID トークンは作られてからおよそ1時間で切れます。長く動くプロセスでは、呼ぶたびに取り直すか、期限の前に取り直します。

動作を確認する

worker のポリシーに、呼び出す側のサービスアカウントだけが載っていることを確かめます。

gcloud run services get-iam-policy worker --region=asia-northeast1

期待する出力(抜粋):

bindings:
- members:
  - serviceAccount:frontend-sa@PROJECT_ID.iam.gserviceaccount.com
  role: roles/run.invoker

allUsers が載っていなければ、インターネットからトークン無しで worker を呼んでも通りません。手元から curl https://WORKER_URL を打って、リクエストが拒否されることを確かめます。そのうえで frontend 経由の処理が通れば設定は完了です。

うまくいかないとき

  • メタデータサーバーがトークンを返さない: Metadata-Flavor: Google ヘッダーが無いと、メタデータサーバーはリクエストを拒否します
  • 手元の PC でメタデータサーバーの URL が引けない: メタデータサーバーから取る方法は Google Cloud の外では使えません。手元で試すときは別の方法でトークンを取ります
  • トークンを付けても通らない: audience が呼び出す先のサービス URL になっているかを確かめます。トラフィックタグ付きの URL へ送るときも、カスタムオーディエンスを設定していなければ aud はサービスの URL のままにします。カスタムドメインは aud に使えません
  • Authorization と X-Serverless-Authorization の両方を付けている: 両方あると Cloud Run は X-Serverless-Authorization しか見ません。アプリ側で Authorization を別の用途に使うときは、Cloud Run 向けのトークンを X-Serverless-Authorization: Bearer ID_TOKEN に入れます
  • しばらく前に動いていた処理が急に通らなくなった: トークンの期限(約1時間)が切れていないかを確かめます
  • --no-allow-unauthenticated を付けてすぐはまだ公開のまま: この設定は反映まで少し時間がかかることがあります
  • allUsers をプロジェクトの IAM に付けようとして失敗する: allUsers と allAuthenticatedUsers はプロジェクト単位には付けられず、サービスごとに付けるものです

参考

この記事を書いた人

渋谷で働くよわよわエンジニア。TypeScript(Next.js/Node.js)/Golang/AWS/Flutterなど
LINE API Expert(2023/1~)

tokkuをフォローする
オススメ記事
在宅ワークが多くなって、デスクの配線周りに困っている方は、拡張デスクの導入がおすすめです。Windows/MacOS/Linuxの複数の機器をスッキリとデスク周りに収める事ができました!リモートからサーバーメンテを行うインフラエンジニアは必見です!
GCP
シェアする
tokkuをフォローする

コメント

タイトルとURLをコピーしました