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

Python 增強提案 (Python Enhancement Proposals)

PEP 752 – 套件儲存庫的隱含命名空間

作者:
Ofek Lev <ofekmeister at gmail.com>, Jarek Potiuk <potiuk at apache.org>
贊助人:
Barry Warsaw <barry at python.org>
PEP 委託人:
Dustin Ingram <di at python.org>
討論於:
Discourse 討論串
狀態:
草案
類型:
標準軌跡 (Standards Track)
主題:
套件封裝 (Packaging)
建立日期:
2024年8月13日
公告歷史:
2024年8月18日, 2024年9月7日

目錄

摘要

本 PEP 規定了一種讓組織為未來上傳預留套件名稱前綴的方法。

「命名空間是一個絕妙的主意 —— 讓我們多做一些吧!」 - PEP 20

動機

目前的生態系統缺乏一種讓擁有大量套件的專案標記其已驗證所有權模式的方法。此類專案分為兩類。

第一類是希望完全控制其命名空間的專案 [1]。以下是一些範例:

  • Amazon、Google 和 Microsoft 等大型雲端供應商為每個功能的對應套件都有一個共同的前綴 [3]。例如,Google 大多數套件的前綴都是 google-cloud-,例如 google-cloud-compute 用於 使用虛擬機
  • OpenTelemetry 是一個用於可觀察性的開放標準,具有 官方套件 作為核心 API 和 SDK,並有 貢獻套件 (contrib packages) 用於從各種來源收集資料。所有套件均以前綴 opentelemetry- 開頭,並帶有 opentelemetry-<component>-<name>- 形式的子前綴。這些貢獻套件位於一個中央儲存庫中,且只有它們具備發布能力。
  • Apache Airflow 是一個用於以程式設計方式編寫、排程和監控工作流程的平台。它有提供者 (providers),其中每個提供者套件的前綴均為 apache-airflow-providers-

第二類是希望分享其命名空間的專案 [2],即某些套件由官方維護,同時鼓勵第三方開發者透過發布自己的套件來參與。以下是一些範例:

此類專案極易受到名稱搶佔 (name-squatting) 攻擊,這最終可能導致 相依性混淆 (dependency confusion)

例如,假設發布了一個新產品,而監控它將會非常有價值。我們可以合理假設 Datadog 最終會將其作為官方整合進行支援。由於路線圖優先順序和實作所需的時間,交付這樣的整合需要相當長的時間。由於不可能預留每一個潛在的套件名稱,攻擊者可能會在此期間建立一個看起來合法但會在執行時執行惡意程式碼的套件。使用者不僅更容易安裝這類套件,這樣做還會玷汙整個專案的觀感。

雖然 PEP 708 嘗試解決此攻擊向量,但它專門針對在相依性解析期間考慮多個儲存庫的情況,並未針對上述用例提供任何保護。

命名空間也將大幅減少 誤植搶註 (typosquatting) 的發生,因為拼字錯誤必須發生在前綴本身,而該前綴是 標準化 的,並且很可能是一個簡短、眾所周知的識別碼,例如 aws-。近年來,誤植搶註已成為一種流行的攻擊向量 [4]

PyPI 目前用於對抗誤植搶註的 保護機制 是將相似字元標準化,但這對於這些用例來說是不夠的。

命名空間可以解決的另一個問題是,在遵循約定命名模式後選擇新套件名稱的問題。通常(例如 Apache Airflow 就是這種情況),在決定建立新套件之前會進行公開討論。該決定基於約定的名稱並遵循現有套件的模式。如果在討論過程中考慮了更多的套件名稱,則必須在討論公開之前透過 PyPI 介面保留所有名稱,否則名稱可能會被其他使用者搶走。正如相關 討論 中所述,這種情況過去曾發生過。

原理

其他套件生態系統通常透過兩種方法之一解決此問題:最小化或最大化向後相容性。

  • NPM 擁有 範圍套件 (scoped packages) 的概念,引入該概念主要是為了對抗可用優質套件名稱稀缺的問題(無論這是一種真實還是感知的現象)。當使用者或組織註冊時,他們會獲得一個與其名稱相符的範圍 (scope)。例如,用於使用 Google Cloud Storage 的 套件@google-cloud/storage,其中 @google-cloud/ 就是範圍。普通使用者帳戶(非組織)可以為公開使用發布 無範圍 (unscoped) 的套件。這種方法的向後相容性最低,因為必須修改每個安裝程式和工具才能處理範圍。
  • NuGet 擁有 套件識別碼前綴保留 的概念,引入該概念主要是為了滿足希望了解套件來源的使用者。套件名稱前綴可以保留給一個或多個擁有者使用。每個保留的套件都在其 頁面上 有特別標註。保留後,如果使用者不是該前綴的擁有者,則任何使用該保留前綴的上傳都將失敗。擁有已歸屬前綴的現有套件可以繼續正常發布。這種方法的向後相容性最高,因為只需要修改像 PyPI 這樣的索引,而不需要更改安裝程式。

本 PEP 規定了在扁平命名空間中進行授權保留的 NuGet 方法。任何需要新套件語法的解決方案都必須建立在現有的扁平命名空間之上,因此透過保留機制獲得的隱含命名空間將是此類顯式命名空間的先決條件。

雖然匹配已保留命名空間的現有套件不會受到影響,但防止未來未經授權的上傳,並針對惡意案例策略性地應用 PEP 541 下架請求,將可將使用者面臨的風險降低到可忽略的程度。

術語

本文件中的關鍵字「MUST」(必須)、「MUST NOT」(絕不)、「REQUIRED」(強制)、「SHALL」(應)、「SHALL NOT」(不應)、「SHOULD」(建議)、「SHOULD NOT」(不建議)、「RECOMMENDED」(推薦)、「MAY」(可以)以及「OPTIONAL」(選用)應依照 RFC 2119 所述進行詮釋。

組織 (Organization)
組織 是擁有專案並與其關聯各類使用者的實體。
授權 (Grant)
授權是對套件儲存庫命名空間的保留。
開放命名空間 (Open Namespace)
開放 命名空間允許來自任何專案擁有者的上傳。
受限命名空間 (Restricted Namespace)
受限命名空間僅允許來自命名空間擁有者的上傳。
父命名空間 (Parent Namespace)
命名空間的父項指的是沒有尾部連字號元件的命名空間,例如 foo-bar 的父項是 foo
子命名空間 (Child Namespace)
命名空間的子項指的是帶有額外尾部連字號元件的命名空間,例如 foo-barfoo 的有效子項,foo-bar-baz 也是。

規範

組織 (Organizations)

任何允許建立專案(例如非鏡像站點)的套件儲存庫都可以提供組織的概念 [6]。組織是擁有專案並與其關聯各類使用者的實體。

組織可以保留一個或多個命名空間。此類保留既不賦予所有權,也不給現有專案授予特殊權限。

命名

命名空間必須是 有效的 專案名稱並在內部 標準化,例如 foo.bar 會變為 foo-bar

語義

命名空間授權賦予以下所有權:

  1. 匹配命名空間本身的專案,例如占位符套件 microsoft
  2. 以命名空間開頭並後跟連字號的專案。例如,命名空間 foo 將匹配標準化的專案名稱 foo-bar,但不會匹配專案名稱 foobar

套件名稱比對基於 標準化 的命名空間。

命名空間是針對每個套件儲存庫的,且不得在儲存庫之間共享。例如,如果 PyPI 有一個由微軟公司擁有的命名空間 microsoft,那麼來自其他非 PyPI 鏡像儲存庫且以 microsoft- 開頭的套件並不會賦予相同層級的信任。

授權不得重疊。例如,如果已存在對 foo-bar 的授權,則禁止對 foo 進行新的授權。重疊的判斷方法是將建議的 標準化 命名空間與每個現有根授權的標準化命名空間進行比較。每次比較必須在建議的和現有的命名空間末尾加上一個連字號。當任何現有命名空間以建議的命名空間開頭時,即偵測到重疊。

上傳 (Uploads)

如果正在上傳的套件名稱與已保留的命名空間相符,且滿足以下任一條件:

  • 專案尚未存在。
  • 該專案並非由擁有該命名空間有效授權的組織所擁有。

則上傳必須失敗,並回傳 403 HTTP 狀態碼。

開放命名空間 (Open Namespaces)

授權的擁有者可以選擇允許其他人發布帶有相關命名空間的新專案。這樣做必須允許任何使用者為匹配該命名空間的新專案進行 上傳

命名空間擁有者可以同時使其開放並允許其他組織使用該授權。在此情況下,授權組織沒有特殊權限,等同於無所有權的開放授權。

隱藏授權 (Hidden Grants)

儲存庫可以建立不對公眾顯示的隱藏授權,以防止其命名空間被他人宣告。此類授權不得為 開放,也不應在 API 中公開。

隱藏授權對於希望執行上傳限制而無需向公眾公開命名空間的儲存庫非常有用。

儲存庫詮釋資料 (Repository Metadata)

JSON API 版本將從 1.2 升級至 1.3。支援此 PEP 的儲存庫必須實作以下 API 變更。不支援此 PEP 的儲存庫不得實作這些變更,以便 API 使用者能夠判斷該儲存庫是否支援此 PEP。

專案詳情 (Project Detail)

專案詳情 回應將按以下方式修改。

如果專案不符合活躍的命名空間授權,namespace 鍵必須為 null。如果專案確實符合命名空間授權,則該值必須是一個包含以下鍵的對映 (mapping):

  • prefix:這是相關聯的 標準化 命名空間,例如 foo-bar。如果專案擁有者擁有過個匹配的授權,則此處必須為字元數最多的命名空間。例如,如果專案名稱同時匹配 foo-barfoo-bar-baz,則此鍵將為後者。
  • authorized:這是一個布林值,如果專案擁有者是組織並且是該授權的目前擁有者之一,則為 true。這對於希望區分官方套件和社群套件的工具很有用。
  • open:這是一個布林值,表示該命名空間是否為 開放

命名空間詳情 (Namespace Detail)

此 URL 的格式為 /namespace/<namespace>,其中 <namespace>標準化 的命名空間。例如,命名空間 foo.bar 的 URL 將為 /namespace/foo-bar

回應將是一個包含以下鍵的對映:

  • prefix:這是命名空間的 標準化 版本,例如 foo-bar
  • owner:這是負責該命名空間的組織。
  • open:這是一個布林值,表示該命名空間是否為 開放
  • parent:這是父命名空間(如果存在)。例如,如果命名空間為 foo-bar 且存在對 foo 的活躍授權,則此處將為 "foo"。如果沒有父項,則此鍵將為 null
  • children:這是所有子命名空間的陣列。例如,如果命名空間為 foo 且存在對 foo-barfoo-bar-baz 的活躍授權,則此處將為 ["foo-bar", "foo-bar-baz"]

授權移除 (Grant Removal)

當保留的命名空間變為未宣告狀態時,儲存庫必須在 API 中將 namespace 鍵設定為 null

先前被宣告但現在未被宣告的命名空間,應允許任何組織再次宣告。

社群認同 (Community Buy-in)

來自下列組織的代表已對此 PEP 表達了支持(連結至討論):

回溯相容性

由於仍然存在一個扁平的命名空間,且安裝程式無需修改,因此不存在內在的疑慮。此外,許多專案已經選擇透過前綴來標記其共同目的,例如 typeshed 所做的那樣

安全性影響

  • 我們有機會在 PEP 740PEP 480 的基礎上進行構建,以便以密碼學方式證明特定的發布版本來自該相關命名空間的擁有者。除了計劃在未來進行這項工作外,本 PEP 沒有試圖描述將如何實作這一點。

如何教學

對於套件使用者,我們將記錄如何在 API 中公開詮釋資料,並可能在未來的說明中指出支援利用命名空間在安裝期間提供額外安全保證的工具。

參考實作

本 PEP 的完整參考實作可在 PR #17691 中找到。

否決的想法

授予使用者保留權 (Granting Reservations to Users)

由於套件儲存庫具有扁平的命名空間,允許任何使用者保留命名空間是站不住腳的,這不僅是因為會存在 有限資源的爭奪,還因為沒有任何儲存庫有足夠的人力來審核任意數量的使用者。

工件級別的命名空間關聯 (Artifact-level Namespace Association)

本 PEP 的早期版本建議在發布時將詮釋資料與個別工件相關聯。這被拒絕了,因為它有可能給使用者造成困惑,他們會期望命名空間授權保證是基於當前授權的專案級別,而不是基於給定版本發布的時間。

組織範圍 (Organization Scoping)

本 PEP 的主要動機是減少相依性混淆攻擊,而允許傳統扁平命名空間的 NPM 風格範圍限制會增加風險。如果文件指示使用者在 foo 命名空間中安裝 bar,那麼使用者必須小心安裝 @foo/bar 而非 foo-bar,反之亦然。Python 套件生態系統對名稱有標準化規則以最大化溝通的便利性,這將是一種回歸。

Python 的執行環境也不利於範圍限制。雖然同一個 JavaScript 套件的多個版本可以共存,但 Python 只允許單一全域命名空間。除非對語言本身進行重大更改,否則這幾乎是不可能的。此外,使用者已經習慣套件名稱通常與他們匯入的名稱相同,消除扁平命名空間將廢除該慣例。

範圍限制會受到組織變更的特別影響,而這些變更是不可避免的。組織可能會因為內部重組、收購或其他任何原因更改名稱。每當這種情況發生時,他們擁有的每個專案實際上都會被重新命名,這將頻繁地為使用者造成不必要的困惑。

最後,對社群的干擾將是巨大的,因為它需要每個套件管理器、安全性掃描器、IDE 等進行更新。採用範圍限制發布的新套件將與舊工具不相容,並會導致使用者的困惑,以及維護者必須處理此類投訴的挫敗感。

鼓勵專用套件儲存庫 (Encourage Dedicated Package Repositories)

最關鍵的是,這給專案帶來了維護自身基礎設施的負擔。對於絕大多數公司來說,這是一個不切實際的期望,對於社群專案來說更是完全不可行。

在大多數情況下這沒有幫助,因為大多數套件管理器的預設行為是使用 PyPI,因此試圖執行簡單 pip install 的使用者無論如何都容易受到惡意套件的攻擊。

在這一理論上的未來中,每個專案都必須記錄如何將其儲存庫新增至相依性解析中,而這對每個套件管理器來說都不同。很少有套件管理器能夠從特定儲存庫下載特定相依性,並且在常見情況下會要求使用者使用冗長的配置。

那些不支援此功能的儲存庫將改用儲存庫的有序枚舉來尋找給定套件,導致相依性混淆。例如,假設使用者想要來自兩個自訂儲存庫 XY 的兩個套件。如果每個儲存庫都有這兩個套件,但一個在 X 上是惡意的,另一個在 Y 上是惡意的,那麼使用者將無法在不遇到惡意套件的情況下滿足其需求。

完全依賴來源斷言 (Exclusive Reliance on Provenance Assertions)

這裡的想法 [5] 是為用戶端設計一種通用方法來進行來源斷言,以驗證相依性的某些屬性,每個屬性都具有自訂語法。以下是一些範例:

  • 該套件是由特定的組織或使用者名稱上傳的,例如 pip install "azure-loganalytics from microsoft"
  • 該套件是由特定網域名稱的擁有者上傳的,例如 pip install "google-cloud-compute from cloud.google.com"
  • 該套件是由具有特定電子郵件地址的使用者上傳的,例如 pip install "aws-cdk-lib from contact@amazon.com"
  • 匹配命名空間的套件是由授權方上傳的(本 PEP)

一個根本性的缺點是它不能很好地與多個儲存庫配合使用。例如,假設使用者想要 azure-loganalytics 套件,並希望確保它來自名為 microsoft 的組織。如果微軟在 PyPI 上的組織名稱是 microsoft,則預設使用 PyPI 的套件管理器可以接受 azure-loganalytics from microsoft。然而,如果使用多個儲存庫進行相依性解析,使用者就必須將儲存庫指定為定義的一部分,這在 斷言套件擁有者名稱 的專用章節中概述的原因下是不切實際的。

這種方法的另一個通用弱點是,嘗試在沒有特殊語法的情況下執行簡單 pip install(這是最常見的場景)的使用者,無論如何都容易受到惡意套件的攻擊。為了克服這個問題,必須有一種預設的信任機制,這在所有情況下都會對每個工具施加特定的 UX 或解析器邏輯。

例如,可以更改套件管理器,以便在第一次安裝套件時,使用者會收到一個顯示來源詳情的確認提示。這會非常令人困惑和干擾,特別是對於新使用者,並且對於現有使用者來說將是一個破壞性的 UX 變更。許多安裝方法在此場景下無法運作,例如在 CI 中執行或從需求檔案安裝,其中使用者可能會收到數百個提示。

使這一點對使用者而言干擾較小的一種解決方案是手動維護一個值得信任的詳情清單(組織/使用者名稱、網域名稱、電子郵件地址等)。這可以透過提供 進入點 (entry points) 的套件來發現,套件管理器可以學習檢測這些點,企業環境可以預設安裝這些點。這有一個主要的缺點,即不提供自動保證,這限制了對更容易受影響的普通使用者的效用。

有兩個想法可用於提供自動保護,這可以基於 PEP 740 認證,或一種用於利用託管詮釋資料的第三方 API 的新機制。

首先,每個儲存庫都可以提供一項服務,使用他們認為適當的任何標準來驗證套件的擁有者。驗證後,儲存庫會將詳情新增至一個將被預設安裝的專用套件中。

這將需要專門的維護,對於大多數儲存庫(即使是目前的 PyPI)來說都是不切實際的。目前尚不清楚沒有像網域名稱這樣資源的社群專案將如何獲得支援。最關鍵的是,在有多個儲存庫的情況下,此解決方案會為使用者帶來額外的困惑,因為每個儲存庫可能有自己的驗證流程、認證標準和包含驗證詳情的預設套件。要獲得每個套件管理器的社群認同,使其在相依性解析之前意識到每個儲存庫選擇的驗證套件並預設安裝它,將是一項挑戰。

如果數位認證成為選定的機制,一個缺點是在自訂套件儲存庫中實作這一點需要大量工作。以 PyPI 為例,在 Trusted Publishing 和隨後的 PEP 740 實作 本身所進行的先決條件工作,花費了一位由企業贊助支付的全職工程師一年的時間。其他組織不太可能實作類似的工作,因為更簡單的機制使實作可重現的建構變得可能。當一切都在內部管理時,認證也不是很有用。社群專案不太可能承擔這項工作,因為他們很可能缺乏維護必要基礎設施的資源,而且 鼓勵專用套件儲存庫 還有重大的缺點。

另一個想法是在外部託管來源斷言並將更多邏輯推向用戶端。一種可能的實作可能是指定一個可以託管在指定相對路徑(如 /provenance)上的來源 API。每個儲存庫上的專案可以配置為指向特定的網域,這些資訊將在安裝期間傳遞給客戶端。

雖然這種分散式方法確實減輕了儲存庫的基礎設施負擔,但它有可能成為安全風險。如果外部來源 API 被破壞,可能會導致安裝惡意套件。如果外部 API 宕機,可能會導致套件安裝失敗,或者套件管理器可能只會發出警告,在這種情況下沒有任何安全益處。

此外,這對於沒有資源維護此類 API 的社群專案不利。他們可以使用免費託管解決方案,就像許多人為文件所做的那樣,但他們在技術上並不擁有基礎設施,如果這些慷慨的服務受到限制,他們將會受到損害。

最後,雖然這兩種理論上的方法尚未具有規範性,但它們意味著在工件級別進行斷言,這已經是一個 被拒絕的想法

斷言套件擁有者名稱 (Asserting Package Owner Names)

這關於斷言套件來自特定的組織或使用者名稱。它與 組織範圍限制 的想法非常相似,只是以扁平命名空間為基礎假設。

這將需要修改每個受支援儲存庫的 JSON API,並可以透過公開額外的詮釋資料或作為適當的 來源斷言 來實作。

與組織範圍限制的想法一樣,需要一種新的 語法,例如 microsoft::azure-loganalytics,其中 microsoft 是組織,azure-loganalytics 是套件。雖然與現有的扁平命名空間相比,這配合得很好,但它保留了對社群造成干擾這一關鍵缺點,因為所需的變更數量龐大。

一個獨特的缺點是名稱是儲存庫的實作細節。在 PyPI 上,組織名稱與使用者名稱是分開的,因此存在衝突的可能性。在有多個儲存庫的情況下,使用者可能會遇到類似 鼓勵專用套件儲存庫 被拒絕想法末尾提到的相依性混淆情況。

為了改善這一點,有人建議擴充語法以同時包含預期的儲存庫 URL,例如 microsoft@pypi.org::azure-loganalytics。這種語法或類似語法非常冗長,可能會導致使用者困惑,更糟糕的是,如果它在那些能夠維護專用基礎設施的人中獲得更廣泛的採用,可能會導致挫敗感(社群專案將無法受益)。

擴充語法是試圖標準化相依性說明符內的解析器行為和配置。這不僅會強制規定工具的 UX,而且在有無套件儲存庫概念的語言生態系統的套件管理器中也沒有先例。在這種情況下,解析器配置與相依性定義是分開的。

語言 工具 解析行為
Rust Cargo 相依性解析可以在 Cargo.toml 中使用 [patch]進行修改
JS Yarn 雖然他們有 協定 (protocols) 的概念(類似於我們 直接參照 的 URL 方案),但使用者在 package.json 檔案中配置 解析 (resolutions) 欄位。
JS npm 使用者可以在 package.json 檔案中配置 覆寫 (overrides) 欄位。
Ruby Bundler Gemfile 允許為 gem 指定 明確來源
C# NuGet 可以透過配置 Directory.Packages.props 檔案來 覆寫套件版本
PHP Composer composer.json 檔案允許為特定套件指定 儲存庫 來源。
Go go go.mod 檔案允許指定 replace 指令。請注意,這用於直接相依性以及傳遞相依性。

使用固定前綴 (Use Fixed Prefixes)

這裡的想法是擁有一或多個用於命名空間保留的頂層固定前綴:

  • com-:保留給企業組織。
  • org-:保留給社群組織。

組織隨後將申請一個以前綴為其組織類型的命名空間。

這將導致永無止境的干擾,因為當專案剛開始時,不知道使用者群體是否會大到足以證明保留命名空間的必要性。每當發生這種情況時,專案都必須重新命名,這將給專案維護者帶來沉重的維護負擔,並會給必須學習引用專案套件新方法的使用者帶來困惑。這種做法導致專案根本不去保留命名空間的可能性很高。

此方法的另一個問題是,專案通常考慮到品牌形象 (範例),並且會不願意更改其套件名稱。

期望每一家公司和專案自願更改其現有和未來的套件名稱是不切實際的。

使用 DNS (Use DNS)

這裡的想法 在 API 的專案中新增一個名為 domain-authority 的新詮釋資料欄位。儲存庫將支援一個透過 HTTPS 驗證網域的新端點。客戶端隨後將支援允許特定網域的選項。

這並沒有解決目標受眾的問題,因為他們不去檢查套件來自哪裡,而且這更多是關於檢查上傳的完整性,而 PEP 740 已經以更安全的方式支援了這一點。

大多數專案沒有網域且無法從中受益,這不公平地偏袒了有財力獲取網域的組織。

待決問題

目前無。

附註


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

最後修改: 2025年3月29日 21:57:33 GMT