如何搭建安全的 CI/CD 管道?

  • 2022 年 9 月 16 日
  • 筆記

Eolink 前端負責人黎芷君進行了《工程化- CI / CD》的主題演講,圍繞 CI/CD 管道安全的實踐,分享自己在搭建 CI/CD 管道過程中所總結的重要經驗,與開發者深入討論 「前後端」 那些事兒。

隨著互聯網越來越受重視,前端開發不再是簡單的實現一個介面,使用 Javascript 讓頁面有一定的交互特效。

在同一個時期的迭代里,我們可能需要同時開發瀏覽器應用、桌面端,甚至是 App、小程式等等。導致了我們迫切的需要考慮一種新的方式,優化我們前端的開發工作。而 CI/CD 是工程化的重要環節之一。

 

為什麼需要 CI/CD ?

我們每次項目迭代過程中都會聽到的各種抱怨:來自測試的抱怨、開發的抱怨,甚至是技術主管、運維的抱怨……

 時間一長,很可能會導致同一個項目組的成員關係越來越差,項目的品質也不會好。在項目迭代過程中的 5 大現狀:

 或許有人會說:項目發版一年只有那麼幾次,比起項目快速的迭代,搭建 CI/CD 系統只是一件必要但是不緊急的事情。

我們先來看看 GitLab 2020 DevSecOps 的調查數據統計:

 頻繁的發版,可能導致我們每天都得耗在發版里,根本沒有時間做新的迭代。這尚且是 2020 年的數據統計,如今已是 2022 年,發版只可能更加頻繁。

那麼,搭建 CI/CD 系統還是一件不緊急的事情嗎?

 

什麼是 CI/CD ?

CI/CD 起源於 70 年代,軟體工程的概念被提出,告訴我們不僅需要會開發軟體,還需要系統的、規範的開發和維護軟體,這標誌著工程化意識的覺醒。

直到幾十年後,2015 年比爾團隊的《鳳凰項目: 一個 IT 運維的傳奇故事》這本書才介紹了 CI/CD 的雛形。現今,CI/CD 已被廣泛地提起以及應用。

 從字面意思理解,CI/CD 是由兩部分組成的。

CI 指代持續集成,是指我們 Push 程式碼後對程式碼進行的一系列質保實踐。通過持續集成,我們可以更早地識別和修復錯誤以及安全問題。CD 是由持續交付和持續部署組成。簡單理解是上線過程的一組實踐,減少人為誤操作的風險。簡單的理解就是,CI/CD 是持續集成和持續交付結合的一組實踐。

傳統上我們將新程式碼從提交到生產中所需的大部分或全部都是人工干預,例如構建、測試和部署,以及基礎設施的配置等等。而 CI/CD,是將一切都自動化了。使用 CI/CD 管道,開發人員只需將更改後的程式碼 Push 上程式碼倉庫,然後 CI/CD 管道會自動構建和測試,最後進行交付和部署。

 

深入了解 CI/CD

回顧完整的 CI/CD 過程圖,我們可以發現版本控制和自動化測試是整個 CI/CD 管道中重要的兩個環節。

版本控制在 CI/CD 中主要用於觸發 CI/CD 流水線,它還有個分支管理策略,用於針對不同的環境的特殊場景做隔離處理。

自動化測試主要是使用自動化的手段去執行測試,包括單元、集成、性能和驗收等等。它可確保假設某一環節測試失敗了,則阻止將功能部署到生產中,並且提升了我們程式碼本身的品質等等作用。

以下是 CI/CD 搭建的基本原則:

 

搭建 PC 項目的 CI/CD 管道實例

怎麼去搭建 PC 也就是 electron 項目的 CI/CD 管道呢?

以 Eolink Apikit 項目作為示例,以下是我們搭建 CI/CD 系統的步驟:

 先給大家解釋一下項目背景, Eolink Apikit 平台除了提供 Web 瀏覽器應用外,還需要同時提供了 PC 桌面端應用,並且是經常性一起發版。

對 Web 應用來說,相對好一些,市面上很多現成的 CI/CD 方案能夠參考。但是 PC 由於存在各種難點,例如需要綁定機器資源、程式碼簽名等等,導致了它的 CI/CD 構建在一定程度上難以解決,並且市面上相關的資料也比較少,於是我們花了半個月時間,逐個去攻破難點,形成了現在的方案。

Eolink Apikit 項目的痛點,除了前面提到的缺乏可見性不存在之外,其他四個痛點:項目交付周期長、項目品質參差不齊、重複的執行測試、部署等操作以、發版等待時間過長,都一一具備。

 

難點

在我們 Eolink 的項目中,自動化測試是由三個環節組成,分別是:

因為單元測試、品質&安全檢測由於都是對程式碼的掃描以及測試,所以不存在表現不一致的情況。而端到端測試 ,它跟作業系統是有一定關係的,例如 Windows 下,關閉按鈕在右上角,而 Macos 是在左上角。

那麼,我們是怎麼解決的呢?

當時我們是在端到端測試的入口配置中引入了 Os 這個庫,通過配置 TestMatch 這個欄位解決的,例如當前運行端到端測試的作業系統是 Windows 時,我們的 TestMatch 設置匹配文件名的規則為 *.windows.spec.ts。這樣,對於依賴作業系統的用例場景,我們就可以快速的獨立處理了。

為什麼我們不建議直接在具體的用例腳本裡面引入 Os 包呢?因為其實我們絕大部分的場景用例都是通用的。所以針對它們,我們應該直接在 CI/CD 管道機器上運行。而針對特殊的場景用例,我們在構建完程式碼後,將它們和構建包一起分發到對應的作業系統,之後再在構建程式前跑一下就可以了。

針對第二個難點 「程式碼簽名需要物理硬體」,我們先看一下不進行程式碼簽名會怎麼樣?

上面兩張圖就是假設沒有進行程式碼簽名,Macos 和 Windows 各自的表現。雖說可以忽略,但是總歸對公司形象不太友好。Macos 程式碼簽名證書是電子憑證的,所以我們構建應用程式的時候只需要存在證書文件 ,使用 P12 & 密鑰就可以了。

Windows 才是問題的所在,首先我們選用的 Windows 簽名證書是 EV 程式碼簽名,相較於標準程式碼簽名,它不需要當應用程式下載量達到某一個值時才可以生效,並且可以直接對內核模式驅動簽名。

不過坑就隨之而來了,它的證書是存儲在一個稱為 Yubikey 的硬體里的。如果你要隨時隨地簽名,那麼你就需要時時刻刻把你的 Yubikey 帶上。

這也有辦法解決,我調研了很多 EV 簽名的公司,發現 ssl.com 是可以進行雲簽名的。但是,也因為是獨此一家,它的費用比較高,100$ 每個月。假設你不介意,這也是個不錯的選擇。

難點三 「構建應用和作業系統強綁定」 ,主要由於我們的 PC 框架是 electron,假設我們需要打 Windows 應用程式,就得先擁有一台 Window 作業系統的物理機器 ,再在裡面打包我們的 electron 項目。Macos、Linux 也是以此類推,得在對應的作業系統上打包,否則就會報錯。

難點四的 「通訊」 問題,主要是由於我們物理機器和 CI/CD 管道所在的伺服器不是同一台伺服器所導致的。最後是通過將打包的物理機器和 CI/CD 管道環境用 Openvpn 形成內網解決。

難點五 「Web 應用和 PC 應用程式碼平滑同步」 ,則是由於 Eolink apikit 項目是同時存在 Web 和 PC 兩個應用的,它們的程式碼大部分相同,只有個別體驗可能不太一樣。因此,我們不能完全使用一套程式碼,這就不屬於 CI/CD 的範疇了。

解決方案

主要問題在工具選型上,市面上的 CI/CD 工具很多,但是根據我們想要的形態,可直接歸為兩類,Github action 以及其他。

Github action 為什麼可以獨佔一類呢?Github action 是 Microsoft 的一個較新的 CI/CD 平台,支援運行在 Linux、MacOS、Windows ,甚至是 ARM 運行器上。它之所以可以獨佔一類是因為它巧妙的運用了 Matrix ,也就是矩陣策略。

前面我們提到 「應用程式構建和作業系統強綁定」 這一難點, Github action 讓我們可以直接無視它。Github action 的 Job 是支援綁定一個叫 Os 的變數的,表達的意思是你可以是在這款作業系統上運行你的任務,可以設定為 Macos 、Windows 、Linux 等等。

我們公司最近推出的 Eoapi 這款開源項目,它的 CI/CD 流水線就是使用 Github action 搭建的。

但是 Eolink Apikit 項目並不使用 Github action ,為什麼呢?最主要的的原因就是 Windows 簽名硬體這個坑。其他的 CI/CD 工具基本是大同小異,選擇用哪個取決於開發團隊的需求,畢竟適合自己的才是最好的。我們 Eolink apikit 項目使用的程式碼倉庫管理工具是 Gitlab ,秉著一路走到底的原則,選擇的 CI/CD 工具也是 Gitlab。

 以上是 Eolink apikit 最終設計的管道流程,當開發 Push 程式碼後,將會自動觸發 CI/CD 流水線,根據我們設定好的分支管理策略將流拆成兩條。

針對臨時分支,開發可能會經常 Push 。這種時候,我們就不在流水線上做任何測試覆蓋率的要求。

針對其他分支,我們將自動構造和單元測試、品質檢測並行執行。在這個過程中,單元測試覆蓋率是要求 80%,質檢需要各個指標為 0。後面就是常規的端到端測試,覆蓋率同為 80%。都沒問題的話,就直接將編譯好的程式碼和針對作業系統的用例分發給各個作業系統,以及最後上傳程式碼。過程中假設出現任意錯誤,就會馬上停止後面的流程。自動觸發告警,並將相關的錯誤資訊發送給提交者。

 從上圖我們可以看到詳細的執行步驟,如果還沒執行完,還可以看到當前哪些 Job 正在執行,哪些 Job 在 Pending。點擊每個 Job ,可以看到具體的 Job 執行資訊,發生錯誤還可以針對這個 Job 進行重試。最後,我們告警系統接入的是飛書機器人。假設執行過程中發生任意錯誤,都會通過飛書機器人發送通知給相應人,讓他們馬上回來調整。

關於 electron CI/CD 管道的搭建,總結了幾條建議,分享給大家:

 

CI/CD 管道安全的實踐

我們為什麼需要關注 CI/CD 管道安全呢?

從數據中,我們可以看到 2021 年世界上的軟體供應鏈攻擊增加了 6 倍多(650%)。同時 Gartner 也預測,到 2025 年全球會有將近 45% 的企業遭遇攻擊。

CI/CD 管道攻擊就屬於供應鏈攻擊。CI/CD 是我們軟體開發周期的重要組成部分,假設我們忽略它的安全,就有可能被人為攻擊管道漏洞,直接竊取我們的敏感資訊,甚至是交付惡意程式碼。

典型的案例有 2020 年 12 月的 Solar Winds 供應鏈攻擊事件,以及去年的攻擊者入侵了數千名開發人員使用的 Bash 上傳器的 Codecov 供應鏈攻擊事件等等。

諸多案例告訴了我們,提高 CI/CD 管道的安全性迫在眉睫。應該怎麼做才能避免呢?關鍵在於我們需要知道具體有哪些可能性風險,才能去逐一攻破。

在此,我們結合資訊安全三要素進行初步的分析,可以了解到 CI/CD 管道涉及敏感數據泄露是造成風險的主要因素,如 IP 、密鑰泄漏或者是漏洞的披露,而源碼被植入後門、惡意挖礦或者是其他惡意行為是供應鏈攻擊的主要一環。

 

十大 CI/CD 安全風險

風險1

不完善的流量控制機制導致的風險。

我們搭建 CI/CD 關注的更多是怎麼提效,往往會忽略它的安全。例如攻擊者會利用 CI 中分支保護規則的漏洞,繞過審查去發布惡意程式碼。

典型的案例是去年 4 月份在 PHP github 倉庫中植入後門的事件,以及上年 10 月份攻擊者使用 GitHub Actions 漏洞繞過審查機制的事件。

我們應該怎麼去防範呢?老話說得好,吃哪補哪,所以針對這個風險最好的方式是建立完善的管道流量控制機制,以確保沒有人或者軟體能夠在沒有驗證的情況下通過管道傳送惡意的程式碼或者軟體。例如,我們可以在受保護的分支上配置分支保護規則,所有用戶提交的程式碼都得經過它去做審查才能去發布。

風險2

假設我們的 CI/CD 環境存在很多身份憑證,不管是授予機器的還是人的。

一旦管理好,比如為了前期減少溝通成本,我們將所有帳號都賦予管理員許可權,就有可能被攻擊者利用,想幹什麼就幹什麼。

典型案例是 2019 年 Stack Overflow TeamCity 發生了一起安全事件,當時出現了一個沒有人認識的帳號,獲得了 Stack Exchange 網路中所有站點的審核者和開發人員級別的訪問許可權。官方追蹤後,發現是因為新註冊的帳號在訪問系統時會被自動分配管理員許可權。

由此可見,我們需要避免創建本地帳號。或者,我們可以使用像 Idap 這種集中式企業工具創建和管理帳號。

有人會說我不管,我就得創建本地帳號。那麼我們就需要確保能夠定時清理帳號,以及所有帳號的安全策略需要與企業策略是一致的。

風險3

相信絕大部分團隊都為項目引用過一些第三方開源組件,因為比起自己去實現,第三方開源組件有時候會考慮得更加全面一些,並且直接引用也更快。但是,我們千萬不要去濫用它。

首先,我們沒法保證引入的第三方開源組件是沒有漏洞的。像上年的 Apache 日誌控制項被爆出遠程程式碼執行漏洞,連這麼穩定的包都可能有漏洞,其他的又怎麼能確保完全安全呢。

其次,我們沒法知道第三方開源資源的貢獻策略是怎麼樣的,是否做了安全檢測。這導致攻擊者有可能擁有訪問開源組件倉庫的許可權,可以直接為開源組件添加惡意後門程式,並對外發布。

這樣,很容易引發大規模的供應鏈攻擊。攻擊者可以利用該後門對 CI/CD 環境進行探測,進而導致整個環境淪陷。

最後,在 CI/CD 管道中,我們通常會引入一些第三方工具對項目進行管理。例如 Nodejs 項目中,通常會引入 Npm 倉庫,若項目直接從 Npm 中央倉庫去拉組件,就無法確定是否會引入了含有漏洞的組件,進而可能導致組件漏洞被攻擊者利用。

針對這種風險,我們建議首先梳理項目中所有依賴的開源組件,可以通過 SBOM 進行梳理,並採用軟體成分分析工具對我們引入的開源資源進行漏洞掃描。當項目中引入了新的開源資源時,也能夠具備針對性的安全管控措施。

風險4

我們稱為 PPE,是「中毒」管道的執行所導致的,主要是中了攻擊者惡意程式碼或者惡意命令的毒。

根據 「中毒」 的手段,PPE 分為以下三種類型:

  • 第一種是攻擊者直接修改有許可權訪問的管道配置文件,在管道運行中觸發惡意命令來達到攻擊我們的效果,我們又稱之為直接 PPE (D-PPE) ;

  • 第二種是攻擊者通過向管道配置中所引用的文件注入惡意程式碼,來間接的毒害管道;

  • 最後一種是攻擊者假設需要通過獲取身份憑證來訪問管道配置文件,那麼他可以通過破壞公共項目達到攻擊私有項目的效果,從而挖掘更多的敏感資訊。

典型案例是上年 GitHub Actions 通過包含惡意程式碼的拉取請求濫用來挖掘加密貨幣事件。具體的解決方案是需要從之前對程式碼的審查,改進為現在同時需要對管道配置文件也進行審查,甚至是定時監控管道的運行情況。

風險5

基於管道的訪問控制不足所導致的風險。

它的存在將導致攻擊者直接將一段惡意程式碼注入到管道執行節點的上下文中,這樣,惡意程式碼就可以具有運行管道階段的完全許可權,可以訪問機密資訊、訪問底層主機,甚至訪問連接到相關管道的任何系統。

毋容置疑的將會導致我們機密數據泄露、CI 環境內的橫向移動,以及被惡意軟體部署到我們管道中,甚至是發布到生產環境中。

Codecov 事件中,就是因為疏於防範導致 Codecov 最終被破壞並用於從構建中竊取客戶的環境變數。我們要怎麼規避它呢?重點就是做好許可權管理,例如 CI/CD 管道作業在控制器節點上的許可權應該設置有限。

風險6

假設我們已經擁有了身份和訪問認證機制了,但是憑證管理不妥當也是會導致風險的。

例如,開發將包含憑證的程式碼推送到程式碼倉庫中,不管是有意還是無意的,這都將會導致我們將憑證暴露給對程式碼倉庫具有訪問許可權的人。即使後面我們發現不對,馬上將它從被推送到的分支上刪除,他們還是會在提交歷史記錄中出現。

調試時將憑證列印到控制台中,可能會使憑證以明文方式在日誌中公開,任何有權訪問構建結果的人都可以查看。這些日誌後面也可能會流入到日誌管理系統中,從而擴大了憑證的暴露面。

回看Codecov 攻擊事件,攻擊者就是通過破壞 CI 中的憑證,去竊取了存儲為環境變數的數千個憑證。解決方案中最重要的就是,不要在 CI/CD 環境中存儲任何敏感的資訊,至於其他都是次要的。

風險7

CI/CD 環境中不安全配置的系統導致的風險。

攻擊者利用有漏洞系統的安全漏洞來獲得對系統未經授權的訪問,或者更糟的情況是,破壞了系統並訪問其他底層作業系統。

都有哪些不安全的作業系統呢?例如使用過時版本或缺少重要安全修補程式的自我管理系統。或者是對底層作業系統具有管理許可權的自託管系統。 SolarWinds 構建系統的入侵就是典型的案例。除了以上的防範手段,還需要去定時為系統打修補程式。

風險8

和第三方開源資源濫用一樣,屬於無監管使用導致的風險。

缺乏對第三方服務的治理,會嚴重影響企業在其 CI/CD 系統維護管道中對於角色訪問控制的操作,企業也會變得很被動,安全性得取決於它們實施的第三方。

而第三方的最小特權,加上圍繞第三方實施過程的最小治理和盡職調查,都會顯著增加企業的攻擊面。

鑒於 CI/CD 系統和環境的高度互聯性,假設第三方的漏洞被利用,造成的危害是遠遠超出第三方所連接的系統範圍的損害的。例如,具有寫入許可權的第三方存儲庫,攻擊者可以將惡意程式碼推送到存儲庫,第三方存儲庫反過來又會觸發構建,並在構建系統上運行攻擊者的惡意程式碼。

這個風險防範手段很簡單,主要是圍繞第三方服務的治理控制,我們應在第三方使用生命周期的每個階段去實施。

風險9

CI/CD 流程是由多個步驟組成的,最終負責將程式碼從倉庫一路帶到生產環境。

每個步驟都可能有多個資源,最終軟體包依賴於分布在不同步驟中的多個來源,它們是由多個貢獻者提供的,從而讓攻擊者有可能通過這些入口點去篡改最終的軟體。如果被篡改的軟體成功地滲透到交付過程中,而不引起任何的懷疑或者沒有遇到任何安全檢測,它很可能就會直接以合法資源的名義繼續流經我們的管道,一直發布到我們的生產環境中。這就是不正確的軟體完整性驗證導致的風險。防止不正確的軟體完整性驗證風險需要一系列措施,跨越軟體交付鏈中的不同系統和階段。

風險10

強大的日誌系統是能夠幫助企業準備、檢測以及調查相關安全事件的,但是如果它不夠強大,那就會引來風險。

隨著攻擊者逐漸將注意力轉移到工程環境漏洞中,那些無法確保圍繞這些環境進行適當的日誌記錄和可見性控制的企業,就有可能無法檢測到違規的行為。

解決方案有哪些呢?雖然工作站、伺服器以及業務應用程式通常在企業的日誌記錄和可見性程式中得到深入介紹,但工程環境中的系統和流程通常並非如此。

鑒於利用工程環境和流程的潛在攻擊向量的數量,我們必須建立適當的能力以在這些攻擊發生時立即檢測到它們。其中許多載體涉及利用針對不同系統的程式化訪問,面臨這一挑戰的關鍵就是圍繞人工和機器訪問創建強大的可見性。

鑒於 CI/CD 攻擊向量的複雜性,系統的審計日誌(用戶訪問、用戶創建、許可權修改)和應用日誌(將事件推送到存儲庫、執行)需要具有同等的重要性構建以及上傳軟體。

 

總結:持續集成、自動化測試和持續部署

最後還需要注意的是,隨著業務越發複雜,系統架構從單體走向微服務,CI/CD 管道的複雜性也會相應增多,每個階段都可能會產生大量的敏感數據,這些敏感數據往往會成為巨大的攻擊槓桿。

試想一旦攻擊者拿到了源碼倉庫的訪問憑證,那麼整個 CI/CD 環境都可能遭到淪陷

在 Gitlab 的 CI/CD 起因統計報告的最後有這樣一句話:

我們之所以這麼頻繁的發布程式,是因為採用了 DevOps 方法,並且主要歸功於 CI/CD 中的持續集成、自動化測試和持續部署。

當然,安全也義不容辭。