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

武井 祐介 (記事一覧)

クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア

Google Cloud Partner Top Engineer 2025 選出。IaC や CI/CD 周りのサービスやプロダクトが興味分野です。

趣味はロードバイク、ロードレースやサッカー観戦です。