Google CloudとGitHub Actions(Terraform)を連携するWorkload Identityを作成するbashスクリプト

記事タイトルとURLをコピーする

G-gen の武井です。当記事では Google Cloud と GitHub Actions (Terraform) を連携する Workload Identity を作成する bash スクリプトを紹介します。

はじめに

概要

当記事で紹介するのは、Google Cloud と GitHub Actions (Terraform) との連携に必要な Workload Identity リソースを作成するスクリプトです。

当スクリプトを使うことで、Google Cloud プロジェクトに対する terraform planterraform apply を、GitHub Actions で自動化できます。

なお、GitHub Actions や Workload Identity に関する詳細は以下の記事からご確認ください。

blog.g-gen.co.jp

blog.g-gen.co.jp

前提条件

当 bash スクリプトは、Debian GNU/Linux 12 (bookworm) 上で開発され、動作確認されています。

また、以下のソフトウェアがインストールされていることが前提です。カッコ内は開発時のバージョンです。

  • gcloud(Google Cloud SDK 486.0.0

スクリプト実行時は、実行先のプロジェクトに対して gcloud CLI を認証する必要があります。

blog.g-gen.co.jp

免責事項

当記事で紹介するプログラムのソースコードは、ご自身の責任のもと、使用、引用、改変、再配布して構いません。

ただし、同ソースコードが原因で発生した不利益やトラブルについては、当社は一切の責任を負いません。

ソースコード

前述の 免責事項 をご理解のうえ、ご利用ください。

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" ]]; then
log_error "必須の変数が設定されていません。変数を確認してください。"
exit 1
fi
# 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="attribute.actor=assertion.actor,google.subject=assertion.sub,attribute.repository=assertion.repository" \
--attribute-condition="assertion.repository=='${GITHUB_REPO}'"
else
log "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; then
log "サービスアカウントを作成中: $SERVICE_ACCOUNT_NAME"
gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
--project="$PROJECT_ID" \
--display-name="GitHub Actions Service Account"
else
log "サービスアカウントは既に存在します: $SERVICE_ACCOUNT_NAME"
fi
# 5. 組織レベルでのロール付与
log "組織レベルでのロール付与の確認"
for role in "roles/resourcemanager.organizationAdmin" "roles/owner"; do
if ! 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; then
log "$role をサービスアカウントに付与中: $SERVICE_ACCOUNT_NAME"
gcloud organizations add-iam-policy-binding $ORGANIZATION_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="$role"
else
log "$role は既にサービスアカウントに付与されています: $SERVICE_ACCOUNT_NAME"
fi
done
# 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"; do
if ! 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; then
log "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"
else
log "Workload Identity プールと GitHub リポジトリのリンクは既に設定されています: $subject"
fi
done
log "Workload Identity 設定が完了しました。"
log "サービスアカウント: $SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com"

スクリプトの使い方

認証

まずは実行先のプロジェクトに対して 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~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-pool
Created workload identity pool [gha-demo-pool].
[INFO] Workload Identity プロバイダを作成中: gha-demo-provider
Created workload identity pool provider [gha-demo-provider].
[INFO] サービスアカウントを作成中: gha-demo-sa
Created service account [gha-demo-sa].
[INFO] 組織レベルでのロール付与の確認
[INFO] roles/resourcemanager.organizationAdmin をサービスアカウントに付与中: gha-demo-sa
Updated IAM policy for organization [0123456789].
~~中略~~
[INFO] roles/owner をサービスアカウントに付与中: gha-demo-sa
Updated 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 プール・プロバイダー

以下のように作成されます。

Workload Identity プール
Workload Identity プロバイダー (1/2)
Workload Identity プロバイダー (2/2)

サービスアカウント

以下のように作成されます。

Workload Identity に紐づけるサービスアカウント
GitHub Actions が当サービスアカウント使用するための権限 (Workload Identity ユーザー)

IAM Policy

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

組織レベルの IAM Policy

デモ

構成

当スクリプトで作成される Workload Identity を使い、Google Cloud プロジェクトに対する terraform planterraform 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 と 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'
service_account: 'gha-demo-sa@gha-demo-prj.iam.gserviceaccount.com'
# 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)

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

プルリクエストをトリガーに terraform plan が自動実行

マージ (terraform apply)

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

マージをトリガーに terraform apply が自動実行

関連記事

サービスアカウントを必要としない Direct Workload Identity リソースを作成する bash スクリプトについてはこちらをご確認ください。

blog.g-gen.co.jp

武井 祐介 (記事一覧)

クラウドソリューション部クラウドエンジニアリング課。

Google Cloud Partner Top Engineer 2025 選出。

趣味はロードレースやサッカー観戦、あとはゴルフと筋トレ。