暗号化したファイルを git 管理する際に、これまで git-secret を使ってきたリポジトリがあります。
git secret を使うには GPG (GnuPG) が必要で、環境整備に少々手間がかかるし、少々の知識も必要です。私も、PC を買い替えたときには、毎回手順を忘れてる。
そこで、『もともと SSH 鍵を運用している』かつ『GPG までは必要ないが平文テキストは残せない』という環境において、age を使って git secret に近いことをできる構成を考えてみました。
本提案は、ローカル環境だけでなくて、GitHub Actions などの CI/CD 環境にも応用できそう。
トークンなどたくさんの secret が増えてくると、それらを CI に登録管理するのは面倒になってくるけど、secret には SSH 鍵 1 本だけを登録しておけば、あとは CI でもローカルと同じ手順で復号できるようになる。トークンを rotate する時も、github の画面にアクセスせずに、git commit 1回で済む。CI の secret 管理とローカルの secret 管理を同じ仕組みに集約できる。
git-secret との比較
| 観点 | git-secret |
本提案 | ||
|---|---|---|---|---|
| 必要ツール |
gpg, git-secret
|
age, (npm) |
||
| 鍵の流用 | △ | GPG 鍵を新規作成・配布 | ◯ | 既存の SSH 鍵をそのまま使える |
| recipients ユーザの追加 | git secret tell [email] |
.age-keys に1行追記 |
||
| recipients の git 履歴 | △ |
.gitsecret/keys/pubring.kbx バイナリなので diff が見えない |
◯ |
.age-keys はテキストなので diff で確認できる |
| github からの公開鍵入手 | ◯ | https://github.com/[user].gpg |
◯ | https://github.com/[user].keys |
| 暗号化対象ファイルの追加 | git secret add [file] |
.age-paths に1行追記 |
||
| 対象ファイルの git 履歴 | △ |
.gitsecret/paths/mapping.cfg (平文のSHA付) |
◯ |
.age-paths (◯ ファイル名のみ) |
| 暗号化したファイルの拡張子 | *.secret |
*.age |
||
| 暗号化の実行 | git secret hide |
npm run age:hide |
||
| 複合化の実行 | git secret reveal |
npm run age:reveal |
既に SSH 鍵を運用しているなら、追加で GPG やほかの暗号化ツールの管理に悩まずに済む、というのがシンプルなメリットです。
package.json の scripts
下記の age:hide と age:reveal を package.json に登録する。
{
"scripts": {
"age:hide": "grep '^[^#]' .age-paths | while read -r f; do age -R .age-keys -o \"${f}.age\" \"$f\" && echo hidden: $f; done",
"age:reveal": "identity=$(ls ~/.ssh/id_ed25519 2>/dev/null || echo ~/.ssh/id_rsa) && grep '^[^#]' .age-paths | while read -r f; do age -d -i \"$identity\" -o \"$f\" \"${f}.age\" && echo revealed: $f; done"
}
}
ここで、暗号化・復号化コマンドの実行時に npm を使うのは、Node.js のプロジェクトだからです。単に age を起動しているだけなので、Makefile 他の手段でもよい。
構成ファイルの差
git secret hide の場合
project/
├── .gitignore # 平文ファイルを ignore する
├── .gitsecret/
│ ├── keys/
│ │ ├── pubring.kbx # バイナリ(公開鍵を格納)
│ │ ├── trustdb.gpg # バイナリ
│ │ └── random_seed # バイナリ
│ └── paths/
│ └── mapping.cfg # 暗号化対象ファイル一覧(ファイル名+平文のハッシュ値)
├── secret.txt # 平文 (gitignored)
└── secret.txt.secret # 暗号化版 (commit 対象)
主役は .gitsecret/keys/pubring.kbx (GPG キーリング) で、これがバイナリかつ実装依存。
npm run age:hide の場合
project/
├── .gitignore # 平文ファイルを ignore する
├── .age-keys # recipients (SSH 公開鍵リスト)
├── .age-paths # 暗号化対象ファイル一覧(ファイル名のみ)
├── package.json # age:hide / age:reveal scripts
├── secret.txt # 平文 (gitignored)
└── secret.txt.age # 暗号化版 (commit 対象)
.age-keys と .age-paths はどちらも cat で中身が読めるし、コメントも書ける。また、git diff で差分確認できるのが安心。
.age-keys の中身
暗号化ファイルを復号化できるユーザの SSH 公開鍵のリスト。コメント可。
公開鍵は、GitHub ユーザなら https://github.com/[user].keys から取得できる。
# age recipients
# このリポジトリの secrets を復号できる SSH 公開鍵
# Alice (workstation)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxxxx alice@workstation
# Bob (laptop)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIyyyyyyy bob@laptop
# CI
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIzzzzzzz ci-runner
.gitsecret/keys/ との対比になるので、.age-keys というファイル名にしています。
あるいは、git-secret 環境との関連がなければ、age の引数 -R 由来で .age-recipients` にしてもよいかも。
.age-paths の中身
暗号化したいファイルのパスを並べるだけ。コメント可。
# 暗号化したいファイルを追記して npm run age:hide
config/credentials.json
secret/api-token.txt
.env
運用フロー
初期化
# 自分の秘密鍵で開けられるように、自分の公開鍵を登録
cat ~/.ssh/id_ed25519.pub >> .age-keys
# 対象ファイルの登録
ls .env secret/credentials.json >> .age-paths
# 対象ファイルの git 除外
ls .env secret/credentials.json >> .gitignore
# 対象ファイルの暗号化
npm run age:hide
# git 登録
git add .age-keys .age-paths .env.age secret/credentials.json.age
git commit -m "secret: 初期暗号化"
recipients (人やマシン) を追加
curl -sS https://github.com/[user].keys >> .age-keys
npm run age:hide # 全ファイル再暗号化
git add .age-keys *.age
git commit -m "secret: new-user を recipients に追加"
平文を編集して再暗号化
npm run age:reveal # *.age → 平文 復号
vi .env
npm run age:hide # 平文 → *.age 再暗号化
git add *.age
git commit -m "secret: .env を更新"
別マシン (.age-keys 登録済) で復号
git pull
npm run age:reveal # 手元の ~/.ssh/id_ed25519 で復号
まとめ
-
.age-keys(recipients リスト) と.age-paths(対象ファイルリスト) の テキスト 2 ファイル だけで git-secret 相当のことを実現 - 既存の SSH 公開鍵をそのまま recipients に使えるので、新規 GPG 鍵を作る必要がない
-
npm run age:hide/age:revealの 2 つで運用が完結 - recipients・対象ファイルリストはテキストなので、diff がテキストで読める
- ローカル開発と CI 環境の secret 管理を、SSH 鍵で統合して、履歴管理できる