この記事は MicroAd Advent Calendar 2022 の13日目の記事です。
概要
GitHub ActionsでTerraformを実行する手順について記載します。
Terraformで作成する対象はGoogle Cloud のリソースを想定し、認証にはWorkload Identity 連携を利用します。
情報としては珍しいものではありませんが、実際に動かすまでに時間がかかったので備忘録として投稿します。
(TerraformやActionsについて基本な説明もありませんので、ご承知ください。)
Workload Identity 連携とは
Workload Identity 連携とは、OpenID Connect(OIDC)やAWSなどのGoogle Cloud以外のIDを利用し、Google Cloudのサービスアカウントの権限を借用する仕組みです。
従来はCIでGoogle Cloudにアクセスするには、サービスアカウントのキーをCIツールに登録する必要がありましたが、Workload Identity 連携を利用することで、CI側にはキーなどのクレデンシャル情報を登録せずにサービスアカウントの権限を利用することができます。そのため、キーの漏洩リスクや管理コストを減らすことができます。
GitHub ActionsからWorkload Identity 連携を利用するために、Workload Identity プールとプロバイダの準備をします。
準備の手順は以下に記載されていますが、今回はTerraformを用いて作成します。
サイト内には、管理コンソールとgcloudコマンドによる手順が記載されていますので、最初は手順を確認しつつ手動での構築がおすすめです。
ID 連携により有効期間の短い認証情報を取得する | IAM のドキュメント | Google Cloud
Terraformでの構築に使ったコードを以下に載せます。
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "4.45.0"
}
}
}
provider "google" {
project = local.project_id
region = local.region
zone = local.zone
}
locals {
project_id = "<your-project-id>"
region = "us-central1"
zone = "us-central1-a"
}
locals {
terraform_sa = "<Actions実行時に借用したい権限を持つサービスアカウント>"
repo_name = "<GitHubユーザ名>/<GitHubリポジトリ名>"
}
resource "google_project_service" "project" {
project = local.project_id
service = "iamcredentials.googleapis.com"
}
resource "google_iam_workload_identity_pool" "github_actions_pool" {
provider = google-beta
project = local.project_id
workload_identity_pool_id = "github-oidc-pool"
display_name = "github-oidc-pool"
description = "GitHub Actionsで使用"
}
resource "google_iam_workload_identity_pool_provider" "github_actions" {
provider = google-beta
project = local.project_id
workload_identity_pool_id = google_iam_workload_identity_pool.github_actions_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "github-actions"
display_name = "github-actions"
description = "GitHub Actionsで使用"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.repository" = "assertion.repository"
}
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
# Actionsで借用したい権限を持つサービスアカウント
data "google_service_account" "terraform_sa" {
account_id = local.terraform_sa
}
# principalSetでどのリポジトリで利用するかを指定している
resource "google_service_account_iam_member" "terraform_sa_workload_identity_user" {
service_account_id = data.google_service_account.terraform_sa.id
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_actions_pool.name}/attribute.repository/${local.repo_name}"
}
GitHub Actions 実行例
Workload Identity プール・プロバイダの準備ができたら、ActionsのWorkflowを追加し、サービスアカウトの権限でTerraformが実行できる試します。
以下にWorkflowの例を示します。
参考にした情報は以下の通りになっており、その他細かい注意点はコード中に記載します。
前半の認証に関わる部分は、前節にも記載している「ID 連携により有効期間の短い認証情報を取得する」にActionsでの記述例が記載されています。
Terraformの実行部分は以下を参考にしました。PRへのコメントなど見やすくなっておりいい感じです。
name: terraform
# PR作成とマージ後のmainブランチへのPushをトリガーとする
on:
push:
branches:
- main
pull_request:
# ./infra ディレクトリ配下に tf ファイルがある想定
defaults:
run:
working-directory: ./infra
jobs:
terraform-workflow:
runs-on: ubuntu-latest
permissions:
# workload identity連携によるToken発行には以下権限が必要
id-token: write
contents: read
# PR画面でterraform plan結果を投稿させるために以下権限が必要
pull-requests: write
steps:
# https://github.com/google-github-actions/auth
- uses: actions/checkout@v3
- id: 'auth'
name: 'Authenticate to Google Cloud'
# 古いバージョンのactionを利用する例がWebで見かけたので最新になっているか注意
uses: 'google-github-actions/auth@v1'
with:
# 以下、プロバイダのIDは管理コンソールから確認できる
workload_identity_provider: 'projects/<project number>/locations/global/workloadIdentityPools/<workload Identity Poolの名前>/providers/<プロバイダ名>'
service_account: '<サービスアカウントをE-mail形式で記載>'
# ここから terraform
# https://github.com/hashicorp/setup-terraform
- uses: hashicorp/setup-terraform@v2
# 上記と別に古いAction(hashicorp/terraform-github-actions)があるので、そちらを使わないように注意(すでにアーカイブ済み)
- name: Terraform fmt
id: fmt
run: terraform fmt -check -recursive
continue-on-error: true
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
run: terraform plan -no-color
continue-on-error: true
- name: Comment Terraform Plan
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Validation Output</summary>
\`\`\`\n
${{ steps.validate.outputs.stdout }}
\`\`\`
</details>
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
# terraform status で失敗した際に workflow も止める
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
# main ブランチへの push した際のみ terraform apply を実行する
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve -input=false
上記Workflowと./infra
配下にtfファイルを追加し、PR作成するとActionsが実行され、TerraformのPlan結果がPR画面に投稿されます。