我不小心提交了一个包含敏感数据的文件。我需要通过删除敏感数据来更新该文件,并确保旧版本不会出现在历史记录中。
我知道那些在本地克隆了 repo 的人仍然可以访问它。但是,一旦他们提取最新的数据,是否可以将其设置为看不到向前移动的敏感数据或无法在日志中看到它?
tor*_*rek 10
虽然 GitLab 通常不像 GitHub 那样公开,但这里适用有关数据的一般规则:如果您将敏感/秘密数据提供给不可信的人,那么您的秘密已经泄露,您应该停止依赖它。
这意味着关键问题不是——或者至少还不是——“我如何说服 GitLab 忘记我的秘密”,而是“我是否完全、完全信任 GitLab 服务器和其他有权访问的人?一直都是那些服务器?” 如果答案是“否”,您无论如何都必须停止依赖这个秘密。
也就是说,这里有关于Git 本身如何存储数据的规则。假设您的 GitLab 服务器仅使用Git(而不是在它们之上构建的一些其他东西,这些东西可能会增加更多访问数据的方式,从而为您的敏感/秘密数据提供更多方式泄露),您所拥有的一切要做的是说服 GitLab 服务器做你会在你自己的 Git 中做的同样的事情。
Git 的底层存储模型是,存储库是 Git 称为对象的集合。每个对象都有一个唯一的哈希 ID,并且是以下四种类型之一:blob、tree、commit和annotated tag。一个BLOB是,大概,文件数据。如果敏感/秘密数据位于文件中,则它们实际上位于 blob 对象中。一棵树配对——嗯,不仅仅是pair,但现在让我们使用它1——每个文件的名称及其 blob 哈希 ID,所以如果文件的名称是敏感/秘密数据,那么你的秘密实际上是在一个树对象中。一次提交对象包含您的姓名,电子邮件地址,时间戳,日志信息,以及以前的一些或散列ID父提交(S),与持有组成快照文件树的哈希ID一起是那个承诺。带注释的标记对象与提交的保存方式大致相同,不同之处在于它通常具有提交的哈希 ID,而不是树对象;这是人们通常存储一个 PGP 签名的地方,该签名将某个特定的提交标记为“有福的”,例如,称为 2.3.4 版或其他版本。
假设您的机密位于一个特定文件中,其名称本身不是机密,此时您的目标是让您的 Git 停止使用保存该特定文件数据的 blob。为此,您必须使对象本身变为unreferenced,然后使用git gcGit 物理删除未引用的对象。在这一点上,对可达性的长期讨论通常很有用,但我会将其外包给Think Like (a) Git。让我们在这里说一下,一般来说,在你不小心提交了一些秘密文件之后,Git 找到提交对象的方式是使用分支名称:
... <-F <-G <-H <--master
Run Code Online (Sandbox Code Playgroud)
该名称 master包含commit的哈希 IDH。CommitH包含其父级 commit 的 hash ID,commit G,因此 Git 要找到 commit G,首先读取名称master(产生哈希 ID H),然后从数据库读取提交对象(产生一个树对象和一个父级提交) hash, G,连同日志消息和您的姓名和电子邮件地址等),抛出除 的散列之外的所有内容G,然后G从数据库中读取实际的提交对象。如果您要求 Git从commit 中获取某个特定文件——或者更准确地说,该文件的内容G,那么它会使用G的树找到包含该文件的 blob 的哈希 ID,然后从数据库中获取 blob 对象,现在 Git 有了内容。
因此,假设您的秘密数据位于附加到 commit 的树的 blob 中H,并且这些相同的数据不在任何其他文件中——因此附加到任何其他提交的树都不会具有该 blob 的哈希 ID。然后,要使H自己不被引用,只需将名称master指向G而不是H:
git checkout master
git reset --hard HEAD~1
Run Code Online (Sandbox Code Playgroud)
现在你有:
...--E--F--G <-- master
\
H [abandoned]
Run Code Online (Sandbox Code Playgroud)
但是,虽然H没有明显的名称来保存它的哈希 ID,但我们还没有完成:git gc不会——至少现在还没有——remove H,这就是事情开始变得复杂的地方。
如果 中有有价值的文件H,我们可以推开H,使用git commit --amend, 进行新的提交I,其父项G不是H,并且master指向I:
... edit files, git add, git commit --amend ...
Run Code Online (Sandbox Code Playgroud)
给予:
H [abandoned]
/
...--E--F--G--I <-- master
Run Code Online (Sandbox Code Playgroud)
1从技术上讲,每个树条目都有:
mode,文本字符串,如100755或100644。该字符串是40000该条目是否用于子树。(模式和名称用空格分隔,名称以 ASCII NUL 结尾,而哈希 ID 以 20 个二进制字节编码。当 Git 切换到 SHA-256 时,这将不得不改变。我不”牛逼认为新的格式,还没有决定,但它可能是那样简单,比如说,使用的模式0n,其中n一个版本号,该模式与前导零抑制八进制,所以没有现有的树将有01作为模式。或者,它可能是一个 NUL 字节后跟一个版本号,因为它目前也是一个无效的树条目。)因此对于子目录,树只列出子树,对于常规文件,有两个值加上一个哈希。对于符号链接,散列 ID 仍然是 blob 的散列 ID,但 blob 的内容是目标符号链接;对于子模块的 gitlinks,哈希 ID 是Git在子模块中应该提交的哈希 ID git checkout。
Git 中确实H为您记住的部分,即使在您git reset离开之后,也就是 Git 所说的reflogs。reflog 会记住引用的先前值。也就是说,分支名称master可能指向H right now,在我们之前git reset。然后它指向GorI 现在,在我们使用git reset --hardorgit commit --amend丢弃 commit 之后H。但是它曾经指向过H,所以它H的哈希 ID 在名称的 reflog 中master。
该@{1}或@{yesterday}语法是你如何让Git查找引用日志这些值。写作master@{1}告诉您的 Git:查看我的masterreflog,并获取master. 该条目存在的事实将使您的 Git 保留提交H,这将使您的 Git 保留包含机密的 blob。
事实上,至少有两个reflog 包含 commit 的哈希 ID H:一个是 for master, in master@{1},一个是HEAD它自己。因此,如果您要说服您的 Git 真正丢弃 commit H,从而丢弃 for 树H,从而丢弃 for 树独有的任何 blob H,您必须使这些 reflog 条目消失。
通常,它们会自行消失,通常在大约 30 天后。发生这种情况是因为每个 reflog 条目也有一个时间戳,并且git reflog expire会根据此时间戳与您计算机上的当前时间过期并删除旧的 reflog 条目。mastergit gc命令git reflog expire为您运行,默认情况下将其设置为在 30 天内使无法访问的提交2过期。(默认情况下,可到达的提交获得 90 天。)因此,在您自己的Git 上,您需要运行:
git reflog expire --expire-unreachable=now --all
Run Code Online (Sandbox Code Playgroud)
告诉您的 Git:查找所有无法访问的提交,例如H现在使它们的 reflog 条目过期。
2从技术上讲,从引用的当前值无法到达。也就是说,Git 不会在这里测试全局可达性,而是做一个更简单的测试:这个 reflog 条目是否指向一个提交,该提交是引用本身现在指向的提交的祖先?
即使在来自HEAD分支名称和分支名称的 reflog 条目都过期后,您会发现您自己的git gc不会立即丢弃 blob 对象。原因是所有Git 对象都有一个宽限期,在此期间git gc不会将它们修剪掉。默认宽限期为 14 天。这为所有Git 命令提供了一些时间,在此期间他们可以创建对象而不必担心它们,只要他们在 14 天内完成所有工作,将所有这些对象链接到一个提交或标记对象或其他任何东西,并做出适当的引用名称(例如分支或标签名称)记录该对象的哈希 ID。
为了让您不小心提交的 blobH消失,您不仅需要使无法访问的 reflog 条目过期,而且还需要告诉 Git 修剪对象,即使它们已经存在0天:
git prune --expire=now
Run Code Online (Sandbox Code Playgroud)
此修剪步骤是git gc实际删除对象的部分,因此通过运行git prune,您无需运行git gc。(git gc还运行 reflog expire 等等,但协调一切以确保 Git 有这些宽限期。由于我们绕过了所有宽限期,我们也绕过git gc了。)
执行此操作时,请确保没有其他 Git 命令正在运行,因为它们可能正在创建在完成工作后希望持续 14 天的对象。
如果您的秘密存储在 Git 称为松散对象的地方,则上述步骤就足够了:该对象将完全消失,并且:
git rev-parse <hash-ID>
Run Code Online (Sandbox Code Playgroud)
将不再找到该对象。它在这个 Git 存储库中的任何地方都不再可用。
但并非所有物体都是松散的。最终,为了节省空间,Git将这些松散的对象打包成打包文件。存储在包文件中的对象针对同一包文件中的其他对象进行压缩。3 在这种情况下,如果您的机密数据已打包,则可以从打包文件中检索它们。
这通常不会很快发生,因此在包文件中很少有刚刚提交的秘密结束。但是,如果它已经发生了,唯一的办法把它清理干净是让Git的重新包装现有的所有包文件。也就是说,您可以让 Git 将包分解为组成它们的松散对象,然后丢弃不需要的对象,然后构建一个新的(通常是单个)包文件——或者至少使用具有这种效果的过程。重建包的 Git 命令是git repack,它有很多选项。我不打算在这里详细介绍,因为我没时间了。
3在瘦包中,对象可能会针对存储库中不在包文件中的其他对象进行压缩,但瘦包仅用于获取和推送操作,之后通过添加丢失的碱基来“增肥” .
为了处理所有这些,您需要能够登录到您的 GitLab 服务器,因为这些维护 Git 命令(也不是 BFG,见下文)都不能通过 fetch 或 push 调用。特别是,虽然您可以使用git push -ffrom 您的客户端使master服务器上的名称不再指向 commit H,但您不能调用git prune使松散对象消失。
如果您确实登录到服务器,您可以检查是否为您的存储库启用了引用日志。如果没有,则无需执行任何 reflog 到期。您还可以通过查看.git/objects目录来查看您的对象是松散的还是打包的。例如,如果您的 blob 哈希 ID 是,0123456789...它将存在于名为.git/objects/01/23456789.... 一旦它被取消引用和修剪,文件就会消失,你就完成了。
您可以通过使用BFG 回购清洁器来避免许多并发症。无论如何,BFG 不尊重任何宽限期,因为它有不同的目的。这也可以解决任何包文件问题。与其他方法一样,这必须在服务器上运行,并且它有自己的怪癖(请参阅链接的问题和答案)。
您可以从历史记录中删除敏感数据。正如您所注意到的,任何已提取当前历史记录的现有克隆仍将拥有该文件。这些存储库必须“修复”才能继续与远程一起工作(请参阅“从上游 Rebase 恢复”下的git rebase文档 - https://git-scm.com/docs/git-rebase)。即使在修复之后,这些存储库的用户仍然可以根据需要获取数据。(事实上,没有什么可以阻止他们在修复之前复制该数据,即使您确实有一个修复过程会强制从其克隆中删除数据。)
考虑到这一点,您实际上只需将该数据视为已泄露即可。例如,如果是密码,则更改密码。
考虑到这一点,重写历史可能并不值得。如果敏感数据属于无法更改的类型,而您所能做的就是减轻现有泄漏并尝试防止其进一步传播,那么历史记录编辑的价值在于它可以防止新克隆进一步暴露数据。但如果它是一个密码,那么更改密码会使旧密码是否保留在源历史记录中变得无关紧要 - 所以它可能不值得修复。
如果您要重写历史记录,可以使用多种工具,具体取决于受影响的历史记录量。所有这些的详细程序已在此讨论过多次,但总而言之:
如果这只是一两个引用的最新提交,那么您可以使用git commit --amend
如果这是一个简单的线性提交历史(并且可能不是很长的历史),您可以进行交互式变基来编辑引入敏感数据的提交
对于历史记录不是特别大的更复杂的情况,您可以使用git filter-branch树过滤器或索引过滤器
您可以使用一些专门的工具,例如 BFG Repo 清理器。