この記事が想定する読者
Docker Composeの定義、運用スクリプト、環境変数ファイルが増えてきて、変更履歴とバックアップのためにGitへ寄せたい。でも .env のような秘密情報を平文のままコミットしたくはない——そういう状況の家庭ラボ運用者を想定しています。
秘密情報管理ツールは「暗号化できた瞬間」よりも、復号できない時、平文が混ざりそうな時、鍵を差し替える時に実用差が出ます。本稿はSOPSとageの紹介ではなく、それらの失敗条件を使い捨てVM上でわざと起こしてみる検証ログです。
結論を先に言うと、今回の検証では「受信者ミスによる復号失敗」までたどり着けませんでした。暗号化より手前で検証チェーンが崩れたためです。ただし、その崩れ方こそが導入前に知っておく価値のある実害でした。
検証環境
- 使い捨てVM(Docker利用可、検証後に破棄)
- Debian bookworm
- age 1.1.1(Debianパッケージ)
- 設定項目 3.9系(公式debをフォールバック導入)
- git 2.39.5
base環境にはsops/ageが無く、信頼できる外向き通信も用意していないため、apt導入が可能な使い捨てVMで検証しました。秘密鍵と内部アドレスは本文に転記していません。
今回やろうとしたこと(意図)
- ageの鍵ペアを生成する
.sops.yamlで暗号化対象を.envに限定する- サンプルの
.envを作ってsops -e -iで暗号化する - 正しい鍵で復号できることを終了コードで確認する
- 別鍵だけを渡して、受信者ミスで復号が失敗することを再現する
- 復号済みファイルがGitに混ざりかける様子と、
.gitignoreでの防止を観測する - 鍵を差し替えて再検証する
- ロールバックと生成物削除まで確認する
実行コマンドと実行ログ
導入と版数記録(成功)
# apt-get update && apt-get install -y age git ca-certificates
# apt-get install -y sops \
|| (curl -fsSL -o /tmp/sops.deb \
https://github.com/getsops/sops/releases/download/v3.9.4/sops_3.9.4_amd64.deb \
&& apt-get install -y /tmp/sops.deb)
# sops --version && age --version
ageとSOPSの導入はexit=0で完了し、版数も記録できました。ここは素直に通ります。
age鍵ペア生成(成功・秘密鍵は非転記)
# age-keygen -o keys.txt
# chmod 600 keys.txt
出力(公開鍵のみ抜粋):
Public key: age1m0luzx...sgq487d
暗号化対象の限定(成功)
.sops.yaml を作り、.env だけを暗号化対象にします。設定ファイルの中身は次のとおりです。
creation_rules:
- path_regex: \.env$
age: age1m0luzx...sgq487d
ここで検証チェーンが崩れた(失敗・exit=128)
問題は次のコマンドチェーンでした。
# git init -q \
&& printf '設定項目=dummy-token-0000\nDB_PASSWORD=dummy-pass-1111\n' > sample.env \
&& git add -A && git commit -qm init \
&& sops -e -i sample.env \
&& git --no-pager diff
出力:
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "[email protected]"
git config --global user.name "Your Name"
fatal: unable to auto-detect email address (got 'root@...(none)')
終了コードは128。git commit がgit identity未設定で失敗しました。
ここで重要なのは、コマンドが && で連結されていた点です。git commit が失敗した時点でチェーンは止まり、その後ろの sops -e -i sample.env(=暗号化本体)は一度も実行されませんでした。
以降の復号がすべてexit=1になる(症状)
暗号化されていない sample.env のまま、正しい鍵で復号を試みます。
# 設定項目=keys.txt sops -d sample.env >/dev/null; echo "exit=$?"
出力:
parsing time "" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "2006"
exit=1
暗号化の失敗とは無関係に見える、時刻パースのエラーが返ってきます。続けて、わざと別鍵だけを渡して「受信者ミス」を再現しようとしました。
# age-keygen -o keys2.txt
# 設定項目=keys2.txt sops -d sample.env; echo "exit=$?"
出力は同じ parsing time ""... で、exit=1。
ここが今回の最大の観測点です。正しい鍵でも別鍵でも、まったく同じエラーが返りました。本物の受信者ミスなら「どの受信者にも一致する鍵が無い」という別系統のエラーになるはずです。同一エラーということは、失敗原因は鍵ではなく、もっと手前にあるという手掛かりでした。
平文と秘密鍵がインデックスに載っていた(平文混入の実物)
ロールバック前のステージ状態を見ると、もう一つの実害が見えます。
# git status --porcelain
出力:
AM .sops.yaml
A keys.txt
A sample.env
?? decrypted.env
?? keys2.txt
?? keys3.txt
git add -A が、秘密鍵を含む keys.txt と、平文の sample.env をまとめてインデックスに載せていました。.gitignore を後から置いても、すでにステージされたものは止められません。今回commitは失敗していたので実害には至りませんでしたが、これは狙って起こそうとしていた「平文混入」を、別経路で本当に踏みかけた瞬間です。
ロールバックとクリーンアップ(成功)
# git checkout -- sample.env
# rm -rf ./issue167-lab
作業ツリーの巻き戻しと生成物の削除はexit=0/期待どおりに完了しました。
原因
git commitがgit identity未設定で失敗(exit=128)&&連結のため、暗号化本体のsops -e -iが実行されなかった- 未暗号化のプレーンな
.envをsops -dに渡したため、SOPSがメタデータ(最終更新時刻)を読もうとして空文字のパースに失敗し、parsing time ""...という、暗号化や鍵とは無関係に見えるエラーを返した
つまり今回のexit=1は、受信者ミスでも鍵の不一致でもありません。「そもそも暗号化されていないファイルを復号しようとした」ことの症状です。
対処と教訓
再発を防ぐ最小の手当ては二つです。第一に、検証や運用に入る前にgit identityを設定しておくこと。
# git config user.email "[email protected]"
# git config user.name "lab"
第二に、これが本質ですが、暗号化が本当に走ったかを次へ進む前に確認することです。
# sops -e -i sample.env
# git --no-pager diff sample.env
git diff に、SOPSが付与する sops メタデータと暗号文への差し替えが出ていれば、暗号化は実際に走っています。差分が出ない、あるいは平文のままなら、その先の復号テストは無意味です。SOPSのエラー文言は原因を素直に示さないことがあるため、ツールの出力を信じる前に「前段が成功したか」を自分で確かめる手順を、運用へ組み込むのが安全です。
比較: 家庭ラボでの3つの運用
| 観点 | 平文.envをGit管理しない | 設定項目+ageで暗号化ファイルだけGit管理 | 復号済みを生成し使い終わったら消す |
|---|---|---|---|
| 平文混入の検出しやすさ | 混入の心配自体が少ない | .gitignoreとgit diffの運用が前提。今回のようにadd -Aで事故りやすい |
生成物の消し忘れと混入が最も起きやすい |
| 復号失敗時の原因の追いやすさ | 該当なし | 今回の通り、エラー文言が原因を直接示さないことがある | 同上に加え、生成タイミングの混乱が増える |
| 日常運用の手数 | 最小 | 中(暗号化・復号・差分確認の手順が要る) | 多(生成と削除の往復) |
| 鍵差し替え時の見通し | 該当なし | updatekeysの正味挙動は本稿では未検証(次回) |
鍵と生成物の両方を追う必要がある |
| ロールバックのしやすさ | 容易 | git checkoutで戻せる(今回確認済み) |
生成物が残ると戻しが曖昧になる |
導入する/しない の判断条件
- まず導入してよい条件: git identityを設定済みで、
.env等の暗号化後に毎回git diffで暗号化を確認する手順を運用に入れられる。.gitignoreに復号済みファイルと鍵ファイルを先に登録し、git add -Aではなくパスを指定してステージできる。 - まだ見送ってよい条件: 暗号化が走ったかの確認を運用に組み込めない、あるいは復号失敗時に終了コードとエラー文言を切り分ける余裕がない。その場合は、当面「平文の秘密情報をGitに置かない」運用のほうが事故が少ない。
まとめ(読者の持ち帰り)
SOPSとageは小さく試せる良い道具ですが、今回の検証で実害が出たのは暗号化アルゴリズムでも受信者設定でもなく、その手前の段取りでした。導入前に決めておくべきは、次の二点です。
- 検証チェーンは、前段の成功(暗号化が実際に走ったか)を
git diffで確かめてから次へ進む。 - SOPSのエラー文言は原因を素直に示さないことがある。
parsing time ""...のような表示が出たら、鍵を疑う前に「そのファイルは本当に暗号化済みか」を疑う。
受信者ミスによる復号失敗、平文混入の本格的な再現、updatekeys による鍵差し替えの正味挙動は、今回は前段の崩れにより観測できていません。これらは次回の独立した検証に回します。本稿は、その手前で誰もが踏みやすい一段を、正直に記録したものです。
この検証を回している環境
この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。
ラボ構成のまとめを見る →