LoginSignup
8
5

More than 5 years have passed since last update.

git で一度 push した履歴は簡単には消えないが、ダウンロードも難しい

Last updated at Posted at 2018-01-11

動機

リモートリポジトリに含めたくないコミット履歴をgit rebaseコマンドで無に帰す方法 等(TODO: 要アップデート)、git において過去の履歴を消す方法が挙げられている。これは本当に安全なのか。

疑問は


という意見による。

結論

結論を先に書くと、一度 push してしまうと、しばらくは(見つけにくいけれども)データが残る可能性がある。ということになった。ただし、cygwin 上のファイルシステムと、ssh 越しにしか実験をしていないため、GitHub, GitLab, bitbucket 等での扱いは別途調べる必要がある。

検証手法

とても長い…

概要

  1. サーバへのアップロード
    1. 本来隠したい情報を持つコミットを行う
    2. git push する # 時点 B
    3. 本来隠したい情報を消すコミットを加える
    4. git rebase で、本来隠したい情報のコミット(コミット自体の存在を)を消す
    5. git push -f する # 時点 C
    6. 別のリポジトリに git push する
  2. サーバからのダウンロード

サーバへのアップロード

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 のサポートにキャッシュを消すよう連絡する必要があります。

https, webdav について追記予定

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5