近年、インフラ構築のコード化には Terraform が広く利用されるようになりました。しかし、Terraform から Google Cloud へリソースをデプロイする際、従来は Service Account キーファイル(JSON)の管理が必要であり、秘密情報の管理コストやキー漏洩リスクといった課題が存在しています。
そこでよりセキュアにデプロイする方法存在するのが、OIDC (OpenID Connect) を使った認証方式です。GitHub Actions と Google Cloud の Workload Identity プール を組み合わせることで、キーファイルを使わずに安全にリソースを作成・更新できるようになります。今回のブログでは、Terraform で OIDC 認証を取り入れ、GitHub Actions 上でセキュアかつシンプルに terraform を実行するための手順を紹介します。
といったステップごとに、コード例も交えて解説していきます。セキュリティを高めつつ、よりスマートな運用を目指す方はぜひ参考にしてみてください。
OIDC とは
OIDC(OpenID Connect) は、OAuth 2.0 をベースとした認証認可の仕様です。ユーザーやアプリケーションが「どのように自分(または特定のエンティティ)が正当な権限を持つか」を証明する仕組みを提供し、主に ID トークンを用いて、各種サービスへのアクセスを安全に制御できるようにします。
ここでは、GitHub Actions から Google Cloud (GCP) のリソースを操作するために OIDC トークンを活用する仕組み を紹介します。以下の図は、実際に GitHub Actions が Google Cloud へアクセス権をリクエスト・受け取りするまでの大まかな流れを示しています。
OIDC トークンの発行依頼 GitHub Actions が、GitHub の OIDC プロバイダ ( https://token.actions.githubusercontent.com ) に対してトークンの発行を依頼します。
OIDC トークンの受け取り ワークフロー内の GitHub Actions が、発行された OIDC トークンを受け取ります。
- この時点では、まだ Google Cloud 側の権限そのものは得られていません。
Security Token Service (STS) への交換リクエスト GitHub Actions は取得したトークンを持って Google Cloud の Security Token Service (STS) に交換(エクスチェンジ)をリクエストし、実際に Google Cloud で認証・認可を受けるための短期認証トークンを得ようとします。
Workload Identity プールの設定チェック Google Cloud の STS は、登録されている Workload Identity プール/プロバイダの情報を参照して、OIDC トークンが正当な発行元 (Issuer) から来ているか、クレーム(Claims) が正しいかなどを検証します。
短期認証トークンの発行 検証が成功すると、STS は短期的に有効な認証トークン(実際には特定のサービスアカウントに紐づいた一時的な資格情報)を発行します。
- これにより「なりすまし」を防ぎつつ、安全にアクセスを移譲できます。
短期トークンを受け取る GitHub Actions は STS から返却された短期トークンを受領し、実際に Google Cloud の各種リソースにアクセスする際に使用します。
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
google_iam_workload_identity_pool_provider
GitHub レポジトリの指定方法
attribute_condition = <<EOT assertion.repository == "${local.github_full_repo_name}" || assertion.repository == "${local.github_org_name}/${local.github_bce_repo_name}" EOT
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 レポジトリの設定
GitHub Actions から Google Cloud に対して Workload Identity 認証を行うためには、Workload Identity プールプロバイダやサービスアカウントの情報を 渡す必要があります。ここでは、例として以下の 2 つの値を GitHub Secrets に格納しています。
WORKLOAD_IDENTITY_PROVIDER
SERVICE_ACCOUNT
- なりすましを行う サービスアカウントのメールアドレス (今回は
iam.tf
で作成したサービスアカウント) を格納します。
- なりすましを行う サービスアカウントのメールアドレス (今回は
シークレットの追加手順
GitHub リポジトリの "Settings" タブに移動 リポジトリ管理画面の上部にある “Settings” を選択します。
"Security" セクション -> "Secrets and variables" セクション -> Actions
Secretsタブ -> New repository secretをクリック
シークレット名と値を入力
Name
フィールドにはWORKLOAD_IDENTITY_PROVIDER(または SERVICE_ACCOUNT)などを設定Secret
フィールドには Google Cloud コンソールや Terraform 出力などからコピーした実際の値を入力
"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
手動実行を可能にしています。- 必要に応じて
push
やpull_request
で、自動実行を設定することも可能です。
Google Cloud 認証の設定
Terraform の実行
terraform fmt
、terraform init
、terraform validate
、terraform plan
の順に実行しています。working-directory
で./terraform/environments/${{env.ENV_NAME}}/default/
を指定しており、同ディレクトリ以下に Terraform のファイル一式がある前提です。- Plan までであれば Google Cloud リソースの変更差分を確認するところまで。Apply を行いたい場合は、最後に
terraform apply
ステップを追加するか、別ワークフローを用意するのが一般的です。