RFC-0145:Eager 套件更新

RFC-0145:搶先更新套件
狀態已接受
區域
  • 軟體交付
說明

系統單體以外的套件會急切更新。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-10-15
審查日期 (年-月-日)2021-12-13

摘要

更新套件的機制 (不屬於完整系統更新),且更新後套件會在重新啟動後保留,同時考量元件架構互動和更新套件的驗證程序。

提振精神

我們需要為套件擁有者提供機制,讓他們在裝置上發布軟體更新,而不必進行單一的全球整合程序。這樣一來,Fuchsia 平台和基礎系統就能與個別套件體驗 (例如網頁瀏覽器或支援資料) 分開發布。

我們目標是支援第一方 (1P) 產品和套件,包括在 Fuchsia 樹狀結構外部建構的產品。我們目前沒有支援第三方 (3P) 產品或套件的規定,但這項設計不應妨礙我們日後新增支援。

利害關係人

協助人員: hjfreyer@google.com

審查者:

  • ampearce@google.com - 安全性
  • computerdruid@google.com - 軟體推送
  • geb@google.com - 元件架構
  • hjfreyer@google.com - FEC、元件平台
  • jsankey@google.com - 結構化設定
  • marvinpaul@google.com - 伺服器基礎架構
  • camrdale@google.com - 鈷藍色

已諮詢:

列出應審查 RFC 的人員,但不必取得他們的核准。

  • aaronwood@google.com
  • abarth@google.com
  • bryanhenry@google.com
  • ddorwin@google.com
  • gstai@google.com
  • 軟體交付團隊

社交:

這份 RFC 文件已在內部經過多輪社交化程序,由審查人員、軟體交付團隊和潛在客戶審查。

RFC 格式定義

本文件中「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「MAY」和「OPTIONAL」等關鍵字,應按照 IETF RFC 2119 的說明解讀。

需求條件

Fuchsia 平台需求

套件更新

套件可以獨立更新,不受 Fuchsia 系統其他部分影響。套件更新時不需要重新啟動系統,這表示套件更新不一定會與系統更新一起發布。

元件關係

套件擁有者必須能夠獨立於基礎系統,驗證多個套件群組的發布版本,並只針對該群組中的套件和元件推送更新。

應可修改 (或設為空值) 套件關係,僅支援單一套件更新。

注意:我們不會在本 RFC 中完整說明這項需求。這對應到下一個工作階段,我們會在後續設計中處理這個問題。這個設計不會妨礙我們日後滿足這項需求。

A/B 更新:只有在套件中的所有 Blob 都已更新時,才使用更新後的套件版本

請先完整下載並驗證套件,再提交更新。

系統版本依附元件

急切更新的套件可能會宣告 Fuchsia 平台特定 ABI 修訂版本的依附元件 (如 RFC-0002 所定義,這是這項工作的依附元件),且只能由支援所需 ABI 修訂版本的 Fuchsia 平台版本下載。

指標

套件更新系統必須支援與系統更新流程類似的指標,並向套件擁有者提供指標,例如更新成功、下載時間、更新大小 (以位元組為單位,目前系統更新沒有這項指標) 等。

我們必須允許套件擁有者根據元件版本取得元件指標,而這些版本是根據執行的版本,不一定是最近提交至磁碟的版本。

下載更新時,不要封鎖元件啟動

也可以解讀為「不要讓工作階段重新啟動等事件非常緩慢」。如果我們在工作階段重新啟動或重新開機時檢查套件更新,且沒有急切的更新檢查程式,工作階段重新啟動的速度就會變慢。這是因為裝置可能必須下載整個套件或一組套件,才能完成工作階段重新啟動。

重新啟動系統後,已成功套用的更新應該不會還原

如果裝置在重新啟動時無法存取網路,我們不得還原為舊版套件,且如果網路不穩定,我們不得封鎖應用程式啟動,除非該應用程式過於舊版。

我們可能會定義「過時」的套件,並拒絕啟動,直到套件更新為止

如果應用程式過時,且已知有可用的更新,我們日後可能會進入「強制更新模式」,防止啟動已知有漏洞或不良的軟體版本。

用戶端程式碼中含有單一網址的套件,必須能夠根據裝置環境包含不同的程式碼或資料

許多開發人員不希望或無法在每次有可用的套件更新時,變更程式碼或資訊清單。您可以在模組化設定中找到這類範例,其中會為模組化元件編碼特定元件網址。如果每次依附元件變更時,都必須變更這些設定,開發人員體驗就會不盡理想,而且在其他情況下,這項作業也可能無法執行。

這表示單一套件網址必須代表可能不同的套件版本,具體取決於使用位置和時間 (不過,特定套件的更新規則也可以嵌入系統 ABI 等需求)。

由於單一元件網址不足以判斷軟體的確切版本,因此我們需要新增指標和意見回饋整合功能,以取得該資訊來偵錯流程。

長期而言,需要相容版本單一全域資訊清單的解決方案並不可行

多位客戶可能想發布套件,但完全不與 Fuchsia 或產品擁有者協調或整合。如果解決方案需要伺服器中預先定義的元組,且該元組包含所有套件版本的單一資訊清單,可能就無法滿足這項需求。(這也會違反「軟體交付」目標,因為平台應能產生可安裝任意軟體的產品,而非在產品建構時已知)。

將套件下載作業分散到一天中的不同時段

如果將套件下載時間限制在特定時段 (例如工作階段重新啟動時),裝置可能會在嘗試更新時長期處於離線狀態。更新作業應會在當天的任何時段下載。

套件代管和發布程序的需求

其中有些規定也會促使平台功能支援伺服器端實作。

套件存放區

急切更新的套件必須在發布基礎架構上另外代管,且與 Fuchsia 系統更新套件和 Blob 無關。這可確保 Fuchsia 平台套件的存放區不會受到應用程式套件變更的影響。這表示平台套件目前不會積極更新,但我們日後可能會放寬這項限制。

只要符合本 RFC 中指定的規定和設計要點,產品就能自行提供發布基礎架構。

更新管道

急切更新的套件必須支援以管道為基礎的推出作業,並使用可能與產品其餘部分發布管道不同的管道名稱。舉例來說,Chrome 可能會想使用的管道數量,多於或少於執行 Chrome 的產品所使用的管道數量。

這需要用戶端支援,才能協商正確的管道指派。

階段推出

階段性推出是管道管理的延伸功能,可確保使用者能根據百分比觸發應用程式的推出作業 (例如 1%、10% 等),以遵循最佳管理做法。

此外,也應提供緊急更新機制,以便將重要推送內容快速發布給 100% 的使用者。請注意,平台提供緊急更新機制,可在五小時內完成 100% 的推出作業。注意:這超出本 RFC 的範圍,但屬於長期需求。

這需要用戶端支援,才能協商正確的階段性推出群組。

踏腳石

「跳板」是指裝置可能必須下載及執行的套件建構版本,才能升級至最新版本。

目前產品沒有明確要求短期內支援套件的里程碑,但這項設計將其視為長期需求,我們很可能需要提出要求。我們不會在本 RFC 的實作中明確建構對這些功能的支援,但不得排除日後支援這些功能的可能性。

這需要用戶端支援,才能協商要下載的版本。

安全性規定

  • 確保無法從可獨立更新的存放區,覆寫基本設定中的套件 - 請參閱可更新套件群組的必要屬性
  • 為符合 Fuchsia 驗證執行(FVX) 規定,軟體推送堆疊從磁碟載入記憶體的任何內容,都必須在載入時重新驗證,而非僅在下載時驗證一次。請參閱跨重新啟動驗證一節
  • 讓使用者輕鬆瞭解及控管套件可包含的可執行檔
    • 請參閱「可執行性控制項」。不含可執行程式碼的套件仍屬於安全敏感性套件,但不在 FVX 實作範圍內。
  • 輕鬆稽核套件發布內容。根據技術控管 (而不只是程序控管) 制定明確的簽署核准程序 - 這項要求不在本 RFC 的範圍內,但其他文件會涵蓋這部分。
  • 套件必須受到經過驗證的啟動鏈涵蓋的反向回溯機制約束 - 請參閱跨重新啟動驗證一節

設計

本節將嘗試擷取包含機器碼的套件,在套件更新時的整體流程。我們將說明可執行性檢查和程式碼驗證的執行位置,以及開發人員體驗。我們會找出所需狀態與目前功能之間的差距,並據此制定實作策略。

我們將以 Chrome 做為代表性用途,因為 Chrome 非常適合這項工作的第一階段:

  • 這是要更新的單一套件 (例如 chrome)
    • 沒有必須同步更新的依附元件
  • 套件含有可執行的機器碼
  • 需要支援管道和階段性推出

可更新套件的必要屬性

本節將詳細說明提案設計在短期內支援的套件類型。我們預期在長期內,將放寬許多這類限制。

  • 在程式碼和設定檔中以非 fuchsia.com 網址參照,避免與基本套件混淆
  • 必須指定特定系統 ABI (依據 RFC-0002 的實作方式)
  • 無法依賴 config-data 套件 (也就是說,不得預期 config-data 會隨著結構定義或內容中的套件而變更)
  • 可能需要結構化設定
  • 必須有 Fuchsia Global Integration 和 Fuchsia 版本資格以外的資格程序,可在 SDK 內含項目上執行
  • 必須符合 2 個基底圖片副本所剩空間的一半,且不得假設基底圖片檔案會重複資料刪除 (以允許套件的 A/B 更新)
  • 無法依附於非基礎套件 (即其他可更新的套件)
  • 如果不是透過完整的系統 OTA 更新套件,則不得以不相容的方式變更套件中元件公開的 ABI,也不得變更從其他非系統套件取用的服務預期 ABI,除非這些互動有版本控管策略。
    • 可以是單一架構套件或多重架構套件
  • 套件的依附元件必須處理該套件的解析錯誤 (例如,某些備用版本的特殊情況會嘗試降級套件,但 Fuchsia 會拒絕執行)。

總覽

我們建議使用已用於檢查正式系統更新的 Omaha 用戶端,檢查套件更新。

我們將在 omaha-client 中新增支援功能,以便檢查多個套件和系統映像檔的更新。

我們會將 omaha-clientpkg-resolver 基礎架構整合,無論開發人員是在本機使用實際的 Omaha 伺服器或開發伺服器,都能進行抽象化。

我們將支援可更新套件的垃圾收集,並採取安全措施,確保套件更新不會阻礙系統 OTA。

我們會與 Omaha 伺服器基礎架構整合,根據特定套件網址協商適當的套件版本。

我們將用途分為兩大事件:更新檢查和套件下載,以及套件解析。前者會在計時器上執行,或由開發人員手動要求,並觸發套件下載和快取。後者會在元件架構解析套件時執行。

以下是新套件解析架構的完整總覽。我們會在後續的章節中,詳細說明。

整體架構

更新檢查和套件下載

檢查更新的時機

omaha-client 會執行狀態機器,且可針對每個「應用程式」設定 (對 Omaha 而言,這表示用戶端檢查更新的一組軟體)。

目前正式版系統更新的 omaha-client 檢查間隔為五小時,並會自動抖動一小時。我們建議對系統映像檔和套件更新使用相同的更新檢查間隔,以減輕 Omaha 伺服器的負擔,並在系統更新和套件更新都可用的情況下簡化實作程序。(請注意,我們預計會隨著時間,將系統更新檢查與套件更新檢查分開,包括間隔和主機。這是短期簡化措施,可讓您更輕鬆地完成初始實作程序。

在哪裡檢查更新 (存放區設定)

我們會為每個可搶先更新的套件新增 Omaha 用戶端設定,並根據產品將這些設定編譯到 SWD 堆疊的設定中。這會包含系統上哪些套件網址應參照該套件的設定。該設定會包含套件名稱和網址,以及 Omaha 應用程式 ID 和其他預設設定選項,例如管道。

這項做法會產生良好的安全性連帶效應:由於主機名稱會硬式編碼為不同的值,因此積極更新的套件無法在 user\* 建構作業中,覆寫其他存放區的套件。積極更新的套件永遠無法覆寫基本套件 (注意:回溯版本不在基本套件中),因為名稱位於 /system/static_packages 清單中的套件永遠不會前往網路進行解析。

花瓣和 fuchsia.git 中的程式碼都必須更新,不得參照套件的 fuchsia.com 網址,而是參照對應新存放區的主機名稱,例如 chrome-fuchsia-updates.googleusercontent.com

我們需要在套件更新設定中新增中繼資料,供套件解析器剖析。具體來說,我們會新增布林值,指定套件是否應包含可執行程式碼。這個布林值會用於執行階段可執行性限制,類似於 pkgfs 中已有的限制。詳情請參閱「可執行性控制項」一節。

啟動時,omaha-client 會取得可更新套件的設定,這些設定源自 vbmeta目前無法進行執行階段設定,因此如要自動測試 omaha-client,我們需要繼續建構 vbmeta,就像在 Omaha E2E 測試中一樣。

套裝方案協商

我們將套件方案協商定義為將套件方案網址 (例如 fuchsia-pkg://chromium.org/chrome) 轉換為 (主機名稱、Merkle) 元組的程序。這項轉換的確切輸出內容會受到多項變數影響,包括:

  • 套件設定的更新管道
  • 裝置上執行的系統版本
  • 是否正在進行階段性推出,以及更新檢查程式所屬的階段性推出群組。

我們建議主要在伺服器端進行套件協商,盡可能簡化用戶端程式碼。

omaha-client 會將要求傳送至套件更新設定中列出的 Omaha 伺服器,至少包含下列資訊:

  • 應用程式 ID (這是 Omaha 服務的套件 ID)
  • 套件更新管道
  • 套件更新版本 (通常會編碼在應用程式 ID 中)
    • 風味可用於要求套件的不同變體,例如含有偵錯符號或工具的變體。由於這項資訊會編碼至應用程式 ID,因此實際工作環境裝置無法在沒有系統 OTA 的情況下,變更在現場執行的風味
  • 支援的系統 ABI (通常會編碼在平台版本欄位中)
  • 階段推出成員
  • 目前的套件版本 (格式為 A.B.C.D,其中 A-D 是 32 位元整數的字串表示法,遵循 版本號碼的 Omaha 規格),Omaha 才能防止降級
    • OMCL 會從最新提交的套件版本 CUP 中繼資料,或從備用版本的 vbmeta 版本中繼資料擷取這項資訊,如果磁碟上目前沒有任何套件版本,則會將這個欄位設為 0.0.0.0
  • 支援的系統架構 (例如 x64、arm64 等,與 Fuchsia 支援的架構清單中的名稱相符)

Omaha 伺服器會計算裝置適用的正確套件版本,並傳回要下載的 Blob 的 Merkle 固定網址,其中包含要下載的 (套件主機、套件名稱、Merkle 根) 三元組。我們必須先根據 Omaha 提供的 Merkle 檢查回應內容,才能使用回應資料。回應格式可能與目前用於系統 OTA 的 update 套件格式相符。

回應會包含該應用程式的裝置同類群組,其中會包含管道資訊。由於整個回應都會經過簽署,因此我們可以在啟動時有效判斷套件的管道。回應同類群組也會用於變更後續要求的管道 (請參閱下方的管道支援一節)。

Omaha 回應必須實作用戶端更新通訊協定 (CUP),該通訊協定會提供 Omaha 回應的簽章。我們將在 Omaha 用戶端中新增對這項通訊協定的支援。我們需要 CUP,才能將回應保留在磁碟中,並在稍後根據儲存在基本套件中且以 vbmeta 為根目錄的公開金鑰,重新驗證回應。

頻道支援

Omaha 通訊協定透過 cohort 概念支援管道。

由於可更新的套件使用的管道組合可能與基本系統不同,因此我們需要為開發人員提供類似 fuchsia.update.channelcontrol 的新 API,以便管理開發中套件的管道設定。我們會將管道資訊儲存在 CUP 回應中,類似於將管道資訊儲存在 vbmeta 中以進行系統更新。我們打算透過 pkgctl 的擴充功能公開這個 API,做為 CLI 工具,並提供給想在執行階段變更元件管道的元件開發人員。

如果裝置同時有已提交的更新和套件的回溯版本,即使回溯版本較新 (即使提交的版本比後備版本舊),我們也應使用已提交更新的管道資訊,以免更新後備版本時覆寫管道指派項目。我們仍須驗證已提交回應中的簽章,才能使用其管道資訊。

如果 Omaha 要求包含不存在的管道,Omaha 伺服器應重新導向至現有管道,或使用備用版本。

在 Fuchsia 裝置上使用管道控制 API 要求變更管道時,除非以包含管道的持續性簽署 CUP 回應形式,否則該設定不會在重新啟動後保留。根據政策,我們不希望信任未簽署的資料。下次檢查更新時,系統會使用記憶體內管道,並在正確的套件版本提交至磁碟後,儲存該更新檢查的回應。

系統映像檔中的 Omaha 設定應包含預設管道,以便積極更新套件。如果特定套件沒有持續性 CUP 回應,Omaha 應使用預設管道。

如果變更管道後傳回的套件版本相同 (因為套件同時存在於兩個管道),下載流程應照常進行。pkg-resolver 就不會下載任何新 Blob,而是傳回磁碟上已有的 Blob。

支援階段推出

伺服器會根據伺服器上的隨機擲骰結果,將裝置指派給群組 (這是現有的 Omaha 功能),並使用該群組 ID 追蹤裝置是否應接收階段性推出版本。伺服器端會為每個應用程式 ID 獨立擲骰。

持續性所需的容量

Omaha 要求和 CUP 回應相當小,大約只有數百位元組。我們可能只需要每個套件不到 1 KB 的空間,就能保存中繼資料。

下載套件

omaha-client 取得 Omaha 伺服器計算出的最終 merkle 固定套件網址後,就會使用新通訊協定 (可能稱為 fuchsia.pkg.cup) 觸發套件下載作業,並將套件下載至 pkg-resolver 元件。如果裝置上已有要求版本的套件,pkg-resolver 就不會重新下載。

如果裝置上沒有新版套件,pkg-resolver 會下載新版本,並保留傳回的目錄 (避免垃圾收集)。將控制代碼傳回套件目錄前,pkg-resolver 必須將 CUP 要求/回應配對提交至儲存空間,以供日後重新驗證。

pkg-resolver 必須保持這個目錄控制代碼開啟,以免套件遭到垃圾收集 (這項設計假設我們有以開啟套件追蹤為基礎的垃圾收集,目前正在開發中)。必須捨棄舊版套件的控制代碼。

我們也會在 pkg-resolver 的設定中,為 pkg-resolver 下載的每個可更新套件新增大小限制。下載完成後,如果套件大於設定的上限,pkg-resolver 會刪除與套件相關聯的所有 Blob,並因錯誤情況而導致更新失敗。

這個流程的圖表如下:

下載套件流程

如果下載失敗,會怎麼樣?

如果下載失敗,更新檢查的 CUP 結果就不得提交。新的解析作業仍會使用先前提交的版本。

空間管理和垃圾收集

下載套件更新時,我們需要維持幾個與空間相關的不變量:

  • 系統一律會保留足夠空間,用於快取核心產品體驗 (例如 Chrome) 執行所需的套件。
  • 系統更新時,裝置一律會有足夠空間,或可建立足夠空間來安裝系統更新。

如果允許搶先更新套件使用足夠的不可回收空間,導致系統無法進行 OTA 更新,就必須執行 FDR 才能解決問題。如要避免發生這個錯誤狀況,必須變更目前的垃圾收集程序。

我們的垃圾收集機制目前會根據下列條件保留套件:

  • 如果套件位於目前執行的基本套件集中,請保留該套件
  • 如果套件是最近解析的非基礎套件版本,且是在目前啟動期間解析,請保留該套件
  • 否則請允許刪除套件。

這項策略目前可行,但有幾個明顯缺點:

  • 這可能會刪除執行中系統日後需要的套件 (如果這些套件在目前的啟動程序中尚未解決)
  • 系統可能不會刪除在目前啟動期間已解決但不再需要的套件 (例如隨著時間累積的測試套件)

在我們改用 pkgfs 託管套件快取之前,我們一直不願變更目前的垃圾收集機制。這項工作即將完成,我們就能實作垃圾收集機制變更,以維護這些不變量,也就是防止垃圾收集目前使用的套件。

在短期內實作搶先更新 (在我們推出以套件追蹤為基礎的垃圾收集功能之前),我們會對系統更新和搶先套件更新進行嚴格的大小檢查。也就是說,可更新的套件必須符合產品負責人為其預算的空間,我們會在上傳套件時檢查空間需求 (目前為集中管理)。從長遠來看,更完善的垃圾收集實作方式可提供更多彈性。

如果儲存空間還是用盡,會發生什麼情況?

儘管我們盡力在發布時檢查套件大小,並確保一切符合預算,但裝置仍有可能在下載更新時空間不足。

為解決這個問題,我們將新增空間管理「核選項」,當 OTA 期間空間不足時,裝置會以新的重新啟動原因 (OUT_OF_SPACE_PANIC) 重新啟動,並在網路連線後立即執行 OTA,不必等待檢查間隔。這會觸發垃圾收集作業。系統更新檢查程式應會封鎖急切的套件更新,直到 OTA 完成為止。

我們會在 system-updater 中新增設定選項,判斷是否應在發生 NO_SPACE 錯誤時重新啟動系統。我們會提供選項,讓產品擁有者在 user* 建構版本中將此選項設為 true,但為了協助偵錯,_eng 建構版本應預設為 false。如果這個選項為 true,且 system-updater 在嘗試安裝更新時空間不足,system-updater 會以 OUT_OF_SPACE_PANIC 原因觸發系統重新啟動。

重新啟動後,系統更新檢查程式元件 (omaha 用戶端或 system-update-checker) 會在開機時啟動。如果更新檢查程式元件發現重新啟動的原因是 OUT_OF_SPACE_PANIC,就會立即嘗試執行 OTA,同時禁止更新套件。

我們也會新增 OUT_OF_SPACE_PANIC 重新啟動的延遲時間,確保裝置因 OUT_OF_SPACE_PANIC 連續重新啟動多次時,不會持續循環重新啟動。

從可更新的套件啟動元件

下次透過元件架構解析元件時,系統會使用可更新套件中的元件。在某些產品上,系統會在夜間自動重新啟動工作階段,藉此重新啟動元件,因此可能不需要更新通知。不過,其他產品元件可能需要更新通知,以便向使用者顯示「請重新啟動」通知。詳情請參閱「未來工作」一節。

新元件的解決流程

所有套件網址仍會採用 fuchsia-pkg://{hostname}/{package_name} 格式。不過,由於部分套件網址的套件協商作業會由裝置上的 Omaha 用戶端管理,部分則會由其他方法管理 (例如 cache_packagesbase_packages),因此我們需要套件解析流程,讓套件堆疊區分應透過 Omaha 管理的套件,以及應透過其他中繼資料管理的套件。

為此,我們建議採用下列解決流程。如要使用 fuchsia.pkg.PackageResolver 通訊協定呼叫來解決特定套件網址,pkg-resolver

  1. 套用基本釘選和重寫規則
  2. 判斷要解析的重寫網址是否由 Omaha + CUP 管理,如果是,請執行下列步驟:
  3. 向管理 CUP 套件的內部程式庫詢問與該網址相關聯的目錄控制代碼。
  4. CUP 程式庫會擷取該網址最近一次提交 CUP 回應時,在 Merkle 樹狀結構中快取的指定套件,或在發生錯誤且符合使用備用版本的條件時,改用備用版本。針對所選套件版本,它會直接前往 pkg-cache 並傳回目錄控制代碼 (避免在寫入新套件時讀取套件,導致各種競爭條件)。

這個流程的圖表如下:

可更新套件的解析流程

可執行性控制項

pkg-resolver 屬於生產裝置上的有效解析度路徑,因此必須參與 FVX 強制執行。我們需要信任的金鑰清單會包含 CUP 金鑰。

我們將在 pkg-resolver 中實作可執行性控制項,確保在使用者* 建構中,唯一可包含可執行程式碼的套件為:

  • 以 base
  • 由 Omaha 管理,並在 pkg-resolver 設定中設定為可執行檔
  • 透過現有的pkgfs_static_packages許可清單 機制加入許可清單,以控管可執行性

這些控制項的具體實作方式將在後續文件中說明。

驗證重新啟動後下載的中繼資料

簽章驗證和可稽核性

如要在重新啟動後保留可更新的套件,我們需要記住其 Merkle 根。我們會保留 Omaha 要求和 CUP 回應。使用保存的資料尋找套件的 Merkle 時,我們會驗證下列事項:

  • 要求/回應組合的簽章與信任的金鑰相符
  • 如果裝置有網際網路連線 (比對 Omaha 應用程式 ID、管道和版本),要求就等同於我們要求執行的動作,且
  • 回應符合簽章參數且有效,以及
  • 回應的預期版本大於或等於系統映像檔中的後擋版本
  • 回應包含支援執行中系統 ABI 版本的套件

如果符合所有條件,pkg-resolver 中的 CUP 模組可以從 CUP 回應傳回保存的 merkle。

這種做法的缺點是,我們需要剖析持續性請求和回應,才能取得持續性 Merkle 根的值。不過,我們認為可以透過隔離實際執行剖析作業的元件,在實作時降低這項風險。

復原防護

這項解決方案依賴 vbmeta 提供的復原保護機制,並使用版本式備援。如果 CUP 回應中的目標版本號碼比系統映像檔設定中的最低必要版本還舊,我們會改用 OTA 映像檔中包含的套件版本 (如有),且不會提交回覆中的版本。

舉例來說,假設可更新套件的 1.0 版有安全漏洞,而 1.1 版已修正這個問題。我們需要指示 Omaha 停止提供 1.0 版,並開始提供 1.1 版,然後向車隊發布系統 OTA,其中包含 1.1 版的新後備版本。這項做法可確保在沒有 Replay Protected Memory Blocks 或同等功能的裝置上,提供不遜於目前單體行為的保證。

如果套件未設定防護版本,我們會加入以時間戳記為準的防護版本,並確認該版本比套件的每個 CUP 回應都舊。如要支援更新系統事先未知的套件,就必須使用這個模式。系統會將最終期限設為系統最終期限,目前是產生系統映像檔的 Fuchsia 建構版本時間戳記。目前,我們沒有更新系統映像檔先前未知的套件相關規定,但設計時應將此納入考量。

早期啟動需要套件的備用版本

啟動程序或裝置連上網路前,可能需要使用可更新套件中的某些套件 (例如,這些套件可能提供「開箱體驗」(OOBE) 的使用者介面,設定網路)。也就是說,如果某些套件過時,我們就無法依賴網路下載這些套件,而且必須在系統更新映像檔中加入這些套件的版本。我們將這些隨附套件稱為「備用版本」,因為通常只有在沒有其他更新版套件時,才會使用這些版本。

備援版本有幾項設計壓力:

  • 必須在沒有網路存取權的情況下執行
  • 這些設定必須在恢復原廠設定 (FDR) 後仍存在,因為 OOBE 可能需要這些設定
  • 必須透過系統更新發布,並通過相關資格認證
  • 我們必須盡量減少備用版本使用的空間,尤其是與其他版本的套件搭配使用時
    • 如果我們必須儲存備援版本、目前執行的版本,以及尚未執行的套件新版本,就表示我們需要同時儲存特定套件的三個副本 (假設我們禁止系統更新和套件更新同時進行)
  • 我們必須盡力避免系統執行套件 N 版本,但系統更新卻強制降級至套件 N-1 版本的情況,因為避免降級是提供給開發人員的實用屬性
  • 同時避免套件降級,我們也必須避免因降級而拒絕系統更新

特定套件擁有者應與產品擁有者協調,決定是否需要套件的回溯版本。並非所有可更新的套件都需要備用版本。

如要在 OTA 映像檔中加入備援版本,我們會將備援版本套件放入系統更新套件,並在系統的 system_image 套件中加入該套件的版本控制中繼資料。即使沒有網路連線,pkg-resolver 也能透過版本控管中繼資料 (主要是 Merkle 根) 回應套件要求。由於版本控管中繼資料包含在 vbmeta 中,因此不會受到 FDR 刪除。

系統更新期間,我們會進行垃圾收集,並刪除更新套件的未提交版本,但我們可能仍需儲存三個版本的套件 (包括備援版本):目前系統映像檔中的備援版本、已下載且可能正在執行的已提交版本,以及新系統映像檔中的新備援版本。

使用備用版本的時機

只有在根據 CUP 中繼資料,備援版本較新 (版本號碼較大),且大於磁碟上最近提交的套件版本時,CUP 模組才會傳回備援版本的目錄。

如果套件的最新更新版本已損毀,手動恢復原廠設定會清除儲存的 CUP 中繼資料,並滿足條件 1,讓我們重設為備援版本。日後,我們也可以使用 Blob 損毀處理常式,判斷更新的套件是否損毀,並使用備援版本 (這部分會在「未來工作」中提及)。

從技術上來說,新系統映像檔中的備援版本可能會比最近提交的套件舊。不過,如要使用降級的備援版本,新系統映像檔必須不支援最近提交的套件所需 ABI。我們認為,如果系統發布時採用較新的系統 ABI,並提供支援該較新系統 ABI 的較舊備援版本,是不可行的做法,但我們會調查是否能新增伺服器端檢查,防止這類情況發生。

如果系統更新將備用版本升級至最近提交的版本以外,我們應根據 CUP 中繼資料中的版本字串,優先使用最近更新的套件版本。

指標

可更新套件的指標

為方便 1P 套件開發人員,我們將新增 Cobalt 指標,這些指標會根據產生指標的元件套件版本自動彙整。目前 Cobalt 在記錄任意字串 (例如可更新套件的雜湊) 方面有一些限制。我們不打算直接在套件格式中嵌入版本號碼或其他任意版本字串,而是要在用戶端將這些版本號碼嵌入 CUP 中繼資料,並使用該資料向工具回報版本。由於 pkg-resolver 可以存取套件的 CUP 中繼資料,因此能夠向 Cobalt 回報已提交套件的人類可讀版本字串。

指標傳送至 Cobalt 的方法有很多種,但都必須支援版本資訊:

  1. 直接從元件記錄
  2. 代表其他元件記錄,例如 Sampler 會讀取元件的檢查資料,並以 Cobalt 指標的形式傳播
  3. 代表多個元件記錄,例如記錄統計資料。這可能看似第 2 點的一般情況,但代表我們選擇的任何解決方案都需要具備可擴充性。

Fuchsia 使用 Cobalt 時,整合可更新套件須符合下列幾項規定:

  1. 元件擁有者不希望使用按 Merkle 根細分的版本資訊主頁,他們偏好使用人類可讀的字串
  2. 為了根據人類可判讀的版本字串準確匯總資料,我們需要在裝置上執行這項操作
  3. 我們無法在每個新版套件發布時更新 Cobalt 登錄檔,因為這樣會失去獨立套件更新發布流程的意義
  4. 提供給 Cobalt 的元件版本資料可能參照不再執行的元件版本,例如記錄統計資料
  5. 短期解決方案不需要支援 v1 元件 (因為這些元件無法動態更新),但我們需要為更新的每個元件回報版本字串。Cobalt 希望每個套件都有版本字串,但我們不會強制所有套件都使用新格式,才能為任何套件啟用版本回報功能。

為了將套件的版本字串記錄到 Cobalt,我們建議採用一種系統,將套件的 Merkle 根連同指標一起傳播,以便追蹤發出特定指標的元件版本。

我們將修改 Cobalt 記錄通訊協定,允許使用選用的套件 ID 欄位。一旦 Cobalt 元件本身與一組特定指標相關聯的 Merkle 根目錄,即可從該 Merkle 根目錄對應至以字串為基礎的版本字串,藉此在本機匯總指標。這個 Merkle 根也可能會與其他來源的元件名稱合併 (例如記錄統計資料或 Sampler 資料)。

為符合 Cobalt 的隱私權規定,我們必須在裝置上依版本字串彙整資料,而不是將資料傳送至裝置外。由於裝置上可能會有可供人閱讀的版本資訊,因此建議在裝置上而非後端系統中進行對應。

此外,客戶也希望使用人類可讀的任意字串來分割指標。目前個別套件支援這項功能,可能會記錄版本,但沒有全系統的系統或標準。Android 將此視為版本名稱

為此,我們建議在 CUP 中繼資料中新增另一個欄位,並以 Omaha 回應的擴充功能形式傳輸,也就是 version-name。這個欄位只會用於處理版本字串,SWD 堆疊不會保證其獨特性,也不會用於防止降級;套件擁有者必須維護自己的版本字串。

驗證版本字串:新版 SWD API

基於隱私權考量,Cobalt 會限制可記錄的字串類型。特別是 Cobalt 無法信任任意字串 (例如版本字串) 不含個人識別資訊 (PII)。如要允許我們記錄元件的版本字串 (Cobalt 無法預先得知,因為元件可能會更新至任意版本),Cobalt 需要透過可信任的系統元件驗證特定版本字串是否為該可信任系統元件所知。

軟體交付團隊會與 Cobalt 團隊合作,驗證套件 ID (Merkle 根或套件網址),並將這些 ID 與人類可讀取的版本字串建立關聯,以利遙測。當 Cobalt 嘗試根據套件 ID 尋找特定套件的版本時,SWD 堆疊會傳回版本名稱和版本號碼 (如有)。

目前我們無法保證與系統映像檔相關聯的任意元件版本資訊,但日後可能會在套件中,針對人類可讀的版本資訊標準化編碼,並擴大此處的保證。

如果記錄元件未將套件 ID 提供給 Cobalt (例如記錄統計資料),Cobalt 應根據套件 ID 回報目前執行中元件的版本。

軟體交付堆疊會預期 Cobalt 將要求的回應快取至使用者可讀取的版本字串。Cobalt 元件必須避免為每個指標呼叫呼叫版本字串 API,否則會產生不必要的資源費用。我們可能會選擇建立 Cobalt 的大量 API,定期呼叫以減少版本字串的 FIDL 流量。

我們日後可能會決定將版本字串和版本名稱整合至套件格式,但目前不打算這麼做。

更新系統本身的指標

我們也需要回報與套件下載程序相關的指標,例如成功/失敗指標。如上所述,基於隱私權考量,Cobalt 對於記錄任意字串設有一些限制。

不過,由於 Omaha 也有自己的後端指標和隱私權保護策略,因此 Omaha 本身可以提供執行特定版本的裝置確切數量指標。相較於 TUF 替代方案,這項解決方案具有優勢。

就更新程序本身的指標而言,我們計畫使用 Omaha 的指標,並與 Cobalt 團隊合作,在極少數情況下 (由可信賴的系統元件驗證),根據任意字串回報維度。

最後,我們會將可更新套件的版本從更新基礎架構記錄到裝置快照,以利排解問題。

實作

我們可能會將這項工作分成多個階段,對應功能增加的情況。

第一階段可能包含基本的 Omaha 型套件擷取通訊協定,這需要:

  • SWD
    • omaha-client支援多個應用程式
    • 導入 CUP,整合至套件解析流程
    • 用於測試的最低限度開放原始碼 Omaha 伺服器
    • 套件設定,將套件網址對應至 Omaha 應用程式 ID。
  • 伺服器端封裝團隊
    • 伺服器端封裝基礎架構中的版本管理,以及頻道和降級保護等基本套件協商
    • 套件儲存空間,並與 Blob 儲存空間整合。
    • 伺服器端套件協商 (管道支援、分階段推出等)

第二階段將導入生產用途所需的額外安全機制,以及空間管理所需的安全措施:

  • SWD

    • 實作 CUP 後備機制
    • 空間管理:實作開放式套件追蹤功能,並全面檢修垃圾收集機制,以實現更彈性的套件管理。
    • pkg-cache 以外的可執行性限制
    • 應用程式專屬資格程序
    • 基底映像檔內建的套件備用套件
    • 新增 Cobalt API,用於驗證版本字串
  • 伺服器端封裝基礎架構

    • 啟用 CUP
    • 簽署前檢查發布時間來源和程式碼簽署
    • 強化套件協商支援,例如分階段推出、ABI 修訂協商等功能。
    • 發布時檢查大小
    • 在套件上傳介面中新增介面,允許指定版本字串

注意:本節未涵蓋 RFC-0002 的實作,因為這是依附元件,但有自己的實作計畫。

效能

我們預期這項變更不會對執行階段效能造成重大影響。 推出這項變更時,我們需要密切監控 omaha-client 的記憶體和 CPU 使用情形,以及 SWD 堆疊的其他部分。我們也會監控啟動和重新啟動時間,以防出現回歸情形。

這項變更可能會導致磁碟用量增加,因為需要額外空間來儲存套件,以及少量額外空間來儲存套件中繼資料。如要瞭解這些考量事項的詳細資訊,請參閱空間管理一節。

人體工學

工程工作流程

我們會建構新的 _eng 工作流程,使用 omaha-client 測試及驗證套件更新。我們預期使用套件的大部分開發人員都會在開發時採用 TUF 型工作流程,但我們需要先建構開放原始碼的 Omaha 伺服器,才能執行自動測試,然後再將平台更新發布至正式版。

我們預期在 SDK 工具上層疊加的產品工具,最終會在工程建構版本中新增對 Omaha 設定的支援。在此之前,產品開發人員可能必須直接與 Omaha 設定互動 (這是設定套件測試工作流程的一部分)。

我們也需要擴充指令列工具,以便在執行階段新增動態設定的可更新套件。

回溯相容性

這項工作應與現有的軟體發布堆疊用途向後相容,主要原因是在開發 Omaha 流程時,我們保留了 TUF 流程,且未變更套件 URL 命名空間結構。

安全性考量

這項解決方案涉及許多安全考量。首先,這項設計首次在使用者裝置上導入間接驗證。這本質上是風險較高的安全態勢,但我們認為這種取捨值得,而且可透過現有的所有防禦措施和規劃的緩解措施進行管理。

以下是與 TUF 替代方案不同的安全性相關工作:

  • 我們需要新增一個信任的通訊協定,以及該通訊協定的剖析器:CUP
  • 我們需要在 vbmeta 中新增 CUP 公開金鑰。
  • 我們需要讓 omaha-client 實作 fuchsia.pkg.PackageResolver 通訊協定
  • 我們需要在 Omaha 用戶端實施某種程度的可執行性限制,可能類似於「可執行性控制項」中詳述的每個存放區設定可執行性位元。

所有這些安全工作都可以追蹤,但需要詳細設計,並與安全團隊協調。

隱私權注意事項

Omaha 伺服器在伺服器端收集的任何指標都無法停用,因為用戶端必須將執行環境 (ABI 修訂版本、應用程式 ID 等) 提供給伺服器,而伺服器可能會決定保留該互動的指標。

Google Omaha 伺服器的 Omaha 指標存取權受到嚴格限制,但可提供精確的數字,瞭解有多少裝置已下載或正在執行特定軟體版本。

如要進一步瞭解 Omaha 通訊協定如何保護隱私權,請參閱這份規格文件

測試

如要瞭解如何評估套件版本,請參閱相關章節

實作部分詳述的變更內容需要經過全面測試。具體來說,我們需要新增對樹狀結構內 Omaha 伺服器的支援,才能針對 Omaha 用戶端執行整合測試。我們也會投入資源,針對 Omaha 用戶端與套件解析器之間的新互動進行整合測試。

我們將使用新的 Omaha 伺服器,針對新的更新流程進行端對端測試。

我們也需要嚴格測試新的垃圾收集機制,包括填滿磁碟的整合測試,確保系統更新仍可安裝。

最後,我們需要嚴格驗證新或變更的安全性介面:CUP 回應驗證和持續性,以及可執行性限制。

說明文件

我們會在推出這些異動時,更新 fuchsia.dev 上的軟體發布文件,詳細說明至少下列事項:

  • 可更新套件的模型和狀態轉換圖
  • 如何設定更新
  • Omaha 伺服器實作作業的必要條件

之後的作業

Update Notification API

在實作這項初步提案後,我們也會為用戶提供通知 API,讓他們訂閱套件的更新資訊 (以便在需要時觸發元件重新啟動)。更新通知有兩種用途:

當套件的元件由父項元件重新解析時,套件解析作業會使用新版套件,這可能發生在元件自行重新啟動或工作階段重新啟動時。

我們提議採用新的元件解析器 API,可執行下列操作:

  • 解析元件
  • 提供管道,接收該元件可用更新的通知
  • 在下載新版本完成且有新版本可用時,通知管道另一端的持有者,然後提供自我重新啟動機制

我們會透過元件解析器,將通知和觸發邏輯傳遞至套件解析器,後者會提供類似的 API。我們也會新增元件向 framework 要求通知元件本身有更新的能力,讓元件觸發自己的重新啟動。

更新套件的 Blob 損毀通知

如果套件下載後損毀,我們應嘗試重新下載損毀的 Blob,或使用該套件的回溯版本 (如有),前提是該版本符合使用回溯版本的規定

這項作業需要整合現有的 Blob 損毀通知 API,並觸發重新下載或清除損毀的可更新套件快取 CUP 中繼資料。

無法啟動的可更新套件的前景更新

如果可更新套件中的元件無法啟動,我們應讓產品擁有者選擇顯示前景更新畫面,同時讓套件系統重新下載套件。這樣一來,使用者就不必將裝置還原為原廠設定,才能執行套件的回溯版本。

更新可更新元件的自我檢查

部分元件可能需要檢查自身更新並觸發下載。我們應調查這項功能有哪些客戶,以及是否應將其整合至元件架構和軟體交付 API。

缺點、替代方案和未知事項

替代方案:使用 TUF 進行套件協商

我們可以使用更新架構,發布套件版本中繼資料,並將特定 fuchsia-pkg:// 網址轉換為 Blob 的主機名稱和 Merkle 樹狀結構,以供下載,不必使用 Omaha 架構的通訊協定。

基本設計如下:

  • pkg-resolver 中新增「急切更新」函式,複製目前 omaha-client 狀態機器的各個層面,並定期檢查套件群組的更新。
  • 全面修訂 TUF 中繼資料格式,並在 Fuchsia 更新伺服器上實作新格式,藉此實作管道、階段性推出、踏腳石等功能。這項作業相當重要。
  • 當 pkg-resolver 收到特定套件網址的要求時,請下載該存放區中所有套件的新中繼資料 (如果沒有網路或更新伺服器的路徑,則使用持續性中繼資料),並根據要求內容 (包括管道、分階段推出成員資格等) 計算正確的 Merkle 根,以便下載。所有這些內容都必須在本機裝置上保留。也就是說,伺服器無法根據情境變更對用戶端的回應,所有運算都會在本地完成。
  • 儲存每個急切更新套件的下載中繼資料,這可能相當重要,視 TUF 存放區中存在的管道和版本數量而定 (根據我們的估計,如果採用簡單方法,每個套件約 3 MiB;如果我們投入更多心力啟用 TUF 中繼資料分片,每個套件約 82 KiB)
  • 找出客戶可接受的 Cobalt 基礎指標方法 (可提供特定版本裝置執行次數等資訊的精確度),且不會違反 Cobalt 的設計原則
  • 透過 ffx 新增工程工作流程,支援急切更新存放區,並更新開發人員工具,支援新的 TUF 中繼資料格式。
  • 實作防止降級的機制 (Omaha 會在伺服器端提供這項機制)。

這個替代方案有幾項優點:

  • 由於 TUF 中繼資料會持續存在,因此用戶端可以「檢查工作」,因為證明套件正確版本所需的所有資料,都會實際儲存在裝置上。
  • 我們已在工程流程中使用 TUF,因此已有許多相關工具。
  • 與 TUF 存放區和套件名稱對應的套件網址概念很有用,而且不需要對應至其他服務專屬 ID,例如 Omaha 應用程式 ID。

不過,這個替代方案也有缺點:

  • 我們已在正式版服務中使用 Omaha 進行系統更新,因此這會是第二個更新伺服器,也是執行正式版 Fuchsia 裝置的必要條件。
  • 我們必須重新導入 Omaha 已提供的許多功能,包括階段性推出、管道支援和墊腳石。
  • 如果系統映像檔中的套件協商程式碼有誤,我們就必須透過 OTA 更新裝置來修正,而不是在伺服器端修正協商邏輯

我們認為這種做法的缺點大於優點,而且這兩種做法的差異大多可以抽象化,因此我們選擇採用上述以 Omaha 為基礎的做法。

既有技術和參考資料

可更新的軟體套件實作方式非常多。其中幾個重要項目如下:

更新中繼資料通訊協定的參考資料如下:

  • 許多 Google 和非 Google 產品 (包括 Fuchsia) 都使用 Omaha 規格
  • 更新架構規格,Fuchsia 目前在某些地方使用這項規格。

附錄:發布時的大小計算

對於大小預算嚴格的裝置,我們不希望使用過多空間而干擾 OTA,因此需要套件的選用大小預算,我們會在套件發布時檢查。

Fuchsia 目前會將資產寫入磁碟時進行串流壓縮,而壓縮層級是在系統組裝時決定。如要預先計算特定套件會佔用多少空間,我們需要瞭解特定系統版本的壓縮層級,因此建議將 blob 壓縮層級和壓縮配置納入系統 ABI。這樣我們就能在伺服器上使用相同的壓縮參數,檢查套件是否符合預算,並將套件傳遞至套件存放區或發布失敗。

在發布程序中,套件伺服器基礎架構會檢查應用程式的壓縮大小 (使用目標 ABI 的壓縮參數和 SDK 中的工具壓縮的一組 Blob 的累計大小),是否符合目標裝置上為套件設定的預算。

即使我們在發布程序中進行的檢查再完善,最終仍可能導致檢查程序中斷,裝置在嘗試下載更新時空間不足。如要進一步瞭解如何管理這項功能,請參閱空間管理和垃圾收集一節。

除了模擬裝置上的壓縮作業來檢查大小限制,我們也可以實作離線壓縮,這樣就能確切瞭解套件中一組特定檔案會佔用多少空間。不過,這項工作並未列為優先事項,而且可能需要大幅變更 SWD 和 Storage 程式碼集。