🐧

CI で private な Git リポジトリから Terraform の Module を安全にダウンロード

2025/02/01に公開

本記事では GitHub Actions で Terraform を実行する際に、 private な Git リポジトリからより安全に Module をダウンロードをする方法について紹介します。
GitHub Actions を前提としますが、 GitHub Actions 以外でも参考になるはずです。

要約

  • terraform init で GitHub から private module をダウンロードする際は認証情報を渡さないと失敗する
  • git config --global で access token を設定するのは ~/.gitconfig に access token が平文で保存され後続の任意の step で access token が参照できてしまい危険
    • Terraform 関係なく Git で private repository を checkout する方法として使われているのを見かけるが、同様の理由で危険
  • gh auth setup-git を実行したあとに環境変数 GH_TOKEN 設定して terraform init するのがより安全

導入

terraform init で private module をダウンロードする際、認証に失敗して以下のようなエラーが起こることがあります。

Initializing modules...
Downloading git::https://github.com/suzuki-shunsuke/test-private-terraform-module.git for foo...
╷
│ Error: Failed to download module
│ 
│   on main.tf line 1:
│    1: module "foo" {
│ 
│ Could not download module "foo" (main.tf:1) source code from
│ "git::https://github.com/suzuki-shunsuke/test-private-terraform-module.git":
│ error downloading
│ 'https://github.com/suzuki-shunsuke/test-private-terraform-module.git':
│ /usr/bin/git exited with 128: Cloning into '.terraform/modules/foo'...
│ fatal: could not read Username for 'https://github.com': No such device or
│ address
│ 
╵
Error: Process completed with exit code 1.

Terraform で Git リポジトリから Module を取得する場合、内部的には git clone によって module はダウンロードされるため、プライベートリポジトリを checkout できるように git の設定をする必要があります。

https://developer.hashicorp.com/terraform/language/modules/sources#generic-git-repository

これを解決する方法として git config で GitHub Access Token をセットする方法が紹介されているのを見かけます。

git config --global url."https://oauth2:${GITHUB_TOKEN}@github.com/$GITHUB_REPOSITORY_OWNER".insteadOf https://github.com/${GITHUB_REPOSITORY_OWNER}

terraform init に限らず CI で private repository を clone する際にこの方法が使われているのを良く見かけます。

なお、 Access Token は Module がホストされているリポジトリへの contents: read 権限が必要なので Module が他のリポジトリでホストされている場合 ${{secrets.GITHUB_TOKEN}} は使えません。

本題

ここまではよくある話で、ここからが本題です。

これで無事 private module がダウンロードできて解決でもいいのですが、これだけだとはあまりセキュアではありません。
なぜかというと GitHub Access Token が git の global な config ~/.gitconfig に平文のまま保存されるからです。
同じ job の後続の任意の step で Access Token を参照することが出来ます。

これは先日自分が書いた記事にある actions/checkout の persist-credentials と全く同じ問題です。

https://zenn.dev/shunsuke_suzuki/articles/github-action-checkout-persist-credentials

ではどうすればいいかというと、 terraform init を実行するときだけ GitHub Access Token を渡してあげればよいわけです。
2 つ方法があります。

  1. terraform init 後に git config --unset で消す
# terraform init 直前にセット
git config --global url."https://oauth2:${GITHUB_TOKEN}@github.com/$GITHUB_REPOSITORY_OWNER".insteadOf https://github.com/${GITHUB_REPOSITORY_OWNER}
terraform init
# terraform init 直後に消す
git config --global --unset url."https://oauth2:${GITHUB_TOKEN}@github.com/$GITHUB_REPOSITORY_OWNER".insteadOf

一時的とはいえ ~/.gitconfig に access token が平文で書き込まれるので、あまり望ましくありません。

  1. GitHub CLI を credential helper として使用する
GitHub Actions Workflow
# GitHub App から token を生成
- id: token
  uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0
  with:
    app_id: ${{secrets.APP_ID}}
    private_key: ${{secrets.APP_PRIVATE_KEY}}
    # permissions は contents:read だけで十分
    permissions: >-
      {
        "contents": "read"
      }
    # repositories は module のリポジトリだけで十分
    repositories: >-
      [
        "test-private-terraform-module"
      ]
# GitHub CLI を Git の credential helper として使用
- run: gh auth setup-git
  env:
    GH_HOST: github.com # これがないと失敗する場合がある
- run: terraform init
  env:
    GH_TOKEN: ${{steps.token.outputs.token}} # terraform init を実行するときだけ access token を渡す

GH_HOST を設定しないと gh auth setup-git が失敗する場合があります。

You are not logged into any GitHub hosts. Run gh auth login to authenticate.

gh auth setup-gitgit config --global を実行して credential helper として gh auth git-credential コマンドを設定します。
~/.gitconfig に access token は保存されません。
この状態で GH_TOKEN を設定して terraform init を実行すると gh auth git-credentialGH_TOKEN から GitHub Access Token を取得して private module がダウンロードできます。

以上、 2 つの方法を紹介しました。
1 は一時的とはいえ ~/.gitconfig に access token が平文で書き込まれるので、 2 のほうが良いでしょう。

おまけ

gh auth setup-git は何をやっているのか

GH_HOST を設定しないと gh auth setup-git が失敗する場合があります。

You are not logged into any GitHub hosts. Run gh auth login to authenticate.

このエラーが良くわからなかったので gh auth setup-git が内部で何をやっているか確認しました。

エラーが起こっているのはここ:

https://github.com/cli/cli/blob/713346c7369f2e29963d04fd7c71cc1236a56bd9/pkg/cmd/auth/setupgit/setupgit.go#L102-L108

https://github.com/cli/cli/blob/713346c7369f2e29963d04fd7c71cc1236a56bd9/pkg/cmd/auth/setupgit/setupgit.go#L81

https://github.com/cli/cli/blob/713346c7369f2e29963d04fd7c71cc1236a56bd9/internal/config/config.go#L281-L286

この辺を見ると GH_HOST が設定されていたら hosts に追加しているようです。

https://github.com/cli/go-gh/blob/13104ed7b2db4b8c1a83de4248ddfaaab7682916/pkg/auth/auth.go#L111-L135

https://github.com/cli/cli/blob/713346c7369f2e29963d04fd7c71cc1236a56bd9/pkg/cmd/auth/setupgit/setupgit.go#L113-L117

git config --global を実行しているのがわかります。

https://github.com/cli/cli/blob/713346c7369f2e29963d04fd7c71cc1236a56bd9/pkg/cmd/auth/shared/gitcredentials/helper_config.go#L20-L65

key はこの辺

https://github.com/cli/cli/blob/713346c7369f2e29963d04fd7c71cc1236a56bd9/pkg/cmd/auth/shared/gitcredentials/helper_config.go#L114-L117

実際 gh auth setup-git を実行して .gitconfig をチェックしてみましょう。

GH_HOST=github.com gh auth setup-git
~/.gitconfig
[credential "https://github.com"]
	helper = 
	helper = !/Users/shunsukesuzuki/.local/share/aquaproj-aqua/pkgs/github_release/github.com/cli/cli/v2.65.0/gh_2.65.0_macOS_arm64.zip/gh_2.65.0_macOS_arm64/bin/gh auth git-credential
[credential "https://gist.github.com"]
	helper = 
	helper = !/Users/shunsukesuzuki/.local/share/aquaproj-aqua/pkgs/github_release/github.com/cli/cli/v2.65.0/gh_2.65.0_macOS_arm64.zip/gh_2.65.0_macOS_arm64/bin/gh auth git-credential

見ての通り gh auth git-credential を実行するように設定してあります。
当然 access token は記録されていません。

gh auth git-credential は何か

gh auth git-credential は Git の credential helper を実装したコマンドです。

https://git-scm.com/book/ja/v2/Git-のさまざまなツール-認証情報の保存

credential を取得する際は gh auth git-credential get が呼ばれます。
コードを見てみましょう。

https://github.com/cli/cli/blob/d10fbbfaeac0f5a16a76dd6eed46d17ced8b8d31/pkg/cmd/auth/gitcredential/helper.go#L58-L144

標準入力を 1 行ずつ読み取り host や protocol を取得し、認証情報を標準出力しているのが分かります。
環境変数 GH_TOKEN ないし GITHUB_TOKEN も読んでいます。
試しに以下のコマンドを実行すると挙動が確認できます。

$ echo "protocol=https\nhost=github.com" | env GH_TOKEN=xxx gh auth git-credential get
protocol=https
host=github.com
username=x-access-token
password=xxx

Discussion