CI で private な Git リポジトリから Terraform の Module を安全にダウンロード
本記事では 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 の設定をする必要があります。
これを解決する方法として 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 と全く同じ問題です。
ではどうすればいいかというと、 terraform init を実行するときだけ GitHub Access Token を渡してあげればよいわけです。
2 つ方法があります。
- 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 が平文で書き込まれるので、あまり望ましくありません。
- GitHub CLI を credential helper として使用する
# 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-git
は git config --global
を実行して credential helper として gh auth git-credential
コマンドを設定します。
~/.gitconfig
に access token は保存されません。
この状態で GH_TOKEN
を設定して terraform init を実行すると gh auth git-credential
が GH_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
が内部で何をやっているか確認しました。
エラーが起こっているのはここ:
この辺を見ると GH_HOST が設定されていたら hosts に追加しているようです。
git config --global
を実行しているのがわかります。
key はこの辺
実際 gh auth setup-git
を実行して .gitconfig をチェックしてみましょう。
GH_HOST=github.com gh auth setup-git
[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 を実装したコマンドです。
credential を取得する際は gh auth git-credential get
が呼ばれます。
コードを見てみましょう。
標準入力を 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