動機
リモートリポジトリに含めたくないコミット履歴をgit rebaseコマンドで無に帰す方法 等(TODO: 要アップデート)、git において過去の履歴を消す方法が挙げられている。これは本当に安全なのか。
疑問は
という意見による。これ本当に大丈夫なんだろうか(参照されないコミットが残ってそうな気がする): リモートリポジトリに含めたくないコミット履歴をgit rebaseコマンドで無に帰す方法 on @Qiita https://t.co/rBL6GgUmQt
— Kazuhiko Kikuchi (@kazuk) 2018年1月10日
結論
結論を先に書くと、一度 push してしまうと、しばらくは(見つけにくいけれども)データが残る可能性がある。ということになった。ただし、cygwin 上のファイルシステムと、ssh 越しにしか実験をしていないため、GitHub, GitLab, bitbucket 等での扱いは別途調べる必要がある。
検証手法
とても長い…
概要
- サーバへのアップロード
- 本来隠したい情報を持つコミットを行う
-
git push
する # 時点 B - 本来隠したい情報を消すコミットを加える
-
git rebase
で、本来隠したい情報のコミット(コミット自体の存在を)を消す -
git push -f
する # 時点 C - 別のリポジトリに
git push
する - サーバからのダウンロード
サーバへのアップロード
SSH 越しサーバに対する実行例
サーバ側準備
mkdir -p git_test
cd git_test
git init --bare repo_bare.git
git init --bare repo_bare_2.git
履歴追加の流れ
git clone ssh://USER@example.com/~/git_test/repo_bare.git repo_a
## warning: You appear to have cloned an empty repository.
cd repo_a
touch test.txt
echo "init" >> test.txt
git add test.txt
git commit -m "Initial commit"
## [master (root-commit) c8c1fa6] Initial commit
## 1 file changed, 1 insertion(+)
## create mode 100644 test.txt
# ここで、最初のコミットを push
git push
# この時点を A とする
echo "IMPORTANT_KEY" >> test.txt
git add test.txt
git commit -m "IMPORTANT_KEY"
## [master 12458d0] IMPORTANT_KEY
## 1 file changed, 1 insertion(+)
# ここで、問題のあるファイルを含むコミットを push
git push
# この時点を B とする
# KEY を消すような編集をコミット
echo "init" > test.txt
echo "KEY_REMOVED" >> test.txt
git add test.txt
git commit -m "KEY_REMOVED"
## [master 988ae1e] KEY_REMOVED
## 1 file changed, 1 insertion(+)
# ここで git push をすると、サーバ上で object が増えるが
# push していなくても大勢の結果は変わらないはず
# 最後の commit を squash する
git rebase -i "HEAD~2"
# 編集画面で
## pick 12458d0 IMPORTANT_KEY
## s 988ae1e KEY_REMOVED
# とし、保存。コミットメッセージは適当に。
## [detached HEAD b7864a0] KEY_REMOVED
## Date: Thu Jan 11 20:36:46 2018 +0900
## 1 file changed, 1 insertion(+)
## Successfully rebased and updated refs/heads/master.
# 上書き
git push -f
# この時点を C とする
## Counting objects: 3, done.
## Writing objects: 100% (3/3), 273 bytes | 39.00 KiB/s, done.
## Total 3 (delta 0), reused 0 (delta 0)
## To ssh://example.com/~/git_test/repo_bare.git
## + 12458d0...b7864a0 master -> master (forced update)
新しいリポジトリには push されないのを確認
git remote add repo_2 ssh://USER@example.com/~/git_test/repo_bare_2.git
git push repo_2
# この時点を D とする
## Counting objects: 6, done.
## Delta compression using up to 4 threads.
## Compressing objects: 100% (2/2), done.
## Writing objects: 100% (6/6), 461 bytes | 115.00 KiB/s, done.
## Total 6 (delta 0), reused 0 (delta 0)
## To ssh://example.com/~/git_test/repo_bare_2.git
## * [new branch] master -> master
実行結果を確認
時点 A でのサーバサイド
cd ~/git_test/repo_bare.git
find . -type f
すると
./objects/c8/c1fa69ff834dcb24f7007b38f6d01e25f48e3e
./objects/e1/29ad3ad5453f614c853331670e9f87156e9d40
./objects/b1/b716105590454bfc4c0247f193a04088f39c7f
./config
./hooks/pre-applypatch.sample
./hooks/pre-push.sample
./hooks/pre-commit.sample
./hooks/pre-rebase.sample
./hooks/post-update.sample
./hooks/applypatch-msg.sample
./hooks/update.sample
./hooks/prepare-commit-msg.sample
./hooks/commit-msg.sample
./HEAD
./info/exclude
./refs/heads/master
./description
となる。以下、hooks は邪魔なので載せない。
時点 B でのサーバサイド
時点 B では
./objects/c8/c1fa69ff834dcb24f7007b38f6d01e25f48e3e
./objects/13/eaf4447e61e19b92fa7d707fd2ec909ed027f2
./objects/12/458d00bfda6ad83156238f0001f6a27d40c01d
./objects/e1/29ad3ad5453f614c853331670e9f87156e9d40
./objects/8a/8368bda34f336c403d2cce0bda106796d45454
./objects/b1/b716105590454bfc4c0247f193a04088f39c7f
./config
./HEAD
./info/exclude
./refs/heads/master
./description
である。いくつかオブジェクトが増えているが、減るものはない。抽出すると
13/eaf4447e61e19b92fa7d707fd2ec909ed027f2
12/458d00bfda6ad83156238f0001f6a27d40c01d
8a/8368bda34f336c403d2cce0bda106796d45454
が増えている。これはそれぞれ(順不同)、時点 B での commit object, tree object, blob object である。内容は
git cat-file -p 8a83
のように確認でき、内容が
init
IMPORTANT_KEY
等と分かる。ここに、消したい情報が含まれている。
時点 C でのサーバサイド
時点 C では
./objects/c8/c1fa69ff834dcb24f7007b38f6d01e25f48e3e
./objects/13/eaf4447e61e19b92fa7d707fd2ec909ed027f2
./objects/49/02ccbada222f23c21482ceae2b68c0922c80bd
./objects/b9/4c8f66fbc9ae8e205b2cae341f34ce75bc98ad
./objects/12/458d00bfda6ad83156238f0001f6a27d40c01d
./objects/e1/29ad3ad5453f614c853331670e9f87156e9d40
./objects/8a/8368bda34f336c403d2cce0bda106796d45454
./objects/b7/864a0792da218c60d042078b9d7f90d76d3759
./objects/b1/b716105590454bfc4c0247f193a04088f39c7f
./config
./HEAD
./info/exclude
./refs/heads/master
./description
である。
49/02ccbada222f23c21482ceae2b68c0922c80bd
b9/4c8f66fbc9ae8e205b2cae341f34ce75bc98ad
b7/864a0792da218c60d042078b9d7f90d76d3759
が増えている。これらは、rebase された後の commit object, tree object, blob object である。
8a/8368bda34f336c403d2cce0bda106796d45454
が消えていないことに注意しよう。つまり、サーバ管理者には、一度アップロードしたコミットの内容は丸見えとなる。
時点 D でのサーバサイド
cd ~/git_test/repo_bare_2.git
find . -type f
./objects/c8/c1fa69ff834dcb24f7007b38f6d01e25f48e3e
./objects/49/02ccbada222f23c21482ceae2b68c0922c80bd
./objects/b9/4c8f66fbc9ae8e205b2cae341f34ce75bc98ad
./objects/e1/29ad3ad5453f614c853331670e9f87156e9d40
./objects/b7/864a0792da218c60d042078b9d7f90d76d3759
./objects/b1/b716105590454bfc4c0247f193a04088f39c7f
./config
./HEAD
./info/exclude
./refs/heads/master
./description
である。(hook は除いている) コミットオブジェクトが少ない。8a/8368bda34f336c403d2cce0bda106796d45454
がないので、情報漏洩はない。
サーバからのダウンロード
ここからが難しい。時点 C で、サーバを直接覗けばファイルがあることは分かる。そのデータを手に入れることはできるのだろうか。
git clone ssh://USER@example.com/~/git_test/repo_bare.git repo_b
cd repo_b
git cat-file -p 8a83
すると、clone
で
Cloning into 'repo_b'...
Enter passphrase for key '/home/tako/.ssh/id_ed25519':
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (6/6), done.
の後、
fatal: Not a valid object name 8a83
となる。つまり、8a83...
という object は入手できていない。つまり、第三者は簡単に入手できない。
しかし、「何らかの方法で入手できる可能性がある」場合、セキュリティ的に問題がないとは言えない。入手する方法はあるだろうか?
ここでは、ファイルそのものの中身は分からないが、「commit object や、tree object の id が分かってしまっている」という仮定を置こう。
$ git cat-file -p 1245
tree 13eaf4447e61e19b92fa7d707fd2ec909ed027f2
parent c8c1fa69ff834dcb24f7007b38f6d01e25f48e3e
author XXX <test@example.com> 1515670606 +0900
committer XXX <test@example.com> 1515670606 +0900
IMPORTANT_KEY
$ git cat-file -p 13ea
100644 blob 8a8368bda34f336c403d2cce0bda106796d45454 test.txt
$ git cat-file -p 8a83
init
IMPORTANT_KEY
である。最初の commit id が 1245 のところは、環境によって異なる。
12458d00bfda6ad83156238f0001f6a27d40c01d
13eaf4447e61e19b92fa7d707fd2ec909ed027f2
8a8368bda34f336c403d2cce0bda106796d45454
さて、これを取得するために、fetch をしようとすると
$ git fetch origin 1245
fatal: Couldn't find remote ref 1245
fatal: The remote end hung up unexpectedly
となる。取得できない。
git
には、fetch-pack
という、object を取得する仕組みがある。これを利用して、オブジェクトの取得を試みる。
git fetch-pack -v ssh://USER@example.com/~/git_test/repo_bare.git 12458d00bfda6ad83156238f0001f6a27d40c01d
Server supports multi_ack_detailed
Server supports side-band-64k
Server supports ofs-delta
Server version is git/2.7.4
Marking b7864a0792da218c60d042078b9d7f90d76d3759 as complete
error: Server does not allow request for unadvertised object 12458d00bfda6ad83156238f0001f6a27d40c01d
取得できない。Server does not allow request for unadvertised object
ということで、おそらく、ref から辿れない object は取得できない。
サーバ側で、git config uploadpack.allowtipsha1inwant true
を実行する、または、config に
[uploadpack]
allowtipsha1inwant = true
を書いておいてみる。
$ git fetch-pack -v ssh://USER@example.com/~/git_test/repo_bare.git 8a8368bda34f336c403d2cce0bda106796d45454
Server supports multi_ack_detailed
Server supports side-band-64k
Server supports allow-tip-sha1-in-want
Server supports ofs-delta
Server version is git/2.7.4
Marking b7864a0792da218c60d042078b9d7f90d76d3759 as complete
want 8a8368bda34f336c403d2cce0bda106796d45454 (8a8368bda34f336c403d2cce0bda106796d45454)
have b7864a0792da218c60d042078b9d7f90d76d3759
done
fatal: git upload-pack: not our ref 8a8368bda34f336c403d2cce0bda106796d45454
fatal: The remote end hung up unexpectedly
で、様相が変わる。なんと
git fetch-pack -v ssh://USER@example.com/~/git_test/repo_bare.git 8a8368bda34f336c403d2cce0bda106796d45454
ができてしまう。
https://git-scm.com/docs/git-config を見ると、いろいろなオプションがあることが分かる。
git fetch-pack -v ssh://USER@example.com/~/git_test/repo_bare.git 12458d00bfda6ad83156238f0001f6a27d40c01d
はいろいろ試してみて私の環境ではだめだったが、サーバ側で
git config uploadarchive.allowUnreachable true
すると
git archive --remote ssh://USER@example.com/~/git_test/repo_bare.git 12458d00bfda6ad83156238f0001f6a27d40c01d
が応答を返すようになる。
git archive --remote ssh://USER@example.com/~/git_test/repo_bare.git 12458d00bfda6ad83156238f0001f6a27d40c01d
pax_global_header00006660000000000000000000000064132256461160014520gustar00rootroot0000000000000052 comment=12458d00bfda6ad83156238f0001f6a27d40c01d
test.txt000066400000000000000000000000231322564611600125770ustar00rootroot00000000000000init
IMPORTANT_KEY
という感じである。
ここまでの結論
object の sha1 つまり、commit id 等が分かっただけでは、ssh 接続で、git コマンドだけを使っている限りは、隠されたデータを取得することはできない。
ただし、サーバ側でオプション(config) を設定している場合はその限りではない。
github について
git プロトコルに git fetch-pack
を利用
# PRIVATE_KEY
git fetch-pack -v git://github.com/USER/github_test.git 12458d00bfda6ad83156238f0001f6a27d40c01d
# Initial commit
git fetch-pack -v git://github.com/USER/github_test.git c8c1fa69ff834dcb24f7007b38f6d01e25f48e3e
は、受信に失敗する。error: Server does not allow request for unadvertised object c8c1fa69ff834dcb24f7007b38f6d01e25f48e3e
等と表示される。一方で、
# KEY_REMOVED
git fetch-pack -v git://github.com/USER/github_test.git b7864a0792da218c60d042078b9d7f90d76d3759
は成功する。何もオブジェクトを持っていない場合は
Looking up github.com ... done.
Connecting to github.com (port 9418) ... 192.30.255.113 done.
Server supports multi_ack_detailed
Server supports side-band-64k
Server supports ofs-delta
Server version is git/github-g7f1c57c193
want b7864a0792da218c60d042078b9d7f90d76d3759 (b7864a0792da218c60d042078b9d7f90d76d3759)
done
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.
b7864a0792da218c60d042078b9d7f90d76d3759 b7864a0792da218c60d042078b9d7f90d76d3759
等となる。
https プロトコル
アウトプットは長いので省略する。
# KEY_REMOVED
# OK
printf "0032want b7864a0792da218c60d042078b9d7f90d76d3759\n00000032have 0000000000000000000000000000000000000000\n0009done\n"| curl -H "User-Agent: git/2.14.1" --data-binary @- "https://github.com/USER/github_test.git/git-upload-pack" -H "Content-Type: application/x-git-upload-pack-request" --trace-ascii /dev/stdout
# PRIVATE_KEY
# NG
printf "0032want 12458d00bfda6ad83156238f0001f6a27d40c01d\n00000032have 0000000000000000000000000000000000000000\n0009done\n"| curl -H "User-Agent: git/2.14.1" --data-binary @- "https://github.com/USER/github_test.git/git-upload-pack" -H "Content-Type: application/x-git-upload-pack-request" --trace-ascii /dev/stdout
# Initial commit
# OK
printf "0032want c8c1fa69ff834dcb24f7007b38f6d01e25f48e3e\n00000032have 0000000000000000000000000000000000000000\n0009done\n"| curl -H "User-Agent: git/2.14.1" --data-binary @- "https://github.com/USER/github_test.git/git-upload-pack" -H "Content-Type: application/x-git-upload-pack-request" --trace-ascii /dev/stdout
ということで、参照するのが難しくなったコミットの PACK は取得できない。
サイトにオブジェクト id を指定してアクセス
ところが、これができてしまった。
(ダミーです)にアクセスできてしまった。よって、github はダメ。
https://qiita.com/dtan4/items/34e41e3bd40a43fd8cbf
にあるように、
実はコミット単体のページは GitHub 上にキャッシュとして存在しており、https://github.com/dtan4/terraforming/commit/64f752dd8d93c5b7326175a69cece9e742fd010b のようにコミットハッシュを直接指定すると普通に閲覧できます。
そこで、https://github.com/contact から GitHub のサポートにキャッシュを消すよう連絡する必要があります。