G-gen の武井です。当記事では Google Cloud と GitHub Actions (Terraform) を連携する Direct Workload Identity を作成する bash スクリプトを紹介します。
はじめに
概要
当記事で紹介するのは、Google Cloud と GitHub Actions (Terraform) との連携に必要な Direct Workload Identity リソースを作成する bash スクリプトです。
以前の記事との違い
以前執筆した記事で紹介したのは、サービスアカウントの権限を借用する形式
の Workload Identity リソースを作成するスクリプトです。
今回ご紹介するのは、Workload Identity プールに必要な権限 (IAM ロール) を直接付与する形式
の Workload Identity リソースを作成するスクリプトです。
この方式は、サービスアカウントの払い出しやサービスアカウントを借用するための権限付与が必要ないため、従来よりもセキュアな連携が可能で、Google Cloud、ならびに GitHub の公式ドキュメント上でも推奨されています。
制限事項
推奨される形式ではあるものの、Direct Workload Identity には対応可能なプロダクトや機能に制限があります。
対応していないプロダクトやその機能を管理したい場合、従来形式 (サービスアカウントの権限を借用する形式) の 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) GITHUB_REPO="" # GitHubリポジトリ名 (ex: gha-demo-org/gha-demo-repo) # ログ出力関数 log() { echo "[INFO] $1" } log_error() { echo "[ERROR] $1" >&2 } # 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; then log "IAM Credential API を有効にしています..." gcloud services enable iamcredentials.googleapis.com --project="$PROJECT_ID" else log "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; then log "Workload Identity プールを作成中: $WORKLOAD_IDENTITY_POOL" gcloud iam workload-identity-pools create $WORKLOAD_IDENTITY_POOL \ --project="$PROJECT_ID" \ --location="global" \ --display-name="$WORKLOAD_IDENTITY_POOL" else log "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; then log "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="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \ --attribute-condition="assertion.repository=='$GITHUB_REPO'" else log "Workload Identity プロバイダは既に存在します: $WORKLOAD_IDENTITY_PROVIDER" fi # 4. 組織レベルでのロール付与 log "組織レベルでのロール付与の確認" for role in "roles/resourcemanager.organizationAdmin" "roles/owner"; do if ! gcloud organizations get-iam-policy $ORGANIZATION_ID --flatten="bindings[].members" --filter="bindings.members:principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$WORKLOAD_IDENTITY_POOL AND bindings.role:$role" --format="value(bindings.role)" | grep "$role" >/dev/null 2>&1; then log "$role をWorkload Identity プールに付与中: $WORKLOAD_IDENTITY_POOL" gcloud organizations add-iam-policy-binding $ORGANIZATION_ID \ --member="principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$WORKLOAD_IDENTITY_POOL/attribute.repository/$GITHUB_REPO" \ --role="$role" else log "$role は既にWorkload Identity プールに付与されています: $WORKLOAD_IDENTITY_POOL" fi done log "Direct Workload Identity 設定が完了しました。"
スクリプトの使い方
認証
まずは実行先のプロジェクトに対して gcloud CLI の認証を通します。
# 実行先プロジェクトの確認 $ gcloud config list [core] account = test-user@demo.g-gen.co.jp disable_usage_reporting = True project = gha-demo-prj Your 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~12行目
の変数に環境情報を入力します。
※ 当スクリプトでは、サービスアカウントの変数定義はありません。
実行
スクリプトに実行権限を付与して実行します。
※ 当スクリプトでは、以下のリソースは作成しません。
サービスアカウント
サービスアカウントを借用するための IAM Policy
Workload Identity プールとサービスアカウントの紐づけ
# 実行権限付与 $ chmod +x init.sh $ ls -l -rwxr-xr-x 1 test-user test-user 3784 Nov 12 14:27 init.sh
# スクリプト実行 $ ./init.sh [INFO] IAM Credential API は既に有効化されています [INFO] Workload Identity プールを作成中: gha-demo-pool Created workload identity pool [gha-demo-pool]. [INFO] Workload Identity プロバイダを作成中: gha-demo-provider Created workload identity pool provider [gha-demo-provider]. [INFO] 組織レベルでのロール付与の確認 [INFO] roles/resourcemanager.organizationAdmin をWorkload Identity プールに付与中: gha-demo-pool Updated IAM policy for organization [0123456789]. ~~中略~~ [INFO] roles/owner をWorkload Identity プールに付与中: gha-demo-pool Updated IAM policy for organization [0123456789]. ~~中略~~ [INFO] Workload Identity 設定が完了しました。
リソースの確認
Workload Identity プール・プロバイダー
以下のように作成されます。
サービスアカウント
前述の通り、Direct Workload Identity にサービスアカウントは不要なため、当スクリプトでは作成しません。
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 プロバイダー
Direct Workload Identity では、google-github-actions/auth@v2
でサービスアカウントを定義する必要はありません。
name: terraform # main ブランチへの Pull request と Merge on: pull_request: branches: - main push: branches: - main # ジョブ (GitHUb runners で実行) jobs: terraform-workflow: runs-on: ubuntu-latest permissions: id-token: write contents: read pull-requests: write strategy: matrix: # tf_working_dir に main.tf (呼び出し側) のディレクトリを指定 tf_working_dir: - ./env/demo steps: - uses: actions/checkout@v4 name: Checkout id: 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' # https://github.com/marketplace/actions/setup-tfcmt - uses: shmokmt/actions-setup-tfcmt@v2 name: Setup tfcmt # https://github.com/marketplace/actions/setup-github-comment - uses: shmokmt/actions-setup-github-comment@v2 name: Setup github-comment # https://github.com/actions/setup-node # https://github.com/hashicorp/setup-terraform/issues/84 - uses: actions/setup-node@v4 with: node-version: '18' - uses: hashicorp/setup-terraform@v3 name: Setup terraform - name: Terraform fmt id: fmt run: | cd ${{ matrix.tf_working_dir }} terraform fmt -recursive continue-on-error: true - name: Terraform Init id: init run: | cd ${{ matrix.tf_working_dir }} terraform init -upgrade - name: Terraform Validate id: validate run: | cd ${{ matrix.tf_working_dir }} terraform validate # main ブランチへ pull request した際に terraform plan を実行 - name: Terraform Plan id: plan if: 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=50 github-comment hide -condition 'Comment.Body contains "No changes."' continue-on-error: true # terraform status で失敗した際に workflow を停止 - name: Terraform Plan Status id: status if: steps.plan.outcome == 'failure' run: exit 1 # main ブランチへ push した際に terraform apply を実行 - name: Terraform Apply id: apply if: 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.tf terraform { backend "gcs" { bucket = "gha-demo-prj-tfstate" prefix = "terraform/state" } } # locals.tf locals { 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.tf module "apis" { source = "../../modules/apis" project_id = local.project_id apis = local.apis } # versions.tf terraform { required_version = "~> 1.9.7" required_providers { google = { source = "hashicorp/google" version = "~> 6.6.0" } } } provider "google" { user_project_override = true }
modules/apis 配下 (モジュール)
# main.tf resource "google_project_service" "apis" { for_each = toset(var.apis) project = var.project_id service = each.value disable_on_destroy = false } resource "null_resource" "delay" { provisioner "local-exec" { command = "sleep 180" } depends_on = [google_project_service.apis] } # outputs.tf output "enabled_apis" { description = "List of enabled APIs for the project" value = [for service in google_project_service.apis : service.id] } # variables.tf variable "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)
Direct Workload Identity でも、main ブランチへのプルリクエストをトリガーに terraform plan
が実行されました。
※ プルリクエストの場合、terraform apply
はスキップされます。
マージ (terraform apply)
Direct Workload Identity でも、main ブランチへのマージをトリガーに terraform apply
が実行されました。
※ マージの場合、terraform plan
はスキップされます。
武井 祐介 (記事一覧)
クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア
Google Cloud Partner Top Engineer 2025 選出。IaC や CI/CD 周りのサービスやプロダクトが興味分野です。
趣味はロードバイク、ロードレースやサッカー観戦です。
Follow @ggenyutakei