Yapodu Tech Blog

株式会社ヤポドゥの技術ブログです。

Terraform + GitHub Actions + OIDC で Google Cloud をセキュアにデプロイする

近年、インフラ構築のコード化には Terraform が広く利用されるようになりました。しかし、Terraform から Google Cloud へリソースをデプロイする際、従来は Service Account キーファイル(JSON)の管理が必要であり、秘密情報の管理コストやキー漏洩リスクといった課題が存在しています。

そこでよりセキュアにデプロイする方法存在するのが、OIDC (OpenID Connect) を使った認証方式です。GitHub Actions と Google Cloud の Workload Identity プール を組み合わせることで、キーファイルを使わずに安全にリソースを作成・更新できるようになります。今回のブログでは、Terraform で OIDC 認証を取り入れ、GitHub Actions 上でセキュアかつシンプルに terraform を実行するための手順を紹介します。

  • OIDC を選ぶメリット
  • Google Cloud 側の Workload Identity プールの作り方
  • GitHub での設定方法
  • Terraform と組み合わせて実際に実行する流れ

といったステップごとに、コード例も交えて解説していきます。セキュリティを高めつつ、よりスマートな運用を目指す方はぜひ参考にしてみてください。

OIDC とは

OIDC(OpenID Connect) は、OAuth 2.0 をベースとした認証認可の仕様です。ユーザーやアプリケーションが「どのように自分(または特定のエンティティ)が正当な権限を持つか」を証明する仕組みを提供し、主に ID トークンを用いて、各種サービスへのアクセスを安全に制御できるようにします。

ここでは、GitHub Actions から Google Cloud (GCP) のリソースを操作するために OIDC トークンを活用する仕組み を紹介します。以下の図は、実際に GitHub Actions が Google Cloud へアクセス権をリクエスト・受け取りするまでの大まかな流れを示しています。

OIDC_TF

  1. OIDC トークンの発行依頼 GitHub Actions が、GitHub の OIDC プロバイダ ( https://token.actions.githubusercontent.com ) に対してトークンの発行を依頼します。

    • GitHub が公式に用意している OIDC プロバイダで、ワークフロー実行中に一時的な ID トークンを発行できます。
  2. OIDC トークンの受け取り ワークフロー内の GitHub Actions が、発行された OIDC トークンを受け取ります。

    • この時点では、まだ Google Cloud 側の権限そのものは得られていません。
  3. Security Token Service (STS) への交換リクエスGitHub Actions は取得したトークンを持って Google Cloud の Security Token Service (STS) に交換(エクスチェンジ)をリクエストし、実際に Google Cloud で認証・認可を受けるための短期認証トークンを得ようとします。

  4. Workload Identity プールの設定チェック Google Cloud の STS は、登録されている Workload Identity プール/プロバイダの情報を参照して、OIDC トークンが正当な発行元 (Issuer) から来ているか、クレーム(Claims) が正しいかなどを検証します。

  5. 短期認証トークンの発行 検証が成功すると、STS は短期的に有効な認証トークン(実際には特定のサービスアカウントに紐づいた一時的な資格情報)を発行します。

    • これにより「なりすまし」を防ぎつつ、安全にアクセスを移譲できます。
  6. 短期トークンを受け取る GitHub Actions は STS から返却された短期トークンを受領し、実際に Google Cloud の各種リソースにアクセスする際に使用します。

  7. Google Cloud リソースの操作 最終的に、GitHub Actions は短期トークンを用いて Google Cloud 上のリソース(Cloud SQL、Cloud Storage、Cloud Run など)を操作します。必要な権限は指定したサービスアカウントのポリシーによって制御されています。

前提:Terraform での Google Cloud デプロイが可能な状態

本記事では、すでにService Account を用いて Terraform から Google Cloud リソースをデプロイできる状態を前提としています。

また、Terraform 自体のインストールや、プロジェクト/サービスアカウントの初期設定などの詳細な手順は、本記事では割愛しています。もしこれらの作業をまだ行っていない場合や、初めて Terraform を使うという方は、先に公式ドキュメントや他の入門記事を参考に、Terraform + Google Cloud の基本的なデプロイ手順をひと通り済ませておいてください。

Google Cloud Workload Identity プールの設定

GitHub Actions で Terraform を実行するためのサービスアカウント作成

iam.tf

########
# Github Actions サービスアカウント / Terraform 用
########
resource "google_service_account" "sa_gha_tf" {
  account_id   = local.gha_tf_ac_id
  display_name = "GitHub Actions - Terraform"
  description  = "GitHub Actions Terraform 実行用"
  project      = local.project
}

# Github Actions / オーナー権限
resource "google_project_iam_member" "gha_serviceaccount_user" {
  project = local.project
  role    = "roles/owner"
  member  = "serviceAccount:${google_service_account.sa_gha_tf.email}"
}

GitHub Actions で Terraform を実行するためのサービスアカウントを作成します。 今回はオーナー権限を付与しますが、実際には環境に合わせて権限を付与するようにしてください。

Workload Identity プールとプロバイダの設定

workload_identity.tf

########
# Workload ID pool
########

# NOTE: destroy 後の再作成を行うと entity already exists となる
# Google Cloud 側の API 経由での作成時のバグの可能性がある
resource "google_iam_workload_identity_pool" "wlid_pool" {
  workload_identity_pool_id = local.wlid_pool_name
  display_name              = local.wlid_pool_display_name
  description               = "Identity Pool for Github"
  disabled                  = false
}


########
# Workload ID Provider
########

# NOTE: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider#example-usage---iam-workload-identity-pool-provider-github-actions
resource "google_iam_workload_identity_pool_provider" "wlid_provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.wlid_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = local.wlid_provider_name
  display_name                       = local.wlid_provider_display_name
  description                        = "Identity Pool Provider for Github"
  disabled                           = false

  # 許可する GitHub リポジトリの条件を指定
  attribute_condition = <<EOT
    assertion.repository == "${local.github_full_repo_name}" || assertion.repository == "${local.github_org_name}/${local.github_bce_repo_name}"
EOT

  attribute_mapping = {
    "google.subject"             = "assertion.sub"
    "attribute.actor"            = "assertion.actor"
    "attribute.repository_owner" = "assertion.repository_owner"
    "attribute.repository"       = "assertion.repository"
  }
  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

########
# WorkloadIdentity ユーザ権限追加
########

# Terraform Repository
resource "google_service_account_iam_member" "gha_repository_yapodu_iac" {
  service_account_id = google_service_account.sa_gha_tf.id
  role               = "roles/iam.workloadIdentityUser"

  member = "principalSet://iam.googleapis.com/projects/${local.project_number}/locations/global/workloadIdentityPools/${local.wlid_pool_name}/attribute.repository/${local.github_full_repo_name}"
}
  • google_iam_workload_identity_pool

    • GitHub Actions と Google Cloud を連携するための「アイデンティティプール」 を定義しています。workload_identity_pool_iddisplay_name はプールを特定するために必要な識別子とラベルで、disabled = false でプールを有効化しています。
  • google_iam_workload_identity_pool_provider

    • プールに紐づくOIDC プロバイダ を設定します。GitHub Actions が発行するトークンはすべてこのエンドポイントを発行元とし、GitHub リポジトリや実行者などの情報を含んでいます。

GitHub レポジトリの指定方法

attribute_condition = <<EOT
  assertion.repository == "${local.github_full_repo_name}" || assertion.repository == "${local.github_org_name}/${local.github_bce_repo_name}"
EOT
  • attribute_condition
    • 発行されたトークンが特定の GitHub リポジトリassertion.repository)からのものであるかどうかをチェックする条件式
  • assertion.repository
    • GitHub Actions で実行しているリポジトリのオーナー名とリポジトリ名(例:my-org/my-repo)が格納
    • サンプルとして local.github_full_repo_namelocal.github_org_name/${local.github_bce_repo_name} を使い、複数のリポジトリに対して条件を OR で指定しています。
  • この条件に合致しないリポジトリからのトークンは無効とみなされ、なりすましを防止する仕組みになっています。

IAM 設定と Workload Identity ユーザ権限

  • google_service_account_iam_member
    • roles/iam.workloadIdentityUser を付与。指定した Workload Identity プールからの呼び出しを許可し、GitHub Actions がこのサービスアカウントをなりすまし(Impersonate)できるようになります。
member = "principalSet://iam.googleapis.com/projects/${local.project_number}/locations/global/workloadIdentityPools/${local.wlid_pool_name}/attribute.repository/${local.github_full_repo_name}"
  • attribute.repository/${local.github_full_repo_name}
    • GitHub Actions の OIDC トークンとattribute_condition によって認証された場合のみ、この principalSet として認識され、Terraform 実行用のサービスアカウントを一時的になりすましして Google Cloud リソースを操作できるようになります。

GitHub レポジトリの設定

GitHub Actions から Google Cloud に対して Workload Identity 認証を行うためには、Workload Identity プールプロバイダやサービスアカウントの情報を 渡す必要があります。ここでは、例として以下の 2 つの値を GitHub Secrets に格納しています。

  • WORKLOAD_IDENTITY_PROVIDER

    • Google Cloud コンソールや Terraform で作成した Workload Identity プールプロバイダのフルパス (例: projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider)
    • GitHub Actions 内でこの値を用いて OIDC トークンを Google Cloud 側に認証させ、サービスアカウントの権限を一時的に委譲します。
  • SERVICE_ACCOUNT

    • なりすましを行う サービスアカウントのメールアドレス (今回は iam.tf で作成したサービスアカウント) を格納します。

シークレットの追加手順

  1. GitHub リポジトリの "Settings" タブに移動 リポジトリ管理画面の上部にある “Settings” を選択します。

  2. "Security" セクション -> "Secrets and variables" セクション -> Actions

  3. Secretsタブ -> New repository secretをクリック

  4. シークレット名と値を入力

    • Name フィールドにはWORKLOAD_IDENTITY_PROVIDER(または SERVICE_ACCOUNT)などを設定
    • Secret フィールドには Google Cloud コンソールや Terraform 出力などからコピーした実際の値を入力
  5. "Add secret" をクリックして保存

今回のサンプルではシークレットを使用しましたが、組織やプロジェクトによっては他の方法(暗号化済みの変数や専用ツール)で安全に保管・参照する方法もあります。状況に応じて適切な方法を選択してください。

GitHub Actions の作成

GitHub Actions を用いて Terraform の Plan コマンドを実行するサンプルワークフローを紹介します。

name: Terraform Plan (Yapodu)
on:
  workflow_dispatch:
env:
  ENV_NAME: yapodu-dev
jobs:
  terraform:
    name: Terraform-plan-yapodu-dev

    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    environment: yapodu-dev
    steps:
      - name: Checkout ref_name=${{github.ref_name}} , ref=${{github.ref}}
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.11.1

      - id: 'auth'
        name: Configure Google Cloud credentials
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{secrets.WORKLOAD_IDENTITY_PROVIDER}}
          service_account: ${{secrets.SERVICE_ACCOUNT}}

      - name: Display env ${{env.ENV_NAME}} vars
        run: env

      - name: Terraform ${{env.ENV_NAME}} fmt
        working-directory: ./terraform/environments/${{env.ENV_NAME}}/default/
        run: |
          terraform fmt

      - name: Terraform ${{env.ENV_NAME}} init
        working-directory: ./terraform/environments/${{env.ENV_NAME}}/default/
        run: |
          terraform init

      - name: Terraform ${{env.ENV_NAME}} validate
        working-directory: ./terraform/environments/${{env.ENV_NAME}}/default/
        run: |
          terraform validate

      - name: Terraform ${{env.ENV_NAME}} plan
        working-directory: ./terraform/environments/${{env.ENV_NAME}}/default/
        run: |
          terraform plan

ワークフローの概要

  • ワークフロー実行トリガー

    • on: workflow_dispatch 手動実行を可能にしています。
    • 必要に応じてpushpull_request で、自動実行を設定することも可能です。
  • Google Cloud 認証の設定

    • google-github-actions/auth@v2 アクションを用い、Workload Identity プールを介した認証を行います。
    • シークレットに格納した WORKLOAD_IDENTITY_PROVIDERSERVICE_ACCOUNT を引数として指定し、GitHub Actions が一時的に Google Cloud のサービスアカウントをなりすますための設定を行っています。
  • Terraform の実行

    • terraform fmtterraform initterraform validateterraform plan の順に実行しています。
    • working-directory./terraform/environments/${{env.ENV_NAME}}/default/ を指定しており、同ディレクトリ以下に Terraform のファイル一式がある前提です。
    • Plan までであれば Google Cloud リソースの変更差分を確認するところまで。Apply を行いたい場合は、最後に terraform apply ステップを追加するか、別ワークフローを用意するのが一般的です。

参考リンク