关于从存储库中删除敏感数据
使用 git-filter-repo 之类的工具更改存储库的历史记录时,了解相关影响至关重要。 重写历史记录需要与协作者仔细协调才能成功执行,并且必须管理一些副作用。
请务必注意,如果需要移除的敏感数据为机密(例如密码/令牌/凭据),通常都如此,那么在第一步中,需要撤销和/或轮换该机密。 一旦密钥被撤销或轮换,该密钥不再能够用于访问,这可能足以解决你的问题。 执行额外的步骤来重写历史记录并移除机密可能没有必要。
重写历史记录的副作用
重写历史记录会产生许多副作用;其中包括:
- 重新污染的风险很高****:不幸的是,很容易将敏感数据重新推送到仓库,从而造成更大的混乱。 如果另一个开发人员拥有你重写之前的一个克隆,并且在你重写之后仅需运行
git pull,然后运行git push,敏感数据就会返回。 他们需要么丢弃其克隆并重新克隆,或仔细按照多个步骤清理其克隆 - 丢失其他开发人员工作的风险****:如果其他开发人员在你尝试清理时继续更新包含敏感数据的分支,你将被迫重新清理或放弃他们的工作。
- 提交哈希发生变更:重写历史记录将更改引入敏感数据的提交的哈希以及之后所有提交的哈希****__。 依赖于提交哈希不变更的任何工具或自动化都将中断或出现问题。
- 分支保护难题:如果有阻止强制推送的任何分支保护,则必须关闭这些保护(至少暂时关闭)才能移除敏感数据。
- 已关闭拉取请求的差异视图损坏:移除敏感数据将需要删除用于显示拉取请求差异视图的内部引用,因此你将无法再查看这些差异 这不仅适用于引入敏感数据的 PR,也适用于在敏感数据 PR 合并后基于历史版本生成的任何 PR(即使后续 PR 没有添加或修改任何包含敏感数据的文件)。
- 与开放拉取请求的交互不佳:更改的提交 SHA 将产生另外的 PR 差异,而旧 PR 差异上的注释可能会失效并丢失,这可能会给作者和审阅者带来困惑。 我们建议在从存储库中删除文件之前合并或关闭所有未完结的拉取请求。
- 提交和标记上的签名丢失****:提交或标记的签名取决于提交哈希;由于提交哈希由历史记录重写修改,因此签名将不再有效,许多历史记录重写工具(包括
git-filter-repo)将单纯移除签名。 事实上,git-filter-repo还将移除在移除敏感数据之前的提交的签名和标签签名。 (技术上,可使用--refs选项配合git-filter-repo进行规避,但需小心确保你指定了历史中包含敏感数据的所有 refs 以及引入敏感数据的提交) - 将其他人直接引导至敏感数据****:Git 在设计时已在提交标识符中内置了加密检查,这样不法之徒就无法侵入服务器并在不受注意的情况下修改历史记录。 从安全角度来看,这很有帮助,但从敏感数据的角度来看,这意味着删除敏感数据是一个颇为复杂的协调过程;这进一步意味着,在修改历史记录时,拥有现有克隆的线索用户将注意到历史记录差异,并利用这一点快速轻松地找到你从中央存储库中移除但克隆中仍然存在的敏感数据。
关于敏感数据泄露
从存储库中移除敏感数据涉及四个大致步骤:
- 使用 git-filter-repo 在本地重写存储库
- 使用本地重写历史记录更新GitHub上的存储库
- 与同事协调,以清理存在的其他克隆
- 防止重复并避免将来的敏感数据溢出
如果仅重写历史并强制推送,包含敏感数据的提交可能仍可在其他地方访问:
- 在存储库的任何克隆或分支中
- 直接通过 GitHub 上缓存视图中的 SHA-1 哈希
- 通过引用它们的任何 pull request
你无法从其他用户的仓库克隆中移除敏感数据;你需要将 手册中“确保其他副本已清理:同事的克隆”中的说明发送给他们,让他们自行操作git-filter-repo 但是,你可以通过联系 GitHub 永久移除 上拉取请求中敏感数据的缓存视图和引用。
如果引入敏感数据的提交存在于任何分支中,则该提交将继续在那里访问。 你需要与分支的所有者协调,要求他们删除敏感数据或完全删除分支。
在决定重写存储库的历史记录时,应考虑这些限制和挑战。
使用 git-filter-repo 从本地仓库的历史记录中清除文件
-
安装
git-filter-repo工具的最新版本。 你需要一个带有--sensitive-data-removal标志的版本,这意味着至少是版本 2.47。 可以手动安装git-filter-repo,也可以使用包管理器安装。 例如,若使用 HomeBrew 安装工具,请使用brew install命令。brew install git-filter-repo有关详细信息,请参阅 INSTALL.md 文件,位于 存储库 中。
-
将仓库克隆到本地计算机。 请参阅“克隆仓库”。
git clone https://HOSTNAME/YOUR-USERNAME/YOUR-REPOSITORY -
导航到存储库的工作目录。
cd YOUR-REPOSITORY -
运行
git-filter-repo命令以清理敏感数据。如果希望从所有分支/标签/引用中删除特定文件,请运行以下命令,将
PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA替换为要删除的文件的 git 路径,而不仅仅是文件名(例如 ****)src/module/phone-numbers.txt:git-filter-repo --sensitive-data-removal --invert-paths --path PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA重要
如果包含敏感数据的文件曾经存在于任何其他路径(因为它被移动或重命名过),你必须为该文件添加一个额外的
--path参数,或者再次运行该命令并指定替代路径。如果你希望替换仓库历史记录中所有非二进制文件中列出的
../passwords.txt文本,可以运行以下命令:git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt -
仔细检查是否已从存储库的历史记录中移除所需的所有内容。
-
找出有多少个拉取请求会受到此次历史记录重写的影响。 后文需要用到此信息。
$ grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs 4可以删除
-c以查看受影响的拉取请求:$ grep '^refs/pull/.*/head$' .git/filter-repo/changed-refs refs/pull/589/head refs/pull/602/head refs/pull/604/head refs/pull/605/head此输出在第二和第三个斜杠之间包含拉取请求编号 如果受影响的拉取请求数大于预期,则可以放弃此克隆,且不会造成不良影响,然后重新进行重写或放弃敏感数据的删除。 进入下一步后,重写将变得不可逆。
-
一旦你对存储库的状态满意,强制推送你的本地更改以覆盖 你的 GitHub Enterprise Server 实例 上的存储库。 尽管
--force已由--mirror隐含,但我们在下方提醒你,这会强制更新所有分支、标签和 refs,并丢弃在清理仓库期间其他人可能对这些 refs 的修改git push --force --mirror origin此命令将无法推送任何以
refs/pull/开头的 refs,因为 GitHub 将这些 refs 标记为只读。 下一部分将处理这些推送失败。 如果其他 refs 推送失败,很可能该分支启用了分支保护,需要暂时关闭并重新推送 重复操作,直到唯一未能更新的 refs 为refs/pull/开头的
从GitHub中完全删除数据
在使用 git-filter-repo 删除敏感数据并将更改推送到 GitHub 之后,还必须执行几个额外步骤,才能将这些数据从 GitHub 中彻底删除。
-
联系 ,并提供以下信息:
-
相关所有者和仓库名称(例如 YOUR-USERNAME/YOUR-REPOSITORY)。
-
在上一步中找到的受影响的拉取请求数。 站点管理员将使用此信息来验证你是否了解将会受到影响的范围。
-
由
git-filter-repo报告的“First Changed Commit”(在其输出中查找NOTE: First Changed Commit(s)) -
如果
NOTE: There were LFS Objects Orphaned by this rewrite出现在 git-filter-repo 的输出中(紧跟在“First Changed Commit”后),请提到你有 LFS 对象被孤立”,并将命名的文件也上传到工单中。
-
如果你已成功清理除 PR 之外的所有引用,且没有派生包含对敏感数据的引用,你的站点管理员将随后:
* 在 GitHub 上取消引用或删除任何受影响的 PR。
* 在服务器上运行垃圾回收,以从存储中删除敏感数据。
* 删除缓存的视图。
* 如果涉及 LFS 对象,请删除和/或清除孤立的 LFS 对象。
若要详细了解站点管理员如何删除无法访问的 Git 对象,请参阅 命令行实用程序。 有关站点管理员如何识别可达提交的信息,请参阅识别可达提交。
- 协作者必须对从旧(受污染)仓库历史创建的任何分支执行 rebase,而非 merge 一次合并提交可能会重新引入您刚刚遇到清除问题的部分或全部污染的历史记录。 他们可能还需要采取额外步骤;请参阅 手册“确保其他副本已清理:同事的克隆”
git-filter-repo
识别可访问提交
要从仓库中完全移除不需要或敏感的数据,首次引入该数据的提交必须在分支、标签、pull request 和 fork 中完全无引用 任何地方的单一引用都会阻止垃圾回收彻底清除该数据
通过 SSH 连接到设备时,可以使用以下命令检查现有引用。 你需要获取最初引入敏感数据的提交的 SHA。
ghe-repo OWNER/REPOSITORY -c 'git ref-contains COMMIT_SHA_NUMBER'
ghe-repo OWNER/REPOSITORY -c 'cd ../network.git && git ref-contains COMMIT_SHA_NUMBER'
如果这些命令中的任何一个返回结果,你需要先删除这些引用,提交才能被成功垃圾回收。 第二个命令将识别存储库分支中存在的引用(如果存储库没有分支,则可以跳过运行它)。
- 以
refs/heads/或refs/tags/开头的结果分别表示仍包含违规提交引用的分支和标签,这表明修改后的仓库未完全清理该提交,或者未进行强制推送。 - 以
refs/pull/或refs/__gh__/pull开头的结果表示引用了违规提交的拉取请求。 这些拉取请求需要被删除,才能允许该提交被垃圾回收。 可以在站点管理员仪表板https://HOSTNAME/stafftools/repositories/OWNER/REPOSITORY/PULL_REQUESTS/<PULL-REQUEST-NUMBER>中删除拉取请求,将<PULL-REQUEST-NUMBER>替换为拉取请求编号。
如果在任何分叉中找到引用,结果将类似,但会以 refs/remotes/NWO/ 开头。 若要按名称识别分支,可以运行以下命令。
ghe-nwo NWO
可以通过在某个 fork 的克隆上操作,从已清理仓库获取更新,然后将包含敏感数据的所有分支和标签重新基于已清理仓库的相关分支或标签,从而从仓库的 fork 中移除敏感数据。 或者,可以将所有分支全部删除,并且如果需要,可以在根存储库清理完成后重新创建存储库分支。
删除提交引用后,请重新运行命令进行仔细检查。
如果任一 ref-contains 命令没有产生结果,您可以通过运行以下命令使用 --prune 标志进行垃圾回收,从而删除未引用的提交。
ghe-repo-gc -v --prune OWNER/REPOSITORY
垃圾回收成功删除提交后,需要浏览到存储库的站点管理员仪表板 https://HOSTNAME/stafftools/repositories/OWNER/REPOSITORY,选择“网络”,然后单击“无效 Git 缓存”以删除任何缓存数据。
避免将来的意外提交
防止参与者进行意外提交有助于防止敏感信息泄露。 有关详细信息,请参阅“防止组织中数据泄露的最佳做法”。
可以执行一些操作来避免提交或推送不应共享的内容:
- 如果在不应由 git 跟踪的文件中发现敏感数据,请将该文件名添加到
.gitignore(并确保提交该更改并将其推送到.gitignore,这样其他开发人员就能受到保护)。 - 避免在代码中对机密进行硬编码。 使用环境变量或机密管理服务(如 Azure 密钥保管库、AWS 机密管理器或 HashiCorp Vault)在运行时管理和注入机密。
- 创建预提交挂钩,以便在将敏感数据提交或推送到任何位置之前对其进行检查,也可以在预提交挂钩中使用已知工具,如 git-secrets 或 gitleaks。 (确保每位协作者都设置了你选择的 pre-commit hook。)
- 使用像 GitHub Desktop 或 gitk 这样的可视化程序来提交更改。 可视程序通常可以更容易地查看每次提交时将添加、删除和修改具体哪些文件。
- 避免在命令行中使用笼统的命令
git add .和git commit -a,而是使用git add filename和git rm filename来单独暂存文件。 - 使用
git add --interactive单独查看和暂存每个文件中的更改。 - 使用
git diff --cached查看为提交暂存的更改。 如果不使用git commit标志,这就是-a将产生的确切差异。 - 为存储库启用推送保护,以检测包含硬编码机密的推送并防止将其提交到代码库。 有关详细信息,请参阅“推送保护”。
其他阅读材料
-
git-filter-repo手册页,特别是“讨论”一节中的“删除敏感数据”小节。 - Pro Git:Git 工具 - 重写历史记录
- 秘密扫描