7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

GCPでGitHub Actions用(OIDC)のサービスアカウントをTerraformで作成する

Last updated at Posted at 2023-07-20

本記事ではGoogle Cloud Platform上でGitHub ActionsからOIDCで接続できるサービスアカウントをTerraformで作成します。
ついでに、作成した情報をGitHub ActionsのSecretsに登録するところまでTerraformでやります。

参考

検証に使用したコードは以下のリポジトリにあります。

環境

  • Terraform v1.5.3
  • gh version 2.31.0 (2023-06-20)
    • メインの部分には関係ありませんが、GitHubのTokenを得るのに使用しています

Terraformの作成

ここではコードの例を紹介しますが、変数の定義は省略しています。
ある程度推測できる命名にしているので、渡している情報の参考にしてください。
上のリンクのリポジトリには変数の定義を含むコードがあるので、そちらも参照してください。

今回作成する構成

GitHubのリポジトリは既存のものを使用します。
Secretsはリポジトリに直接紐付けます。
Secretsに接続に使用するサービスアカウントの情報とidentity providerの情報を登録します。
リポジトリに登録するため、GCP側でもリポジトリのみで認証をするように設定します。(Environmentsは設定しません)

サービスアカウントにはGitHubのリポジトリから使用できるようにWorkload Identityユーザーの権限を登録するところまでやります。

複数のGitHubリポジトリで同じサービスアカウントを使い回すような設計にしていますのでご了承下さい。1つのリポジトリに1つのサービスアカウントにする場合はサービスアカウントを作成するmoduleをfor_eachで繰り返し適用することになるのかなと思います。

使用するプロバイダー

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4"
    }
    github = {
      source  = "integrations/github"
      version = "~> 5.0"
    }
  }
}

GCPを操作するのでgoogleのプロバイダー、GitHubのリソース作成のためにgithubのProviderを使用します。
GitHubのプロバイダーはdeprecatedになった昔のものを使用しないようにして下さい。
GitHubのリソースを作成するmoduleの中ではterraformブロックを定義して確実にintegrations/githubのモジュールを使わせるようにし、deprecatedになったプロバイダーが混ざることのないようにして下さい。

各Providerの設定は以下のようにしました。

provider "google" {
  project = var.project_name
  region  = var.project_region
  zone    = var.project_zone
}

provider "github" {
  token = var.gh_token
  owner = var.github_owner
}

GCPのプロバイダーにはとりあえずregionやzoneを登録していますが、今回作成するリソースには使われていません。
projectには操作するプロジェクトの名前を入れています。(IDの番号ではないです)
GitHubのtokenには gh auth token で得られるものを使用しています。
ownerにはリポジトリの所有者を入れています。

GCPのWorkload Identity連携の作成

まずはGitHub Actionsから接続するためのOIDCの設定から作成します。

resource "google_iam_workload_identity_pool" "pool" {
  workload_identity_pool_id = var.pool_id
  description               = "OpenID Connection"
  display_name              = var.pool_id
  timeouts {}
}

output "workload_identity_pool_name" {
  value = google_iam_workload_identity_pool.pool.name
}

resource "google_iam_workload_identity_pool_provider" "gh_provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.pool.workload_identity_pool_id
  workload_identity_pool_provider_id = var.provider_id
  display_name                       = "GitHub Identity Provider"
  disabled                           = false
  oidc {
    issuer_uri        = "https://token.actions.githubusercontent.com"
    allowed_audiences = []
  }
  attribute_mapping = {
    "google.subject"       = "assertion.sub",
    "attribute.actor"      = "assertion.actor",
    "attribute.repository" = "assertion.repository",
  }
  # attribute_condition = "attribute.actor == '${var.github_owner}'"
  timeouts {}
}

output "workload_identity_pool_provider_name" {
  value = google_iam_workload_identity_pool_provider.gh_provider.name
}

作成するリソースはgoogle_iam_workload_identity_poolgoogle_iam_workload_identity_pool_providerの2つです。
providerをまとめるためのpoolと接続の詳細が入っているproviderです。
名前や説明は適当です。
poolの名前もproviderの名前も後で使うのでoutputに指定しています。

providerの方にはGitHubのドキュメントで指定されているURLを指定しています。
またリポジトリや実行者の情報を認証に使用できるように設定しています。
認証に使用する属性についてはGCPのドキュメントGitHubのドキュメントを参照して下さい。
リポジトリのみ参照するようにしたいのでattribute_conditionはコメントアウトしています。
個人での開発など、ユーザーが1人なら設定してもいいと思います。

GCPのサービスアカウントの作成

次にGCP上で操作を行うprincipalであるサービスアカウントを作成します。

resource "google_service_account" "account" {
  account_id   = var.iam_id
  display_name = var.iam_name
  description  = "Access from GitHub Actions(${var.github_access_from})"
  disabled     = false
  timeouts {}
}

output "service_account_email" {
  value = google_service_account.account.email
}

resource "google_service_account_iam_policy" "policy" {
  policy_data        = data.google_iam_policy.policy.policy_data
  service_account_id = google_service_account.account.name
}

data "google_iam_policy" "policy" {
  binding {
    members = [
      for s in var.github_repositories :
      "principalSet://iam.googleapis.com/${var.workload_identity_pool_name}/attribute.repository/${s}"
    ]
    role = "roles/iam.workloadIdentityUser"
  }
}

サービスアカウント自体は他のリソースの情報を使うことなく作成できます。
このアカウントを使用できる権限の設定にはgoogle_service_account_iam_policyを使用しました。
これで複数のリポジトリから同じアカウントを使い回すための設定の登録ができます。
ここのprincipalの設定でIdentity Poolの名前を使用しています。

サービスアカウントのEmailはGitHub ActionsのSecretsに登録するのでoutputに入れています。

実際にCloud StorageにアクセスしたりSecret Managerから読み出したりするときにはこのアカウントに対して権限の設定を行います。

GitHub ActionsのSecretsの作成

ここではdeprecatedになったプロバイダーを使用しないように気をつけて下さい。
また、github_repositoryはリポジトリが見つからない時nameにnullを入れてくるので、planした際にはよく確認するといいと思います。
github_actions_secretのrepositorynullが入っているとplanが通らないので気づけますが...

data "github_repository" "repo" {
  full_name = var.repo_full_name
}

resource "github_actions_secret" "identity_provider" {
  repository      = data.github_repository.repo.name
  secret_name     = "GCP_IDENTITY_PROVIDER"
  plaintext_value = var.workload_identity_pool_provider_name
}

resource "github_actions_secret" "service_account" {
  repository      = data.github_repository.repo.name
  secret_name     = "GCP_SERVICE_ACCOUNT"
  plaintext_value = var.service_account_email
}

GCP_IDENTITY_PROVIDERGCP_SERVICE_ACCOUNTを登録しています。
今回はSecretsに入れていますが、これが流出しても直ちにアクセスされるわけではないのでplaintext_valueにしています。

plaintext_valueにした値はtfstateファイルに平文で保存されているのでファイルの扱いには注意して下さい。

Plan

ここまでの内容で作成したTerraformでterraform planを実行した例を紹介します。
検証に使用したGitHubのリポジトリを対象に実行しています。

module.github_secrets["k-kojima-yumemi/curly-couscous"].data.github_repository.repo: Reading...
module.github_secrets["k-kojima-yumemi/curly-couscous"].data.github_repository.repo: Read complete after 0s [id=curly-couscous]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # module.github_secrets["k-kojima-yumemi/curly-couscous"].github_actions_secret.identity_provider will be created
  + resource "github_actions_secret" "identity_provider" {
      + created_at      = (known after apply)
      + id              = (known after apply)
      + plaintext_value = (sensitive value)
      + repository      = "curly-couscous"
      + secret_name     = "GCP_IDENTITY_PROVIDER"
      + updated_at      = (known after apply)
    }

  # module.github_secrets["k-kojima-yumemi/curly-couscous"].github_actions_secret.service_account will be created
  + resource "github_actions_secret" "service_account" {
      + created_at      = (known after apply)
      + id              = (known after apply)
      + plaintext_value = (sensitive value)
      + repository      = "curly-couscous"
      + secret_name     = "GCP_SERVICE_ACCOUNT"
      + updated_at      = (known after apply)
    }

  # module.main_github_identity_pool.google_iam_workload_identity_pool.pool will be created
  + resource "google_iam_workload_identity_pool" "pool" {
      + description               = "OpenID Connection"
      + display_name              = "github-actions-openid-koma"
      + id                        = (known after apply)
      + name                      = (known after apply)
      + project                   = (known after apply)
      + state                     = (known after apply)
      + workload_identity_pool_id = "github-actions-openid-koma"

      + timeouts {}
    }

  # module.main_github_identity_pool.google_iam_workload_identity_pool_provider.gh_provider will be created
  + resource "google_iam_workload_identity_pool_provider" "gh_provider" {
      + attribute_mapping                  = {
          + "attribute.actor"      = "assertion.actor"
          + "attribute.repository" = "assertion.repository"
          + "google.subject"       = "assertion.sub"
        }
      + disabled                           = false
      + display_name                       = "GitHub Identity Provider"
      + id                                 = (known after apply)
      + name                               = (known after apply)
      + project                            = (known after apply)
      + state                              = (known after apply)
      + workload_identity_pool_id          = "github-actions-openid-koma"
      + workload_identity_pool_provider_id = "token-actions-githubusercontent"

      + oidc {
          + allowed_audiences = []
          + issuer_uri        = "https://token.actions.githubusercontent.com"
        }

      + timeouts {}
    }

  # module.main_service_account_github.data.google_iam_policy.policy will be read during apply
  # (config refers to values not yet known)
 <= data "google_iam_policy" "policy" {
      + id          = (known after apply)
      + policy_data = (known after apply)

      + binding {
          + members = [
              + (known after apply),
            ]
          + role    = "roles/iam.workloadIdentityUser"
        }
    }

  # module.main_service_account_github.google_service_account.account will be created
  + resource "google_service_account" "account" {
      + account_id   = "github-actions-koma"
      + description  = "Access from GitHub Actions(k-kojima-yumemi)"
      + disabled     = false
      + display_name = "GitHub Actions koma"
      + email        = (known after apply)
      + id           = (known after apply)
      + member       = (known after apply)
      + name         = (known after apply)
      + project      = (known after apply)
      + unique_id    = (known after apply)

      + timeouts {}
    }

  # module.main_service_account_github.google_service_account_iam_policy.policy will be created
  + resource "google_service_account_iam_policy" "policy" {
      + etag               = (known after apply)
      + id                 = (known after apply)
      + policy_data        = (known after apply)
      + service_account_id = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.

Applyするとリソースが作成されます。
a.png

実験的に、7つのリポジトリに対してPolicyを設定すると以下のようにroles/iam.workloadIdentityUser(Workload Identity ユーザー)が設定されたprincipalが7つ作成されます。
b.png

GitHub Actionsの実行

検証のため、以下でWorkflowを定義しました。

name: Login

on:
  workflow_dispatch:
  push:
    branches: main

jobs:
  check-storage:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v3
      - uses: google-github-actions/auth@v1
        with:
          workload_identity_provider: ${{secrets.GCP_IDENTITY_PROVIDER}}
          service_account: ${{secrets.GCP_SERVICE_ACCOUNT}}

checkoutしてからGCPの認証をするだけのWorkflowです。
c.png
このように正常に動作することが確認できます。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?