虽然 GitLab 通常不像 GitHub 那样公开,但有关数据的一般规则适用于此:如果您已将敏感/秘密数据提供给不可信的人,那么您的秘密已经泄露,您应该停止依赖它。
这意味着关键问题不是——或者至少现在不是——“我如何说服 GitLab 忘记我的秘密”,而是“我是否完全、完全信任 GitLab 服务器和其他有权访问的人”一直都是那些服务器?”如果答案是否定的,你无论如何都必须停止依赖这个秘密。
也就是说,这里有关于如何进行的规则Git 本身存储数据。假设您的 GitLab 服务器正在使用onlyGit(而不是在它们之上构建的一些其他东西可能会添加更多访问数据的方式,从而为您的敏感/秘密数据泄露提供更多方式),您所要做的就是说服 GitLab 服务器做同样的事情你会在你自己的 Git 中做。
Git's underlying storage model is that a repository is a collection of what Git calls objects. Each object has a unique hash ID, and is one of four types: blob, tree, commit and annotated tag. A blob is, roughly, file data. If the sensitive / secret data are inside a file, they are actually inside a blob object. A tree pairs up—well, more than pair, but let's use that for now1—each file's name with its blob hash ID, so if the file's name is the sensitive / secret data, your secret is actually inside a tree object. A commit object contains your name, email address, time stamp, log message, and the hash ID of some previous or parent commit(s), along with the hash ID of the tree that holds the files that make up the snapshot that is that commit. An annotated tag object holds much the same as a commit except that instead of a tree object, it usually has the hash ID of a commit; this is where one usually stores a PGP signature marking some particular commit as "blessed" and, say, called version 2.3.4 or whatever.
假设您的秘密位于一个特定文件中,该文件的名称本身并不是秘密,此时您的目标是使 Git 停止使用保存该特定文件数据的 blob。为此,您必须使对象本身变成未引用的,然后使用git gc
让 Git 物理删除未引用的对象。此时,一旁长成可达性总的来说很有用,但我会把它外包给像 Git 一样思考 http://think-like-a-git.net/。在这里我们就说一下,一般来说,在你不小心提交了一些秘密文件之后,Git 会如何查找commit对象正在使用分支名称:
... <-F <-G <-H <--master
The name master
包含hash ID提交的H
。犯罪H
包含其父提交的哈希 ID,commitG
,这样 Git 才能找到提交G
,它从读取名称开始master
(生成哈希 IDH
)然后从数据库读取提交对象(这会产生一个tree物体和一parent提交哈希值,G
,以及日志消息以及您的姓名和电子邮件地址等),抛出除哈希值之外的所有内容G
,然后读取实际的提交对象G
从数据库中。如果您要求 Git 获取某个特定文件(或更准确地说,该文件的内容)from commit G
,然后它使用G
的树来查找包含该文件的 blob 的哈希 ID,然后从数据库中获取 blob 对象,现在 Git 就拥有了内容。
因此,假设您的秘密数据位于附加到附加到提交的树上的 blob 中H
,并且这些相同的数据不在any其他文件——这样就没有树附加到任何other提交将具有该 blob 的哈希 ID。然后,为了使H
本身未引用,只需命名即可master
指向G
代替H
:
git checkout master
git reset --hard HEAD~1
现在你有:
...--E--F--G <-- master
\
H [abandoned]
但同时H
has no obviousname 持有其哈希 ID,我们还没有完成:git gc
不会——至少不会yet—remove H
,这就是事情开始变得复杂的地方。
如果里面有有价值的文件H
,我们可以推H
一边,使用git commit --amend
,进行新的提交I
谁的父母是G
代替H
,并且有master
指向I
:
... edit files, git add, git commit --amend ...
giving:
H [abandoned]
/
...--E--F--G--I <-- master
1Technically, each tree entry has:
- 该条目的
mode
,一个文本字符串,例如100755
or 100644
。该字符串是40000
如果条目是子树。
- 保存文件名的字节字符串,通常采用 UTF-8 编码
- 与条目相关的哈希 ID
(模式和名称由空格分隔,名称以 ASCII NUL 结尾,而哈希 ID 以 20 个二进制字节编码。当 Git 切换到 SHA-256 时,这一点必须改变。我不知道我认为新的格式尚未确定,但它可以像使用某种模式一样简单0n
where n
是一个版本号,因为模式是八进制且前导零被抑制,所以现有的树不会有01
作为一种模式。或者,它可能是一个 NUL 字节,后跟一个版本号,因为它当前也是一个无效的树条目。)因此,对于子目录,树仅列出子树,对于常规文件,有两个值加上一个哈希。对于符号链接,哈希 ID 仍然是 blob 的哈希 ID,但 blob 的content is the target符号链接;对于子模块的 gitlink,哈希 ID 是commitgit 应该git checkout
在子模块中。
主要的并发症是 reflogs
Git 的部分does记住H
为了你,即使在你之后git reset
它消失了,这就是 Git 所说的reflogs。转发日志会记住previous的参考值。也就是分支名称master
可能指向H
现在,在我们之前git reset
它。然后它指向G
or I
现在,我们使用后git reset --hard
or git commit --amend
放弃提交H
。但它used to指向H
, so H
的哈希 ID 位于名称的引用日志中master
.
The @{1}
or @{yesterday}
语法是告诉 Git 查找这些引用日志值的方式。写作master@{1}
告诉你的 Git:看看我的master
reflog,并获取之前的值master
.该条目存在的事实将使您的 Git 保留提交H
这将使您的 Git 保留包含秘密的 blob。
事实上至少有two包含提交哈希 ID 的引用日志H
: 一个用于master
, in master@{1}
,还有一个用于HEAD
本身。所以如果你要说服你的 Git 真正放弃提交H
,因此丢弃该树H
,因此丢弃树特有的任何斑点H
,您必须让这些引用日志条目消失。
Normally, they go away on their own, generally after about 30 days. This happens because each reflog entry has a time-stamp as well, and git reflog expire
will expire—and remove—old reflog entries based on this time-stamp, vs the current time on your computer. The master git gc
command runs git reflog expire
for you, and sets it up to expire unreachable commits2 in 30 days by default. (Reachable commits get 90 days by default.) So on your own Git, you would need to run:
git reflog expire --expire-unreachable=now --all
告诉你的 Git:查找所有无法访问的提交,例如H
并立即使他们的转发条目过期。
2Technically, it's unreachable from the current value of the reference. That is, Git is not going to test a global reachability here, but rather do a somewhat simpler test: does this reflog entry point to a commit that is an ancestor of the commit to which the reference itself points right now?
第二个复杂因素是对象修剪宽限时间
即使双方的引用日志条目都已过期HEAD
和分支名称,你会发现你自己的git gc
不会立即丢弃 blob 对象。原因是allGit 对象有一个宽限期,在此期间git gc
不会把它们修剪掉。默认宽限期为 14 天。这给出了allGit 命令在一段时间内可以创建对象而无需担心它们,只要它们finish他们在这 14 天内的所有工作,方法是将所有这些对象链接到提交或标记对象或其他对象中,并使用适当的引用名称(例如分支或标记名称)记录该对象的哈希 ID。
为了让你不小心犯下的blobH
走开,那么,您不仅需要使无法访问的引用日志条目过期,还需要告诉 Git 修剪对象,即使它们是zero天数:
git prune --expire=now
这个修剪步骤是git gc
实际上删除了该对象,因此通过运行git prune
,您无需运行git gc
. (git gc
还运行 reflog expire 等,但协调一切以确保 Git 有这些宽限期。由于我们绕过了所有宽限期,所以我们只是绕过git gc
以及。)
确保执行此操作时没有其他 Git 命令正在运行,因为它们可能会创建期望在完成工作时持续 14 天的对象。
最后一个复杂因素是包文件
如果你的秘密存储在 Git 所谓的loose对象,上述步骤就足够了:该对象将完全消失,并且:
git rev-parse <hash-ID>
将不再找到该对象。此 Git 存储库中的任何位置都不再提供它。
But not all objects are loose. Eventually, to save space, Git packs these loose objects into pack files. Objects stored inside pack files get compressed against other objects in the same pack file.3 In this case, if your secret data have become packed, it's possible to retrieve them from the pack file.
这通常不会很快发生,因此很少会在包文件中出现刚刚提交的秘密。但如果它has发生了,清理它的唯一方法是让 Gitre-pack所有现有的包文件。也就是说,您可以让 Git 将包分解为其组成的松散对象,然后丢弃不需要的对象,然后构建一个新的(通常是单个)包文件,或者至少使用具有该效果的进程。重建包的 Git 命令是git repack
它有很多选择。由于时间有限,我不会在这里讨论更多细节。
3In thin packs, objects may be compressed against other objects in the repository that are not in the pack file, but thin packs are used only for fetch and push operations, after which they're "fattened up" by adding the missing bases back.
服务器通常没有重新日志
为了处理所有这些,您需要能够登录到 GitLab 服务器,因为这些维护 Git 命令(也不是 BFG,见下文)都不能通过 fetch 或 push 调用。特别是,虽然您可以使用git push -f
来自您的客户的名字master
在服务器上不再指向提交H
,你不能调用git prune
使松动的物体消失。
如果并且当你do登录服务器,您可以检查您的存储库是否启用了引用日志。如果没有,则无需执行任何重新记录到期操作。您还可以通过查看物品来了解物品是否松散或包装。.git/objects
目录。如果您的 blob 哈希 ID 是,0123456789...
它将存在于一个名为的文件中.git/objects/01/23456789...
。一旦它被取消引用和修剪,该文件就会消失,您就完成了。
使用 BFG 仓库清理器
您可以通过使用来避免很多并发症BFG 回购清理器 https://stackoverflow.com/q/49580624/1256452。 BFG 无论如何都不尊重任何宽限期,因为它有不同的目的。这也可以解决任何包文件问题。与其他方法一样,这必须在服务器上运行,并且它有自己的怪癖(请参阅链接的问题和答案)。