Terraform で GitHub Actions の secret を管理する
GitHub Actions の secret を手動で追加していくと、あとからコンテキストを追いかけるのが大変です。追加した値は平文で見ることができないので、変更するのもためらいます(平文の値が残っていないと何かあっても戻せない)。なので、コードで管理したいと思っていました。
そんなとき、Terraform の GitHub provider で secret の管理ができることを知ったので試してみました。
今回は repository secret で試していますが、organization secret もほぼ同じ手順です。
暗号化された値の生成
github_actions_secret
には plaintext_value
か encrypted_value
を指定できますが、平文でコミットしたくないので encrypted_value
を使うケースがほとんどだと思います。
"Create or update a repository secret" で述べられていますが、GitHub Actions の secret は LibSodium で暗号化されています。 encrypted_value
に渡す値も同じように LibSodium で暗号化する必要があります。
まず repository の public key を取得します。
$ curl \
-u "manabusakai:$GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/manabusakai/(repository)/actions/secrets/public-key
{
"key_id": "568250167242549743",
"key": "WYmnVsmInmBH/nxkxsTk7Z0tQ7vxonruUd9C6VVra3w="
}
次にローカルで平文を暗号化します。 "Create or update a repository secret" にいくつかの実装サンプルが載っています。ここでは Ruby を使います。
libsodium ライブラリと Gem をインストールします。
$ brew install libsodium
$ echo 'gem "rbnacl"' > Gemfile
$ bundle install
Ruby スクリプトで暗号化された値を生成します。
require "rbnacl"
require "base64"
key = Base64.decode64(ARGV[0])
public_key = RbNaCl::PublicKey.new(key)
box = RbNaCl::Boxes::Sealed.from_public_key(public_key)
encrypted_secret = box.encrypt(ARGV[1])
puts Base64.strict_encode64(encrypted_secret)
今回は "foobar" という値を暗号化します。第 1 引数が public key、第 2 引数が暗号化したい文字列です。
$ bundle exec ruby generate_encrypted_value.rb "WYmnVsmInmBH/nxkxsTk7Z0tQ7vxonruUd9C6VVra3w=" "foobar"
Terraform で secret を追加する
暗号化された値が準備できたら Terraform で secret を追加します。ミニマムのコードだと次のようになります。
terraform {
required_providers {
github = {
source = "integrations/github"
version = "~> 4.0"
}
}
}
provider "github" {}
data "github_repository" "example" {
full_name = "manabusakai/(repository)"
}
resource "github_actions_secret" "test_secret" {
repository = data.github_repository.example.name
secret_name = "TEST"
encrypted_value = "(snip)"
}
apply して "Settings > Secrets" の画面に出てくれば OK です!
値の確認
念のため次のような workflow を追加して、期待通りの値が設定されているか確認しました。
name: Verify
on: push
jobs:
echo:
runs-on: ubuntu-latest
steps:
- name: Vefify
if: ${{ env.TEST == 'foobar' }}
run: echo "OK"
env:
TEST: ${{ secrets.TEST }}
まとめ
暗号化された値を作るのがちょっと面倒ですが、それ以外は普通の Terraform と同じです。 IaC と同じライフサイクルで secret を管理できるのは素晴らしいですね!