讀零信任網路:在不可信網路中構建安全系統12原始碼和構建系統

躺柒發表於2024-08-08

1. 建立應用信任

1.1. 軟體正在吞噬整個世界

1.2. 零信任網路需要關注應用程式的安全性,這似乎違反直覺,畢竟網路是不可信的,因此可以預見網路上存在不可信的應用

1.3. 執行在資料中心的軟體堪稱一切魔法之源,因此,毋庸置疑,任何人都期望軟體能按照預期執行

1.4. 在可信裝置上執行的程式碼能夠準確地執行,裝置可信是程式碼可信的前提

1.5. 實現裝置可信只完成了一半,同時還必須信任程式碼本身和編寫它們的程式設計師

1.6. 建立程式碼信任的要求

  • 1.6.1. 確保建立程式碼的人可信

  • 1.6.2. 確保從程式碼生成應用的過程可信

  • 1.6.3. 確保應用被部署到基礎設施的過程可信

  • 1.6.4. 持續監控可信應用,防止應用程式被惡意程式所操縱

2. 應用流水線

2.1. 採用流水線可將可信開發人員編寫的軟體轉換成應用並最終部署到基礎設施中

2.2. 在計算機系統中,程式碼的建立、交付和執行構成了一條非常敏感的事件鏈

  • 2.2.1. 每個步驟中都存在攻擊向量,潛在的破壞難以察覺

  • 2.2.2. 必須確保事件鏈中每個環節的潛在破壞都能夠被監測

  • 2.2.3. 為支撐軟體交付鏈的安全,整個過程的每個環節都需要完全審計並且在關鍵節點進行加碼驗證措施

2.3. 4個不同階段

  • 2.3.1. 原始碼

  • 2.3.2. 構建/編譯

  • 2.3.3. 分發

  • 2.3.4. 執行

3. 原始碼

3.1. 編寫原始碼是軟體執行的第一步

  • 3.1.1. 如果開發人員不可信,那麼其編寫的原始碼就很難得到信任

  • 3.1.2. 即使經過了仔細的程式碼審查,惡意開發人員仍然有可能在眾目睽睽之下故意植入惡意程式碼(並隱藏!)​

  • 3.1.3. 非惡意的開發人員也可能在無意中造成應用缺陷

  • 3.1.4. 零信任網路其實更專注於識別惡意使用,而不是去除這部分使用者的信任

3.2. 原始碼儲存在一個集中的程式碼庫中,許多開發人員在上面互動並提交程式碼

  • 3.2.1. 這些程式碼庫也必須嚴格控制,尤其是在允許構建/編譯系統直接訪問程式碼庫的情況下,更不可掉以輕心

3.3. 保護程式碼庫

  • 3.3.1. 對於程式碼庫的防護,傳統的安全措施依然有效,並且還有更多的高階安全措施可以考慮,其中包括一些基本安全原則,比如最小許可權原則,使用者只能獲得完成任務所必需的程式碼庫訪問許可權

  • 3.3.2. 對於集中程式碼庫而言,傳統的安全防護機制依然有效

  • 3.3.3. 雖然傳統安全機制依然有效並值得推薦,但隨著分散式原始碼控制的引入,情況會有所不同

  • 3.3.3.1. 由於程式碼可以透過多種方式進入分散式程式碼庫,因此安全問題變得更加難以解決

3.4. 驗證程式碼和審計跟蹤

  • 3.4.1. 許多版本控制系統(Version Control System,VCS)​,特別是分散式VCS,使用加密技術儲存原始碼歷史記錄

  • 3.4.1.1. 這種技術被稱為內容定址儲存,在資料庫中使用內容的加密雜湊作為該物件的標識,而不是位置或座標

  • 3.4.1.2. 透過這種方式計算原始檔雜湊,並將其作為原始檔在資料庫中存放的標識,可以確保原始檔中的任何更改都會有新的雜湊對應

  • 3.4.1.3. 這種機制意味著檔案的儲存是不可變的:一旦儲存,就不可更改

  • 3.4.2. 一些VCS系統對內容定址儲存機制進行了完善,將歷史資料本身儲存為物件

  • 3.4.2.1. Git

>  3.4.2.1.1. 將歷史提交記錄儲存為一個有向無環圖(Directed Acyclic Graph, DAG)​

>  3.4.2.1.2. 將“提交”本身作為資料庫中的物件,儲存提交時間、作者和前序提交標識等細節

>  3.4.2.1.3. 將前序提交的加密雜湊儲存在每個提交記錄上,可以形成一棵Merkle可信樹,透過加密方式驗證整個提交鏈是否未經修改

>  3.4.2.1.4. 隨著原始碼歷史記錄被分發給貢獻者,系統會獲得一項好處:不可能在其他貢獻者不注意的情況下改變歷史記錄

>  3.4.2.1.5. 以這種方式儲存DAG能夠確保歷史資料不被篡改——要徹底改變歷史是不可能的

>  3.4.2.1.6. 這種儲存並不能確保新的提交是經過授權且真實的

  >   3.4.2.1.6.1. 基於受信任開發人員的推送訪問許可權,這個惡意提交就出現在儲存庫中了

  >   3.4.2.1.6.2. 惡意的提交者可以在該欄位中放置他們想要的任何細節

>  3.4.2.1.7. 為防範這種攻擊向量,Git允許使用受信任開發人員的GPG金鑰對提交和標籤進行簽名

  >   3.4.2.1.7.1. 標籤指向特定歷史中起始提交的位置,可以使用GPG金鑰來簽名以確保某次釋出的真實性

  >   3.4.2.1.7.2. 對提交進行簽名能夠進一步驗證整個Git的歷史記錄,這使得攻擊者除非先竊取提交者的GPG金鑰,否則無法假冒其他提交者

>  3.4.2.1.8. 簽名的原始碼提供了顯著的好處,應該儘可能地使用它

  >   3.4.2.1.8.1. 不僅為人類提供了健壯的程式碼驗證,也為機器的程式碼驗證提供了魯棒性

  >   3.4.2.1.8.2. 一個完整簽名的歷史資料允許構建系統在編譯部署之前對程式碼進行身份驗證

  >   3.4.2.1.8.3. 第一個簽名的提交實際上預設信任之前的所有提交

3.5. 程式碼審查

  • 3.5.1. 為單個使用者賦予過大許可權非常危險

  • 3.5.2. 簽名能夠使我們對提交程式碼的開發人員進行身份驗證,但不能確保其提交的程式碼是正確或安全的

  • 3.5.3. 對開發人員賦予信任並不意味著開發人員就可以單方面將程式碼提交給敏感專案

  • 3.5.4. 在程式碼審查過程中,所有的貢獻都必須經過一個或多個額外的開發人員的批准

  • 3.5.4.1. 這個簡單的過程不僅極大地改善了軟體的質量,還降低了有意或無意的漏洞引入率

4. 構建系統的信任

4.1. 構建伺服器經常受到持續威脅的攻擊

  • 4.1.1. 構建伺服器具有更高的訪問許可權,並能夠生成直接在產品中執行的程式碼

4.2. 在構建階段難以發現程式碼的篡改和惡意程式碼的植入,因此,對構建服務實施強有力的保護非常重要

4.3. 風險

  • 4.3.1. 評估

  • 4.3.1.1. 構建所用的原始碼是符合預期的

  • 4.3.1.2. 構建流程/配置是符合預期的

  • 4.3.1.3. 構建的執行本身安全可靠,未被操縱

  • 4.3.2. 構建系統可以輸入簽名程式碼,編譯結果也可以簽名輸出,但輸入輸出之間的過程(構建本身)卻缺乏可靠的加密保護

  • 4.3.2.1. 這是構建系統的重要攻擊向量

  • 4.3.2.2. 如果缺乏正確的處理和驗證,那麼這類攻擊和破壞就很難被發現,甚至根本不可能被發現

  • 4.3.2.3. 構建配置及其執行缺乏密碼技術保護,這個環節的斷裂蘊含了巨大的威脅,是一個強大的攻擊向

  • 4.3.3. 考慮到構建過程的敏感性,如果要將這項工作外包,則務必謹慎評估

  • 4.3.3.1. 準確評估你成為高價值目標的可能性,從而決策是否構建外包

  • 4.3.4. 可重現構建這樣的技術可以幫助識別這方面的攻擊和破壞​,但卻無法防止其分發

  • 4.3.5. 構建伺服器本身的安全性很重要

  • 4.3.5.1. 如果構建伺服器被破壞,那麼它就不能再被信任來忠實地履行職責

  • 4.3.5.2. 可重現構建、不可變的主機和零信任模型本身可以在這方面有所幫助

4.4. 對輸入輸出建立信任

  • 4.4.1. 如果將構建系統看作是一個可信操作,那麼為了產生可信輸出顯然需要首先信任構建系統的輸入

  • 4.4.2. 作為版本控制系統的使用者,構建系統負責驗證原始碼的信任度

  • 4.4.2.1. 應透過一個經過身份驗證的通道(如TLS通道)來訪問版本控制系統

  • 4.4.2.2. 為了獲得額外的安全保障,標籤和/或提交應被簽名,構建系統在啟動構建之前應該首先驗證這些簽名或者簽名鏈

  • 4.4.3. 構建系統的另一個重要輸入是構建配置

  • 4.4.3.1. 對構建配置的攻擊可能會導致構建系統連結到惡意庫

  • 4.4.3.2. 即使是看似安全的最佳化選項,在關鍵的安全相關程式碼中也可能是惡意的,比如定時攻擊緩解程式碼可能會因為程式碼最佳化而意外失去作用

  • 4.4.3.3. 將構建配置置於原始碼管理之下,可以透過提交簽名來對其進行版本控制和驗證,從而確保構建配置也是可信輸入

  • 4.4.4. 構建系統需要為生成的目標程式進行簽名,以便下游系統能夠驗證其真實性

  • 4.4.4.1. 保護構建生成的目標程式和雜湊,將其分發給下游使用者,即可完成構建系統的可信輸出

4.5. 構建的可重現性

  • 4.5.1. 可重現構建是防止構建流水線被破壞的一個較好的工具

  • 4.5.2. 支援可重現構建的軟體採用確定的方式進行編譯,以保證無論給定的原始碼由誰構建,其生成的二進位制檔案都完全相同

  • 4.5.3. 允許多方檢查原始碼並生成相同的構建,從而確信用於生成某個二進位制檔案的構建過程沒有被篡改

  • 4.5.4. 可以很容易地檢測到構建過程中的惡意干擾或程式碼注入,將其與原始碼簽名結合,能夠實現一個健壯的流程,對原始碼和由它產生的目標二進位制檔案進行驗證

  • 4.5.5. 可重現構建聽起來很容易,但重現每個位元組都相同的目標二進位制檔案非常困難

  • 4.5.5.1. 釋出系統在虛擬檔案系統(Chroot Jail, Chroot“監牢”​)中儲存了所有的歷史構建包,並涵蓋構建配置中所需的所有依賴項

  • 4.5.5.2. 虛擬機器或容器可以是確保構建環境與執行構建的主機完全隔離的有用工具

4.6. 解耦釋出版本和工件(Artifact)版本

  • 4.6.1. 不變性(Immutable)構建對於確保構建和釋出系統的安全性至關重要

  • 4.6.2. 如果缺少不可變構建機制,那麼可能導致一個已知的“好”版本被替換掉,從而為針對底層構建工件的攻擊敞開大門,這會使得攻擊者將“壞”版本偽裝成一個“好”版本

  • 4.6.3. 構建系統生成的工件應該遵循“單寫多讀”​(Write Once Read Many)的原則

  • 4.6.4. 鑑於工件的版本不變性需求為其版本管理帶來了不小的壓力,許多專案傾向於使用有意義的版本號(如語義化版本號)​,以便告知使用者升級軟體可能導致的潛在影響

  • 4.6.5. 使用一個補丁級別的版本重新發布,或者修改規則並使用一個新的構建工件以相同的版本號重新發布

  • 4.6.5.1. 在許多專案中會選擇第二種方式,寧願確保市場版本釋出的版本號和當初宣稱的一致,也不重新變更版本號

>  4.6.5.1.1. 顯然不是一個好的選擇
  • 4.6.6. 在建立一個構建系統時,最好讓其能獨立產生一個不可變的版本(號)​,而不要受到釋出版本號的影響

  • 4.6.6.1. 可透過下游系統(如版本分發系統)管理釋出版本和工件版本之間的對映關係,這樣可以確保在不犧牲可用性或引入不安全實踐的前提下維護工件版本的不變性

相關文章