Open8

GitHub上のコミット履歴から機密情報を削除する

DANDAN

状況

  • あるメンバーが、機密情報をコミットしてGitHubにPushしてしまった
  • 1年以上経ってからそれに気がついた
  • 該当の機密情報をファイルから削除しても、過去のコミット履歴を見れば機密情報が参照できてしまうので、参照できないようにしたい
DANDAN

2つの選択肢

  • git filter-branch
    • gitの内部コマンドなので、追加のツールをインストールする必要がない
    • 細かい調整がしやすい
    • 使い方がやや複雑で、処理速度もやや遅め
  • BFG Repo-Cleaner
    • 細かい調整ができないけど、シンプルに使える
    • brew install等でインストールして使う

シンプルにやりたいので、BFG Repo-Cleanerが良さそう

DANDAN

BFG Repo-Cleanerを使う場合の手順

ツールのインストール

$ brew install bfg
$ bfg --version
bfg 1.14.0

ブランチを整理する

  • 不要なブランチを全て削除する
  • マージした時に、自動的にブランチが削除されるように設定する

今やってる作業を整理する

  • それぞれのローカルにある差分を全部GitHubにpushする
  • GitHub上でOpenな状態になっているPRを全てマージするかcloseする

その他

  • 使ってる全てのブランチの該当箇所から、機密情報を削除する?
DANDAN

適当なアプリを作って試してみる

$ mkdir bfg-sample
$ cd bfg-sample
$ git init
$ echo 'password123' > secret.txt
$ git add secret.txt
$ git commit -m 'Add secret'

(GitHubのGUIで、bfg-sampleという名前のリポジトリを作る)
https://github.com/nyshk97/bfg-sample

$ git remote add origin git@github.com:nyshk97/bfg-sample.git
$ git branch -M main
$ git push -u origin main
$ cd ..
$ git clone --mirror git@github.com:nyshk97/bfg-sample.git
$ bfg --delete-files secret.txt bfg-sample.git

ここで以下のエラーが発生

Using repo : /Users/hoge/dev/bfg-sample.git

Found 2 objects to protect
Found 2 commit-pointing refs : HEAD, refs/heads/main

Protected commits
-----------------

These are your protected commits, and so their contents will NOT be altered:

 * commit 06de1b9c (protected by 'HEAD') - contains 1 dirty file : 
	- secret.txt (12 B )

WARNING: The dirty content above may be removed from other commits, but as
the *protected* commits still use it, it will STILL exist in your repository.

Details of protected dirty content have been recorded here :

/Users/hoge/dev/bfg-sample.git.bfg-report/2023-07-26/12-54-34/protected-dirt/

If you *really* want this content gone, make a manual commit that removes it,
and then run the BFG on a fresh copy of your repo.
       

Cleaning
--------

Found 1 commits
Cleaning commits:       100% (1/1)
Cleaning commits completed in 19 ms.

BFG aborting: No refs to update - no dirty commits found??

最新のcommitは保護されているので、うまくいかないっぽい。
secret.txtを手動で削除して、その変更をcommitしてから、再度実行しろとのこと。
以下の手順でやり直す。

$ cd bfg-sample
$ rm -f secret.txt
$ git add -A
$ git commit -m 'Remove secret.txt'
$ git push origin main

この状態だと、普通に過去のcommit履歴から値を参照できる。

$ cd ..
$ rm -rf bfg-sample.git
$ rm -rf bfg-sample.git.bfg-report # bfgの実行時に生成される、レポートのディレクトリ
$ git clone --mirror git@github.com:nyshk97/bfg-sample.git
$ bfg --delete-files secret.txt bfg-sample.git

以下のメッセージが表示された。成功っぽい。

Using repo : /Users/hoge/dev/bfg-sample.git

Found 1 objects to protect
Found 2 commit-pointing refs : HEAD, refs/heads/main

Protected commits
-----------------

These are your protected commits, and so their contents will NOT be altered:

 * commit 2357094b (protected by 'HEAD')

Cleaning
--------

Found 2 commits
Cleaning commits:       100% (2/2)
Cleaning commits completed in 25 ms.

Updating 1 Ref
--------------

	Ref               Before     After   
	-------------------------------------
	refs/heads/main | 2357094b | f5a165e6

Updating references:    100% (1/1)
...Ref update completed in 27 ms.

Commit Tree-Dirt History
------------------------

	Earliest      Latest
	|                  |
	    D         m     

	D = dirty commits (file tree fixed)
	m = modified commits (commit message or parents changed)
	. = clean commits (no changes to file tree)

	                        Before     After   
	-------------------------------------------
	First modified commit | 06de1b9c | 371366ef
	Last dirty commit     | 06de1b9c | 371366ef

Deleted files
-------------

	Filename     Git id          
	-----------------------------
	secret.txt | ad366d9e (12 B )


In total, 3 object ids were changed. Full details are logged here:

	/Users/hoge/dev/bfg-sample.git.bfg-report/2023-07-26/13-29-27

BFG run is complete! When ready, run: git reflog expire --expire=now --all && git gc --prune=now --aggressive
$ cd bfg-sample.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), done.
Building bitmaps: 100% (2/2), done.
Total 3 (delta 1), reused 1 (delta 0), pack-reused 0

$ git push --force
Enumerating objects: 3, done.
Writing objects: 100% (3/3), 252 bytes | 252.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
remote: Resolving deltas: 100% (1/1), done.
To github.com:nyshk97/bfg-sample.git
 + 2357094...f5a165e main -> main (forced update)

該当のCommitを見ても、ファイルが見れなくなっている。

DANDAN

ミラーリポジトリについて

bfg --delete-files secret.txt bfg-sample.gitgit reflog expire --expire=now --all && git gc --prune=now --aggressive
などの操作は、開発時に触っていたbfg-sampleではなく、git clone --mirror git@github.com:nyshk97/bfg-sample.gitでcloneしたミラーリポジトリbfg-sample.gitに対して行っている。

これは、ミラーリポジトリは全てのブランチ、タグ、履歴を含んでいるから。
通常のリポジトリは、たとえばmasterブランチと自分が作業したことのあるブランチの情報しか持ってない。

DANDAN

reflogエントリを期限切れにすることについて

過去にgitに載せてしまって機密情報を見れなくするためには、git reflog expireコマンドで過去の全てのreflog情報を削除する必要がある。

過去の全てのreflog情報を削除することで発生しうる問題は以下の2つ。

  1. リポジトリの全体的な履歴が失われ、過去の状態への復元が難しくなる
  2. 他のエンジニアが今やってる作業がうまくマージできなくなる可能性がある
DANDAN

チーム開発をしている場合の進め方

1. 機密情報が載っているファイルを削除する

該当のファイルを削除→commit→push→mergeする。
ファイルを削除しても、特定のファイルが含まれるcommitを見ると中身が見えてしまうことを確認する。

2. 全体ミーティングのスケジューリング

他のエンジニアのカレンダー等を見て、全員が集まれる日時でweb会議の予定を立てる

3. 状況の説明

  • 機密情報がGitHubにアップされてしまっている
  • 機密情報をただ消しただけでは、gitの履歴を辿れば見れてしまうので、gitの履歴から消す必要がある
  • BFG Repo-Cleanerというツールを使って、gitの履歴から機密情報を完全に削除する予定
  • 上記のツールを使うことで、以下の問題が発生する
    • 今あるPRがうまくマージできなくなる可能性がある
    • 修正前のdevelopブランチから派生したブランチの修正が、修正後のdevelopブランチにマージできなくなる可能性がある
  • だから以下をやりたい
    • BFG Repo-Cleanerを使うタイミングを決める
      • やり途中の大きな修正がないタイミングで実行したい
    • BFG Repo-Cleanerを使う前に、既存のPRを全てMerge or Closeする
    • BFG Repo-Cleanerを実行する
    • 実行後、各エンジニアは再度cloneリポジトリをcloneし直して、開発環境を作り直す
$ mv app-name app-name-old
$ git clone git@github.com:user-name/app-name.git
# cloneできたら、READMEを参考に環境構築をやり直す。

4. 実施

STEP3で決めた実施日に、実行する。
BFG Repo-Cleanerを実行する手順は以下。

$ git clone --mirror git@github.com:organization/app-name.git
$ bfg --delete-files hoge.txt app-name.git
$ cd app-name.git
$ git push --force

GitHubにアクセスして、該当のファイルを削除したcommit履歴にアクセスし、中身が見れなくなっていることを確認する。

5. 代わりとなるファイルを作成

機密情報が載ってない版の、似たようなファイルを作ってcommit→push→mergeする