サーバーの秘密鍵やAPI呼び出しのトークンといった機密情報を間違ってGit commitに入れてしまったとき、どうしたらよいのかまとめました。原則として、機密情報はcommitに入れてはいけません。.gitignore
を活用して、commitに絶対に含まれない仕組みづくりが大切です。
でも、新規開発の時など開発環境があわただしいときは、ふとした瞬間に機密情報がcommitに紛れ込んでしまう場合もなくはないですよね。。。
結論
- ローカルレポジトリへのcommitだけなら、commitへの参照をすべて消去して
git gc
することでcommitを消去できます。 => 対処法3 - GitHubにpushしてしまった後なら、GitHubのサポートに連絡して消してもらうしかないです。ただ、それ以外にも善後策はあります。 => GitHubにpush済みの場合
対処法1: revert (おすすめしませんが簡単です)
git revert
コマンドをつかってcommitを打ち消します。こうすることで、「見かけ上は」機密情報を入れてしまったcommitをなかったことにできます。
git revert <commit>
「見かけ上」といったのは、commit履歴に機密情報commitが残ってしまうため、後からいくらでも機密情報を参照できてしまうからです。
例えば、コード追加 -> 機密情報追加 -> テスト追加の順番でcommitをした後、機密情報追加のcommitをrevertで打ち消すことを考えます。
$ git commit -m "add code"
$ git commit -m "add secret"
$ git commit -m "add test"
$ git revert HEAD~
すると、機密情報はブランチから削除することができますが、commit履歴にはしっかりと残ってしまいます。
$ git log --oneline
427d14d Revert "add cred"
0b17cbe add test
8476fcd add cred
87996b0 add code
そのため、機密情報commitを指定してcheckoutすれば機密情報を見ることができてしまいます。
$ git checkout 8476fcd
$ cat <credential> # => 機密情報が表示される!!!
なんだかCTFの問題にでてきそうな状況ですね。。。
対処法2: rebase (まあ悪くないです)
rebaseを使ってcommit履歴を書き換えて機密情報commitを削除しましょう。
$ git commit -m "add code"
$ git commit -m "add secret"
$ git commit -m "add test"
$ git rebase -i HEAD~2
git rebase
に-i
オプションをつけると、rebaseがインタラクティブモードで起動します。
pick 8476fcd add cred
pick 0b17cbe add test
今回は8476fcd
のcommitを削除したいので、この部分のpickをdropに変えます。
drop 8476fcd add cred
pick 0b17cbe add test
すると、commit履歴から機密情報commitが削除されます。
$ git log --oneline
41b8d7c add test
87996b0 add code
万事OKかと思いきや、実はcommit hashを使うと機密情報commitにcheckoutができてしまいます。
$ git checkout 8476fcd
$ cat <credential> # => 機密情報が表示される
なぜかというと、gitはcommit履歴からcommitが削除されても、すぐにはcommitの情報を消去しないからです。これには理由があって、例えばgit reset HEAD~
で間違ってresetしてしまったときなどに、元のcommitに戻るときなどに使われています。
$ git reset HEAD~ # 間違えた!!
$ git reflog
41b8d7c (HEAD -> main) HEAD@{0}: reset: moving to HEAD~
58f92da (HEAD -> main) HEAD@{1}: ...
$ git reset HEAD@{1} # => もとのcommitに戻る
rebase後のcommmit状況は以下のようになっています。
rebaseによってmainブランチではコード追加commitの直後にテスト追加commitが移動しています。ただ、機密情報追加commitも情報としては残っていて、ブランチからは参照されない宙ぶらりん(dangling)の状態になっています。
この状態のcommitは、いずれは(通常90日で)gitのgarbage collectionによって消去されるので、このまま放置していてもダメというわけではないです。(参考: Atlassian Git Tutorial)
対処法3: rebase + gc (面倒だけど確実)
rebaseにgcを組み合わせて、commitを完全に消去しましょう。
$ git rebase -i <commit>
$ git reflog expire --expire=now --all # reflogを消去
$ git gc --aggressive --prune=now # commitを消去
gitのgc(garbase collection)は、どこからも参照されていないcommitを消去するための仕組みです。今回は、rebaseでブランチのcommit履歴から削除した機密情報commitを消去するために使います。
git gc
を実行する前に、reflogを消去する必要があります。というのも、commitへの参照はブランチからの参照のほかにreflogからの参照も含まれるからです。
GitHubにpush済みの場合
これまで見てきた対処法はローカルブランチで機密情報をcommitしてしまった場合についてのものです。もし、ローカルブランチでcommitした後、GitHubにpushしていると、commitの消去は難しくなります。
手順としては以下の通りです。
- 対処法2または対処法3を使ってローカルブランチから機密情報を削除します。
- GitHub サポート ポータルに連絡して、GitHubレポジトリから機密情報を削除します。
これで機密情報のcommitを削除できます。とはいえ、一度GitHubにpushした機密情報は、漏洩したものとみなすほうがよいです。機密情報の性質に応じて、漏洩時の処理を行ってください。
GitHubにpushした機密情報がサーバー秘密鍵などだった場合は、鍵の無効化によって漏洩に対処できます。その場合、commitを急いで消去する必要性は低くなるため、GitHub サポート ポータルへの連絡はしなくてもよいかもしれません。