🛠️

GitHub の管理を GitHub Actions から Terraform でやってみた

2025/01/07に公開

クラウドエースの北野です。
GitHub リポジトリ, Team などのリソースを GitHub Actions から Terraform を使って管理する方法を紹介します。

概要

本記事では、以下の GitHub リポジトリ、Team と Team のアクセス権限を設定する Terraform コードを GitHub Actions から実行する方法を紹介します。

resource "github_repository" "main" {
  name       = <Repository Name>
  visibility = "private"
}

resource "github_team" "main" {
  name                      = <Team Name>
  create_default_maintainer = true
  privacy                   = "closed"
}

resource "github_team_repository" "main" {
  team_id    = github_team.main.id
  repository = github_repository.main.name
  permission = <Permission>
}

この記事では、以下の環境から Terraform を実行して GitHub のリソースを管理する方法紹介します。

  1. ローカル環境から Terraform の実行
  2. GitHub Actions から Terraform の実行

また、GitHub Actions での認証に個人用アクセストークンを使います。

はじめに

サービスの新しい機能拡張による GitHub リポジトリの作成、新しい開発メンバーの参画による組織への追加や、リポジトリへの権限追加など GitHub の運用作業は非常に多いです。しかし、作業の特性上、その頻度は非常に少なく、手動で運用していると作業ミスが起こりやすいです。この作業ミスには、人への権限の付け間違いやリポジトリの公開設定のミスなどがあり、設定ミスをすると情報漏洩の可能性が高いです。

そのため、GitHub の管理をコード化して作業を自動化するメリットは非常に高いです。また、CD ツールを使ってコードの実行を自動化すると、運用が作業者に依存しなくなるため運用作業のリードタイムを減らせます。

この記事では、Terraform で GitHub の管理をコード化して、その実行を GitHub Actions で実施する方法を紹介します。
まず、Terraform による GitHub リソースのコード化をおこない、そのコードをローカル環境から実行する方法を紹介します。続いて、コード化した内容を GitHub Actions から実行する方法を紹介します。
記事の内容を試す場合、紹介する Terraform コードの <> の部分は、実環境に沿う様に書き替えてください。

GitHub リソースの Terraform によるコード化

Terraform による GitHub のリソースのコード化に、integrations/github のプロバイダーを使います。プロバイダーは以下の様に定義します。
このとき管理する GitHub の組織は provider の owner 変数に定義します。

terraform {
  required_providers {
    github = {
      source = "integrations/github"
    }
  }
}

provider "github" {
  owner = <GitHub Organization>
}

本記事で Terraform で作成する GitHub リソースとそのコード

Terraform で以下のリソースを作成します。

  • GitHub リポジトリ
  • Team
  • Team のリポジトリへのアクセス権限

上記の内容の Terraform コードは以下の通りです。

resource "github_repository" "main" {
  name       = <Repository Name>
  visibility = "private"
}

resource "github_team" "main" {
  name                      = <Team Name>
  create_default_maintainer = true
  privacy                   = "closed"
}

resource "github_team_repository" "main" {
  team_id    = github_team.main.id
  repository = github_repository.main.name
  permission = <Permission>
}

GitHub の認証方法

上記のコードを実行させるためには、実行環境を GitHub に認証させる必要があります。
GitHub の認証には、以下の 3つの方法があります。

  • GitHub CLI
  • OAuth/Personal Access Token (以降 PAT)
  • GitHub App Installation

本記事では、ローカル環境での認証に GitHub CLI を使い、GitHub Actions の認証に OAuth/Personal Access Token を使います。

ローカル環境からの実行

GitHub CLI でローカル環境を認証させて、Terraform コードを実行させてみます。

GitHub CLI での認証

GitHub CLI はリポジトリの作成・削除や Pull Request の作成・クローズなどの GitHub 上の操作をターミナルから行う gh コマンドです。GitHub CLI のインストールは公式ページを参考にしてください。

GitHub CLI の認証は gh auth login コマンドでおこないます。
gh auth login を実行すると、認証方法などを対話的に入力して認証に進みます。以下の条件を対話的に入力すると、ワンタイムコードが出力され Web ブラウザが起動します。

  • GitHubの起動先: GitHub.com
  • git 操作をおこなうプロトコル: HTTPS
  • GitHub クレデンシャルで Git 認証許可する: Yes
  • GitHub CLI の認証方法: Web ブラウザでのログイン

Web ブラウザ上で認証させるアカウントを選択すると、ワンタイムコードを入力する画面に遷移します。

コンソールの ! First copy your one-time code: の行に表示されているコードを入力します。

認証を許可すると、認証が完了します。

認証に成功すると、Congratulations, you're all set と表示され、カーソルがコンソールに戻り標準出力に Logged in as <GitHub Account ID> が表示されます。

最後に gh repo list コマンドを実行して、ユーザーのリポジトリの一覧を表示させてみます。

❯ gh repo list


Showing  of  repositories in @<GitHub Account ID>

NAME                                                     DESCRIPTION  INFO     UPDATED
Repository Infomation

実行結果

ローカル環境から Terraform を実行すると、以下のようになります。

❯ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # github_repository.main will be created
  + resource "github_repository" "main" {
      + allow_auto_merge            = false
      + allow_merge_commit          = true
      + allow_rebase_merge          = true
      + allow_squash_merge          = true
      + archived                    = false
      + default_branch              = (known after apply)
      + delete_branch_on_merge      = false
      + etag                        = (known after apply)
      + full_name                   = (known after apply)
      + git_clone_url               = (known after apply)
      + html_url                    = (known after apply)
      + http_clone_url              = (known after apply)
      + id                          = (known after apply)
      + merge_commit_message        = "PR_TITLE"
      + merge_commit_title          = "MERGE_MESSAGE"
      + name                        = <Repository Name>
      + node_id                     = (known after apply)
      + primary_language            = (known after apply)
      + private                     = (known after apply)
      + repo_id                     = (known after apply)
      + squash_merge_commit_message = "COMMIT_MESSAGES"
      + squash_merge_commit_title   = "COMMIT_OR_PR_TITLE"
      + ssh_clone_url               = (known after apply)
      + svn_url                     = (known after apply)
      + topics                      = (known after apply)
      + visibility                  = "private"
      + web_commit_signoff_required = false

      + security_and_analysis (known after apply)
    }

  # github_team.main will be created
  + resource "github_team" "main" {
      + create_default_maintainer = true
      + etag                      = (known after apply)
      + id                        = (known after apply)
      + members_count             = (known after apply)
      + name                      = <Team Name>
      + node_id                   = (known after apply)
      + parent_team_read_id       = (known after apply)
      + parent_team_read_slug     = (known after apply)
      + privacy                   = "closed"
      + slug                      = (known after apply)
    }

  # github_team_repository.main will be created
  + resource "github_team_repository" "main" {
      + etag       = (known after apply)
      + id         = (known after apply)
      + permission = <Permission>
      + repository = <Repository Name>
      + team_id    = (known after apply)
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

github_team.main: Creating...
github_repository.main: Creating...
github_team.main: Creation complete after 4s [id=<Team ID>]
github_repository.main: Creation complete after 8s [id=<Repository Name>]
github_team_repository.main: Creating...
github_team_repository.main: Creation complete after 2s [id=<Team ID>:<Repository Name>]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

GitHub Actions からの実行

PAT を GitHub Actions の環境を認証させ、そこから Terraform を実行する方法を紹介します。
PAT の管理に Google Cloud の Secret Manager を使い、Terraform のステートファイルの管理に Google Cloud の Cloud Storage を使います。また、GitHub Actions から、Google Cloud への認証に Google Cloud の Workload Identity を使います。

Workload Identity を使って GitHub Actions の環境を Google Cloud に認証させる方法の詳細は、この記事を参考にしてください。

Google Cloud 側の設定

Google Cloud に以下のリソースを作成します。

Google Cloud リソースの種類 目的
Workload Identity GitHub Actions の認証
Service Account Workload Identity により GitHub Actions の環境に利用させるサービスアカウント
Secret Manager PAT の管理
Cloud Storage Terraform のステートファイルの管理

サービスアカウントに付与する権限は、以下の通りです。

付与する権限 アクセス制御するリソース
roles/storage.objectAdmin Terraform のステートファイルを管理する Cloud Storage バケット
roles/secretmanager.secretAccessor PAT を管理する Secret Manager
roles/secretmanager.viewer PAT を管理する Secret Manager

上記の作成は以下の Terraform コードで作成します。

variable "project" {}

resource "google_iam_workload_identity_pool" "main" {
  workload_identity_pool_id = <Workload Identity Pool Name>

  project = var.project
}

resource "google_iam_workload_identity_pool_provider" "main" {
  workload_identity_pool_provider_id = <Workload Identity Pool Provider ID>

  workload_identity_pool_id = google_iam_workload_identity_pool.main.workload_identity_pool_id
  project                   = var.project

  attribute_condition = <CONDITION>
  attribute_mapping = {
    "google.subject" = "assertion.repository"
  }
  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

resource "google_service_account" "main" {
  account_id = <Service Account ID>

  project = var.project
}

resource "google_secret_manager_secret" "main" {
  secret_id = <Secret Manager ID>

  project = var.project
  replication {
    user_managed {
      replicas {
        location = "asia-northeast1"
      }
    }
  }
}

resource "google_storage_bucket" "main" {
  name = <Bucket Name>

  project       = var.project
  location      = "asia-northeast1"
  storage_class = "STANDARD"
}

resource "google_service_account_iam_member" "main" {
  service_account_id = google_service_account.main.name

  role   = "roles/iam.workloadIdentityUser"
  member = format("principal://iam.googleapis.com/%s/subject/%s", data.google_iam_workload_identity_pool.main.name, "<GitHub Organization>/<GitHub Repository Name>")
}

resource "google_storage_bucket_iam_member" "main" {
  bucket = google_storage_bucket.main.name

  role   = "roles/storage.objectAdmin"
  member = google_service_account.main.member
}

resource "google_secret_manager_secret_iam_member" "accessor" {
  secret_id = google_secret_manager_secret.main.secret_id
  
  project = var.project
  role    = "roles/secretmanager.secretAccessor"
  member  = google_service_account.main.member
}

resource "google_secret_manager_secret_iam_member" "viewer" {
  secret_id = google_secret_manager_secret.main.secret_id

  project = var.project
  role    = "roles/secretmanager.viewer"
  member  = google_service_account.main.member
}

PAT による認証

github プロバイダーに token 変数を設定すると、Terraform 実行時にトークンで GitHub に認証されされます。このトークンには、PAT を指定します。PAT はパスワードの代わりに使われる認証情報で、GitHub リソースへのアクセスを自動化する目的で利用されます。
PAT には personal access token (fine-grained personal access token)personal access token (personal access token (classic)) の2種類があります。各トークンの違いなどは、公式ドキュメントを参考にしてください。
まだ、personal access token (fine-grained personal access token) はベータ版のため、この記事では personal access token (personal access token (classic)) を使います。

続いて personal access token (personal access token (classic)) の作成方法を説明します。

GitHub アカウントの Settings ページにアクセスし、Developer settings を選択し Personal access tokens の Tokens(classic) クリックして PAT (classic) の一覧ページにアクセスします。

画面上の Generate new tokenGenerate new token (classic) をクリックします。

Note、Expiration、Select scopes に必要な情報を入力し、ページ下の Create token のボタンをクリックすると、PAT が作成されます。作成されると以下の PAT 部分に PAT 情報が表示されます。この情報を github provider の token に入力すると、PAT の内容で認証・認可され Terraform が実行されます。

本記事では、PAT を先に作った Secret Manager で管理します。Cloud Console から Secret Manager にアクセスし、作成した Secret Manager を選択して、新しいバージョンをクリックして PAT をシークレットの値に入力して追加します。

実行結果

Secret Manager に保存された PAT から provider の token への代入は以下の様にします。

terraform {
  backend "gcs" {
    bucket = "<Bucket Name>"
  }

  required_providers {
    github = {
      source = "integrations/github"
    }
    google = {
      source = "hashicorp/google"
    }
  }
}

provider "github" {
  token = data.google_secret_manager_secret_version.main.secret_data
  owner = "<GitHub Organization>"
}

data "google_secret_manager_secret_version" "main" {
  secret  = <Secret Manager ID>
  project = var.project
}

variable "project" {}

Terraform を実行する GitHub Actions の Workflow は以下の様に定義します。

name: Manage GitHub Resources

on:
  workflow_dispatch:

jobs:
  manage_gethub_group:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    defaults:
      run:
        working-directory: <Working Directory>

    steps:
      - uses: actions/checkout@v2

      - name: Authenticate to Google Cloud
        id: authorizaation-googlecloud
        uses: google-github-actions/auth@v1
        with:
          workload_identity_provider: projects/<Project Number>/locations/global/workloadIdentityPools/<Workload Identity Pool Name>/providers/<Workload Identity Pool Provider ID>
          service_account: <Service Account ID>@<Project ID>.iam.gserviceaccount.com

      - name: setup terraform
        id: terraform-setup
        uses: hashicorp/setup-terraform@v3.1.2

      - name: terraform apply
        id: terraform-apply
        run: |
          terraform init
          terraform apply --auto-approve
        env:
          TF_VAR_project: <Project ID>

上記の内容を実行すると、以下の様になります。

まとめ

GitHub リソースを Terraform で管理する方法を紹介しました。リポジトリのルールや Team の管理などをコード化でき、運用ミスを減らすことができます。
また、GitHub Actions の Variables や Secrets も Terraform で設定できます。そのため、本記事の Workflow に定義している workflow_identity_provider や service_account の値も、 Terraform で Variables に設定し、 Variables を参照するように Workflow を定義すると再利用可能になり運用ミスを減らすことができます。
みなさまも GitHub のリソースを Terraform で管理して、素早くかつミスなく GitHub を運用してみてください。

Discussion