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

Python 增強提案

PEP 759 – 外部 Wheel 託管

作者:
Barry Warsaw <barry at python.org>, Emma Harper Smith <emma at python.org>
PEP 代理人:
Donald Stufft <donald at python.org>
討論至:
Discourse 帖子
狀態:
已撤回
型別:
標準跟蹤
主題:
打包
建立日期:
2024 年 10 月 1 日
釋出歷史:
2024 年 10 月 10 日, 2025 年 1 月 31 日
決議:
2025 年 1 月 31 日

目錄

摘要

此 PEP 提出了一種機制,允許託管在 pypi.org 上的專案在 PyPI 以外的外部網站上安全地託管 wheel 構件。此 PEP 明確提議託管專案、包或其元資料。此功能已透過外部託管獨立包索引可用。由於此 PEP 僅提供專案自定義特定已釋出 wheel 構件下載 URL 的機制,因此像 pipuv 等常見安裝工具已實現的依賴項解析無需更改。

此 PEP 定義了在此上下文中“安全”的含義,以及一種名為 .rim 檔案的新包上傳檔案格式。它定義了 .rim 檔案如何影響包的 Simple Repository API 在 HTML 和 JSON 格式下的元資料,以及傳統 Wheel 如何輕鬆轉換為 .rim 檔案。

PEP 已撤銷

此 PEP 已由作者於 2025 年 1 月 31 日撤銷。我們對討論串中的情緒的理解是,儘管此 PEP 試圖解決的問題是有效的,但大多數人會傾向於不同的方法。具體來說,我們認為大多數使用者更希望能夠更好地控制指定多個索引、這些索引如何互操作以及這些索引的優先順序和信任斷言。例如,像 PEP 766 這樣的解決方案可能提供了更好的前進方向。現有的臨時措施(例如 PyPI 限制增加請求和 “wheel-stub” 方法)在間歇期內已經足夠(即使不是理想的)。作者感謝所有為建設性討論做出貢獻的人,特別是那些公開和私下表示支援此 PEP 的人。

基本原理

Python 包索引,託管在 https://pypi.org,對上傳構件檔案大小(100 MiB)和總專案大小(10 GiB)施加了 預設限制。大多數專案在專案生命週期內,透過多年的上傳,都可以舒適地保持在這些限制內。少數專案遇到了這些限制,並獲得了檔案大小和專案大小的例外,允許它們繼續上傳新版本,而無需採取更極端的措施,例如刪除可能仍在使用中的檔案(例如透過版本固定)。

一個相關的解決方案是 “wheel stub” 方法,它提供了 PyPI 和外部第三方包索引之間的間接連結,在那裡可以避免此類限制。Wheel stubs 是 源發行版(也稱為“sdists”),它使用 PEP 517 構建後端,該後端不將原始碼轉換為二進位制 wheel,而是執行一些邏輯來計算現有、外部託管 wheel 的 URL 以下載和安裝。這種方法有效,但它模糊了 PyPI、sdist 和外部託管 wheel 之間的聯絡,因為沒有辦法將此資訊呈現給 pip 或其他此類工具。

歷史背景

2013 年,PEP 438 提出了一種“向後相容的兩階段過渡過程”,以修改 PyPI 上釋出檔案託管的多個方面。正如本 PEP 所述,PyPI 最初僅支援專案和釋出註冊,而不允許構件檔案託管。因此,大多數專案都在其他地方託管了釋出檔案構件。後來添加了構件託管,但外部和 PyPI 託管檔案的混合導致了廣泛的可用性和潛在的安全問題。PEP 438 旨在提供多種設施來支援外部託管,同時推廣 PyPI 優先託管。

PEP 438 很複雜,有三種不同的“託管模式”,簡單 HTML 索引頁中的 rel 元資料用於指示託管位置,以及影響 PyPI 和安裝程式的兩階段過渡計劃。PEP 438 最終在 2015 年被 PEP 470 撤銷,後者承認 PEP 438 確實成功地...

吸引了更多人使用 PyPI 的儲存庫功能,這總之是一件好事,因為 PyPI 驅動的全球 CDN 為許多人提供了加速 […]

PEP 470 提倡使用顯式的多個儲存庫,而不是外部託管,提供完整的包索引和構件託管,並透過安裝程式工具支援,例如 pip install --extra-index-url 允許 pip essentially 將多個儲存庫視為 一個單一的全域性儲存庫 進行包安裝解析。由於多年來這一直是首選的標準,因此所有 Python 包安裝工具都支援查詢多個索引來進行依賴項解析。

多個索引的問題

那麼,為什麼此 PEP 會提議允許更有限形式的外部託管,以及此提案如何避免 PEP 470 中記錄的問題?

整合多個索引所帶來的一個眾所周知的問題是 依賴項混淆攻擊,Python可能特別容易受到此類攻擊,因為 pip install 用於解析包依賴項和首選版本的演算法。 uv 工具透過支援附加的 索引策略 選項來解決此問題,使用者可以選擇,例如,一個與 pip 相容的策略,以及一個更有限的策略,以防止此類依賴項混淆攻擊。

PEP 708 提供了關於依賴項混淆攻擊的額外背景,並採取了不同的方法來防止它們。其核心是,PEP 708 允許儲存庫所有者指明專案跨越不同的儲存庫,這使得安裝程式能夠確定在跨多個儲存庫組合時如何處理全域性包名稱空間。PEP 708 已暫定接受,但需滿足 PEP 708 中概述的幾項必要條件,其中一些條件可能具有不確定的未來。正如 PEP 708 本身所說,這本身並不能解決依賴項混淆攻擊,但它是提供足夠資訊給安裝程式的途徑之一,以幫助最大程度地減少這些攻擊。

雖然完全獨立的包索引仍然有有效的用例(例如,為 GPU 提供更豐富的平臺支援,直到接受了一個完整的 變體提議),但此 PEP 採取了一種不同的、更簡單的方法,並且不替換任何現有的、提議的或已批准的包索引協作規範。

此 PEP 還保留了 PyPI 的核心目的,並允許它繼續成為所有 Python 包的傳統、規範、中心化的索引。

解決 PyPI 的限制

此提案還解決了 PyPI 施加的大小限制問題,其中存在 100 MiB 的預設構件大小限制和 10 GiB 的預設總體專案大小限制。大多數包和構件可以輕鬆地在這些限制內,即使是包含多種平臺二進位制擴充套件模組的包。一小部分重要的包會經常超出這些限制,需要它們提交 PyPI 例外請求支援票。獲得此類例外的解決並不一定困難,但這是一個特殊流程,需要一些時間來解決,而且授予此類例外的標準並未得到充分記錄。

降低操作複雜性

設定和維護整個包索引可能是一個複雜的操作解決方案,既耗時又耗資源。如果此類索引的主要目的是僅僅為了避免檔案大小限制,那麼尤其如此。外部索引方法還對外部索引上專案的消費者施加了棘手的使用者體驗,要求他們理解 --external-index-url 等 CLI 選項的工作原理,以及這些標誌的安全含義。對於大型 Wheel 包的生產者和消費者來說,只需設定和維護一個簡單的 Web 伺服器會更容易,該伺服器能夠提供單個檔案,其 API 不比 HTTP GET 複雜。這種介面也很容易快取或放在 CDN 後面。簡單的 HTTP 伺服器在安全審計方面也容易得多,更容易代理,通常執行、支援和維護所需的資源也少得多。即使是像 Amazon S3 這樣的服務也可以用來託管外部 Wheel。

此 PEP 提出了一種偏向此類操作簡單性的方法。

規範

定義了一種新型可上傳檔案,稱為“RIM”(即 .rim)或“遠端可安裝元資料”檔案。該名稱令人聯想到去掉輪胎的 Wheel 的影像,並強調 .rim 檔案很容易從 .whl 檔案派生。將 .whl 轉換為 .rim 的過程在下面概述。檔名格式與 Wheel 檔案命名格式 規範完全匹配,只是 RIM 檔案使用 .rim 字尾。這意味著用於區分 .whl 檔案的所有標籤也區分了不同的 .rim 檔案,因此可以在依賴項解析步驟中像今天的 .whl 檔案一樣使用。在這方面,.whl.rim 檔案是可互換的。

一個 .rim 檔案內容與 .whl 檔案幾乎相同,但 .rim 檔案必須僅包含 Wheel 中的 .dist-info 目錄。在 .rim zip 檔案中不允許包含任何其他頂級檔案或目錄。 .dist-info 目錄必須包含一個額外檔案,除了允許.whl 檔案的 .dist-info 目錄中的檔案之外:一個名為 EXTERNAL-HOSTING.json 的檔案。

這是一個 JSON 檔案,包含以下鍵

version
這是檔案格式版本,對於此 PEP,必須1.0
owner
必須是此外部託管檔案的 PyPI 組織所有者的名稱,原因將在下文詳細介紹
uri
這是一個單一 URL,命名了託管在外部網站上的物理 .whl 檔案的位置。此 URL必須使用 https 方案。
size
這是一個整數值,描述了遠端主機上物理 .whl 檔案的大小(以位元組為單位)。
hashes
這是一個字典,格式如PEP 694中所述,用於捕獲物理 .whl 檔案的PEP 694,具有與該 PEP 中提出的相同約束。由於這些雜湊值一旦上傳到 PyPI 就不可變,因此它們作為關鍵驗證,以確保外部託管的 wheel 沒有損壞或被洩露。

RIM 檔案的影響

一個 .rim 檔案的唯一影響是更改 HTML 和 JSON 介面中 simple repository API 中 Wheel 構件的下載 URL。在包版本的 HTML 頁面中,href 屬性必須uri 鍵的值,包括一個 #<hashname>=<hashvalue> 片段。此雜湊片段必須簽名 Wheel 檔案格式.dist-info/RECORD 檔案中描述的格式完全相同。雜湊演算法和編碼的選擇規則與此處相同。

同樣,在JSON 響應中,指向下載檔案的 url 鍵必須是 uri 鍵的值,並且 hashes 字典必須包含值,這些值從上面的 hashes 字典填充。

在所有其他方面,相容的包索引應將 .rim 檔案視為與 .whl 檔案相同,但有一些其他小的例外情況如下所述。例如,.rim 檔案可以像任何 .whl 檔案一樣被刪除和撤銷(PEP 592),具有完全相同的語義(即刪除是永久性的)。當一個 .rim 被刪除時,索引不得允許匹配的 .whl.rim 檔案被(重新)上傳。

可用性順序

在上傳相應的 .rim 檔案到 PyPI 之前,外部託管的 Wheel必須可用,否則會引入釋出競爭條件,儘管對於上傳到 PEP 694 暫緩釋出的 .rim 檔案,此要求可能會放寬。

Wheel 可以覆蓋 RIM

索引必須拒絕 .rim 檔案,如果存在具有完全相同檔名標籤的匹配 .whl 檔案。但是,只要該 .rim 檔案未被刪除或撤銷,索引可以接受一個 .whl 檔案,如果存在匹配的 .rim 檔案。這允許上傳者用索引託管的 Wheel 檔案替換外部託管的 Wheel 檔案,但反之則禁止。由於預設是將 Wheel 託管在包含包元資料的同一個包索引上,因此不允許在上傳後“降級”現有的 Wheel 檔案。當一個 .whl 替換一個 .rim 時,索引必須使用其自己的託管檔案服務提供包的下載 URL。上傳覆蓋性 .whl 檔案時,包索引必須驗證現有 .rim 檔案中的雜湊值,並且這些雜湊值必須匹配,否則覆蓋性上傳必須被拒絕。

PyPI API 升級不必要

很可能更改是向後相容的,以至於不需要增加 PyPI 儲存庫版本。由於 .rim 檔案本質上只是對上傳 API 的更改,包解析器和包安裝程式可以繼續使用它們一直支援的 API。

外部託管的彈性

導致 PEP 438 在 PEP 470 中被撤銷的關鍵顧慮之一是,當外部索引消失時,使用者可能會感到困惑。來自 PEP 470

這種困惑在於專案終端使用者沒有意識到專案是託管在 PyPI 上還是依賴於外部服務。這通常在外部服務停止而 PyPI 沒有停止時顯現出來。人們會看到 PyPI 工作正常,其他專案也工作正常,但這個特定的專案不行。他們通常不知道他們需要聯絡誰來解決這個問題,或者他們的補救步驟是什麼。

雖然此 PEP 沒有直接解決外部 Wheel 託管服務停止執行的問題,但採取了幾項保護措施,以大大減輕 PyPI 管理員的潛在負擔。

因此,此 PEP 提議:

  • 僅允許由組織賬戶擁有的包進行外部 Wheel 託管。外部託管是組織範圍內的設定。
  • 組織賬戶不會自動獲得外部託管 Wheel 的能力;此功能必須由 PyPI 管理員酌情明確啟用。由於這不是一個常見的請求,我們不認為其開銷會像PEP 541 解決方案、賬戶恢復請求,甚至檔案/專案大小增加請求那樣繁重。外部託管請求將以與這些請求相同的方式處理,即透過 PyPI GitHub 支援跟蹤器
  • 請求外部 Wheel 託管的組織賬戶必須註冊自己的支援聯絡 URI,無論是用於聯絡電子郵件地址的 mailto URI,還是組織支援跟蹤器的 URL。對於不使用外部 Wheel 檔案託管的組織,此類聯絡 URI 是可選的。

結合 EXTERNAL-HOSTING.json 檔案中的 owner 鍵,這使得安裝工具能夠明確地將任何下載錯誤從 PyPI 支援管理員重定向到組織的(支援)管理員。

雖然儲存和檢索此組織支援 URL 的確切機制將單獨定義,但舉例來說,假設一個名為 foo 的包在 `https://foo.example.com <https://foo.example.com>`__ 上外部託管 Wheel 檔案,並且該主機變得不可達。當安裝工具嘗試下載和安裝 foo 包的 Wheel 時,下載步驟將失敗。然後,安裝工具將能夠查詢 PyPI,向終端使用者提供有用的錯誤訊息

  • 安裝程式下載 .rim 檔案,並讀取 .rim zip 檔案內的 EXTERNAL-HOSTING.json 檔案中的 owner 鍵。
  • 安裝程式查詢 PyPI 以獲取外部託管 Wheel 的組織所有者的支援 URI。
  • 然後將顯示一條資訊性錯誤訊息,例如:
    無法下載外部託管的 Wheel 檔案 foo-....whl。請聯絡 support@foo.example.com 獲取幫助。請勿將此問題報告給 PyPI 管理員。

解除安裝 Wheel

通常很容易從現有的 .whl 檔案生成 .rim 檔案。這可以透過 PEP 518 構建後端附加命令列選項,或一個單獨的工具來實現,該工具以 .whl 檔案作為輸入並建立相關的 .rim 檔案。為了完成類比,將 .whl 轉換為 .rim 的過程稱為“解除安裝”。此類工具將執行的步驟是

  • 接受源 .whl 檔案、包的組織所有者、.whl 將要託管的 URL 以及用於報告下載問題的支援 URI 作為輸入。這些實際上可以包含在 pyproject.toml 檔案中,但這超出了本 PEP 的範圍。
  • 解壓 .whl 並建立 .rim zip 存檔。
  • .rim 檔案中省略.dist-info 目錄為根的 .whl 中的任何路徑。
  • 計算源 .whl 檔案的雜湊值。
  • 將包含上述 JSON 鍵和值的 EXTERNAL-HOSTING.json 檔案新增到 .rim 存檔中。

工具的更改

理論上,安裝程式工具不需要任何更改,因為當它們識別出要下載和安裝的 Wheel 時,它們只需查詢 PyPI 的 Simple API 返回的下載 URL。但實際上,像 pipuv 這樣的工具可能對允許下載的託管伺服器有限制,例如 PyPI 自帶的 pythonhosted.org 域名。

在這種情況下,這些工具將需要放寬這些限制,但具體策略由安裝程式工具自行決定。可以實現任何數量的方法,例如下載 .rim 檔案並驗證 EXTERNAL-HOSTING.json 元資料,或者簡單地信任具有匹配校驗和的 Wheel 的外部下載。它們還可以查詢 PyPI 以獲取專案的組織所有者和支援 URI,然後再信任下載。它們可以警告使用者何時遇到外部託管的 Wheel 檔案,和/或要求使用命令列選項來啟用其他下載主機。任何這些驗證策略都可以透過配置檔案選擇。

安裝程式工具在外部託管的 Wheel 檔案無法下載時(例如,因為主機不可達)可能還需要提供更好的錯誤訊息。如上所述,這些工具可以從 PyPI 查詢足夠的元資料,以提供清晰且區別的錯誤訊息,將使用者指向包的外部託管支援電子郵件或問題跟蹤器。

外部託管服務的約束

以下約束條件可確保可靠且相容的外部 Wheel 託管服務

  • 外部 Wheel必須透過 HTTPS 提供服務,並使用由 Mozilla 的根證書儲存簽名的證書。這確保了與 pipuv 的相容性。在撰寫本文時,Python 3.10 或更高版本上的 pip 24.2 除了第三方 certifi Python 包提供的 Mozilla 儲存外,還使用系統證書儲存。 uv 使用 webpki-roots crate 提供的 Mozilla 儲存,但不使用系統儲存,除非提供了 --native-tls 標誌 [1]PyPI 管理員可能會在將來修改此要求,但與流行安裝程式的相容性不會受到損害。
  • 外部 Wheel 主機使用內容分發網路(CDN),就像 PyPI 一樣。
  • 外部 Wheel 主機必須承諾為其託管的所有 Wheel 提供穩定的 URL。
  • 外部託管的 Wheel不得從外部 Wheel 主機中刪除,除非相應的 .rim 檔案首先從 PyPI 中刪除,並且不得刪除已撤銷版本的外部 Wheel。
  • 外部 Wheel 主機必須支援 HTTP Range 請求
  • 外部 Wheel 主機支援 HTTP/2 協議。

安全

本提案中描述的幾個因素應能緩解外部託管 Wheel 的安全問題,例如:

  • Wheel 檔案校驗和必須包含在 .rim 檔案中,並且一旦上傳就無法更改。由於儲存在 PyPI 上的校驗和是不可變的且必需的,因此即使所有者組織失去了對其託管域的控制,也無法偽造外部 Wheel 檔案。
  • 外部託管的 Wheel必須透過 HTTPS 提供服務。
  • 為了提供外部託管的 Wheel,組織必須獲得 PyPI 管理員的批准。

當用戶發現 PyPI 託管專案中的惡意軟體或漏洞時,他們現在可以使用 PyPI 上的惡意軟體報告工具,如本部落格文章中所述。相同的流程可用於報告外部託管 Wheel 中的安全問題,並且應使用相同的補救流程。此外,由於啟用了外部託管的組織必須提供支援聯絡 URI,因此在某些情況下可以使用該 URI 向託管組織報告安全問題。此類組織報告對於惡意軟體可能沒有意義,但對於報告外部託管 Wheel 中的安全漏洞來說,可能確實是一種非常有用的方式。

被拒絕的想法

已考慮並拒絕了幾種想法。

  • 要求對外部託管的 Wheel 檔案進行數字簽名,無論是附加的還是除雜湊值之外的。我們認為這是不必要的,因為校驗和要求應該足以驗證 PyPI 上 Wheel 的元資料是否與下載的 Wheel 完全匹配。金鑰管理的額外複雜性會抵消此類數字簽名可能帶來的任何額外好處。
  • .rim 檔案上傳進行雜湊驗證。PyPI可以在接受上傳之前驗證上傳的 .rim 檔案中的雜湊值是否與外部託管的 Wheel 匹配,但這需要下載外部 Wheel 並執行校驗和,這也意味著在下載和驗證此外部 .whl 檔案之前,無法接受 .rim 檔案的上傳。這會增加 PyPI 的頻寬並減慢上傳查詢速度,儘管 PEP 694 草稿上傳可能潛在地緩解了這些擔憂。儘管如此,收益可能不值得額外的複雜性。
  • 索引定期驗證下載 URL。PyPI 可以嘗試定期確保外部 Wheel 主機或外部 .whl 檔案本身仍然可用,例如透過HTTP HEAD 請求。這可能是多餘的,並且如果響應[2]中不包含檔案的校驗和,可能不會帶來太多額外的好處。
  • 此 PEP 允許組織提供備用下載主機,這樣在主主機出現故障時,次級主機就可以使用。我們認為基於 DNS 的複製是一種更好、更廣為人知且可能更具彈性的技術。
  • .rim 檔案替換。雖然允許 .whl 檔案替換現有的 .rim 檔案(只要 a) .rim 檔案未被刪除或撤銷,b) 校驗和匹配),但我們不允許用 .rim 檔案替換 .whl 檔案,也不允許 .rim 檔案覆蓋現有的 .rim 檔案。後者可能是一種更改外部託管 .whl 的託管 URL 的技術;然而,我們認為這不是一個好主意。如上所述,“修復”外部主機 URL 有其他方法,我們不希望鼓勵大規模重新上傳現有的 .rim 檔案。

腳註


來源:https://github.com/python/peps/blob/main/peps/pep-0759.rst

最後修改:2025-01-31 18:51:56 GMT