なぜ「Cloud Runのデフォルト設定」が、攻撃者の格好の入り口になるのか?
現場でコードをレビューしていると、いまだに「Cloud Runのサービス間通信で、APIキーを環境変数にベタ書きしている」ケースや、「IAMロールを『プロジェクト編集者』に設定して全開放している」ケースに出くわす。これがどれほど危険か、君たちは正しく理解しているだろうか?
攻撃者は、アプリケーションの脆弱性(RCEやLFIなど)を突いて環境変数をダンプする。もしそこにAPIキーがあれば、彼らは君たちの認証の「鍵」を手にすることになる。そして、IAMがガバガバであれば、その鍵を使ってプロジェクト内の他のサービス、あるいはGCSのバケットを全探索し、データをごっそりと持ち去る。
今日は、「IDトークン(OIDC)」を用いたサービス間認証と、「IAMの最小権限原則」を、明日から現場で使える形に落とし込む。
—
1. 攻撃シナリオ:なぜIAMの境界が崩れるのか?
もし、サービスAからサービスBを呼び出す際、「認証なし(public)」でアクセスできるように設定していたらどうなるか。
攻撃者は、サービスBのURLを特定した瞬間、インターネット経由で直接リクエストを投げられる。これはもはや社内ネットワークの境界など無意味だ。
攻撃者のPoC(概念実証):
1. サービスAに脆弱性を突き、踏み台にする。
2. 内部ネットワークの探索(`curl http://service-b.run.app`)を行い、サービスBのレスポンスを確認。
3. 認証がなければ、直接サービスBへペイロードを送信し、バックエンドのデータベースや決済処理APIを操作する。
これを防ぐ唯一の解は、「IDトークンをヘッダーに含め、受け手側でGoogleのインフラ層での検証を強制すること」だ。
—
2. 実装:IDトークンの生成と検証(Python)
サービスA(呼び出し元)がIDトークンを取得し、サービスB(受け手)がそれを検証する実装を見ていこう。
サービスA:呼び出し元(IDトークンの付与)
Googleが提供する `google-auth` ライブラリを使えば、メタデータサーバーから自動的にIDトークンを取得できる。
import google.auth.transport.requests
import google.oauth2.id_token
import requests
サービスBのURL
SERVICE_B_URL = “https://service-b-hash-an.a.run.app”
def call_service_b():
# サービスBのAudienceを指定してトークンをリクエスト
auth_req = google.auth.transport.requests.Request()
id_token = google.oauth2.id_token.fetch_id_token(auth_req, SERVICE_B_URL)
headers = {
“Authorization”: f”Bearer {id_token}”
}
response = requests.get(SERVICE_B_URL, headers=headers)
return response.json()
サービスB:受け手(IAM設定の重要性)
コードを書く前に、「Cloud RunのIAM設定」が肝だ。サービスBの設定で、「認証を必須にする」にチェックを入れる。これだけで、Googleのインフラがトークンを検証し、無効なリクエストを弾いてくれる。
terraformでの設定例:
resource “google_cloud_run_service_iam_member” “invoker” {
service = google_cloud_run_service.service_b.name
location = google_cloud_run_service.service_b.location
role = “roles/run.invoker”
# サービスAのサービスアカウントにのみ実行権限を付与する
member = “serviceAccount:service-a-sa@project-id.iam.gserviceaccount.com”
}
—
3. 実務で守るべき「3つの鉄則」
コードを書くだけがセキュリティではない。運用フェーズで以下の3点を徹底してくれ。
1. サービスアカウントの分離
- サービスAとサービスBで、必ず「異なるサービスアカウント」を使え。「Default Compute Service Account」を使い回すのは、すべての鍵を玄関マットの下に置くのと同じことだ。
2. `roles/run.invoker` のスコープ
- 「オールユーザー(allUsers)」に実行権限を与えるのは、テスト環境であっても厳禁。必ず特定のサービスアカウントのみに許可すること。
3. トークンの有効期限を確認する
- IDトークンは1時間で期限が切れる。トークンの再取得ロジックがライブラリで適切に処理されているかを確認し、キャッシュの持ちすぎに注意せよ。
—
最後に:セキュリティは「面倒」の先にある
「IAMの設定が面倒」「トークンのハンドリングが難しい」。そう思うかもしれない。しかし、インシデントが起きた後の損害賠償や、顧客からの信頼喪失、そして夜中3時の緊急対応に比べれば、今のこの一手間は圧倒的に安い。
セキュリティを「機能の一部」として設計に組み込むこと。それが、君たちを「ただのエンジニア」から「信頼されるエンジニア」に変える境界線だ。
何か実装で詰まったら、いつでもコードを持ってこい。レビューしてやる。

コメント