kakakakakku blog

Weekly Tech Blog: Keep Learning!

Google Cloud の Direct Workload Identity Federation を使って GitHub Actions から Terraform を実行する

はじめに

GitHub Actions から Google Cloud にアクセスするときに「サービスアカウントキー」ではなく「Workload Identity Federation (WIF)」を使うことが推奨されている.長期的な認証情報を管理する必要がなくて安全に使える.今回はプリンシパルに直接アクセス権限を渡す Direct Workload Identity Federationgoogle-github-actions/auth に載っている名称)を使って GitHub Actions で Google Cloud プロジェクトに対して Terraform コマンドを実行する仕組みを Terraform で作る❗️

docs.cloud.google.com

Workload Identity プール

まず最初に Workload Identity プールを作る.google_iam_workload_identity_pool リソースを使う.

resource "google_iam_workload_identity_pool" "github_actions" {
  workload_identity_pool_id = "github-actions"
  display_name              = "GitHub Actions"
}

Workload Identity プロバイダー

次に Workload Identity プールにプロバイダを紐付ける.google_iam_workload_identity_pool_provider リソースを使う.GitHub Actions から OIDC トークンを受け付けられるようにする設定で attribute_condition で GitHub リポジトリのオーナーを制限できる.イシュアとして設定する URL は GitHub Actions のドキュメントに載っている https://token.actions.githubusercontent.com にしておく👌

docs.github.com

resource "google_iam_workload_identity_pool_provider" "github_actions" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.github_actions.workload_identity_pool_id
  workload_identity_pool_provider_id = "github"
  display_name                       = "GitHub Actions"

  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.actor"      = "assertion.actor"
    "attribute.repository" = "assertion.repository"
  }

  attribute_condition = "assertion.repository_owner == 'kakakakakku'"

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

IAM

Direct Workload Identity Federation ではサービスアカウントを使わず,Workload Identity プールのプリンシパルに対して直接 IAM ロールを付与できる.今回は Terraform の CI/CD で使う検証環境なので roles/writer を付与しているけど,本来は最小権限の原則を意識する必要がある⚠️

resource "google_project_iam_member" "github_actions_writer" {
  project = "xxxxx"
  role    = "roles/writer"
  member  = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_actions.name}/attribute.repository/kakakakakku/xxxxx"
}

ちなみに今回検証で使っている Terraform プロジェクトでは google_billing_budget リソースを使った Google Cloud プロジェクトの「予算アラート」も設定しているため,さらに請求アカウントに対する roles/billing.viewer の付与も必要だった.

resource "google_billing_account_iam_member" "github_actions_billing_viewer" {
  billing_account_id = "XXXXXX-XXXXXX-XXXXXX"
  role               = "roles/billing.viewer"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_actions.name}/attribute.repository/kakakakakku/xxxxx"
}

Terraform の Google Cloud Storage (GCS) Backend

今回のように Direct Workload Identity Federation を使っていて,Terraform のステートを Google Cloud Storage (GCS) で管理している場合は「均一なバケットレベルのアクセス」を有効化しておく必要がある.ドキュメントには以下のように書いてあった.

Uniform bucket-level access must be enabled to grant Workforce Identity Federation or Workload Identity Federation entities access to Cloud Storage resources.

google_storage_bucket リソースだと uniform_bucket_level_access = true で設定できる👌

docs.cloud.google.com

resource "google_storage_bucket" "tfstate" {
  name     = "xxxxx"
  location = "asia-northeast1"

  uniform_bucket_level_access = true

  versioning {
    enabled = true
  }
}

GitHub Actions ワークフロー

最後に GitHub Actions ワークフローを作る.google-github-actions/authDirect Workload Identity Federation を使ってアクセス権限を取得しつつ,最終的に terraform plan コマンドを実行している💪

github.com

name: Terraform Plan

on:
  pull_request:
    branches:
      - main
  workflow_dispatch:

jobs:
  plan:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
        with:
          workload_identity_provider: 'projects/00000000000/locations/global/workloadIdentityPools/github-actions/providers/github'
          project_id: 'xxxxx'

      - uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0
        with:
          terraform_version: 1.14.0

      - run: terraform init
      - run: terraform plan

適当に Pub/Sub トピックを追加するプルリクエストを作ったら,自動的に terraform plan を実行できた \( 'ω')/

まとめ

今回は GitHub Actions で Google Cloud プロジェクトに対して Terraform コマンドを実行する仕組みを作りたくて,google-github-actions/auth に載っている3種類の選択肢の中で Preferred と書いてある Direct Workload Identity Federation を使ってみた.期待した通りに terraform plan コマンドを実行できるようになって良かった❗️

  1. (Preferred) Direct Workload Identity Federation
  2. Workload Identity Federation through a Service Account
  3. Service Account Key JSON

Hurl で負荷テストを実行する

Hurl では API のテストシナリオを使って負荷テストを実行することができる.今回は Hurl ファイルに指定する repeat と CLI のオプション --repeat の違いを確認しつつ,さらに CLI のオプション --delay も試す👌Hurl のドキュメント「Running Tests」にも「Stress and Performance Tests」というセクションがある.

hurl.dev

準備

まずはローカル環境に適当な API を起動しておく.今回は httpbin を使った.

Hurl ファイルで repeat を指定する

まず Hurl ファイルの [Options] セクションに repeat を指定すると同じリクエストを繰り返し実行できる.Hurl ファイルに指定する repeat は1つずつ順番に実行するため,あくまで逐次実行になる.

GET http://localhost:8080/status/200
[Options]
repeat: 10
HTTP 200

以下のように実行できる.

$ hurl --test 1-repeat.hurl
Success 1-repeat.hurl (10 request(s) in 13 ms)
--------------------------------------------------------------------------------
Executed files:    1
Executed requests: 10 (714.3/s)
Succeeded files:   1 (100.0%)
Failed files:      0 (0.0%)
Duration:          14 ms (0h:0m:0s:14ms)

Hurl ファイルで repeatdelay を組み合わせる

Hurl ファイルには repeat 以外に delay を指定することもできる.リクエストとリクエストの間の待機時間(ミリ秒)を指定することができて,負荷テストの間隔を調整できる.

GET http://localhost:8080/status/200
[Options]
repeat: 10
delay: 1000
HTTP 200

以下のように実行できる.実行時間が10秒になっていて,合計10回のリクエストが1秒待機しながら1つずつ順番に実行されていることがわかる.

$ hurl --test 2-repeat-delay.hurl
Success 2-repeat-delay.hurl (10 request(s) in 10076 ms)
--------------------------------------------------------------------------------
Executed files:    1
Executed requests: 10 (1.0/s)
Succeeded files:   1 (100.0%)
Failed files:      0 (0.0%)
Duration:          10079 ms (0h:0m:10s:79ms)

CLI のオプションで --repeat を指定する

同じ repeat だけど Hurl ファイルではなくて CLI のオプションで --repeat を指定すると,今度は Hurl ファイル自体を並列で実行できる💡

hurl.dev

以下のように実行できる.実行時間が10秒になっていて,並列で合計50回のリクエストが実行されていることがわかる.

$ hurl --test --repeat 5 2-repeat-delay.hurl 
Success 2-repeat-delay.hurl (10 request(s) in 10091 ms)
Success 2-repeat-delay.hurl (10 request(s) in 10109 ms)
Success 2-repeat-delay.hurl (10 request(s) in 10112 ms)
Success 2-repeat-delay.hurl (10 request(s) in 10113 ms)
Success 2-repeat-delay.hurl (10 request(s) in 10113 ms)
--------------------------------------------------------------------------------
Executed files:    5
Executed requests: 50 (4.9/s)
Succeeded files:   5 (100.0%)
Failed files:      0 (0.0%)
Duration:          10118 ms (0h:0m:10s:118ms)

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

Pulumi で Filesystem Backend から Google Cloud Storage (GCS) Backend にステートを移行する

Pulumi で Filesystem Backend を使ってステートをローカル環境で管理している構成から Google Cloud Storage (GCS) Backend でステートを管理する構成に移行する流れを試してみた❗️基本的な流れは公式ドキュメントの Migrating Between State Backends というセクションにまとまっていて参考になる.

www.pulumi.com

準備

まずは pulumi new gcp-typescript --dir sandbox-pulumi-state-migration コマンドを実行して Pulumi プロジェクトを作る.そして index.ts を以下のように修正しておく.Cloud Storage バケット1つと Pub/Sub トピック1つでシンプル👌

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

new gcp.storage.Bucket("sandbox-pulumi-state-migration", {
    name: "sandbox-pulumi-state-migration",
    location: "asia-northeast1",
});

new gcp.pubsub.Topic("sandbox-pulumi-state-migration", {
    name: "sandbox-pulumi-state-migration",
});

Filesystem Backend でログイン

次に pulumi login コマンドを実行して Filesystem Backend でログインする.

$ pulumi login file://./

pulumi up コマンドを実行してデプロイすると .pulumi ディレクトリの下にステート .pulumi/stacks/sandbox-pulumi-state-migration/dev.json ができていた.

$ tree .pulumi/stacks/sandbox-pulumi-state-migration
.pulumi/stacks/sandbox-pulumi-state-migration
├── dev.json
├── dev.json.attrs
├── dev.json.bak
└── dev.json.bak.attrs

1 directory, 4 files

www.pulumi.com

Google Cloud Storage (GCS)

今度は Google Cloud Storage (GCS) Backend として使う Cloud Storage バケットを gcloud storage buckets create コマンドでデプロイする.バージョニング設定も有効化しておく.gcloud コマンドの認証まわりは割愛する.

$ gcloud storage buckets create gs://kakakakakku-sandbox-pulumi-states \
  --location=asia-northeast1 \
  --project=kakakakakku

$ gcloud storage buckets update gs://kakakakakku-sandbox-pulumi-states --versioning

ステートのエクスポート

Google Cloud Storage (GCS) Backend にステートをインポートするために pulumi stack export コマンドでステートをエクスポートする.

$ pulumi stack export --show-secrets --stack dev --file dev-state.json

www.pulumi.com

Google Cloud Storage (GCS) Backend でログイン

ここで pulumi login コマンドを実行して Google Cloud Storage (GCS) Backend でログインする.

$ pulumi login gs://kakakakakku-sandbox-pulumi-states

ちなみにログインをした時点で Cloud Storage バケットに .pulumi/meta.yaml が作られていた✅️

ステートのインポート

最後に pulumi stack init コマンドでスタックをセットアップして,pulumi stack import コマンドでステートをインポートすれば完了❗️

$ pulumi stack init dev
Created stack 'dev'

$ pulumi stack import --stack dev --file dev-state.json
Import complete.

www.pulumi.com

差分確認

最後に pulumi preview コマンドを実行して「差分なし」になっていることを確認できた \( 'ω')/

$ pulumi preview
Previewing update (dev):
     Type                 Name                                Plan     
     pulumi:pulumi:Stack  sandbox-pulumi-state-migration-dev           

Resources:
    3 unchanged

お掃除

不要になったステートのエクスポートと Filesystem Backend.pulumi ディレクトリを削除しておく🗑️

$ rm dev-state.json
$ rm -rf .pulumi

まとめ

あくまで検証用のシンプルな Pulumi プロジェクトだけど,Pulumi で Filesystem Backend から Google Cloud Storage (GCS) Backend にステートを移行する流れを確認できて良かった❗️

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

Hurl で Basic 認証付きのリクエストを送る

Hurl で Basic 認証付きのリクエストを送る場合は username と password を Hurl ファイルに指定する方法と CLI のオプションで指定する方法がある👌

準備

まずは nginx で Basic 認証が必要なウェブサイトを準備しておく🔐

Hurl ファイルで BasicAuth を指定する

Hurl では Hurl ファイルに [BasicAuth] を指定すると Basic 認証付きのリクエストを送ることができる.

GET http://localhost:8080
[BasicAuth]
myuser: mypassword
HTTP 200

以下のように実行できる.しかし Hurl ファイルに直接 username と password を指定することになるため微妙ではある🙅‍♂️

$ hurl 1-basic-auth.hurl

hurl.dev

CLI のオプションで --user を指定する

Hurl ファイルには username と password を指定せず,CLI のオプション --user で指定することもできる.

GET http://localhost:8080
HTTP 200

以下のように実行できる.

$ hurl --user myuser:mypassword 2-basic-auth-cli.hurl

hurl.dev

変数を使う

Hurl ファイルに [BasicAuth] を指定しつつ,値は変数から取得することができる.

GET http://localhost:8080
[BasicAuth]
{{username}}: {{password}}
HTTP 200

以下のように実行できる.

$ hurl --variable username=myuser --variable password=mypassword 3-basic-auth-variable.hurl

もし変数を CLI のオプションではなくファイルを指定する場合は --variable ではなく --variables-file を指定すれば OK👌

$ hurl --variables-file variables.env 3-basic-auth-variable.hurl

関連記事

kakakakakku.hatenablog.com

Hurl でカスタマイズした User-Agent を指定する

Hurl で HTTP リクエストを送るときにデフォルトの User-Agent は hurl/7.1.0 のようなバージョンを含んだ値になる.カスタマイズした User-Agent を使う場合は Hurl ファイルにヘッダーとして指定する方法と CLI のオプションで指定する方法がある👌

デフォルト

まずデフォルトの挙動を確認する.nginx を起動して,以下のようなシンプルな Hurl ファイル 1-default.hurl を作る.

GET http://localhost:8080
HTTP 200

実行すると hurl/7.1.0 になる.

Hurl ファイルで User-Agent を指定する

次は Hurl ファイルで User-Agent ヘッダーを指定する.今回は以下のような 2-custom.hurl を作る.

GET http://localhost:8080
User-Agent: MyTestRunner/1.0
HTTP 200

実行すると MyTestRunner/1.0 になる.

hurl.dev

CLI のオプションで User-Agent を指定する

Hurl ファイルには User-Agent を指定せず,CLI のオプション --user-agent で指定することもできる.

$ hurl --user-agent MyTestRunner/1.0 1-default.hurl

実行すると MyTestRunner/1.0 になる.

hurl.dev