GCP サービスアカウント認証 (WIF + ADC)
UZU は GCP リソースへの認証を Workload Identity Federation (WIF) + Application Default Credentials (ADC) に統一している。JSON service account key の発行・配布は廃止。
WARNING
このページは GCP の サービス間認証 (Cloud Storage / BigQuery / Firebase Admin / Play Store API 等) の話。Firebase Authentication 経由の ユーザー認証 は 認証基盤 (auth platform) を見る。
方針
| 経路 | 仕組み |
|---|---|
| GKE 上の Pod | Workload Identity for GKE (KSA → GSA impersonation) |
| GitHub Actions | google-github-actions/auth の WIF (workload_identity_provider) |
| ローカル開発 | gcloud auth application-default login で ADC |
| Cloudflare Workers | Service Account JSON ではなく、必要なら別経路 (例: WIF + workers binding) を検討 |
option.WithCredentialsFile / cert({ clientEmail, privateKey }) / GOOGLE_APPLICATION_CREDENTIALS 環境変数は使わない。SDK の引数なし初期化に任せて ADC/WIF を暗黙利用させる。
per-service GSA 命名
1 リポジトリ単位の用途で 1 GSA を切る。サブサービス (graph/grpc/asynq 等) を分けない (権限セットがほぼ同じになるため)。
| 用途 | GSA 名 (各 env) | 参考実装 |
|---|---|---|
| backend (Go: graph / grpc / asynq / CronJobs) | backend@uzu-{env} | infra/terraform/modules/iam/backend.tf (例) |
| mdms_jp backend | mdmsjp@uzu-{env} | 同 |
| auth (Node.js) | auth-server@uzu-{env} | 同 |
| studio_editor_backend (Node.js) | studio-editor-backend@uzu-{env} | 同 |
| analytics_server (Dart) | analytics-server@uzu-{env} | 同 |
| ESO controller | external-secrets@uzu-{env} | infra/terraform/{env}/eso.tf |
| GitHub Actions (env 毎) | github-actions@uzu-{env} / github-actions-terraform@uzu-{env} | infra/terraform/modules/github_actions_deploy/ |
Terraform 側 (例)
resource "google_service_account" "backend" {
account_id = "backend"
display_name = "Backend (graph/grpc/asynq/CronJobs)"
}
resource "google_project_iam_member" "backend_firebase_admin" {
project = var.project_id
role = "roles/firebase.admin"
member = "serviceAccount:${google_service_account.backend.email}"
}
# Workload Identity binding (KSA ↔ GSA)
resource "google_service_account_iam_member" "backend_wi" {
service_account_id = google_service_account.backend.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[${var.namespace}/backend]"
}
# Firebase CustomToken / GCS SignedURL を発行する場合は self TokenCreator が必須
resource "google_service_account_iam_member" "backend_self_token_creator" {
service_account_id = google_service_account.backend.name
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${google_service_account.backend.email}"
}cross-project (uzu-shared の GCS / uzu-prd の BigQuery 等) を読む場合は 読み先プロジェクト側の Terraform に google_storage_bucket_iam_member 等を追加する。dev/stg/prd の GSA を 同一の Resource にまとめて binding すれば抜けにくい。
K8s 側
apiVersion: v1
kind: ServiceAccount
metadata:
name: backend
annotations:
iam.gke.io/gcp-service-account: backend@uzu-${ENV}.iam.gserviceaccount.com
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
serviceAccountName: backend
# secretVolume の keys マウントは付けないkind: Job は spec が immutable。既存 Job に serviceAccountName を追加する場合は Job を delete してから apply か 新名で作り直す。
アプリ側
| 言語 | 書き方 |
|---|---|
| Go | storage.NewClient(ctx), firestore.NewClient(ctx, projectID) — 引数なしで ADC |
| Node.js | new Storage(), new Firestore(), admin.initializeApp({ credential: applicationDefault() }) |
| Python | google.auth.default() |
| Dart (server) | googleapis_auth の clientViaApplicationDefaultCredentials() |
Firebase Admin SDK で createCustomToken / verifyIdToken を使う場合、private key を持たない credentials 環境では内部で iamcredentials.signBlob を呼ぶので、GSA に roles/iam.serviceAccountTokenCreator の self binding が必須 (上記 Terraform 例参照)。Node.js 側は serviceAccountId で署名対象 GSA を明示する:
admin.initializeApp({
credential: applicationDefault(),
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
serviceAccountId: process.env.FIREBASE_SERVICE_ACCOUNT_ID, // 例: auth-server@uzu-{env}
});ローカル開発
gcloud auth application-default logintask doctor で ADC とアクセス権をチェックできる (gcloud auth application-default print-access-token が成功するか)。.env に GOOGLE_APPLICATION_CREDENTIALS を残すと ADC より優先されるので、見つけたら削除。
GCS の署名付き URL を出す機能 (追加コンテンツ / シナリオ動画 / /config の asset) をローカル backend で動かす場合は、個人アカウントの ADC に署名鍵が無いので backend GSA を impersonate する:
gcloud auth application-default login --impersonate-service-account=backend@uzu-dev.iam.gserviceaccount.comsally-inc.jp の Google アカウントには dev の backend GSA への roles/iam.serviceAccountTokenCreator が付与済み (infra/terraform/dev/iam_local_dev.tf)。stg/prd の GSA には付与していない。impersonation は ADC 全体に効くので、素の個人権限に戻すときは --impersonate-service-account 無しで再ログインする。
GitHub Actions
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/.../locations/global/workloadIdentityPools/github/providers/uzu
service_account: github-actions@uzu-${{ env.ENV }}.iam.gserviceaccount.comgithub-actions@uzu-{env} GSA は Terraform 管理下 (modules/github_actions_deploy)。role 変更は modules 側で。
既知の落とし穴
| 症状 | 原因 / 対応 |
|---|---|
Firebase Admin の CustomToken() が PERMISSION_DENIED: iam.serviceAccounts.signBlob | GSA に self serviceAccountTokenCreator 未付与。Terraform に backend_self_token_creator 相当を追加 |
Play Store Developer API が 401 permissionDenied | Play Console > Users & Permissions で GSA を招待。反映に最大 24h かかるため prd 切替前に事前招待 |
kind: Job の apply が失敗 | Job は immutable。delete → apply |
Terraform apply が IAM_PERMISSION_DENIED (setIamPolicy) | github-actions-terraform@uzu-{env} に roles/iam.serviceAccountAdmin (dev は加えて roles/resourcemanager.projectIamAdmin) を手動 gcloud で付与 |
| immutable な Deployment field エラー | spec.selector / spec.template.metadata.labels は変更不可。serviceAccountName だけ足す形にする |
| cross-project の権限不足 | uzu-shared / uzu-prd 側の Terraform で google_*_iam_member を追加 |
| ローカルで JSON key が優先される | .env / taskfiles/*.yaml の GOOGLE_APPLICATION_CREDENTIALS を削除 |
ローカルで storage: unable to detect default GoogleAccessID | 個人 ADC には署名鍵が無い。--impersonate-service-account=backend@uzu-dev... で ADC を取り直す (上記「ローカル開発」参照) |
参照
- Secret 管理 (ESO + Secret Manager) — credential 以外の secret はこちら
- 完了済み移行 PR: backend (OTH-12212), mdms_jp (MJP-1464), analytics_server / auth / studio_editor_backend / GitHub Actions ほか