可信发布:软件包安全的新标杆

本文深入探讨了可信发布技术,这是一种基于OpenID Connect的新型认证机制,可消除长期API令牌和密码的使用,显著降低供应链攻击和凭证泄露风险,并简化发布流程。

可信发布:软件包安全的新标杆

在过去一年中,我们与Python Package Index(PyPI)合作,引入了一种名为“可信发布”的新型、更安全的认证方法。可信发布消除了对长期API令牌和密码的需求,降低了供应链攻击和凭证泄露的风险,同时简化了发布工作流程。PyPI上的关键软件包已经在使用可信发布,使其发布过程更加安全。

如果您向PyPI发布软件包,请使用官方PyPI文档为您的项目设置可信发布。本文的其余部分将介绍可信发布的技术原理和原因,以及我们希望在未来看到类似技术应用的地方。

我们乐于帮助扩展语言生态系统的信任。如果您参与包装生态系统(例如NPM、Go、Crates等)并希望采用更多此类技术,请联系我们!

可信发布:入门指南

可信发布的核心是“仅仅”另一种认证机制。从这个意义上说,它与密码或长期API令牌没有什么不同:您向索引提供某种证明,声明您的身份和预期权限;索引验证该证明,如果有效,则允许您执行与这些权限相关的操作。

可信发布的有趣之处在于它如何在不要求预先存在的共享密钥的情况下实现认证。让我们深入了解!

OpenID Connect和“环境”凭证

可信发布建立在OpenID Connect(OIDC)之上,这是一个基于OAuth2构建的开放身份证明和验证标准。OIDC使身份提供商(IdPs)能够生成可公开验证的凭证,证明特定身份(如hamilcar@example.com)。这些凭证在底层是JSON Web Tokens(JWTs),意味着OIDC下的身份是JWT中的相关声明集。

为了更清楚地说明,以下是GitHub的OIDC IdP提供的用户身份声明集(略有编辑)的示例:

1
2
3
4
5
6
7
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:octo-org/octo-repo:ref:refs/heads/main",
  "aud": "https://github.com/octo-org",
  "ref": "refs/heads/main",
  "sha": "example-sha"
}

(在实际的JWT中,此声明集会附带数字签名,证明其对于IdP持有的受信任签名密钥的真实性。没有该数字签名,我们就没有理由信任这些声明!)

在OpenID Connect方案中,任何人都可以是IdP。然而,OIDC的大部分实际价值来自于与大型、假定可信且安全良好的IdPs的交互。证明对GitHub和Google账户等事物的所有权是有价值的,特别是对于单点登录(SSO)和服务联合等场景。

到目前为止,一切都很好,但这些与像PyPI这样的包装索引并不特别相关。PyPI可以允许用户使用OIDC而不是密码登录,但尚不清楚这将如何使发布工作流程(特别是基于CI的流程)更加方便。

OIDC对像PyPI这样的包装索引有用的地方在于,观察到OIDC身份不一定需要是人类:它可以是机器标识符、源代码仓库,甚至是CI运行的特定实例。此外,它不需要通过交互式OAuth2流程获得:它可以“环境地”提供,作为只有身份(机器等)可以访问的对象或资源。

CI提供商不久前就意识到了这一点:GitHub Actions在2021年底添加了对环境OIDC凭证的支持,而GitLab在几个月前也添加了该功能。以下是在GitHub Actions上检索这些凭证之一的示例:

1
2
3
4
5
6
- name: Retrieve OIDC token
  uses: actions/github-script@v6
  with:
    script: |
      const token = await core.getIDToken()
      core.setOutput('oidc-token', token)

以下是GitHub Actions工作流程运行的声明集(再次过滤)可能的样子:

1
2
3
4
5
6
7
8
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:octo-org/octo-repo:environment:production",
  "aud": "https://pypi.org",
  "environment": "production",
  "repository": "octo-org/octo-repo",
  "workflow": "release.yml"
}

这是一个丰富的上下文:假设我们信任IdP并且签名检查通过,我们可以验证身份到确切的GitHub仓库、运行的工作流程、触发工作流程的用户等。这些中的每一个都可以成为认证系统中的约束。

信任是一切

回顾一下:OpenID Connect为我们提供了验证来自IdP的身份证明(以OIDC令牌的形式)所需的上下文和机制。这些证明中的身份可以是任何事物,包括特定仓库中的GitHub Actions工作流程的身份。

任何第三方服务(如PyPI)都可以接受OIDC令牌,并基于它们确定一组权限。因为OIDC令牌在密码学上与特定OIDC IdP的公钥绑定,攻击者无法伪造OIDC令牌,即使他们知道其中的声明。

但等一下:我们如何从包含身份的OIDC令牌到特定的PyPI项目?我们如何知道哪些PyPI项目应该信任哪些OIDC身份?

这需要一些可信设置:用户(在PyPI上)必须登录并配置每个项目与授权代表项目发布的发布者(即OIDC身份)之间的信任关系。

这只需要做一次,就像普通的API令牌一样。然而,与API令牌不同,它只涉及一方:不需要向CI(和OIDC)提供商提供令牌或任何其他秘密材料。此外,即使是可信设置部分也由完全公开的信息组成:它只是用户认为可用于发布目的的声明值集。对于GitHub Actions发布到PyPI,可信设置将包括以下内容:

  • GitHub用户/仓库slug
  • 执行发布的GitHub Actions工作流程的文件名(例如release.yml)
  • 可选地,工作流程使用的GitHub Actions环境的名称(例如release)

这些状态允许依赖方(例如PyPI)接受OIDC令牌,确认它们由受信任的身份提供商(例如GitHub Actions)签名,然后将签名的声明与一个或多个已建立对这些声明的信任的PyPI项目匹配。

看,没有秘密!

此时,我们拥有允许通过OIDC验证的身份发布到PyPI所需的一切。以下是GitHub案例中的样子:

  1. 开发人员(或自动化)触发GitHub Actions工作流程以发布到PyPI。
  2. 正常的构建过程(python -m build或类似)开始。
  3. 自动化检索当前工作流程运行的OIDC令牌,通过GitHub Actions的OIDC IdP证明当前工作流程的身份(用户/仓库、工作流程名称、环境等)。
  4. 该OIDC令牌发送到PyPI。
  5. 如果有效,PyPI验证它并将其交换为短期PyPI API令牌,该令牌仅限信任这些令牌声明的PyPI项目。
  6. PyPI返回短期API令牌作为对OIDC令牌的响应。
  7. 工作流程继续,使用短期API令牌执行正常的PyPI发布步骤(例如使用twine)。

对于99%的软件包发布者,步骤3到7完全是实现细节:官方的PyPA GitHub Action for publishing to PyPI封装了它们,使用户面对的部分只是这样:

1
2
- name: Publish to PyPI
  uses: pypa/gh-action-pypi-publish@release/v1

为什么我应该关心?

此时,您可能会合理地认为:

“我是一个有能力的工程师,我已经做对了所有事情。我的令牌正确范围到所需的最小权限,它们存储为工作流程(或每个环境)秘密,并且我仔细审核我的发布工作流程,以确保所有第三方代码都是可信的。” – 您,一个有能力的工程师

事情是这样的:您一直在做正确的事情!直到现在,最安全的PyPI认证方法是执行以下操作:

  1. 创建项目范围的API令牌。
  2. 将其存储为CI中的(范围)秘密。
  3. 在您已审核并建立信任的发布工作流程中小心访问它。

这足以满足许多用例,但从可用性和安全性的角度来看,还有很多不足之处:

  • 可用性。手动管理和创建API令牌是繁琐的,特别是在单个源代码仓库托管多个PyPI软件包的情况下:每个都需要自己单独范围的令牌、唯一的秘密名称等。您和您的工程师同事有更好的方式花费时间!
  • 预泄露安全性。并非所有攻击者都相同:有些是被动的,有些是主动的,有些可能只能破坏您发布过程中的特定步骤等。减少(或完全消除)这些攻击者之一的力量是有用的,即使所涉及的缓解措施不会对其他攻击者产生有意义的影响。不幸的是,使用长期令牌很难做到这一点:长期令牌同样容易受到任何获得访问权限的攻击者的攻击。
  • 后泄露恢复。为安全设计意味着试图挫败攻击者,并为成功攻击者带来的风险做准备和缓解。对于长期凭证(密码或API令牌),这是缓慢、繁琐且容易出错的:缺少单个凭证会为攻击者留下返回的缺口。一个更好的系统一开始就不会有这个问题。

可信发布解决了这些问题以及更多:

  • 可用性。使用可信发布者,不需要手动API令牌管理:配置发布者是每个项目的一次性操作,包括尚未创建的项目。这避免了发布全新项目时涉及的烦人API令牌舞蹈,以及工程师在尝试将API令牌交给负责将其添加到CI秘密的一方时玩的“凭证热土豆”游戏。不再有带有API令牌的Slack DM!
  • 预泄露安全性。可信发布减少了对手的数量:仅访问某些GitHub Actions环境或特定(非权限)步骤的攻击者无法铸造使用可信发布者所需的OIDC凭证。这与存储在GitHub Actions秘密中的长期令牌形成鲜明对比,其中任何步骤(和经常任何环境)都可以访问凭证!
  • 后泄露恢复。可信发布本质上是短暂的:所涉及的凭证(OIDC和PyPI凭证)每次只存活几分钟,意味着在后泄露响应期间失去访问权限的攻击者会自动被封锁,无需任何人工干预。这意味着更少的手动步骤和更少可能的人为错误。

安全和威胁模型考虑

可信发布是安全认证到包装索引的另一种方式。像每个安全功能一样,它必须针对威胁模型设计和实现。该威胁模型必须证明可信发布的存在合理性,既用于解决先前认证方法未解决的攻击者,也用于它暴露的新攻击场景。

现有威胁:账户接管和供应链攻击

账户接管(ATO)是包装生态系统中的一个已知问题:成功破坏合法用户的PyPI或GitHub账户的攻击者可以上传恶意版本(甚至覆盖以前的版本),而没有任何外部不真实迹象。

在一般情况下,ATO是一个无法解决的问题:像PyPI和GitHub这样的服务可以改善对安全功能的访问(甚至强制这些功能),但基本上无法防止用户披露其凭证(例如通过网络钓鱼),更不用说保护他们免受他们使用的每个可能易受攻击的软件的影响。

同时,像可信发布这样的功能可以减少账户接管的范围:包装索引允许软件包选择仅可信发布的未来是这样一个未来,其中包装索引本身的ATO不允许攻击者上传恶意版本。

类似地,“供应链安全”这些天风靡一时:公司和个人都在重新审视失控的依赖树及其经常无法问责和追踪的组件。

没有可信发布,GitHub Actions的现状是您信任您执行的每个第三方操作:它们都可以读取您配置的秘密。这是非常不理想的,并且是可信发布旨在保护的关键攻击模型之一。

新威胁:“账户复活”和恶意提交者

可信发布之所以有效,是因为它与“可信身份”的概念绑定:另一侧(例如GitHub Actions)的可信身份是用户/仓库、工作流程名称和可选环境名称的元组。

但等等:如果用户更改了他们的用户名,而攻击者接管了他们的旧用户名,会发生什么?我们称之为“账户复活”,并且大多数服务明确支持:用户名不意图是底层身份的永久、稳定标识符。

这打开了一个全新的攻击向量:信任hamilcar/cartago的PyPI项目可能突然开始信任攻击者控制的hamilcar/cartago,所有因为原始hamilcar现在是hannibal(而合法的hamilcar/cartago现在是hannibal/cartago)。

我们在为PyPI设计可信发布时考虑到了这一点,并与GitHub合作添加了一个额外的声明,将OIDC令牌不仅绑定到用户,还绑定到他们唯一、稳定的用户ID。这为我们提供了防止复活攻击所需的状态:即使攻击者成功成为GitHub上的hamilcar,他们的底层用户ID也不会改变,PyPI将拒绝他们提供的任何身份令牌。

可信发布还揭示了项目信任模型中的新(潜在)划分:对于任何给定项目,您是否信任该项目的每个成员也是潜在的发布者?在许多情况下,答案是肯定的:许多项目只有一个或两个仓库成员,两者也都是包装索引上的所有者或其他特权用户。

然而,在某些情况下,答案是否定的:许多项目有数十个低活动或不活动的成员,并非所有成员都可能遵循保护其账户的最佳实践。这些成员可能由于社区政策或因为他们需要访问不频繁(但关键)的项目活动而无法移除。这些用户不一定应该获得向包装索引发布版本的能力,仅仅因为他们拥有仓库的提交权限。

这也是我们在设计可信发布时考虑的一个因素,这就是为什么PyPI的实现支持可选的GitHub Actions环境:对于提交用户和发布用户不完全重叠的社区,可以使用环境来施加额外的工作流程限制,这些限制在OIDC令牌中反映(并随后由PyPI遵守)。PyPI自己的安全模型文档中给出了一个详细的示例。

即将来到您附近的包装索引

我们在PyPI上的工作由不可思议的Google开源安全团队(GOSST)资助,我们还与他们合作开发了用于Python生态系统整体安全的新工具。特别是,我们要感谢Dustin Ingram与我们不懈合作,并指导PyPI可信发布的整体节奏和设计。

目前,PyPI是我们所知的唯一提供可信发布的包装索引。也就是说,可信发布的任何内容都不是Python或Python包装独有的:它可以同样容易地被Rust的Crates、Ruby的RubyGems、JavaScript的NPM或任何其他从第三方服务(如GitHub Actions或GitLab的CI/CD)发布常见的生态系统采用。

我们的观点是,就像2019年的双因素认证一样,这种可信发布方案将成为开源包装安全模型的关键。我们将其视为所有后续改进的构建块,包括能够生成强大的密码学证明,证明PyPI版本是从特定源工件构建的。

如果您或您的公司对此工作感兴趣,请与我们联系!我们在开源生态系统安全功能方面拥有多年经验,并且始终在寻找更多方式为关键开源项目和服务做出贡献。

如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计