Github ActionsでGoogle Cloud Storageにアクセスし、バケットからデータをダウンロードできるようにする。Google Cloud Storageへの認証は、Workload Identity 連携を使用する。
背景は、Github Actionsで定期的にビルドしているgitリポジトリ内にそれなりに大きい(数十MB以上)jsonファイルがいくつかあり、当初はGit LFSにデータを置いてそこからダウンロードしていたが、このダウンロードするデータ量がGithubが無料で提供しているGit LFSの帯域幅(1ヶ月あたり10GiB)をそれなりに使用していたことが問題だったことから。将来的に無料枠を使い切る懸念があったため、代わりにGoogle Cloud Storageからダウンロードすることを試みた。Google Cloud Storageだと特定のリージョン(us-west1とか)において1ヶ月あたり100GBまでの外向きのデータ転送が無料なので、Githubの10GiBよりは余裕がある。また、当該jsonファイルはそもそもバージョン管理する必要もなかったため、その点でもgit LFSをやめる動機になった。
※上記の無料枠の情報は2025年9月時点での話のため、注意。
前置き
Google Cloudの認証の話
Github ActionsからGoogle Cloudにアクセスするために、一般的な感覚だと、Google Cloud側で何らかの鍵(APIキーのようなもの)を事前に発行しておいて、その鍵をGithub Actions側で使って認証することで、Github ActionsからGoogle Cloudのデータにアクセスできるようになる、といったことを想像すると思うが、実際にはもう少しややこしかった。
まず、Github ActionsでGoogle Cloudに認証するために使うアクションとして、'google-github-actions/auth'がある。(この記事では、@v2を使った)
アクションとしてはこれ1つなのだが、アクションで認証する方法が以下の通りいくつもある。(README.mdを参照のこと)
- Workload Identity 連携
- Workload Identity 連携(サービスアカウント経由)
- Service Account Key JSON
- Generating OAuth 2.0 access tokens
- Generating ID tokens
一番上のWorkload Identity 連携(英語だと、'Direct Workload Identity Federation')が推奨されている。
ややこしいのは、2つ目にもWorkload Identity 連携があること(ただしこちらは、サービスアカウント経由)。英語だと'Workload Identity Federation through a Service Account'で、これは、Workload Identity 連携ではあるのだが、その引数にサービスアカウントを指定する。
サービスアカウントというのは、人間ではなくアプリケーションやプログラムがGoogle Cloudのリソースにアクセスするための特別なGoogleアカウントのことで、Google Cloud上で何ができるかの権限がサービスアカウントに紐づくため、基本的にはサービスアカウントは認証情報と同じで機密情報として秘匿すべきものらしい。サービスアカウント単体ですぐに情報が漏れるわけではないが、攻撃者からすると攻撃の入口になってしまうらしい。
よって本記事では、サービスアカウントを指定しないWorkload Identity 連携を使ってGoogle Cloudに認証する。
Google Cloudの認証の話(おまけ)
ちなみに、Workload Identity 連携のサービスアカウントの指定の有無は、Github Actionsでいうと以下の違いになる。
Workload Identity 連携
- uses: 'google-github-actions/auth@v2' with: workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
Workload Identity 連携(サービスアカウント経由)
- uses: 'google-github-actions/auth@v2' with: workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
※サービスアカウントもsecrets(や環境変数など)で指定するのでログなどで簡単に見えるわけではないのだが、そもそも指定する必要さえない方がセキュリティ的にはよりいいよねという話(たぶん)。
設定
前置きが長くなった。
Google Cloudの設定
Workload Identityプロバイダを作成する
「IAMと管理」→「Workload Identity 連携」→「プールを作成」
IDプールを作成する
| 項目 | 値 | 備考 |
|---|---|---|
| 名前 | (適当に書く) | 例:Github Actions Pool |
| 説明 | (適当に書く) |
プールにプロバイダを追加する
| 項目 | 値 | 備考 |
|---|---|---|
| プロバイダの選択 | OpenID Connect(OIDC) | OpenID Connect - GitHub Docs |
| プロバイダ名 | (適当に書く) | 例:Github Actions Provider |
| プロバイダID | (適当に書く) | 例:github-actions-provider |
| 発行元(URL) | https://token.actions.githubusercontent.com | Google Cloud Platform での OpenID Connect の構成 - GitHub Docsに記載されている |
| オーディエンス | デフォルトのオーディエンス |
プロバイダの属性を構成する
属性のマッピング
| OIDC | 備考 | |
|---|---|---|
| google.subject | assertion.sub | |
| attribute.repository | assertion.repository |
属性条件
| 値 | 備考 |
|---|---|
| attribute.repository == "octcat/oct-repo" | 左記は、octcatというGithubアカウントのoct-repoというリポジトリの例 |
個人的にはこの「属性のマッピング」と「属性条件」が、当初Workload Identity 連携を理解する上で、一番意味がわからなかった。
以下は自分なりの理解。
「属性のマッピング」とは、OIDCプロトコルが提供するデータの各属性を、Google Cloudが認証で使う属性に対応付け(or 関連付け)ること。対応付けるので、「マッピング(Mapping)」という名前になっている。
ちなみにOIDCプロトコルが提供するデータは「OIDCトークン」と呼ぶらしく、OpenID Connect - GitHub Docsの「OIDCトークンの概要」のところに実際にGithubが提供するデータの例が記載されている。その記載を一部抜粋したのが以下。
{ "sub": "repo:octo-org/octo-repo:environment:prod", "environment": "prod", "aud": "https://github.com/octo-org", "ref": "refs/heads/main", "repository": "octo-org/octo-repo", "repository_owner": "octo-org" }
jsonデータになっていて、よく見るとGithubのリポジトリ名やアカウント名、ブランチ名などなどに関するデータが含まれている。これらのデータが認証時にGoogle Cloudへ提供される。
「属性条件」とは、認証OK、認証NGの判定基準。上記で設定したattribute.repository == "octcat/oct-repo"の場合、Githubのリポジトリ"octcat/oct-repo"からの認証リクエストはOKとし、それ以外はNGとするということ。なのでこの条件を変えることで、例えばアカウント名が自分のアカウントだったらすべて認証OKとか、特定のリポジトリの特定のブランチだけ認証OKにする、とかができる。
仮にこの属性条件を指定しないと、ここで作成したWorkload Identityプロバイダについて、Githubからの認証リクエストはすべてOKになる(つまり、別の誰かがこのWorkload IdentityプロバイダのIDを入手したら、Github Actionsから限定ではあるが、あなたのGoogle Cloudにアクセスできるということ。たぶん)。
Workload IdentityプロバイダにGoogle Cloud Storageのアクセス権限を付与する
「Cloud Storage」→「バケット」→(アクセス対象のバケット)→「権限」→「アクセスを許可」
プリンシパルの追加
| 項目 | 値 | 備考 |
|---|---|---|
| 新しいプリンシパル | principalSet://iam.googleapis.com/projects/(自分のPROJECT_NUMBER)/locations/global/workloadIdentityPools/(自分のPOOL_ID)/attribute.repository/octcat/oct-repo | 左記はoctcat/oct-repoというリポジトリの場合 |
公式マニュアルの'All identities in a workload identity pool with a certain attribute'の箇所を参照すると、
principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.ATTRIBUTE_NAME/ATTRIBUTE_VALUE
とあり、PROJECT_NUMBER, POOL_ID, ATTRIBUTE_NAME, ATTRIBUTE_VALUEは自分の環境に応じた値を設定する必要がある。
PROJECT_NUMBER, POOL_IDは、さきほど作成したWorkload Identityプロバイダの詳細画面で確認できる。
「IAMと管理」→「Workload Identity 連携」→「Github Actions Pool(上記の例だと)」へ行き、プールの詳細に「IAMプリンシパル」という項目がある。以下のようになっているはずなので、PROJECT_NUMBER, POOL_IDを確認する。
principal://iam.googleapis.com/projects/(自分のPROJECT_NUMBER)/locations/global/workloadIdentityPools/(自分のPOOL_ID)/subject/SUBJECT_ATTRIBUTE_VALUE
なおプリンシパルに関して「principalSet」と「principal」(Setが付いてる、付いてない)は意味が違うので注意。
ロールを割り当てる
「Storageオブジェクト閲覧者」(いわゆるread権限)を割り当てる。write権限も必要であれば、「Storageオブジェクト管理者」などを設定する。
Github Actionsの設定
ジョブの設定
steps: - name: Checkout uses: actions/checkout@v4 - id: 'auth' uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCP_PROJECT_ID }} workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - name: 'Set up Google Cloud SDK' uses: 'google-github-actions/setup-gcloud@v2' with: version: '>= 363.0.0' - name: 'Use gcloud CLI' run: 'gcloud info' - name: 'Access Cloud Storage' run: 'gcloud storage cp gs://YOUR_BUCKET_NAME/hoge.json' ./
注意点
- 'google-github-actions/auth@v2'よりも前に'actions/checkout@v4'を実行しておくこと。README.mdにPrerequisitesとして記載されているため。
- 'google-github-actions/auth@v2'のproject_idは、Cloud Storageのバケットが所属しているプロジェクトのIDを設定すること。
- 'google-github-actions/auth@v2'のworkload_identity_providerは、projects/(自分のPROJECT_NUMBER)/locations/global/workloadIdentityPools/(自分のPOOL_ID)/providers/(自分のPROVIDER_ID) の形式で書く。
- 'google-github-actions/setup-gcloud@v2'のバージョンは、363.0.0以降にすること。README.mdに、Workload Identity連携はそのバージョン以降が必要と記載されているため。
- 上記の例では、GCP_PROJECT_ID, GCP_WORKLOAD_IDENTITY_PROVIDERは、Github Actionsのsecretsに登録している。
- 最後のgcloud storage cpでGoogle Cloud StorageからGithub Actionsのローカルにファイルをコピーしている。
一発でうまくいくことの方が少ないと思われるため、トラブルシュートとしては、各ステップごとに成功するかを前から順番に確認していく。
'google-github-actions/auth@v2'は、Workload Identityプロバイダが作成できていて、引数workload_identity_providerに適切な値を渡せていれば成功する。project_idは渡さなくても成功したはずで、ただしproject_idが指定されていないというWarningのようなメッセージが出た気がする(たしか)。
'google-github-actions/setup-gcloud@v2'は、自分の環境では失敗しなかった。
'gcloud info'は、authが成功していれば、成功する。このコマンドが成功していれば、少なくともGoogle Cloudには認証したことを示す。
'gcloud storage cp'が、自分の場合、一番ハマったところだった。エラーとしては、対象のCloud Storageのバケットにアクセス権限がない、というのが大半だった。Workload Identityプロバイダに対して、対象のCloud Storageのバケットのアクセス権限を適切に付与できていなかったのだが、その原因となったのが主に「属性のマッピング」と「属性条件」だった。この2つは設定値が変な値、辻褄が合わない値でも設定自体はできてしまうので、正しい値を設定できているのか否かの判別が難しかった。
最後に
Workload Identity 連携に関して、ネット上ではサービスアカウントを使用する場合の情報が多く、今回のようにサービスアカウントを使用しない場合の情報は少ない印象だった。
GeminiにWorkload Identity 連携について質問しても、サービスアカウントを紐付けたWorkload Identity 連携についての回答しかしてこなかったので、それなりに悪戦苦闘した。
最後にはなんとかGithub ActionsからGoogle Cloud Storageへアクセスできるようになったので、定期的なビルド時にGit LFSを使わずに数十MBのjsonファイル群をダウンロードできるようになった。
参考
- Workload Identity 連携を利用して GitHub Actions を動かす
- GitHub Actions+OIDCでCloud Runにデプロイする(Node.js) - くらげになりたい。
- Google Cloud Platform での OpenID Connect の構成 - GitHub Docs
- OpenID Connect - GitHub Docs
- Workload Identity 連携 | IAM Documentation | Google Cloud
- GitHub - google-github-actions/auth: A GitHub Action for authenticating to Google Cloud.
- GitHub - google-github-actions/setup-gcloud: A GitHub Action for installing and configuring the gcloud CLI.