GitHub Actions で使うシークレットをセキュアに参照可能にして管理したい
はじめに
GitHub Actions でパスワードなどを扱うときシークレットを使うかと思います。
このシークレットに保存している値をあとから見返すときはどうすればいいのだろうともやもやを抱えていました。
見返せるように Notion や Google スプレットシートを使って管理していましたがそれはそれでどうなのだろうと。。。
(もし参照する方法を知っている方がいらっしゃいましたら教えていただけると嬉しいです。)
そんなもやもやを抱えているなか「GitHub CI/CD実践ガイド」[2]を読んでいたところ GitHub CLI でシークレットの設定ができることを知りました。(これまでは Web UI でぽちぽち設定していました。)
GitHub CLI で登録できるなら仕組みを作ればなんかいい感じのことができそう!という感じでネタを思いついたので共有です。
TL;DR
ツール
本記事での仕組みを利用するためには以下のツールを利用します。
※ Mac 環境での動作です。
GitHub CLI
シークレットを CLI にて登録するために利用します。
SOPS
暗号化・復号のために利用します。
age
暗号化・復号のための鍵を生成するのに利用します。
aqua
SOPS および age を管理するために利用します。
※ なくても大丈夫です。
鍵生成
age を使って暗号化・復号の鍵を生成します。
age-keygen
コマンドを実行すると以下の結果が得られます。
# created: 2024-12-15T13:39:59+09:00
# public key: age15ejyvpj84w5f42t0v6wnlz86qtnyyrxqx0k9ghtlqr5tfs23tcnsjupyay
AGE-SECRET-KEY-1WPUDMFQNYQ572CMUVMQSEH6U83VXE99FGAXJF7E8HPK64ZCRLRVQHJS0RH
public key:
である age15ejyvpj84w5f42t0v6wnlz86qtnyyrxqx0k9ghtlqr5tfs23tcnsjupyay
は暗号化するために使う鍵です。
これは公開しても問題ない情報です。
AGE-SECRET-KEY-1WPUDMFQNYQ572CMUVMQSEH6U83VXE99FGAXJF7E8HPK64ZCRLRVQHJS0RH
は復号に使う鍵です。
これは公開してしまうとダメな情報です。
安全な場所で管理してください。
※ 今回はサンプルなので載せています。& サンプルコードとしてコミットもしています。
参照のためのファイル
シークレット値を後から見返すために init.sh
スクリプトを用意します。
secret
ディレクトリにて管理する想定です。
#!/bin/bash -ue
name=$1
if [[ -z "${name}" ]]; then echo "please specify secret name"; exit 1; fi
touch secret/${name}.in.txt
touch secret/${name}.out.txt
シークレットの名前を指定して空のテキストファイルを生成するだけです。
init.sh <name>
.in.txt
と .out.txt
はコミットしない想定です。
このファイルをシークレット値の参照に使います。
※ .in.txt
と .out.txt
を用意しているのはプレーンテキストを一方向で管理したかったからです。
.in.txt
に暗号化したい値を入力します。
ex)
sample
暗号化
age にて生成した鍵と SOPS を使って暗号化するために encrypt.sh
スクリプトを用意します。
#!/bin/bash -ue
key=age15ejyvpj84w5f42t0v6wnlz86qtnyyrxqx0k9ghtlqr5tfs23tcnsjupyay
name=$1
if [[ ! -e secret/${name}.in.txt ]]; then echo "secret/${name}.in.txt not exists"; exit 1; fi
sops encrypt \
--age ${key} \
secret/${name}.in.txt > secret/${name}.json.tmp
cat secret/${name}.json.tmp | jq . > secret/${name}.enc.json
rm secret/${name}.json.tmp
以下のコマンドにて先ほど設定したシークレット名を指定して実行します。
encrypt.sh <name>
すると暗号化された json 形式のファイルが出力されます。
{
"data": "ENC[AES256_GCM,data:ftJ9oOA6,iv:hb1dXrT7wIhGggoqV8DMcNxm3GVd9USkr9fXysxh6Jk=,tag:Xcqk3Q9w2uPFXd3aO4LEaw==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age1tvx0ufxk5vkucmlyzha3xvkw4u29nl8x5u7m43qqv75num7tda2qzzxtt4",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXcDdTN1FrTnN5TWY5aHJD\nRHdjLzNpcE90N01TZ05lSGRzbEVkOGVWTURVCnVaRGExYy9DcXM4VEFRbTl4cE95\ndkJRZzZhaWtDcGJ4VUlvWVBZVkEySVkKLS0tIHlBL3NyMlp0VkZ1L0JCTGlPVFEx\nN1Brd3RMb1hWVHZleFgxZTNpbXlTRnMKuJ8OyS0cHQBfZW7oxt4Rqlg5CZGfSDeB\nwAd0FN78xsh59930yPjK38QmoeZKCsX1PY+7GqthslttKwhLTt/8dA==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2024-12-14T16:50:05Z",
"mac": "ENC[AES256_GCM,data:mlwor2jWjwilUWAI6MjIiAWaVX4IaoUS2pwiCIyVh3jTooW2GsuAS/+cjsjGg7dgl0yW7udqKndVVuyj/FdSmf6P4MMFUl2d07JRic276Jr6PDTUJ304ENXVT/9+vi1sml0EtJmX1uWYnVP6YtHWLekhmTwpNzZzgmctr5Hxm6I=,iv:ZRKXBeJFdCrI/t4Bn15J2NX3KSkpXCHqrLaXel8i8m0=,tag:ugtjAE42UdUJj65cqX2ahA==,type:str]",
"pgp": null,
"unencrypted_suffix": "_unencrypted",
"version": "3.9.2"
}
}
※ json ファイルはコミットして問題ないです。
復号
age にて生成した鍵と SOPS を使って復号するために decrypt.sh
スクリプトを用意します。
#!/bin/bash -ue
name=$1
source ./.env
export SOPS_AGE_KEY="${SOPS_AGE_KEY}"
if [[ ! -e secret/${name}.enc.json ]]; then echo "secret/${name}.enc.json not exists"; exit 1; fi
sops decrypt secret/${name}.enc.json > secret/${name}.tmp.json
touch secret/${name}.in.txt
cat secret/${name}.tmp.json | jq .data | tr -d '"' > secret/${name}.out.txt
rm secret/${name}.tmp.json
復号で使う鍵は .env
で管理します。
このファイルはコミットしないでください。
SOPS_AGE_KEY="AGE-SECRET-KEY-1WPUDMFQNYQ572CMUVMQSEH6U83VXE99FGAXJF7E8HPK64ZCRLRVQHJS0RH"
以下のコマンドにて先ほど設定したシークレット名を指定して実行します。
decrypt.sh <name>
すると復号されて .out.txt
に値が記載されます。
ex)
sample
シークレットセット
GitHub CLI と SOPS を組み合わせて GitHub にシークレット値を登録する set.sh
スクリプトを用意します。
#!/bin/bash -ue
name=$1
source ./.env
export SOPS_AGE_KEY="${SOPS_AGE_KEY}"
if [[ ! -e secret/${name}.enc.json ]]; then echo "secret/${name}.enc.json not exists"; exit 1; fi
sops decrypt secret/${name}.enc.json > secret/${name}.tmp.json
touch secret/${name}.in.txt
cat secret/${name}.tmp.json | jq .data | tr -d '"' > secret/${name}.out.txt
rm secret/${name}.tmp.json
NAME=$(echo ${name} | awk '{print toupper($0)}')
gh secret set ${NAME} --body "$(cat secret/${name}.out.txt)"
以下のコマンドにて先ほど設定したシークレット名を指定して実行します。
set.sh <name>
これにてリポジトリにシークレットが登録されます。
動作確認
本当にシークレットが登録されているか実際に GitHub Actions で参照して確認します。
以下のようなワークフローを定義し値を確認します。
※ シークレットを可視化するための実装です。シークレットがログに出力されるので推奨されません。
name: secret
run-name: ${{ github.ref_name }} by @${{ github.actor }} at ${{ github.workflow }}
on:
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
echo:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
PASSWORD: ${{ secrets.PASSWORD }}
steps:
- name: echo
run: echo "${PASSWORD:0:1} ${PASSWORD#?}"
ワークフローを起動すると以下のような出力が得られます。
Run echo "${PASSWORD:0:1} ${PASSWORD#?}"
echo "${PASSWORD:0:1} ${PASSWORD#?}"
shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
env:
PASSWORD: ***
s ample
指定した通りシークレットの1文字目と2文字目の間にスペースが含まれ値が参照できました。
おわりに
こんな感じでリポジトリ内でシークレットを参照する方法を考案してみました。
いやいや、SOPS_AGE_KEY
はどうやってセキュアに管理するの???
はい、サボりました。
理想としては AWS Key Management Service や Cloud Key Management を使って鍵は管理したいです。
SOPS_AGE_KEY
の代わりに利用すればもうちょっとセキュアに管理できるはずです。
が、環境構築がめんどくさかったので悪しからず...
今回実装したコードは以下に置いておきます。バージョン情報などは以下のリポジトリをご確認ください。
-
空いていたので後追いで執筆しました。 ↩︎
Discussion