トークンが履歴に入った — 消したのにまだ出てくる
目標
履歴から機密ファイルと大きなファイルを、実際に取り除きます。そして、取り除いたあとにも残るものが何かまで確認します。
なぜ重要なのか
gitは、内容アドレス方式のストアです。ファイル1つ1つがblobオブジェクトになり、そのblobを指すツリーとコミットが残っている限り、オブジェクトは生きています。そのため、git rmは「今後はない」という意味にすぎず、「なかったこと」ではありません。機密情報が入ってしまったとき、この違いを知らないと、削除してコミットしたあとで安心したまま、トークンを使い続けることになります。このラボは、その違いをコマンドの出力で見せ、実際になくす順序(書き換え → バックアップ参照の削除 → reflogの期限切れ → gc)を身につけます。最後のステップが結論です。すでに広まったコピーにはそのまま残るため、機密情報は、掃除ではなく取り消しが先です。
ステップ
/root/gitx6/repoに、トークンのファイルと900KBの塊が混ざった履歴を作り、ワーキングツリーから削除するコミットまで入れます。- 同僚のコピー
/root/gitx6/peerを取っておき、現在の状態をnotes/before.txtに残します。 git filter-branchで、すべてのrefを書き換えます。- まだオブジェクトが残っている理由を
notes/original.txtに書き、その参照を取り除きます。 - reflogを期限切れにして、
gc --prune=nowで実際に削除したあと、サイズをnotes/after.txtに残します。 - ハッシュがすべて変わった事実を、
notes/rewrite.txtに残します。 /root/gitx6/peerを新しい履歴に追従させ、そのコピーに機密情報が残っているかを、notes/collab.txtに書きます。notes/report.mdに、事故対応の順序をまとめます。
参考
conf/prod.envは、次の2行で作成します。すべて合成した偽の値で、あとのステップがこの内容のblobが消えたかどうかを確認するため、1文字も変えないでください。LABHUB_FAKE_TOKEN=deadbeefdeadbeefdeadbeefdeadbeefとDB_PASSWORD=not-a-real-password-1234です。- コミットの時刻は、
GIT_AUTHOR_DATEとGIT_COMMITTER_DATEで固定してください。 git filter-branchは、警告を表示して10秒待ちます。FILTER_BRANCH_SQUELCH_WARNING=1を先頭に付けると、飛ばせます。- よくある間違い: ステップ3までで終わったと思うことです。
refs/original/が残っている限り、オブジェクトは1つも消えません。 - よくある間違い: ステップ7で、
peerを削除して再度cloneすることです。このラボは、すでに広まったコピーに何が残るかを見るものなので、もともとあったコピーをそのまま追従させる必要があります。
トークンと大きな塊が混ざった履歴を作る
/root/gitx6/repoに、トークンのファイルと900KBの塊が混ざった履歴を作り、ワーキングツリーから削除するコミットまで入れます。
コミットを5つ積みます。アプリのファイル、conf/prod.env、assets/blob.bin、アプリの修正、そしてgit rm conf/prod.envのコミットです。途中でsideブランチを1つ作り、注釈付きタグv1も付けてください。書き換えがすべてのrefに及ぶことをあとで見るには、ブランチとタグが必要です。conf/prod.envの内容は、指示文の## 참고(「参考」の節)に書かれた2行のままである必要があります。あとのステップが、そのblobが消えたかどうかを内容で確認するためです。
削除したのに原文が出る
同僚のコピー/root/gitx6/peerを取っておき、現在の状態をnotes/before.txtに残します。
まずgit clone /root/gitx6/repo /root/gitx6/peerで、書き換え前のコピーを取っておきます(ステップ7で使います)。そのあと、git count-objects -vHでサイズを測り、git rev-list --objects --allで2つのパスが出るかを見て、git cat-file -p <커밋>:conf/prod.envでトークンの原文がそのまま出るところまで、ファイルに入れてください(プレースホルダーはコミットです)。今のHEADのハッシュも書いておく必要があります。ステップ6で比べます。
すべてのrefを書き換える
git filter-branchで、すべてのrefを書き換えます。
--index-filterにgit rm -r --cached --ignore-unmatch <경로들>を指定し(プレースホルダーはパスです)、--prune-empty・--tag-name-filter cat・-- --allを一緒に使います。ブランチを1つだけ指定すると、sideとv1が古いコミットを掴んだまま残ります。10秒の警告は、FILTER_BRANCH_SQUELCH_WARNING=1で飛ばします。
まだ1つも消えていない
まだオブジェクトが残っている理由をnotes/original.txtに書き、その参照を取り除きます。
書き換えが終わったのに、git rev-list --objects --allに2つのパスがそのまま出ます。git for-each-ref refs/originalを見ると、理由がわかります。filter-branchが、元に戻せるように、元のrefをすべてバックアップしておいたのです。一覧を取り出して、git update-ref -dで1つずつ削除してください。
reflogを空にして、実際に削除する
reflogを期限切れにして、gc --prune=nowで実際に削除したあと、サイズをnotes/after.txtに残します。
reflogも参照です。git reflog expire --expire=now --expire-unreachable=now --allで空にします。そのあとgit gc --prune=nowを実行します。--prune=nowがないと、デフォルトの猶予期間が過ぎるまで削除されません。前後のgit count-objects -vHを並べて書き、サイズがどれだけ減ったかが見えるようにしてください。
ハッシュがすべて変わった
ハッシュがすべて変わった事実を、notes/rewrite.txtに残します。
ステップ2に書いておいた古いHEADのハッシュを、今のリポジトリでgit cat-file -eで探してみてください。ありません。コミットのハッシュは、親とツリーを含めて計算されるため、履歴の前のほうで1つ直すと、その後がすべて変わります。タグv1が今どのコミットを指しているかも、一緒に書いてください。
すでに持っていかれたコピーにはそのまま残っている
/root/gitx6/peerを新しい履歴に追従させ、そのコピーに機密情報が残っているかを、notes/collab.txtに書きます。
peerでgit fetchすると、origin/mainが新しい履歴に変わります。git reset --hard origin/mainで追従させてください。そのあと、そのコピーで機密のblobをgit cat-file -eで探してみてください。ここで出る結論が、このラボの要点です。そのため何を先にすべきかを書いてください。
事故対応の順序としてまとめる
notes/report.mdに、事故対応の順序をまとめます。
番号付きの順序で書いてください。何が1つ目かが最も重要で、掃除の4つのステップ(書き換え・バックアップ参照の削除・reflogの期限切れ・gc)が、なぜその順序なのかも、1行ずつ付けてください。チームに何を周知すべきかも、忘れないでください。