加固你的发布管道安全
CI/CD管道是复杂的,攻击面很大。基本上,发布管道是远程代码执行环境,应该谨慎对待(有关一些攻击示例,请参见Haymore A., Smart I., Gazdag V., Natesan D., & Fernick J., 2022)。设计管道时需要谨慎,尤其是当你构建高度定制化的管道时,遵循最佳实践非常重要。如果不确定,最好寻求外部帮助,而不是等到问题发生时才后悔。
保护你的运行器
如果你使用的是 GitHub 托管的运行器,它们负责保持安全。由于这些运行器是临时的,每次执行都会从一个干净的状态开始。但你执行的代码可以访问 GitHub 中的资源,包括密钥和机密。确保对 GitHub Actions 进行安全加固(参见“确保你的 Actions 安全”部分),并限制 GitHub_TOKEN 的权限(工作流应以最小权限运行)。
自托管的运行器运行在你的环境中,你需要负责确保它们的安全!以下是一些规则:
-
永远不要将自托管的运行器用于公共仓库。
-
让你的运行器具有临时性(或者至少每次运行后清理,不要在磁盘或内存中留下工件)。
-
保持镜像精简并及时打补丁(只安装所需的工具,并保持所有内容的最新状态)。
-
不要为所有团队和技术使用通用的运行器。保持镜像的分离和专用。
-
将运行器保持在隔离的网络中(只允许运行器访问其所需的资源)。
-
只运行安全的 Actions。
-
将运行器纳入你的安全监控,并检查异常进程或网络活动。
最佳解决方案是拥有一个动态可扩展的环境(例如,Kubernetes 服务),并运行临时的、精简并打补丁的镜像。
有关自托管和托管运行器的详细信息,请参见第7章《运行你的工作流》。
保护你的 Actions
GitHub Actions 非常有用,但它们是你执行的代码,并且授予了访问你资源的权限。因此,你需要非常小心使用哪些 Actions,特别是在自托管运行器中。来自可信源(如 GitHub、Microsoft、AWS 或 Google)的 Actions 通常没有问题,但即使这些公司也会接受拉取请求,因此仍有可能存在漏洞。对于 Actions 的最佳实践如下:
-
始终审查 Action 的代码。 还要查看所有者、贡献者数量、提交的次数和日期、星标数等指标,确保该 Action 属于一个健康的社区。
-
始终通过显式的提交 SHA 引用 Action。 SHA 是不可变的,而标签和分支可能会被修改,这可能导致新的代码在你不知情的情况下执行。
-
如果你正在与 fork 一起工作,要求所有外部协作者批准,而不仅仅是首次贡献者。
-
使用 Dependabot 来保持你的 Actions 最新。
如果你自托管运行器,你应该更加严格并限制可以使用的 Actions。以下是两种可能的方式:
-
仅允许本地 Actions,并创建你已分析过的 Action 的 fork,并引用该 fork。这虽然需要额外的工作,但能让你完全控制所使用的 Actions。你可以将这些 Actions 添加到本地市场中,以便更容易发现(参见 Rob Bos, 2022)。
-
仅允许来自 GitHub 和特定允许的 Actions 列表(白名单)的选定 Actions。 你可以使用通配符来允许同一所有者下的所有 Actions(例如,Azure/*)。这个选项比第一个选项不那么安全,但维护起来也更轻松。
你可以将这些选项配置为企业政策或每个组织的设置。
Actions 是你在环境中执行的来自其他人的代码。它们是依赖项,可能会破坏你发布的能力并引入漏洞。确保你的政策在速度和安全性之间找到适合你需求的最佳平衡。
保护你的环境
在将发布部署到环境之前,使用环境保护规则并要求审阅者批准发布(参见第9章《部署到任何平台》中的分阶段部署)。这样可以确保在访问环境的机密并执行代码之前,发布已经经过审查。
结合分支保护和代码所有者(参见第3章《团队合作与协同开发》),仅允许某些分支进入你的环境。通过这种方式,确保在批准部署时,已执行必要的自动化测试并获得代码所有者的审批。
尽可能使用令牌
与其使用存储为机密的凭据连接到云提供商(如 Azure、AWS、GCP 或 HashiCorp),你可以使用 OpenID Connect (OIDC)。OIDC 将交换短期令牌进行身份验证,而不是凭据。你的云提供商也需要在其端支持 OIDC。
使用 OIDC,你不需要将云凭据存储在 GitHub 中,能够对工作流可以访问的资源进行更精细的控制,并且你拥有会在工作流运行后过期的短期令牌。
下图 15.11 显示了 OIDC 如何与云提供商集成:

OIDC 工作流程概览
-
创建 OIDC 信任关系:在云提供商和 GitHub 之间创建 OIDC 信任关系。将信任限制到一个组织和一个仓库,并进一步限制对环境、分支或拉取请求的访问。
-
GitHub OIDC 提供者自动生成 JSON Web Token (JWT):在工作流运行时,GitHub 会自动生成一个 JWT。该令牌包含多个声明,用于为特定的工作流作业建立安全且可验证的身份。
-
云提供商验证声明并提供短期访问令牌:云提供商验证这些声明并提供一个仅在作业生命周期内有效的短期访问令牌。
-
使用访问令牌访问资源:使用访问令牌来访问该身份可以访问的资源。你可以使用该身份直接访问资源,或者通过安全的密钥库(例如 Azure Key Vault 或 HashiCorp Vault)获取凭据。这种方式可以安全地连接到不支持 OIDC 的服务,并实现密钥的自动轮换。
在 GitHub 中,你可以找到有关如何配置 AWS、Azure 和 GCP 的 OIDC 的说明(参见 [GitHub 文档](https://docs.github.com/en/actions/deployment/securityhardening-your-deployments))。这些步骤很简单。例如,在 Azure 中,你需要在 Azure Active Directory (AAD) 中创建一个应用注册:
$ az ad app create --display-name AccelerateDevOps
接着,使用注册输出中的应用 ID 创建一个服务主体:
$ az ad sp create --id <appId>
然后,你可以在 AAD 中打开应用注册,并在“证书和机密” | “联合凭证” | “添加凭证”中添加 OIDC 信任关系。填写表单,如图 15.12 所示:

接着,为服务主体在订阅级别分配角色。打开订阅并进入“访问控制(IAM)” | “角色分配” | “添加” | “添加角色分配”,然后按照向导操作。选择一个角色(例如 Contributor),然后点击“下一步”。选择“用户”、“组”或“服务主体”,并选择你之前创建的服务主体。
在 GitHub 中,你的工作流需要写权限以使用 id-token:
permissions:
id-token: write
contents: read
在 Azure 登录 Action 中,使用客户端 ID(appId)、租户 ID 和订阅 ID 从 Azure 获取令牌:
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
之后,你可以使用 Azure CLI 访问资源:
- run: az account show
你还可以与其他 Azure Actions 配合使用,省略身份验证部分,在此示例中是发布配置文件。它们将使用由登录 Action 提供的访问令牌:
- name: Run Azure webapp deploy action using OIDC
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.APPNAME }}
slot-name: Production
package: website
每个云提供商的配置有所不同,但文档会帮助你快速入门:[GitHub 文档](https://docs.github.com/en/actions/deployment/securityhardening-your-deployments)。
收集安全遥测数据
为了确保从代码到生产的整个流水线安全,您需要在各个层级上获取实时的洞察。不同层次上有不同的监控解决方案(见图 15.13):

所有这些层次应将其数据报告到您的SIEM(安全信息和事件管理)系统,以执行分析并利用人工智能检测异常。许多组织收集了不同层次的数据,但由于不同的责任划分,往往忘记将其纳入监控系统。为了增强发布过程的安全性,您应考虑以下几点:
-
将所有监控源和事件包含到您的SIEM解决方案中。
-
监控整个流水线,包括您的代理和测试环境。包括所有进程和网络活动。
-
记录部署事件及相应的版本。如果在部署后突然出现新的进程或打开了端口,您希望能够将这些变化与该次部署关联起来,以便进行取证。
-
收集实时应用程序安全数据,并在工程师的仪表板上显示。这可能包括异常的程序终止、SQL注入尝试、跨站脚本攻击(XSS)、失败的登录(暴力破解攻击)或分布式拒绝服务攻击(DDoS),但这取决于您的产品。为了检测SQL注入或XSS,您必须在编码用户输入之前进行额外的日志记录,如果输入包含可疑字符或元素。
创建安全意识的最佳方式是让大家看到威胁的现实存在。