開発環境・ツール

GitHub ActionsからCloud Runへキーレスデプロイ|OIDC(Workload Identity Federation)の設定と詰まりどころ

GitHub ActionsからCloud Runへデプロイする方法を調べると、サービスアカウントキー(JSON)をGitHubのシークレットに登録する例がまだ多く見つかります。しかしキーは期限のない認証情報で、漏れると誰でもそのサービスアカウントとして操作できます。この記事では、キーを一切持たずにOIDCでデプロイする構成と、設定の途中で詰まりやすい3つのエラーをまとめます。

この記事は2026年10月時点のGoogle Cloud・google-github-actions/authの公式ドキュメントの記述をもとにしています。プロジェクト番号・リポジトリ名などはすべて架空の値です。

結論:キーは不要。詰まりやすいのは3か所

先に結論です。

  • Workload Identity Federation(WIF) を使うと、GitHub Actionsが発行する短命のOIDCトークンをGoogle Cloudの認証情報に交換できます。JSONキーを作る必要も、GitHubに置く必要もありません。
  • 詰まりやすいのは次の3つです。
エラーの手がかり 原因 対処
rejected by the attribute condition プロバイダーの属性条件が、実行したリポジトリ・ブランチを許可していない 条件式とトークンの値を見比べて直す
artifactregistry.repositories.downloadArtifactsがdenied 別プロジェクトのレジストリを、Cloud Runのサービスエージェントが読めない サービスエージェントに読み取り権限を付ける
already been set with a different type 同じ名前の環境変数を、平文とシークレットの両方で設定した ワークフローでは設定せず、サービス側の設定に任せる

構成の全体像

GitHub Actions ──(OIDC トークン)──▶ Workload Identity プロバイダー
                                        │ 属性条件でリポジトリ・ブランチを確認
                                        ▼
                               デプロイ用サービスアカウントの権限を借用
                                        │
          ┌─────────────────────────────┴───────────────┐
          ▼                                             ▼
 Artifact Registry(イメージ用プロジェクト)     Cloud Run(実行用プロジェクト)
   docker push                                    gcloud run deploy

イメージを保管するプロジェクトと、Cloud Runを動かすプロジェクトを分けている構成を例にします。エラー2はこの構成で起きます。

環境

  • GitHub Actions(ubuntu-latest)
  • google-github-actions/auth v3 / google-github-actions/setup-gcloud v3
  • Cloud Run・Artifact Registry
  • 確認環境と本番はGitHubのEnvironments(staging / production)で切り替え

ワークフローの実装

ポイントは3つです。id-token: writeを付ける、アクションはコミットSHAで固定する、キーではなくプロバイダーのパスとサービスアカウントを渡す。

name: Deploy

on:
  push:
    branches: [staging, main]

concurrency:
  group: deploy-${{ github.ref_name }}
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ github.ref_name == 'main' && 'production' || 'staging' }}
    permissions:
      contents: read
      id-token: write # OIDC トークンの発行に必要
    env:
      IMAGE: asia-northeast1-docker.pkg.dev/my-image-project/my-repo/my-service:${{ github.sha }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
        with:
          workload_identity_provider: ${{ vars.WIF_PROVIDER }}
          service_account: ${{ vars.DEPLOY_SERVICE_ACCOUNT }}

      - uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1

      - run: gcloud auth configure-docker asia-northeast1-docker.pkg.dev --quiet

      - run: |
          docker build -t "$IMAGE" .
          docker push "$IMAGE"

      - run: |
          gcloud run deploy my-service \
            --image "$IMAGE" \
            --project "${{ vars.GCP_PROJECT_ID }}" \
            --region asia-northeast1 \
            --quiet

WIF_PROVIDERには、次の形式のプロバイダーのフルパスを入れます。

projects/123456789/locations/global/workloadIdentityPools/github/providers/my-repo

公式のトラブルシューティングでは、よくある間違いとして次の2つが挙げられています。

  • プールのパス(.../workloadIdentityPools/github)で止めてしまう
  • プロジェクトIDを入れてしまう(WIFが受け付けるのはプロジェクト番号)

なお、authのステップはactions/checkoutの後に置きます。順番が逆だと、後のステップで認証情報を使えません。

エラー1:rejected by the attribute condition

authのステップで、次の文言を含むエラーが出ることがあります。

Failed to generate Google Cloud federated token
...
The given credential is rejected by the attribute condition.

Workload Identityプールへの入場で拒否されたという意味です。プロバイダーの属性条件(attribute condition)に、今回のトークンが合っていません。

たとえば、条件を次のように書いていたとします。

assertion.repository == 'my-org/my-repo' && assertion.ref == 'refs/heads/main'

この場合、stagingブランチからの実行は拒否されます。確認環境と本番でプロバイダーを分けるなら、それぞれの条件にそのブランチだけを書きます。

見落としやすい点が2つあります。

  • 属性マッピングに含めていない値は、条件に使えません
  • 大文字と小文字が区別されます。GitHubではMy-Orgとmy-orgは同じですが、Google Cloudでは別の値です

なおGitHubのドキュメントによると、2026年7月15日以降に作られたリポジトリでは、トークンのsubの既定の形式が変わり、オーナーとリポジトリのIDを含むようになりました。subを条件に使う場合は、実際のトークンの値を確認してください。上の例のようにrepositoryとrefで絞る書き方なら影響はありません。

また、IAMやプロバイダーの変更は反映まで最大5分かかることがあります。直したのに通らないときは、少し待ってから再実行してください。

スポンサーリンク

エラー2:別プロジェクトのレジストリを読めない

docker pushは成功するのに、gcloud run deployで次のようなエラーが出ます。

Permission "artifactregistry.repositories.downloadArtifacts" denied on resource ...

イメージを取り込む(pullする)のは、デプロイ用のサービスアカウントではなく、Cloud Runのサービスエージェントです。サービスエージェントはCloud Runを動かすプロジェクトに自動で作られるアカウントで、次の形式のアドレスを持ちます。

service-987654321@serverless-robot-prod.iam.gserviceaccount.com

987654321は、Cloud Runを動かすプロジェクトの番号です(WIFのパスに入れた番号とは別のプロジェクトのこともあります)。

公式ドキュメントでは、別プロジェクトのイメージをデプロイする場合、イメージ用プロジェクトでこのサービスエージェントに「Artifact Registry読み取り」(roles/artifactregistry.reader)を付けるよう案内されています。

gcloud artifacts repositories add-iam-policy-binding my-repo \
  --project=my-image-project \
  --location=asia-northeast1 \
  --member="serviceAccount:service-987654321@serverless-robot-prod.iam.gserviceaccount.com" \
  --role="roles/artifactregistry.reader"

デプロイ用のサービスアカウントに権限を足しても、このエラーは消えません。どのアカウントがイメージを読んでいるかに注意してください。

エラー3:already been set with a different type

gcloud run deployに--set-env-varsや--update-env-varsで環境変数を渡すと、次の文言を含むエラーで失敗することがあります。

Cannot update environment variable [API_KEY] to string literal because it has already been set with a different type.

Cloud Runのサービスで、同じ名前の変数がすでにSecret Managerの参照として設定されているのが原因です。平文の環境変数とシークレットの参照は別の種類として扱われるため、同じ名前を平文で上書きしようとすると、gcloudがAPIを呼ぶ前に止めます(gcloud CLI自体のチェックで出るエラーで、公式ドキュメントに説明は見当たりません)。--set-env-varsは平文の変数をいったん全部消しますが、シークレットの変数は消さないので、同じエラーになります。

いちばん簡単な対処は、ワークフローでは環境変数を設定しないことです。公式ドキュメントでは、設定を変えると新しいリビジョンが作られ、以降のリビジョンもその設定を自動で引き継ぐと説明されています。gcloud run deployに--imageだけを渡せば、シークレットの設定はそのまま残ります。

  • パスワードやAPIキーなどの秘密情報は、Secret Managerに置いてサービス側で参照する
  • ワークフローはイメージを差し替えるだけにする

こうしておけば、秘密情報がワークフローのログやYAMLに出ることもありません。

補足:ビルド時に必要な値と、実行時に読む値を分ける

Next.jsのように、NEXT_PUBLIC_で始まる変数をビルド時にコードへ埋め込むフレームワークでは、その値はdocker buildの--build-argで渡す必要があります。Cloud Runの環境変数として設定しても、ビルド済みのコードには反映されません。

  • ビルド時に埋め込む値:公開してよい値だけ(Environmentsの変数から--build-argで渡す)
  • 実行時に読む値:秘密情報を含む値(Secret Managerからサービスが参照)

確認方法

  1. ワークフローのログで、authのステップが成功していることを確認する
  2. gcloud run services describeで、新しいリビジョンのイメージのタグがgithub.shaと一致していることを確認する
  3. リポジトリやブランチの条件を変えたときは、許可していないブランチから実行して、拒否されることも確かめる

チェックリスト

  • ジョブのpermissionsにid-token: writeがあるか
  • WIF_PROVIDERはプロバイダーまでのフルパスで、プロジェクト番号を使っているか
  • 属性条件で、リポジトリ(と必要ならブランチ)を絞っているか
  • 別プロジェクトのレジストリを使う場合、Cloud Runのサービスエージェントに読み取り権限があるか
  • ワークフローで、シークレットと同じ名前の環境変数を設定していないか
  • アクションをコミットSHAで固定しているか
  • 不要になったサービスアカウントキーを削除したか

よくある質問

Q. サービスアカウントを使わず、プールに直接権限を付けられますか?

A. できます。authのREADMEでは、サービスアカウントを介さない「Direct Workload Identity Federation」が推奨されています。ただし、すべてのGoogle Cloudリソースが対応しているわけではないので、使うサービスの対応状況を確認してください。

Q. WIF_PROVIDERはシークレットに入れるべきですか?

A. パスそのものは認証情報ではないので、Environmentsの変数(vars)で十分です。ただし、プロジェクト番号を公開したくない場合はシークレットに入れても構いません。

まとめ

  • WIFを使えば、JSONキーなしで GitHub ActionsからCloud Runへデプロイできる
  • rejected by the attribute conditionは、属性条件とトークンの値の不一致。大文字・小文字とマッピングも確認する
  • 別プロジェクトのイメージを読むのはCloud Runのサービスエージェント。そこに読み取り権限を付ける
  • already been set with a different typeは、平文とシークレットの衝突。ワークフローはイメージの差し替えだけにする

キーを持たない構成は、一度組めば漏えいの心配が1つ減ります。これからデプロイを組む方の参考になれば幸いです。

参考

スポンサーリンク