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



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

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

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

但。。。

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

但。。。

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

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

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

关于如何删除敏感提交有很多答案,例如,从 Git 历史记录中删除敏感文件及其提交。 任何好的答案都会警告你,无论如何可能为时已晚,这是真的。 没有太多人详细介绍何时以及为什么为时已晚,但答案非常简单:它不是没有太多处。 这个答案的其余部分是关于何时以及为什么为时已晚,以及为什么仅通过交互式变基删除提交是不够的。

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

表示和查找提交

每个提交(实际上是 Git 主数据库中的每个对象1)都通过其哈希 ID 进行访问。 分支中最后一次提交的哈希 ID 位于第二个较小的数据库中。 本质上,像master这样的分支名称说:master的提示提交是a123456...,它提供了提交对象的哈希 ID,以便您或 Git 可以返回主数据库并说:获取我对象a123456...

每个提交都可以列出某些先前或提交的哈希 ID。 也就是说,在获得对象a123456...后,您可以在其中四处寻找父哈希 ID。 如果a123456...的(单个)父哈希 ID 是9876543...,然后返回主数据库并说:*获取我对象9876543...,你有上一个提交。 这就是你和 Git 可以从分支的末尾开始并向后工作的方式,一次一个提交:

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

如果我们使用单个大写字母来代替哈希 ID,并且只记住箭头(从子级到父级)总是指向后方,那么当您有多个分支时,我们会得到一些更容易绘制的内容:

...--E--F--G   <-- master

H  <-- develop

但在所有情况下,每当你做一些事情来"改变"你的历史记录时——例如,如果我们决定提交G是不好的,必须被替换——你实际上并没有改变任何东西。 相反,Git 实际上只是将错误的提交移开:

G
/
...--E--F--I   <-- master

H  <-- develop

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

无论您如何"删除"或"更改"提交,相同的描述都是正确的:Git 最终只是复制了所有其他提交,因此"已删除"或"更改"的提交(此处G要删除)现在位于不同的分支线上:

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

成为:

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

其中H'是改编在F之后而不是G之后的H的副本,J'是改编为在H'之后的J的副本,依此类推。 同样,G并没有真正消失,它只是被推开了,连同它的所有后代。 它的所有后代都被略微更改的副本所取代,并具有新的、不同的哈希 ID。


1有四种类型的对象。提交、blob对象协同工作以将文件存储在提交中,带批注的标记对象构成第四种类型。 每个提交引用一棵树;如果需要,该树会引用其他子树,并引用用于保存该提交附带文件的 Blob。


删除提交

那么,何时、如何以及为什么提交最终会消失呢? 答案是 Git 有一个维护命令,git gc,其工作是遍历每个对象的整个主数据库,同时还遍历可以找到对象的所有名称的另一个数据库。 如果没有名称可以找到提交G,在执行上述操作后,git gc将确定是这种情况,并最终使用操作系统的正常删除功能将G踢出主数据库删除文件。阿拉伯数字

更正式地说,要git gc从主数据库中删除对象,该对象必须无法访问。 有关可访问性概念的漂亮讨论,请参阅像 (a) Git 一样思考。 不幸的是,对于您的特定用例,我们可以到达提交的名称集包括任何reflog中的任何提交。


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


查找提交第 2 部分:引用日志

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

找到G的引用日志条目最终被删除。 特别是,git reflog expire会自动删除足够旧的、因此过期的引用日志条目。 您可以配置足够旧的年龄,但它默认为 30 天或 90 天,3在本例中为 30 天。

这意味着默认情况下,G将一直存在,直到git gc使用git reflog删除 reflog 条目,一旦它们足够旧(即至少从现在起 30 天)。 如果您想加快该部分的速度,您可以使用git reflog(请参阅文档)更快地删除或过期G条目;或请参阅下面的克隆。

一旦 reflog 条目消失,以至于G确实(全局)无法访问,git gc将删除它。 你可以说这已经发生了,因为git showhashgit rev-parsehash会告诉你他们不知道你在说什么哈希ID。

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

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


3此处在 30 到 90 天之间进行选择取决于 reflog 中的值是否可以从引用本身指向的提交中访问。 在这种情况下,名称master指向提交I,例如,不可能从I回到G,因此master@{1}中的值指向G,无法从master上的值到达。 这意味着过期时间gc.reflogExpireUnreachable(默认为 30 天)而不是默认为 90 天的gc.reflogExpire

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

4您可以使用git clone --mirror,但这会给您一个露的存储库,以及一个具有不适当的默认fetch设置的存储库。 然后你可以修复这两个,但如果你知道如何做这一切,你可能想使用--mirror以外的其他东西。😀


总结

如果:

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

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

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

最新更新