🐕

GitHub Actions Secrets を Terraform で管理する

2023/05/02に公開

前提

  • GitHub Actions Secrets は、 GitHub の Public Key を用いて LibSodium で暗号化された値が設定されている
    • 環境を揃えれば、ローカル環境でも暗号化を行うことができる
  • Terraform の GitHub Provider を用いると Terraform 経由で GitHub Actsion Secrets の登録が行える

Public Key と LibSodium を用いた Secrets の暗号化

以下の手順で、GitHub Actions Secrets に登録する値をローカルでも暗号化することができます。

Public Key の取得

GitHub Actions Secrets は organization, 個々のリポジトリ、それぞれに値の設定を行うことができます。
暗号化に用いる Public Key も、organization、個々のリポジトリ、それぞれ異なるものを使用することになります。

organization public key

  curl \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${GITHUB_TOKEN}"\
    -H "X-GitHub-Api-Version: 2022-11-28" \
    -s\
    https://api.github.com/orgs/${ORGANIZATION_NAME}/actions/secrets/public-key

上記を実行すると以下のような JSON が返却されます。このうち key が Public Key になります。

{
  "key_id": "123456789...",
  "key": "abcdefg...."
}

repository public key

curl \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${GITHUB_TOKEN}"\
    -H "X-GitHub-Api-Version: 2022-11-28" \
    -s\
    https://api.github.com/repos/openlogi/"${REPOSITORY}"/actions/secrets/public-key

上記を実行すると、organiztion の時と同じように以下のような JSON が返却されます。

{
  "key_id": "123456789...",
  "key": "abcdefg...."
}

LibSodium を用いた Secrets の暗号化

暗号化をするためには、LibSodium を使用した簡単なプログラムを作成する必要があります。
各言語ごとの作成例については以下のページに紹介されていますので、こちらを参考にするのが良いと思います。

https://docs.github.com/en/rest/actions/secrets?apiVersion=2022-11-28#create-or-update-a-repository-secret

ここでは python を例に話を進めます。
動作させるためには pynacl のインストールが必要です。

import sys
from base64 import b64encode
from nacl import encoding, public

def encrypt(public_key: str, secret_value: str) -> str:
  """Encrypt a Unicode string using the public key."""
  public_key = public.PublicKey(public_key.encode("utf-8"), encoding.Base64Encoder())
  sealed_box = public.SealedBox(public_key)
  encrypted = sealed_box.encrypt(secret_value.encode("utf-8"))
  return b64encode(encrypted).decode("utf-8")
  
public_key=sys.argv[1]
secret_value=sys.argv[2]
result = encrypt(public_key, secret_value)

print(result)

上記のスクリプトを用いて以下のように暗号化を行うことができます。

$ python encrypt.py ${public_key} ${secret_value}
abcdefg...

Terraform を用いた GitHub Actions Secrets の登録

上記で暗号化した値を Terraform を用いて GitHub Actions Secrets に登録することができます。

https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret

resource "github_actions_secret" "example_secret" {
  repository       = "example_repository"
  secret_name      = "example_secret_name"
  encrypted_value  = "....."
}

Terraform の provider では、GitHub Actions Secrets の変更管理については tfstate ファイルのみを参照して行い、実際に GitHub 上にどのような値が登録されているかが考慮されません。
なのですでに登録された値があっても、terraform plan を行っても差分は表示されませんし、apply すると強制的に上書きがされるのは注意が必要です。

また Secrets の値を一件ずつ定義していくのも面倒臭いので、JSON や CSV でデータを管理し、それらの値をまとめて適用するような仕組みにしておくのが便利だと思います。
以下は CSV で管理するようにした例です。

# organizations
locals {
  secrets_org_csv = csvdecode(file("${path.module}/secrets/organizations.csv"))
  secrets_org     = { for secrets_org_csv in local.secrets_org_csv : secrets_org_csv.name => secrets_org_csv }
}
resource "github_actions_organization_secret" "org" {
  for_each        = local.secrets_org
  secret_name     = each.value.name
  visibility      = "private"
  encrypted_value = each.value.value
}

CSV は以下のような形。

name,value
SOME_SECRET,abcdefg...

管理上の問題ですが、暗号化された値だけを CSV に保存しておくと何の値が入っているか判別が付きづらいので、 description のような項目を CSV に追加して説明を加えておくと管理しやすくなるかもしれません。

また、Terraform 上では暗号化された値だけを管理することになり、暗号化処理前の秘匿情報は 1 password など別のセキュアな手段で管理する必要があります。

Discussion