Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 470 – 移除 PyPI 上的外部托管支持

作者:
Donald Stufft <donald at stufft.io>
BDFL 委托
Paul Moore <p.f.moore at gmail.com>
讨论至:
Distutils-SIG 邮件列表
状态:
最终版
类型:
流程
主题:
打包
创建日期:
2014年5月12日
发布历史:
2014年5月14日,2014年6月5日,2014年10月3日,2014年10月13日,2015年8月26日
取代:
438
决议:
Distutils-SIG 消息

目录

摘要

本 PEP 提议废弃和移除对 PyPI 外部托管文件的支持,以及废弃和移除 PEP 438 添加的功能,特别是用于分类不同类型链接的 rel 信息和指示 API 版本的 meta-tag。

基本原理

历史上,PyPI 没有任何托管文件的方法,也没有任何自动检索可安装文件的方法,它更专注于提供一个中心名称注册表,以防止命名冲突,并作为发现项目使用的手段。随着时间的推移,setuptools 开始抓取这些面向人类的页面以及从这些页面链接的页面,寻找可以自动下载和安装的东西。最终,这演变为“简单”API,它使用类似的 URL 结构,但删除了所有无关的链接和信息,以提高 API 的效率。此外,PyPI 增加了项目直接上传发布文件到 PyPI 的能力,使 PyPI 除了作为索引之外,还能作为仓库。

这赋予 PyPI 在 Python 生态系统中两个同样重要的角色:作为索引以方便发现 Python 项目,以及作为中心仓库以方便托管、下载和安装 Python 项目。由于 PyPI 的历史和其经历的有机增长,这两个角色之间的界限模糊不清,这种模糊性给这两个角色的最终用户带来了困惑,进而导致了试图以不同能力使用 PyPI 的人之间产生不满,最常见的是当最终用户希望将 PyPI 用作仓库而作者只想将 PyPI 纯粹用作索引时。

这种困惑归结为项目最终用户没有意识到一个项目是否托管在 PyPI 上,或者它是否依赖于外部服务。这通常在外部服务宕机但 PyPI 没有宕机时表现出来。人们会看到 PyPI 运行正常,其他项目也运行正常,但这个特定的项目不正常。他们常常没有意识到需要联系谁来解决这个问题,或者他们的补救措施是什么。

PEP 438 试图通过允许项目明确声明是否使用仓库功能来解决这个问题,如果不是,它让安装程序将找到的链接分类为“内部”、“可验证外部”或“不可验证外部”。PEP 438 已被接受并实现在 pip 1.4 中(2013年7月23日发布),最终过渡在 pip 1.5 中实现(2014年1月2日发布)。

PEP 438 成功地促使更多人利用 PyPI 的仓库功能,考虑到为许多人提供加速的全球 CDN 支持的 PyPI,这完全是件好事,但它通过引入一个新的困惑和痛苦点给最终用户和作者都带来了问题。

通过转向使用明确的多个仓库,我们可以使这两个角色之间的界限更加明确,并消除当前处理不希望将 PyPI 用作仓库的人的实现所造成的“隐藏”意外。

主要用户体验预期

  1. 在系统、用户或虚拟环境级别进行适当配置后,轻松地让外部托管“正常工作”。
  2. 从用户体验中(安装和发布包时)消除所有令人困惑的“可验证外部”和“不可验证外部”的区别。
  3. PyPI 的仓库方面应该成为 仅仅 是默认的包托管位置(即,在大多数客户端工具的默认配置中,它是唯一被视为选择退出而不是选择加入的)。除此之外,在 PyPI 上托管不应提供比托管自己的包仓库更佳的用户体验。
  4. 在提供默认行为的同时,确保其安全地抵御大多数低于国家级对手的攻击者。

为什么需要额外的仓库?

pip 和 easy_install/setuptools 这两个常用的安装工具都支持额外的文件搜索位置概念,并已支持多年。这意味着无需“分阶段”引入新的标志或概念,并且从 PyPI 以外的仓库安装项目的解决方案将起作用,无论最终用户的安装程序有多旧(在合理范围内)。这个概念不仅在 Python 工具中存在了一段时间,而且它是一个跨语言存在的概念,甚至扩展到操作系统级别,操作系统包工具几乎普遍使用多仓库支持,这使得某人很可能已经熟悉这个概念。

此外,多仓库方法是一个在允许希望被包含在 PyPI 的索引部分但不想利用 PyPI 的仓库部分的狭窄范围之外也很有用的概念。这包括公司可能希望托管包含其内部包的仓库,或者项目可能希望拥有多个发布“通道”,例如 alpha、beta、发布候选和最终发布。这也可以用于希望托管无法上传到 PyPI 的文件的项目,例如数千兆字节的数据文件,或者至少目前是 Linux Wheels。

为什么不是 PEP 438 或类似方案?

虽然 pip 和 setuptools 中额外的搜索位置支持已经存在了相当长一段时间,但 PEP 438 的支持仅在 pip 1.4 版本中存在,并且尚未在 setuptools 中实现。PEP 438 的设计确实意味着即使使用较旧的安装程序,用户仍然受益于不需要外部文件的项目,但是对于 确实 需要外部文件的项目,用户仍然默默地被提供可能不可靠甚至更糟糕的不安全文件进行下载。这个系统也是 Python 独有的,因为它源于 PyPI 的历史,这意味着几乎可以肯定,这个概念对大多数(如果不是所有)用户来说是陌生的,直到他们在尝试使用 Python 工具链时遇到它。

此外,PEP 438 提出的分类系统在实践中被证明对最终用户来说极其混乱,以至于本 PEP 的立场是,现状完全不可持续。使用此系统的常见用户模式是尝试安装项目,可能会收到错误消息(或者如果项目曾经上传到 PyPI 但后来切换而未删除旧文件,则可能不会),看到错误消息建议 --allow-external,他们重新发出命令并添加该标志,很可能收到另一条错误消息,看到这次错误消息建议也添加 --allow-unverified,然后再次发出命令第三次,这次终于得到了他们想要安装的东西。

这种用户体验失败有几个原因。

  1. 如果 pip 能够为 Simple API 上的项目找到任何文件,它将直接使用该文件而不是尝试寻找更多文件。这通常是正确的做法,因为尝试寻找更多文件将消除 PEP 438 的大部分好处。这意味着如果项目 曾经 上传过与用户请求安装的文件匹配的文件,无论它有多旧,都将被使用。
  2. PEP 438 做了一个隐含的假设,即大多数项目要么将自己上传到 PyPI,要么将自己更新为直接链接到发布文件。虽然大量项目最终确实决定上传到 PyPI,但其中一些只是因为 PEP 438 的用户体验太差,以至于他们感到被迫这样做。更令人担忧的是,很少有项目选择直接安全地链接到文件,相反,它们仍然只是链接到必须抓取才能找到实际文件的页面,从而使安全变体 (--allow-external) 基本上无用。
  3. 即使作者希望直接链接到他们的文件,安全地这样做也并非显而易见。它要求在 URL 的哈希中包含一个 MD5 哈希(出于历史原因)。如果他们不包含这个,那么他们的文件将被视为“未验证”。
  4. PEP 438 采取了以安全为中心的观点,并禁止对未验证项目进行任何形式的全局选择加入。虽然这通常是一件好事,但它会创建极其冗长和重复的命令调用,例如
    $ pip install --allow-external myproject --allow-unverified myproject myproject
    $ pip install --allow-all-external --allow-unverified myproject myproject
    

多仓库/索引支持

安装程序 应该 实现或继续提供,将安装程序指向多个 URL 位置的能力。用户指示他们希望使用额外位置的确切机制留给每个单独的实现。

此外,当使用多个仓库时,发现安装候选的机制也由每个单独的实现决定,但是一旦配置好,实现不应该仅仅因为它不是默认仓库而劝阻、警告或以其他方式对使用仓库投以负面眼光。

目前 pip 和 setuptools 都通过使用它们能从任一仓库找到的最佳安装候选来实现多仓库支持,本质上将其视为一个大型仓库。

安装程序 还应该实现某种机制来移除或禁用默认仓库的使用。实现方式的具体细节由每个单独的实现决定。

安装程序 也应该实现某种机制,用于白名单和黑名单用户希望从特定仓库安装的项目。实现方式的具体细节由每个单独的实现决定。

Python 打包指南 必须更新,增加一个详细说明设置自己的仓库的选项的部分,以便任何希望将来不在 PyPI 上托管的项目都可以参考该文档。这应该包括建议依赖于托管自己仓库的项目在其项目描述中记录如何安装其项目。

更改摘要

仓库端

  1. 废弃并移除 PEP 438 定义的托管模式。
  2. 将简单 API 限制为仅列出仓库中包含的文件。

客户端

  1. 实现多仓库支持。
  2. 实现某种机制来移除/禁用默认仓库。
  3. 废弃 / 移除 PEP 438

影响

为了确定影响,我们查看了所有使用类似于 pip 和 setuptools 搜索 PyPI 方法的项目,并搜索了 PyPI 上可用、从 PyPI 安全链接、从 PyPI 不安全链接以及最后在 PyPI 外部不安全可用的所有文件。当同一文件在多个位置找到时,会进行去重,并根据以下偏好仅在一个位置计数:PyPI > 安全地不在 PyPI > 不安全地不在 PyPI。这给了我们最广泛的影响定义,这意味着该项目的任何单个文件可能不再默认可见,但是该文件可能已经有几年历史,或者它可能是一个二进制文件,而 PyPI 上有 sdist 可用。这意味着 实际 影响可能要小得多,但为了避免错误计数,我们采用了最广泛的定义。

在撰写本文时,PyPI 上托管着 65,232 个项目,其中 59 个项目依赖于安全托管在 PyPI 外部的外部文件,931 个项目依赖于不安全托管在 PyPI 外部的外部文件。这表明 1.5% 的项目将以某种方式受到此更改的影响,而 98.5% 的项目将继续像往常一样运行。此外,只有 5% 的受影响项目使用 PEP 438 提供的功能在 PyPI 外部安全托管,而 95% 的项目通过中间人攻击将其用户暴露于远程代码执行。

常见问题

由于 我无法在 PyPI 上托管我的项目,我该怎么办?

首先,您应该决定 是 PyPI 固有的东西,还是 PyPI 可以发展一个功能来为您解决 。如果 PyPI 可以添加一个功能使您能够在 PyPI 上托管您的项目,那么您应该提出该功能。然而,如果 是 PyPI 固有的东西,例如希望保持对您自己的文件的控制,那么您应该设置自己的包仓库并在您的项目描述中指导您的用户将其添加到他们选择的安装程序将使用的仓库列表中。

我的用户对本 PEP 的体验比以前更差,我该如何解释?

这个答案的一部分将针对每个项目而异,您需要向您的用户解释是什么让您决定将项目托管在自己的仓库中,而不是利用他们安装程序默认仓库列表中的仓库。然而,这个答案的一部分还将解释,以前透明地包含外部链接的行为既是安全隐患(考虑到在大多数情况下,它允许 MITM 在最终用户机器上执行任意 Python 代码),也是可靠性问题,并且 PEP 438 试图通过让他们明确选择加入来解决这个问题,但 PEP 438 带来了一系列严重的可用性问题。PEP 470 代表了模型的简化,使其成为许多用户熟悉的模型,这在 Linux 发行版中很常见。

切换到仓库结构会破坏我的工作流或不被我的主机允许?

有许多廉价或免费的主机乐意支持仓库所需的一切。特别是,您实际上不需要将文件上传到不同的位置,只要您可以生成一个具有正确结构并指向您的文件实际位置的主机。许多这些主机使用共享域名提供免费 HTTPS,并且可以从 StartSSL 或在不久的将来从 LetsEncrypt 获得免费 HTTPS 证书,或者可以从任何数量的提供商那里廉价获得。

你为什么不提供

这里的答案将取决于 是什么,但答案通常是以下之一:

  • 我们以前没有想到过,也没人提出过。
  • 我们对 没有足够的经验来正确设计解决方案,欢迎领域专家帮助我们提供。
  • 我们是一个开源项目,还没有人决定自愿设计和实现

提出额外功能的 PEP 总是受欢迎的,但它们需要有时间和专业知识的人来准确设计 。这个特定的 PEP 旨在让我们达到一个境地,即 PyPI 的功能直截了当,具有易于理解的基线,类似于现有的模型,例如 Linux 发行版仓库。

如果我无论如何都运行自己的仓库,为什么我还要在 PyPI 上注册?

PyPI 为 Python 生态系统提供了两个关键功能。其中之一是作为实际由 pip 或其他包管理器下载和安装的文件的中央仓库,这也是本 PEP 关注的功能,如果您正在运行自己的仓库,您将替换这个功能。然而,它还提供了一个中央注册表,用于管理谁拥有什么名称以防止命名冲突,可以将其视为 Python 包的 DNS。除了确保名称以先到先得的方式分配之外,它还为用户提供了一个查找和发现新项目的单一场所。因此,简单的答案是,您仍然应该在 PyPI 上注册您的项目,以避免命名冲突并确保人们仍然可以轻松发现您的项目。

被拒绝的提案

允许更容易地发现外部托管的索引

本 PEP 的早期版本包含一项新功能,添加到 PyPI 和安装程序中,允许项目作者在 PyPI 中输入 URL 列表,指示安装程序忽略上传到 PyPI 的任何文件,而是返回错误,告知最终用户这些额外的 URL,他们可以将其添加到安装程序中以使安装工作。

此功能已从 PEP 的范围中删除,因为它被证明太难以开发一个避免与 PEP 438 解决方案中造成许多问题的用户体验问题类似的解决方案。如果需要,未来的 PEP 可以重新审视这个想法。

保持当前分类系统但调整选项

本 PEP 拒绝了几个相关的提案,这些提案试图修复当前系统的一些可用性问题,但同时仍然保留 PEP 438 的核心思想。

这包括:

  • 默认允许安全外部托管文件,但禁止不安全托管。
  • 默认禁止安全外部托管文件,只提供一个全局标志来启用它们,但禁止不安全托管。
  • 继续沿着 PEP 438 建议的路径,移除不安全外部托管的选项,但继续允许安全外部托管的选项。

这些提案被拒绝的原因是:

  • PEP 438 中引入的分类系统是 PyPI 独有的概念,即使在 Python 打包的上下文中也无法普遍适用。添加额外的概念是有代价的。
  • 分类系统本身难以解释,并且预先确定项目需要哪种分类链接需要检查项目的 /simple/<project>/ 页面,以及可能从该页面链接的任何 URL。
  • 能够在外部托管同时仍可被自动发现,这在很大程度上是一个历史遗留问题,它带来了相当大的痛苦和复杂性,而回报却很少。
  • 由于需要进行隐式链接抓取,安装程序优化或清理用户界面的能力有限。这延伸到 --allow-* 选项,以及无法确定链接是否预期会失败。
  • 该机制在启用选项时会采取非常宽泛的做法,而 PEP 438 试图通过每个包选项来限制这一点。然而,一个长期存在的项目通常会在其简单索引中列出几个不同的 URL。其中至少一个不再受项目控制的情况并不少见。虽然未注册的域名在大多数时候相对无害,但 pip 会在每个发现阶段继续尝试从中安装。这意味着攻击者只需查看依赖不安全外部 URL 的项目并注册过期域名即可攻击用户。

来源:https://github.com/python/peps/blob/main/peps/pep-0470.rst

最后修改:2025-02-01 08:59:27 GMT