GitHub Actions 是一个越来越流行的 CI/CD 平台。它们允许自动化几乎所有开发周期的任务,同时保持易于访问。但是,由于它们经常使用外部代码,因此需要应用一些安全措施。我们已尝试在此备忘单中收集主要技巧以保护您的 GitHub 操作:
什么是 GitHub 操作?
GitHub Actions 是 GitHub 的 CI/Cd 服务。它是用于运行从开发系统到生产系统的工作流程的机制。操作由 GitHub事件 触发(提交拉取请求、打开问题、合并 PR 等),并且几乎可以执行任何命令。例如,它们可用于格式化代码、格式化 PR、将问题的评论与另一个票务系统的评论同步、为新问题添加适当的标签,或触发全面的云部署。
一个工作流由一个或多个作业组成,这些作业在它们自己的虚拟机或容器(运行器)中运行,执行一个或多个步骤。 步骤可以是 shell 脚本,也可以是动作,是专门为 GitHub CI 生态打包的可复用代码。
由于 GitHub 托管着数百万个可以通过拉取请求进行分叉和贡献的开源项目,因此GitHub Actions 安全对于防止供应链攻击至关重要。
此备忘单可帮助您注意某些 GitHub Action 工作流程带来的风险,无论您是否维护开源项目。
让我们深入了解最佳实践:
设置凭证的最小范围
这是您的工作流使用的所有凭据的一般安全原则,但让我们关注 GitHub 特定的一个:GITHUB_TOKEN。
此令牌授予每个运行者与存储库交互的权限。它是临时的,这意味着它的有效性从工作流开始和结束。
默认情况下,令牌的权限是“允许的”(大多数范围的读/写)或“受限的”(大多数范围默认没有权限)。在任何一种情况下,forked repos 最多只有一个 read-access。无论您选择一个选项还是另一个选项,GITHUB_TOKEN 都应始终被授予 执行工作流/作业所需的最低权限。
您应该在工作流中使用“权限”键来配置工作流或作业所需的最低权限。这将允许对 GitHub Actions 的权限进行细粒度控制。调用 GitHub API 的每个端点所需的权限集已被广泛记录,您应该验证默认 权限是什么以匹配和调整它们。
这个原则也适用于环境变量。要限制环境变量的范围,您应该始终在步骤级别声明它们,这样其他阶段就无法访问它们。相反,在作业级别定义它们将使它们可用于所有阶段,包括可能受损的代码(稍后会详细介绍)。
使用特定操作版本标签
通常,当人们在 GitHub 上创建自己的工作流程时,他们会使用其他人创建的操作。几乎所有工作流程都从如下步骤开始:
“嗯,是的,这只是获取我的代码。那有什么危险呢?
要考虑的重要部分是它如何检查您的代码。以“uses”开头的那一行意味着幕后正在进行一些工作,以将代码从 GitHub 存储库获取到运行工作流程的服务器。对于“actions/checkout”动作,幕后的东西存在于它自己的 repo中。如果您阅读了源代码,实际上有很多内容是您不看就不会知道的!不仅如此,您无需维护在该操作中运行的代码。
仔细想想,盲目相信所有这些行为是有风险的。第三方操作正在与您的代码进行交互,并且可能在您拥有的服务器上运行,但您是否看过它们在后台实际执行的操作?您是否在监控作者发布更新时对操作所做的更改?对于几乎每个人来说,这两个问题的答案可能是否定的。
考虑这种威胁场景:您正在使用第三方操作对您的代码运行 linter 来检查格式问题。您决定使用 GitHub Actions Marketplace 中的操作来执行您需要的操作,而不是自己安装、配置和运行 linter。你给它一个测试运行,它工作!由于它可以满足您的需求,因此您可以在使用它的存储库中设置一个工作流程:
使用此操作几个月后,您突然开始遇到API 密钥被盗和滥用的问题。经过一番调查,您发现第三方 linter 操作的作者最近向 GitHub Marketplace 推送了更新。您转到该操作的源代码库,看到最近将代码添加到 linter 操作以将环境变量发送到某个随机网址。
在那个假设的场景中,第三方操作的作者(或劫持他们帐户的人)向操作添加了恶意代码并将其重新标记为“v1”。每个使用“someperson/linter-action@v1”的人现在都在他们的工作流程中运行恶意代码。既然我们看到了这种情况的威胁,我们如何保护自己免受威胁?
没有人有时间关注他们使用的每个第三方操作的更新,但幸运的是 GitHub 为我们提供了一种方法来防止更新改变我们使用的操作。您可以使用提交哈希,而不是使用存储库中的标签运行操作。例如,当我自动将容器镜像推送到 Docker Hub 时,我在工作流程中使用以下步骤进行身份验证:
通过准确指定我在向 Docker Hub 进行身份验证时要使用的提交,我永远不必担心操作会发生变化或表现不同。您可以对您在工作流程中使用的任何操作执行相同的操作。
不要使用纯文本机密
这个稍微明显一点,但还是要说一下。源代码并不是唯一以纯文本形式存储 API 密钥和密码的地方。事实上,最好的说法是没有任何地方可以这样做,CI 工作流文件也不例外。GitHub Secrets 是一项功能,它允许您以安全的方式存储您的密钥,并在您的工作流程中使用 ${{}} 括号引用它们。确保将所有纯文本机密保留在 GitHub 操作之外。
当然,您还应该利用您的工作流程来扫描源代码本身的秘密:这里是您可以免费使用的ggshield-action 。
不要引用你无法控制的值
GitHub 允许您使用 ${{}} 括号从您的 GitHub 环境中引用秘密和其他值。遗憾的是,您可以参考的某些值可能不是您自己设置的。这是在许多开源存储库中发现的极其常见的错误!
让我们看一下我在开源存储库中找到并修复 的这个工作流程:
如果您查看工作流程的“lint”步骤,您会发现运行命令包含来自拉取请求的一些输入。特别是,它获取了拉取请求的标题,该标题由提交者设置。假设有人向这个存储库提交了一个拉取请求,名称如下:
在这种情况下,工作流 YAML 的评估方式如下:
在此示例中,威胁行为者下载并执行了恶意软件,但他们本可以做其他事情,例如窃取运行者的 GITHUB_TOKEN。根据工作流的运行上下文,令牌可能具有对原始存储库的写入权限,这意味着可以修改存储库内容,包括发布!另一个例子是从 CI 中泄露敏感数据,这将允许收集可用于横向移动的秘密。
拉取请求标题不是外部各方设置的唯一 GitHub 环境值。拉取请求正文以及问题标题和正文也是不可信值的示例。当您在 GitHub Actions 的步骤中引用这样的变量时,确保您控制它们的来源非常重要。