😸

Direct Workload Identity Federation + Github Actions + Cloud Run のCD構築

に公開

背景

私は日ごろから業務や個人開発で GCP のお世話になりがちなのですが、認証周りをサービスアカウントの認証情報 JSON ファイルを使用した認証に頼ってしまうことが多くありました。

実際、一部のプロジェクトでは Github Actions を用いて Cloud Run へ自動デプロイを行う CD パイプラインを構築しているのですが、その際にサービスアカウントの認証情報 JSON ファイルを Github Secrets に登録して、それを用いて GCP への認証を行うという方法をとっていました。

しかし、これではサービスアカウント認証情報の管理が煩雑になってしまう上、セキュリティ的にもリスクがあり、あまりよろしくありません。

そこで、サービスアカウントの認証情報 JSON ファイルを使用せずに認証を実施すべく、 Direct Workload Identity Federation (Workload Identity)を利用した認証方法を試してみることにしました。

(Direct Workload Identity Federation 自体は 2023 年 11 月から追加されている機能らしいので、かなり今更ではありますが。)

Direct Workload Identity Federation とは

Direct Workload Identity Federation は、サービスアカウントやその認証情報 JSON ファイルを使用せずに、外部の ID プロバイダ (IDP) を通じて GCP のサービスにアクセスするための認証方法です。

これにより、外部の ID に対して IAM ロールを付与し、GCP リソースへの直接アクセスを可能にします。

https://cloud.google.com/iam/docs/workload-identity-federation

https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation

GCP 環境の構築

デプロイ前の準備として、GCP 環境を構築します。

環境構築は Terraform を使用して行います。gcloud コマンドや UI コンソールを使用しても同様の操作が可能ですが、ここでの説明は割愛します。

#  GCP 環境構築用のディレクトリを作成
mkdir gcp-setup
cd gcp-setup
touch main.tf
main.tf
provider "google" {
  project = "my-project-id" # GCP プロジェクト ID を指定
}

Artifact Registry のリポジトリ作成

Cloud Run へデプロイするためのコンテナイメージを格納するためのリポジトリを作成します。

resource "google_artifact_registry_repository" "repo" {
  location = "asia-northeast1"
  repository_id = "my-repository"
  format = "DOCKER"
}

Workload Identity プールの作成

Workload Identity プールを作成します。

resource "google_iam_workload_identity_pool" "pool" {
  workload_identity_pool_id = "my-pool"
  display_name              = "my-pool"
}

Workload Identity プロバイダの作成

上記で作った Workload Identity プールに対して、Github Actions からの認証を許可するプロバイダを作成します。

repository_owner は、リポジトリ URL( https://github.com/<repository-owner>/<repository-name> )の <repository-owner> の部分です。

resource "google_iam_workload_identity_pool_provider" "provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-actions-provider"
  display_name                       = "github-actions-provider"
  attribute_mapping = {
    "google.subject"             = "assertion.sub"
    "attribute.repository"       = "assertion.repository"
    "attribute.actor"            = "assertion.actor"
    "attribute.repository"       = "assertion.repository"
    "attribute.repository_owner" = "assertion.repository_owner"
  }
  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
  attribute_condition = "assertion.repository_owner == 'my-github-repository-owner'"

  depends_on = [google_iam_workload_identity_pool.pool]
}

Workload Identity プリンシパルに対する IAM ロールの付与

Github Actions から Cloud Run へのデプロイを許可するために、Workload Identity プリンシパルに対して必要な IAM ロールを付与します。

Artifact Registry へのコンテナのプッシュ、および Cloud Run へのデプロイには、以下のロールが必要です

  • roles/run.developer
  • roles/iam.serviceAccountUser
  • roles/artifactregistry.writer (作成した Artifact Registry リポジトリに対して)

対象のプリンシパルは、attribute.repository 属性が自分の Github リポジトリである ID グループにしておきます。

variable "principal" {
  default = "principalSet://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/<workload_identity_pool_id>/attribute.repository/<repository-owner>/<repository-name>"
}

resource "google_project_iam_member" "run_developer" {
  project = "my-project-id"
  role = "roles/run.developer"
  member = var.principal
}

resource "google_project_iam_member" "iam_service_account_user" {
  project = "my-project-id"
  role = "roles/iam.serviceAccountUser"
  member = var.principal
}

resource "google_artifact_registry_repository_iam_member" "repository_writer" {
  project = "my-project-id"
  location = "asia-northeast1"
  repository = "my-repository"
  role = "roles/artifactregistry.writer"
  member = var.principal
}

Terraform の実行

これまでの Terraform コードを main.tf に追加し、以下のコマンドで GCP 環境を構築します。

# Terraform の初期化
terraform init

# Terraform の適用内容確認
terraform plan

# Terraform の適用
terraform apply

Github Actions の設定

Github Actions のワークフローを作成します。

name: Deploy to Cloud Run

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      id-token: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Authenticate to GCP
        id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: "projects/<project-number>/locations/global/workloadIdentityPools/<workload_identity_pool_id>/providers/github-actions-provider"

    - name: Set up Google Cloud SDK
      uses: google-github-actions/setup-gcloud@v2

    - name: Configure Docker
      run: |
        gcloud auth configure-docker asia-northeast1-docker.pkg.dev

    - name: Build and Push Docker Image
        run: |
            docker build -t asia-northeast1-docker.pkg.dev/<project-id>/my-repository/my-image:${{ github.sha }} .
            docker push asia-northeast1-docker.pkg.dev/<project-id>/my-repository/my-image:${{ github.sha }}

    - name: Deploy to Cloud Run
        run: |
            gcloud run deploy my-service \
            --project <project-id> \
            --image asia-northeast1-docker.pkg.dev/<project-id>/my-repository/my-image:${{ github.sha }} \
            --region asia-northeast1 \
            --platform managed

こちらを作成したら、main ブランチにプッシュして、デプロイが完了することを確認してください。

まとめ

Direct Workload Identity Federation を使用するようにしたことで、サービスアカウントの認証情報 JSON ファイル無しで Cloud Run への自動デプロイが実現できました。

チーム内でのキーの共有や管理だったり、Gtihub Secrets への登録だったり、そういった作業が面倒だなあと思っていたので、これでかなり楽になりました。

セキュリティリスクも減らせるので一石二鳥です。

参考

実装や記事の執筆にあたって、以下の記事を大変参考にさせていただきました。この場を借りて感謝申し上げます。

https://paper2.hatenablog.com/entry/2024/06/29/143947

Discussion