G-gen の武井です。当記事では Google Cloud と GitHub Actions (Terraform) を連携する Workload Identity を作成する bash スクリプトを紹介します。
はじめに
概要
当記事で紹介するのは、Google Cloud と GitHub Actions (Terraform) との連携に必要な Workload Identity リソースを作成するスクリプトです。
当スクリプトを使うことで、Google Cloud プロジェクトに対する terraform plan
や terraform apply
を、GitHub Actions で自動化できます。
なお、GitHub Actions や Workload Identity に関する詳細は以下の記事からご確認ください。
前提条件
当 bash スクリプトは、Debian GNU/Linux 12 (bookworm)
上で開発され、動作確認されています。
また、以下のソフトウェアがインストールされていることが前提です。カッコ内は開発時のバージョンです。
- gcloud(
Google Cloud SDK 486.0.0
)
スクリプト実行時は、実行先のプロジェクトに対して gcloud CLI を認証する必要があります。
- 参考 : ユーザー アカウントを使用して認可する
- 参考 : サービス アカウントを使用して承認する
免責事項
当記事で紹介するプログラムのソースコードは、ご自身の責任のもと、使用、引用、改変、再配布して構いません。
ただし、同ソースコードが原因で発生した不利益やトラブルについては、当社は一切の責任を負いません。
ソースコード
前述の 免責事項
をご理解のうえ、ご利用ください。
init.sh
#!/bin/bash# エラーハンドリング: エラーが発生したらスクリプトを終了set -e# 変数の設定PROJECT_ID=" " # プロジェクトID (ex: gha-demo-prj)PROJECT_NUMBER=" " # プロジェクト番号 (ex: 1234567890)ORGANIZATION_ID=" " # プロジェクトの組織ID (ex: 0123456789)WORKLOAD_IDENTITY_POOL=" " # Workload Identityプール名 (ex: gha-demo-pool)WORKLOAD_IDENTITY_PROVIDER=" " # Workload Identityプロバイダ名 (ex: gha-demo-provider)SERVICE_ACCOUNT_NAME=" " # サービスアカウント名 (ex: gha-demo-sa)GITHUB_REPO=" " # GitHubリポジトリ名 (ex: gha-demo-org/gha-demo-repo)# ログ出力関数log() {echo "[INFO] $1"}log_error() {echo "[ERROR] $1" >&2}# 変数のチェック: すべての変数が設定されているか確認if [[ -z "$PROJECT_ID" || -z "$PROJECT_NUMBER" || -z "$ORGANIZATION_ID" || -z "$WORKLOAD_IDENTITY_POOL" || -z "$WORKLOAD_IDENTITY_PROVIDER" || -z "$SERVICE_ACCOUNT_NAME" || -z "$GITHUB_REPO" ]]; thenlog_error "必須の変数が設定されていません。変数を確認してください。"exit 1fi# 1. IAM Credential API を有効化if ! gcloud services list --enabled --filter="name:iamcredentials.googleapis.com" --format="value(name)" | grep "iamcredentials.googleapis.com" >/dev/null 2>&1; thenlog "IAM Credential API を有効にしています..."gcloud services enable iamcredentials.googleapis.com --project="$PROJECT_ID"elselog "IAM Credential API は既に有効化されています"fi# 2. Workload Identity プールの作成if ! gcloud iam workload-identity-pools describe $WORKLOAD_IDENTITY_POOL --location="global" --project="$PROJECT_ID" >/dev/null 2>&1; thenlog "Workload Identity プールを作成中: $WORKLOAD_IDENTITY_POOL"gcloud iam workload-identity-pools create $WORKLOAD_IDENTITY_POOL \--project="$PROJECT_ID" \--location="global" \--display-name="$WORKLOAD_IDENTITY_POOL"elselog "Workload Identity プールは既に存在します: $WORKLOAD_IDENTITY_POOL"fi# 3. Workload Identity プロバイダの作成if ! gcloud iam workload-identity-pools providers describe $WORKLOAD_IDENTITY_PROVIDER --workload-identity-pool="$WORKLOAD_IDENTITY_POOL" --location="global" --project="$PROJECT_ID" >/dev/null 2>&1; thenlog "Workload Identity プロバイダを作成中: $WORKLOAD_IDENTITY_PROVIDER"gcloud iam workload-identity-pools providers create-oidc $WORKLOAD_IDENTITY_PROVIDER \--project="$PROJECT_ID" \--location="global" \--workload-identity-pool="$WORKLOAD_IDENTITY_POOL" \--display-name="$WORKLOAD_IDENTITY_PROVIDER" \--issuer-uri="https://token.actions.githubusercontent.com" \--attribute-mapping="attribute.actor=assertion.actor,google.subject=assertion.sub,attribute.repository=assertion.repository" \--attribute-condition="assertion.repository=='${GITHUB_REPO}'"elselog "Workload Identity プロバイダは既に存在します: $WORKLOAD_IDENTITY_PROVIDER"fi# 4. サービスアカウントの作成if ! gcloud iam service-accounts describe $SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com >/dev/null 2>&1; thenlog "サービスアカウントを作成中: $SERVICE_ACCOUNT_NAME"gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \--project="$PROJECT_ID" \--display-name="GitHub Actions Service Account"elselog "サービスアカウントは既に存在します: $SERVICE_ACCOUNT_NAME"fi# 5. 組織レベルでのロール付与log "組織レベルでのロール付与の確認"for role in "roles/resourcemanager.organizationAdmin" "roles/owner"; doif ! gcloud organizations get-iam-policy $ORGANIZATION_ID --flatten="bindings[].members" --filter="bindings.members:serviceAccount:$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com AND bindings.role:$role" --format="value(bindings.role)" | grep "$role" >/dev/null 2>&1; thenlog "$role をサービスアカウントに付与中: $SERVICE_ACCOUNT_NAME"gcloud organizations add-iam-policy-binding $ORGANIZATION_ID \--member="serviceAccount:$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com" \--role="$role"elselog "$role は既にサービスアカウントに付与されています: $SERVICE_ACCOUNT_NAME"fidone# 6. Workload Identity プールと GitHub リポジトリのリンク作成log "Workload Identity プールと GitHub リポジトリのリンク作成の確認"for subject in "principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$WORKLOAD_IDENTITY_POOL/subject/repo:$GITHUB_REPO:pull_request" "principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$WORKLOAD_IDENTITY_POOL/subject/repo:$GITHUB_REPO:ref:refs/heads/main"; doif ! gcloud iam service-accounts get-iam-policy $SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com --flatten="bindings[].members" --filter="bindings.members:$subject" --format="value(bindings.role)" | grep "roles/iam.workloadIdentityUser" >/dev/null 2>&1; thenlog "Workload Identity プールと GitHub リポジトリのリンクを作成中: $subject"gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com \--role="roles/iam.workloadIdentityUser" \--member="$subject"elselog "Workload Identity プールと GitHub リポジトリのリンクは既に設定されています: $subject"fidonelog "Workload Identity 設定が完了しました。"log "サービスアカウント: $SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com"
スクリプトの使い方
認証
まずは実行先のプロジェクトに対して gcloud CLI の認証を通します。
# 実行先プロジェクトの確認$ gcloud config list[core]account = test-user@demo.g-gen.co.jpdisable_usage_reporting = Trueproject = gha-demo-prjYour active configuration is: [gha-demo-prj]# gcloud CLI の認証$ gcloud auth login~~中略~~You are now logged in as [test-user@demo.g-gen.co.jp].Your current project is [gha-demo-prj].
変数設定
7~13行目
の変数に環境情報を入力します。
実行
スクリプトに実行権限を付与して実行します。
# 実行権限付与$ chmod +x init.sh$ ls -l-rwxr-xr-x 1 test-user test-user 5294 Oct 18 11:49 init.sh
# スクリプト実行$ ./init.sh[INFO] IAM Credential API は既に有効化されています[INFO] Workload Identity プールを作成中: gha-demo-poolCreated workload identity pool [gha-demo-pool].[INFO] Workload Identity プロバイダを作成中: gha-demo-providerCreated workload identity pool provider [gha-demo-provider].[INFO] サービスアカウントを作成中: gha-demo-saCreated service account [gha-demo-sa].[INFO] 組織レベルでのロール付与の確認[INFO] roles/resourcemanager.organizationAdmin をサービスアカウントに付与中: gha-demo-saUpdated IAM policy for organization [0123456789].~~中略~~[INFO] roles/owner をサービスアカウントに付与中: gha-demo-saUpdated IAM policy for organization [0123456789].~~中略~~[INFO] Workload Identity プールと GitHub リポジトリのリンク作成の確認[INFO] Workload Identity プールと GitHub リポジトリのリンクを作成中:~~中略~~[INFO] Workload Identity 設定が完了しました。[INFO] サービスアカウント: gha-demo-sa@gha-demo-prj.iam.gserviceaccount.com
リソースの確認
Workload Identity プール・プロバイダー
以下のように作成されます。



サービスアカウント
以下のように作成されます。


IAM Policy
以下のように作成されます。
※ IAM ロールは適用先のセキュリティポリシーに応じて調整してください。

デモ
構成
当スクリプトで作成される Workload Identity を使い、Google Cloud プロジェクトに対する terraform plan
や terraform apply
を、GitHub Actions で自動化します。
ワークフローや Terraform ソースコードは次項に記載のものを使用します。
ご利用される場合は、前述の 免責事項
をご理解のうえ、ご利用ください。
ソースコード (Terraform)
Terraform ディレクトリ構成
.├── .github│ └── workflows│ └── terraform.yaml├── env│ └── demo│ ├── backend.tf│ ├── locals.tf│ ├── main.tf│ └── versions.tf├── modules│ └── apis│ ├── main.tf│ ├── outputs.tf│ └── variables.tf├── .gitignore├── init.sh└── README.md
ワークフロー (terraform.yaml)
以下の値をご自身の環境で作成したリソースに置き換えてください。
38行目
: Workload Identity プロバイダ39行目
:サービスアカウント
name: terraform# main ブランチへの Pull request と Mergeon:pull_request:branches:- mainpush:branches:- main# ジョブ (GitHUb runners で実行)jobs:terraform-workflow:runs-on: ubuntu-latestpermissions:id-token: writecontents: readpull-requests: writestrategy:matrix:# tf_working_dir に main.tf (呼び出し側) のディレクトリを指定tf_working_dir:- ./env/demosteps:- uses: actions/checkout@v4name: Checkoutid: checkout# Workload Identity 連携# https://cloud.google.com/iam/docs/using-workload-identity-federation#generate-automatic- id: 'auth'name: 'Authenticate to Google Cloud'uses: 'google-github-actions/auth@v2'with:workload_identity_provider: 'projects/1234567890/locations/global/workloadIdentityPools/gha-demo-pool/providers/gha-demo-provider'service_account: 'gha-demo-sa@gha-demo-prj.iam.gserviceaccount.com'# https://github.com/marketplace/actions/setup-tfcmt- uses: shmokmt/actions-setup-tfcmt@v2name: Setup tfcmt# https://github.com/marketplace/actions/setup-github-comment- uses: shmokmt/actions-setup-github-comment@v2name: Setup github-comment# https://github.com/actions/setup-node# https://github.com/hashicorp/setup-terraform/issues/84- uses: actions/setup-node@v4with:node-version: '18'- uses: hashicorp/setup-terraform@v3name: Setup terraform- name: Terraform fmtid: fmtrun: |cd ${{ matrix.tf_working_dir }}terraform fmt -recursivecontinue-on-error: true- name: Terraform Initid: initrun: |cd ${{ matrix.tf_working_dir }}terraform init -upgrade- name: Terraform Validateid: validaterun: |cd ${{ matrix.tf_working_dir }}terraform validate# main ブランチへ pull request した際に terraform plan を実行- name: Terraform Planid: planif: github.event_name == 'pull_request'run: |cd ${{ matrix.tf_working_dir }}export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}tfcmt -var target:${{ matrix.tf_working_dir }} plan -- terraform plan --parallelism=50github-comment hide -condition 'Comment.Body contains "No changes."'continue-on-error: true# terraform status で失敗した際に workflow を停止- name: Terraform Plan Statusid: statusif: steps.plan.outcome == 'failure'run: exit 1# main ブランチへ push した際に terraform apply を実行- name: Terraform Applyid: applyif: github.ref == 'refs/heads/main' && github.event_name == 'push'run: |cd ${{ matrix.tf_working_dir }}export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}tfcmt -var target:${{ matrix.tf_working_dir }} apply -- terraform apply -auto-approve -input=false --parallelism=50
env/demo 配下 (呼び出し側)
# backend.tfterraform {backend "gcs" {bucket = "gha-demo-prj-tfstate"prefix = "terraform/state"}}# locals.tflocals {project_id = "gha-demo-prj"apis = ["artifactregistry.googleapis.com","cloudapis.googleapis.com","cloudasset.googleapis.com","cloudresourcemanager.googleapis.com","iam.googleapis.com","iamcredentials.googleapis.com","servicemanagement.googleapis.com","serviceusage.googleapis.com","sts.googleapis.com",]}# main.tfmodule "apis" {source = "../../modules/apis"project_id = local.project_idapis = local.apis}# versions.tfterraform {required_version = "~> 1.9.7"required_providers {google = {source = "hashicorp/google"version = "~> 6.6.0"}}}provider "google" {user_project_override = true}
modules/apis 配下 (モジュール)
# main.tfresource "google_project_service" "apis" {for_each = toset(var.apis)project = var.project_idservice = each.valuedisable_on_destroy = false}resource "null_resource" "delay" {provisioner "local-exec" {command = "sleep 180"}depends_on = [google_project_service.apis]}# outputs.tfoutput "enabled_apis" {description = "List of enabled APIs for the project"value = [for service in google_project_service.apis : service.id]}# variables.tfvariable "apis" {description = "List of APIs to enable"type = list(string)}variable "project_id" {description = "The ID of the project to create resources in"type = string}
プルリクエスト (terraform plan)
main ブランチへのプルリクエストをトリガーに terraform plan
が実行されます。
※ プルリクエストの場合、terraform apply
はスキップされます。

マージ (terraform apply)
main ブランチへのマージをトリガーに terraform apply
が実行されます。
※ マージの場合、terraform plan
はスキップされます。

関連記事
サービスアカウントを必要としない Direct Workload Identity リソースを作成する bash スクリプトについてはこちらをご確認ください。
武井 祐介 (記事一覧)
クラウドソリューション部クラウドエンジニアリング課。
Google Cloud Partner Top Engineer 2025 選出。
趣味はロードレースやサッカー観戦、あとはゴルフと筋トレ。
Follow @ggenyutakei