删除提交的 git 交互式变基是否会真正消除 API 密钥/秘密/密码的暴露?

Will a git interactive rebase that deletes a commit truly remove exposure of API keys / secrets / passwords?

重要的是不要在代码库中存储密码和秘密。

有时我们在开发应用程序时会硬编码 API 密码。我们删除它,通常是通过将它变成我们用 export (Unix) 设置的环境变量。显然,更好的做法是从一开始就使用环境变量。

但是如果我们不那么小心并且我们提交了暴露密码的更改,会发生什么。
第一步是快速删除它们并提交并推送该更改。
好的

但是...

密码仍在 git 历史记录中,因此任何有权访问 git 存储库的人都可以获得密码。不好。

但是...

然后我们进行 git 交互式变基并 删除 (不是挤压)有问题的提交 = 历史记录中添加了密码的提交。

这会解决问题并确保密码不再可用 以任何方式 在 git 中吗?

当我拉出这个提交时,这将如何影响代码。如果除了带有密码的行之外还有其他代码,我大概需要重做那些会丢失的更改。如果提交是很久以前的,我可以想象如果任何提交也更改了同一行,就会出现问题。希望不会。

有很多关于如何删除敏感提交的答案,例如Remove sensitive files and their commits from Git history。任何好的答案都会警告您,无论如何都可能为时已晚,这是事实。没有太多关于 何时以及为什么 为时已晚的详细信息,但答案非常简单: 使用 并不是很多。这个答案的其余部分是关于何时以及为什么为时已晚,以及为什么仅使用交互式 rebase 删除提交是不够的。

问题的核心是无法更改提交,并且 Git 被连接以添加 new 提交。删除旧的/死的提交(和其他死的 objects)作为副作用发生,你几乎没有控制权。当你几乎做任何事情时——不管是什么:git commit --amendgit rebase -igit reset --hard、none 这很重要——任何 现有的 提交保留在您的提交数据库中,未更改,不受干扰,并且仍然可以通过其哈希 ID 获得。尽管如此, 可以真正删除提交。很难以可控和正确的方式做到这一点。

表示和查找提交

每个提交——实际上,每个 object1 在 Git 的主数据库中——都通过其哈希 ID 访问。分支中 last 提交的哈希 ID 在第二个较小的数据库中找到。本质上,像 master 这样的分支名称表示:master 的提示提交是 a123456...,它提供提交的哈希 ID object 这样你——或者 Git—— 就可以回到主数据库并说:找我 object a123456....

每个提交都可以列出一些先前或 parent 提交的哈希 ID。也就是说,在获得 object a123456... 之后,您可以在其中四处寻找 parent 哈希 ID。如果 a123456... 的(单个)parent 散列 ID 是 9876543...,那么你回到主数据库并说: *Get me object 9876543... and你有以前的承诺。这就是您和 Git 可以从分支的 end 开始并向后工作的方式,一次提交一个:

... <-grandparent <-parent <-last-commit   <--branchname

如果我们使用单个大写字母代表散列 ID,只需 记住 箭头(从 child 到 parent)始终指向向后,当你有多个分支时,我们会得到更容易绘制的东西:

...--E--F--G   <-- master
         \
          H  <-- develop

但在所有情况下,每当您对 "change" 您的历史记录进行某些操作时——例如,如果我们决定提交 G 是错误的并且必须被替换——你实际上 改变任何东西。相反,Git 实际上只是将错误的提交移开:

          G
         /
...--E--F--I   <-- master
         \
          H  <-- develop

主object数据库不会立即清除,如果你有任何方法来记住哈希ID在提交 G 之后,您可以通过该哈希 ID 向 Git 请求 G。 Git 将呈现给您,因为 它在数据库中!

无论您如何 "delete" 或 "change" 提交,同样的描述都是正确的:Git 最终只是复制每个 other 提交,这样 "deleted" 或 "changed" 提交(这里, G 将被删除)现在在不同的 branch-line:

...--o--F--G--H--J--...   <-- branch

变为:

          G--H--J--...   [previous branch, now abandoned]
         /
...--o--F--H'-J'-...   <-- branch

其中 H'H 的副本,适用于 F 而不是 G 之后,J' 是 [=35= 的副本]适配到H'之后,以此类推。同样,G 并不是真的 消失了 ,它只是被推到一边,连同它的所有后代。它的所有后代都被 slightly-altered 个副本替换,具有新的、不同的哈希 ID。


1有四种类型的object。 Committreeblob object 一起工作以在提交中存储文件,使用 注释标签 object 制作第四种类型。每个提交指的是一棵树;如果需要,该树会引用其他 sub-trees,并引用 blob 来保存与该提交一起使用的文件。


删除提交

那么,什么时候——如何以及为什么——提交最终会消失?答案是Git有一个维护命令,git gc,其工作是遍历[=​​149=]everyobject的整个主数据库,同时也遍历另一个可以找到object的所有名称的数据库。如果没有没有可以找到commit G的名称,经过上述操作后,git gc将确定是这种情况,并且最终—将G踢出主数据库,使用操作系统的正常删除功能删除文件。2

更正式地说,git gc 要从主数据库中删除 object,object 必须 无法访问 。有关可达性概念的完整讨论,请参阅 Think Like (a) Git。不幸的是,对于您的特定用例,我们可以用来访问提交的名称集包括任何 refl 中的任何提交g.


2通常这是一个不安全的删除,因此如果您控制了底层存储介质,您仍然可以获得数据以这种方式返回,但现在显然要困难得多。无论如何,现在没有人可以通过哈希 ID 向 Git 存储库请求提交 G。但是,当心支持快照的文件系统:您可以回到以前的快照并恢复快照时的整个存储库!


查找提交第 2 部分:reflogs

每个分支名都有一个reflog,比如master,加上HEAD一个。 (可能还有其他 reflog,但这是这里的两个重要的。)在上面的示例中,commit G 不再可以从名称 master 访问,但仍然有两个 reflog 条目,master@{1}HEAD@{1},这两个服务器都可以找到提交 G。所以 git gc 不会删除提交 G——无论如何,现在还不会。

找到 G 的 reflog 条目最终将被删除 。特别是,git reflog expire 自动删除 sufficiently-old,因此 expired reflog 条目。您可以配置多长时间足够大,但默认为 30 天或 90 天,3,在本例中为 30 天。

this 的意思是,默认情况下,G 会一直存在,直到 git gc 使用 git reflog 删除 reflog 条目,一旦他们足够大——即从现在起至少 30 天。如果你想加快那部分的速度,你可以使用 git reflog(见 the documentation)来更快地删除或使 G 的条目过期;或查看下面的克隆。

一旦 reflog 条目消失,G 确实(全局)无法访问,git gc 将删除它。你可以看出这是因为 git show <em>hash</em> and git rev-parse <em>hash</em> 会告诉你他们不知道你在说什么哈希 ID。

还请记住,如果您的 Git 联系了另一个 Git,您的 Git 可能已经向其他 Git 提交了 G。特别是,当您 运行 git push 时,您 Git 调用另一个 Git 并向它们提供提交。如果您已经 他们 提交 G,您在自己的存储库中所做的任何事情都无法收回。如果您允许其他用户从您的存储库中 git fetch,他们可能已经获取了 G 的副本,同样,您在自己的存储库中所做的任何事情都无法收回:您必须说服 他们 放弃提交。

Reflogs 不会被 git clone 复制,所以另一种无需等待即可摆脱 G 的方法是克隆您自己的存储库。 git clone 所做的是创建一个 new 存储库,然后从原始存储库中获取。提取获取的提交是那些可以从源存储库公开的名称访问的提交。因此,与其手动使一些 reflog 条目过期然后 运行ning git gc,不如克隆自己的存储库。这里有一个缺点:您失去了所有 reflog 的安全网,并且您自己的分支名称成为新存储库的 origin/* 名称。4


3此处 30 天和 90 天之间的选择取决于引用本身指向的提交是否可以访问 reflog 中的值。在这种情况下,名称 master 指向提交 I,例如,并且不可能从 I 返回到 G,因此 [=48] 中的值=],指向 G,无法从 master 上的值访问。这意味着到期时间是 gc.reflogExpireUnreachable——默认为 30 天——而不是 gc.reflogExpire,后者默认为 90 天。

请注意,我们再次通过有向图依赖于 可达性 的概念。这是理解Git.

的关键之一

4你可以使用 git clone --mirror,但这会让你得到一个 bare 存储库,并且一个默认值不合适 fetch设置。然后你可以修复这两个,但是如果你知道如何做所有这些,你可能想要使用 --mirror 以外的东西。


总结

如果:

  • 您没有与任何人分享不需要的提交(没有提取或推送),
  • 您删除所有对提交的引用,或者等待 30 天,然后 运行 git gc

然后 提交将真正消失,没有通过 file-system 级别快照的任何形式的复活。您可以将哈希 ID 提供给 git showgit rev-parse 以验证它是否已消失。但是,如果提交可能已被复制到其他任何地方,您将无法再对其进行任何控制。

安全的默认设置是假设如果提交在任何时间段内对任何其他人可见,它已经被复制,并且其中的秘密是不再是秘密。