TT Lab
はじめる
学ぶ 学習パス コース

Git実戦

トークンが履歴に入った — 消したのにまだ出てくる

TT Labで続きを見る

目標

履歴から機密ファイルと大きなファイルを、実際に取り除きます。そして、取り除いたあとにも残るものが何かまで確認します。

なぜ重要なのか

gitは、内容アドレス方式のストアです。ファイル1つ1つがblobオブジェクトになり、そのblobを指すツリーとコミットが残っている限り、オブジェクトは生きています。そのため、git rmは「今後はない」という意味にすぎず、「なかったこと」ではありません。機密情報が入ってしまったとき、この違いを知らないと、削除してコミットしたあとで安心したまま、トークンを使い続けることになります。このラボは、その違いをコマンドの出力で見せ、実際になくす順序(書き換え → バックアップ参照の削除 → reflogの期限切れ → gc)を身につけます。最後のステップが結論です。すでに広まったコピーにはそのまま残るため、機密情報は、掃除ではなく取り消しが先です。

ステップ

  1. /root/gitx6/repoに、トークンのファイルと900KBの塊が混ざった履歴を作り、ワーキングツリーから削除するコミットまで入れます。
  2. 同僚のコピー/root/gitx6/peerを取っておき、現在の状態をnotes/before.txtに残します。
  3. git filter-branchで、すべてのrefを書き換えます。
  4. まだオブジェクトが残っている理由をnotes/original.txtに書き、その参照を取り除きます。
  5. reflogを期限切れにして、gc --prune=nowで実際に削除したあと、サイズをnotes/after.txtに残します。
  6. ハッシュがすべて変わった事実を、notes/rewrite.txtに残します。
  7. /root/gitx6/peerを新しい履歴に追従させ、そのコピーに機密情報が残っているかを、notes/collab.txtに書きます。
  8. notes/report.mdに、事故対応の順序をまとめます。

参考

トークンと大きな塊が混ざった履歴を作る

/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行ずつ付けてください。チームに何を周知すべきかも、忘れないでください。