PEP 807 – 信任发布索引支持
- 作者:
- William Woodruff <william at yossarian.net>
- 发起人:
- Donald Stufft <donald at stufft.io>
- PEP 代理人:
- Donald Stufft <donald at stufft.io>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建日期:
- 2025年9月19日
- 发布历史:
- 2025年8月8日, 2025年9月29日
摘要
本PEP提出了一种标准机制,通过该机制,任意Python包索引可以支持“信任发布”,这是一种已由Python包索引(PyPI)实现的防滥用凭证交换方案。
本PEP中提出的机制旨在封装PyPI的现有信任发布实现,同时允许其他索引以可被现有Python包上传客户端发现和互操作的方式实现相同的方案。
动机
“信任发布”是PyPI的专用术语,指使用OpenID Connect (OIDC) 标准,将来自受信任的第三方服务(如CI/CD或云提供商)的短期*身份凭证*交换为可用于发布到索引的短期、最小范围的*上传凭证*。
信任发布最初于2023年在PyPI上设计并启用,作为一项非标准(PyPI特定)功能,与现有上传API非常相似。它在此方面已被广泛采用:截至2025年9月,已有超过一百万个文件使用信任发布者发布到PyPI,约占PyPI可用以来上传文件总数的八分之一。此外,PyPI的设计也启发了Rust (crates.io)、Ruby (RubyGems)和JavaScript (npm)生态系统中类似的设计。
缺乏信任发布标准对采用构成长期障碍:第三方索引(即PyPI和TestPyPI以外的索引)无法在不参考PyPI非标准化设计的情况下轻松实现信任发布。这反过来又构成了与非标准化上传API类似的长期成熟度风险:包上传客户端(如Twine和uv)必须要么接受索引之间的行为差异(导致各种临时解决方案的累积),要么继续拒绝非PyPI信任发布实现。
基本原理
缺乏现有信任发布标准是本PEP的主要理由。
本PEP中提出的设计与PyPI的现有实现密切相关,并增加了一层发现机制,使上传客户端能够确定任意索引是否支持信任发布,而无需做出PyPI特定的假设。
此设计的理由如下:
- PyPI上现有(非标准化)的信任发布实现拥有经过验证的良好记录,并且已被上传工具广泛采用。显著偏离现有设计将引入不必要的兼容性风险。
- 本PEP中提出的发现机制旨在与机器间协议的现有标准保持一致,即RFC 8615(众所周知的URI)。此外,此发现机制旨在允许在单个域名下托管多个索引,这是第三方索引主机常见的拓扑结构。
总而言之,本PEP的理由是标准化PyPI的现有接口*并*使其可发现,同时允许不符合PyPI拓扑结构的索引主机实现信任发布。
规范
本PEP的规范包含两部分:
- 一种*发现*机制,包上传客户端可以使用它来确定任意Python包索引主机是否支持信任发布。
- 一种*令牌交换*机制,包上传客户端可以使用它将身份凭证交换为上传凭证。
限制
除非另有明确说明,否则本PEP规范的所有部分都适用以下限制:
- 所有URL**必须**具有潜在受信任的来源。实际上,这意味着所有URL**必须**使用
https
方案,是本地回环的某种变体(localhost
、127.0.0.1
等),或在交互上下文中被*先验*视为可信(例如,内部网络)。上传客户端**必须**拒绝任何不符合此限制的URL。
- 所有服务器提供的URL(即发现响应中的URL)**必须**与用户提供的上传URL具有相同的主机子组件。上传客户端**必须**拒绝任何不符合此限制的URL。
实际上,这意味着对
https://upload.example.com/.well-known/pytp/{key}
的发现请求只能返回主机为upload.example.com
的URL。 - 所有客户端请求**应该**包含
Accept: application/vnd.pypi.pytp.v1+json
头部。在没有Accept
头部的情况下,接收服务器**必须**表现得如同此头部存在。如果存在任何其他
Accept
头部,接收服务器**应该**以406 Not Acceptable
状态码响应。
信任发布发现
所有Python包上传目前都是“端点驱动”的,这意味着上传客户端(如*twine*和*uv*)会获得一个上传URL(而不是仅仅一个域名)。
例如,要上传到PyPI,上传客户端需要连接到https://upload.pypi.org/legacy/
。
下面提出的发现机制利用了这一事实,允许单个域宣传对多个索引(及其相应的上传端点)的支持。
发现机制如下:
- 上传客户端会获得一个上传URL,例如
https://upload.example.com/legacy/
。 - 上传客户端提取URL的*路径组件*,如RFC 3986中所定义。如果路径组件为空,则应使用空字符串。
对于上述示例,路径组件是
/legacy/
。 - 上传客户端对路径组件进行SHA2-256哈希运算,生成*发现密钥*。
对于上述示例,发现密钥是
0cace9579789849db6e16d48df183951c8f17582200d84bc93c7678d6c8f78a7
。[1] - 上传客户端通过获取上传URL的方案和权限组件(如RFC 3986中定义)并附加
/.well-known/pytp/
和发现密钥来构建*发现URL*。对于上述示例,发现 URL 是
https://upload.example.com/.well-known/pytp/af030c06750716b1b35852298fe852b90def13dcbd012a5fe5148470f1206bfc
。 - 上传客户端向发现URL执行HTTP GET请求。
- 如果索引支持给定上传URL的信任发布,服务器会以
200 OK
状态码和包含JSON对象的正文进行响应。JSON对象**必须**包含以下字段:audience-endpoint
:一个字符串,包含在令牌交换期间使用的OIDC受众端点的URL。token-mint-endpoint
:一个字符串,包含在令牌交换期间使用的令牌铸造端点的URL。
对于上述示例,一个有效的响应正文将是:
{ "audience-endpoint": "https://upload.example.com/_/oidc/audience", "token-mint-endpoint": "https://upload.example.com/_/oidc/mint-token" }
如果服务器不支持给定上传URL的信任发布,它**必须**以404 Not Found
状态码响应。当以404 Not Found
响应时,服务器**不应**包含响应正文。如果包含响应正文,客户端**必须**忽略它。
服务器**可以**额外响应任何其他400或500范围内的标准HTTP错误代码以指示错误情况。
非200 OK
、非404 Not Found
响应**可以**包含正文,如果存在,**必须**是包含错误响应的JSON对象。
信任发布令牌交换
一旦上传客户端成功执行了发现流程,它就可以继续执行实际的信任发布令牌交换。
令牌交换分三步进行:
- 上传客户端使用在发现过程中获得的*受众端点*,向索引请求其预期的OIDC受众。
- 上传客户端使用预期的受众,从正在使用的信任发布提供者(即执行上传操作的CI/CD或云提供商)获取适当绑定的*身份凭证*。此步骤的详细信息取决于提供商,超出了本PEP的范围。[2]
- 上传客户端使用在发现过程中获得的*令牌铸造端点*,将获得的身份凭证交换为可用于上传到索引的短期*上传凭证*。
受众检索
要检索预期的OIDC受众,上传客户端会向在发现过程中获得的*受众端点*执行HTTP GET请求。
成功后,服务器会以200 OK
状态码和包含以下字段的JSON对象正文进行响应:
audience
:一个字符串,包含预期的OIDC受众。
如果失败,服务器**必须**以400或500范围内的任何标准HTTP错误代码响应以指示错误情况。失败响应**可以**包含正文,如果存在,**必须**是包含错误响应的JSON对象。
令牌铸造
在上传客户端执行受众检索并从信任发布提供者获得身份凭证后,它可以继续铸造上传凭证。
为了铸造上传凭证,上传客户端向在发现过程中获得的*令牌铸造端点*执行HTTP POST请求。POST请求的负载**必须**是一个包含以下内容的JSON对象:
token
:一个字符串,包含从信任发布提供者获得的身份凭证。
成功后,服务器会以200 OK
状态码和包含以下字段的JSON对象正文进行响应:
token
:一个字符串,包含上传凭证。上传凭证的格式由实现定义,并特定于索引。expires
:一个**可选的**整数,包含一个Unix时间戳,指示上传凭证何时到期。如果此字段不存在,上传客户端**可以**假设其请求时间后不超过15分钟(900秒)的到期点。服务器**不得**签发有效期少于15分钟(900秒)或超过6小时(21,600秒)的临时上传凭证,以请求时间为准。
最长有效期为6小时,以匹配GitHub Actions等流行CI/CD提供商的常见运行时限制。
上传客户端**可以**使用此时间(或上述指定的最小值)来确定何时需要刷新上传凭证。
如果失败,服务器**必须**以400或500范围内的任何标准HTTP错误代码响应以指示错误情况。失败响应**必须**包含正文,如果存在,**必须**是包含错误响应的JSON对象。
错误响应
当包含错误响应体时,它**必须**是一个包含以下字段的 JSON 对象:
message
:一个字符串,包含错误简短、高级的- 人类可读的摘要。
errors
:一个包含一个或多个对象的数组,每个对象包含- 以下字段:
code
:一个字符串,包含机器可读的错误代码。description
:一个字符串,包含错误的人类可读描述。
本PEP未指定任何特定的错误代码。客户端**不应**假定错误代码在不同索引之间是一致的,而**必须**将错误代码视为不透明的字符串。
安全隐患
本PEP旨在通过正式标准化PyPI已使用的信任发布流程来提高Python打包生态系统的安全性和透明度。
本PEP未发现与信任发布发现或交换流程本身相关的任何正面或负面安全影响。
与流程分开,信任发布*本身*在PyPI上具有安全模型,并被认为是比长期API令牌或密码更安全的替代方案。信任发布的主要积极安全影响是:
向后兼容性
本PEP不改变任何现有行为,并且完全向后兼容现有上传客户端和索引。
执行 PyPI 非标准信任发布上传流程的现有客户端将继续正常工作,所有不实现信任发布的索引的现有上传也将继续正常工作。
如何教授
本PEP是信任发布的*正式化*,它已在Python打包生态系统中得到广泛采用。这种采用伴随着各种教育资源,指导最终用户采用信任发布,其中包括:
- Python打包用户指南:使用GitHub Actions CI/CD工作流发布包分发版本
- PyPI:使用信任发布者发布到PyPI
- pyOpenSci:为通过GitHub Actions安全自动化发布设置信任发布
被拒绝的想法
“横向”发现
本PEP的发现机制使用了RFC 8615中定义的.well-known
位置方案。该方案被机器间协议广泛采用,包括OpenID Connect本身(用于OpenID Connect发现)。
另一种考虑的替代方案是使用“横向”发现机制,即上传客户端将尝试通过构建相对于上传URL的相邻路径进行发现。例如,对于https://upload.example.com/legacy/
,上传客户端将尝试在https://upload.example.com/legacy/pytp
(或类似的)处发现信任发布支持。
这种方法的优点是它不需要索引操作员控制他们的(子)域,而.well-known
方案是期望的(因为众所周知的URI只能从域的根目录提供)。
然而,这种方法也有缺点:
- 它假设任意索引可以提供相邻路径而不会干扰现有功能,这不一定成立。例如,给定的第三方实现可能已经将
/legacy/{*}
下的所有路由用于其他目的。 - 它与现有机器间协议约定的一致性较差,后者绝大多数使用
.well-known
方案。在此处开发自定义位置方案将需要为习惯于.well-known
方案的服务器管理员和操作员提供额外的说明材料。
“隐式”发现
另一种替代方案是执行“隐式”发现,类似于PyPI目前对信任发布所做的事情:上传客户端可以跳过明确的发现步骤,直接尝试受众和令牌铸造步骤,并处理可能出现的任何错误。
这种方法的优点是简单:它消除了发现步骤所需的网络往返,并消除了从发现响应中获取受众和令牌铸造端点的间接性。
这种方法也有缺点:
- 它隐式地将给定域限制为单个索引/上传实现,因为PyPI上的隐式“发现”步骤是根据上传URL的基础域构建受众和令牌铸造端点。此限制在像PyPI这样的单个索引主机的情况下是可以接受的,但不能推广到其他索引拓扑(如提供隔离私有索引的索引主机)。
- 它依赖于完全静态的受众和令牌铸造端点构造规则,这意味着如果这些端点需要更改,将对现有客户端造成重大干扰。
脚注
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0807.rst