本文作者:王振威 - CODING 研發總監
CODING 創始團隊成員之一,多年系統軟體開發經驗,擅長 Linux,Golang,Java,Ruby,Docker 等技術領域。近兩年來一直在 CODING 從事系統架構和運維工作。
不同型別的企業資產有不同的管理辦法,但守護資產的安全性無一例外都是重中之重,但對如何保障程式碼資產安全並沒有形成統一認知。本文將就“程式碼資產的安全性”這一話題展開全面的闡述,嘗試從程式碼管理的生命週期進行全鏈路分析,讀者可以據此來審視自己企業的程式碼資產安全。
程式碼資產安全是什麼
程式碼資產安全不等於資訊保安
程式碼資產安全不等於資訊保安,這是很容易理解的。整個企業的資訊系統組成不僅僅是程式碼資產,甚至可以說大多數情況下不涉及程式碼資產。企業的資訊系統往往由基礎計算設施、網路平臺、軟體、資料庫等方面組成。資訊保安重點是關注上述資訊設施在投產之後執行過程中的安全問題。而大多數軟體執行的程式包是經由原始碼編譯的結果,跟原始碼本身是分割開來的。資訊保安關注的方面更為全面,程式碼資產安全只是其中的一部分,而且往往不是最為關注的一部分。
程式碼資產安全不等於程式碼安全
程式碼資產安全不等於程式碼安全,這不太容易理解。程式碼安全往往指代程式碼本身的安全性,如程式碼中是否有遠端過程執行漏洞,注入漏洞等等。而程式碼資產安全是一個管理概念,強調管理過程的安全,而非程式碼本身安全,例如某研究機構需要研究某種計算機病毒,他們需要在原始碼庫中存放對應病毒的原始碼。這病毒的原始碼就是這個機構的重要資產。
程式碼管理系統不審視原始碼中的漏洞或者惡意行為,而是必須忠實地確儲存儲的程式碼的原始檔案。
程式碼資產管理是圍繞程式碼倉庫的全生命週期管理
程式碼資產管理的核心是程式碼倉庫。倉庫裡存放著企業的全部程式碼,配置檔案以及全部歷史版本。守護程式碼資產安全的核心就是圍繞程式碼倉庫的三個關鍵環節構建起全鏈路的安全能力,這三個環節分別是檢入,儲存和檢出。
檢入安全
檢入可以理解為開發者在開發環境上編輯好程式碼,並且把程式碼傳送到程式碼倉庫的過程。這個環節關注兩個方面,分別是機密性和完整性。
機密性
機密性是指開發者把開發環境中的程式碼檢入程式碼倉庫的過程不被第三方竊取,一般通過傳輸過程加密來實現。Git 程式碼倉庫最常用的是 HTTPS 和 SSH 傳輸協議。
HTTPS 協議是通過 HTTP 協議加上傳輸層安全協議(TLS)實現的。HTTP 協議是明文傳輸協議,這意味著如果沒有 TLS,網路節點中的路由裝置都可以輕鬆竊取程式碼。TLS 可以在 TCP 協議之上建立雙向加密能力,配合 HTTP 協議上就是 HTTPS。HTTPS 客戶端和服務端先通過非對稱加密協商加密演算法和金鑰,再使用協商的演算法和金鑰來進行對稱加密傳輸。本文不涉及具體演算法的安全性介紹,不過隨著密碼學的發展,演算法在與時俱進,我們可以認為加密演算法本身是安全的。
然而這一過程並不完備,攻擊者可以製作中間伺服器,使得客戶端在發起連線的時候誤連線了中間伺服器,從而跟這個中間伺服器進行加密通訊。這將導致即便是加密傳輸,但最終還是會被惡意伺服器竊取,這就形成了中間人攻擊。
行業推出了 CA(證照授權機構)機制應對此問題,即伺服器在提供加密傳輸服務前,要把自己的公鑰和服務的域名繫結,並且在全球公信的 CA 處登記。這樣一來,HTTPS 客戶端在嘗試建立加密連結的時候,會要求伺服器出示 CA 簽發的證照,客戶端可以使用預安裝在作業系統或者瀏覽器內的 CA 公鑰進行驗證,確認伺服器對域名的所有權,這樣一來就可以確保不會有中間人攻擊。有行業公信力 CA,也有企業內部 CA,而後者需要在客戶端安裝企業內部 CA 的證照檔案。
知名安全機構 Qualys 可以線上對 HTTPS 伺服器進行 SSL/TLS 多方面的報告評估,如下圖為兩家國內雲端計算公司推出的程式碼託管伺服器的評估:
HTTPS 雖然解決了傳輸安全,但在認證使用者身份這裡,Git 程式碼倉庫還是依賴 Basic Auth 機制來實現。Git 程式碼倉庫會要求 HTTPS 客戶端提供賬號密碼,並附在請求體中一併傳輸給伺服器,由伺服器來確認操作者身份。在傳輸過程中,賬號和密碼是被 TLS 一併加密傳輸的,我們不必擔心傳輸過程的密碼洩露問題。但開發者通常為了不必每次操作都輸入賬號密碼,會讓電腦記住密碼,如果不妥善處理,可能會導致洩露。這裡重點是一定不能把賬號密碼拼接在遠端倉庫訪問地址裡面,正確的做法是使用 Git 在各種作業系統下的 憑據管理器,如 macOS 是使用鑰匙串管理,Windows 是使用 Git Credential Manager for Windows 來進行管理。
SSH 是一種常用於遠端管理 Linux/Unix 伺服器的安全加密協議,其功能非常多樣。以 Git 為基礎的程式碼託管也常使用這個協議進行加密程式碼傳輸。使用者提前把自己的公鑰檔案配置在伺服器上後,可以在後續的傳輸過程中確認身份。
SSH 使用非對稱加密(使用者的公鑰)確認身份,用對稱加密傳輸資料。跟 HTTPS 不同的是,SSH 協議無法指定域名,所以無法引入 CA 機制來防止中間人攻擊。
但 SSH 客戶端在與未知伺服器進行連線時,會提示伺服器的公鑰指紋資訊,使用者應當對比服務供應商官方提供的公鑰公告和命令列提示資訊來確認伺服器身份,確保不被中間人攻擊。
如圖展示騰訊雲 CODING SSH 伺服器的公鑰指紋公示:
如圖所示,SSH 客戶端嘗試連線伺服器時給出的伺服器公鑰指紋確認:
在使用者確認身份(輸入 yes 並按下回車)後,SSH 客戶端會把伺服器的公鑰資訊記錄在 ~/.ssh/known_hosts 中,下次即可直接連線,不再詢問。
要點小結
- 程式碼的傳輸要使用雙向加密協議,HTTPS 和 SSH 都可以
- HTTPS 協議需要關注伺服器的證照籤發方(CA)的權威性
- HTTPS 協議需要關注客戶端是否安裝了不受信任的 CA 檔案(防止 CA 欺詐)
- 使用 Git 憑據管理器保管 Git HTTPS 協議的賬號密碼
- SSH 協議在使用的時候需要仔細比對伺服器提供的公鑰指紋跟服務提供商公告的公鑰指紋是否完全一致,防止中間人攻擊
- 客戶端需要注意防止攻擊者惡意篡改 ~/.ssh/known_hosts 檔案內容或者 SSH 的客戶端配置(可以通過忽略伺服器公鑰信任機制)
- 妥善保管 SSH 私鑰檔案(往往存放於 ~/.ssh/id_rsa),如 Linux 下確保此檔案的許可權是 400 等,防止他們讀取
完整性
程式碼檢入的完整性包含兩個方面:
- 開發者一次提交的程式碼變動是否完整(內容不被篡改)
- 某次提交是否確為某開發者做出的變動(不被冒名頂替)
以 Git 為例子,這個程式碼版本控制軟體已經從內生機制上確保了內容不被篡改。Git 採用一種類 Merkel 雜湊樹的機制來實現分層校驗。
雜湊是一種把任意資料對映成等長資料的演算法,且不可逆。雜湊演算法有的特點是原始資料發生一點變化,對映的結果會產生較大變化,而且這一變化毫無規律。對映後的等長的資料被稱為指紋。
雜湊演算法非常適合用來快速比較兩段資料是否完全一致(指紋一致幾乎可以推斷原文一致)。在我們上文中提到的對比 SSH 伺服器出示的公鑰指紋,和服務提供商公告的指紋就是這種原理的應用。
Merkel 雜湊樹:
Git 對倉庫中的每一個檔案內容和其基本資訊整合進行雜湊。會將一個目錄樹下的所有檔案路徑和檔案雜湊值組合再雜湊形成目錄樹的雜湊。會把目錄樹和提交資訊組合再雜湊,此雜湊結果就是 Git 的版本號。這意味著每次提交都產生一個完全不同的版本號,版本號即雜湊。在給定一個版本號,我們可以認為這個版本背後對應的全部檔案內容,歷史記錄,提交資訊,目錄結構都是完全一致的。對於確定的版本號就沒有篡改的可能性。
雜湊演算法小概率會產生衝突(同一個指紋對應多個不同原始資料的情況),這時可能導致一致性校驗失效。所以雜湊演算法也在與時俱進,如當下 MD5 演算法已經幾乎過時,Git 當前正在使用 SHA1 演算法,未來可能會升級到更為安全的 SHA256 演算法。
如圖展示 Git 中的某個目錄樹的內容資訊:
即便開發者自己提交的版本經過 Git 的層層雜湊,可以確保內容不被惡意篡改,但仍然有被冒名頂替的危險。
因為 Git 在提交過程不需要驗證使用者身份,而且提交可以被不同的人在各種傳輸過程中傳輸和展示。設想攻擊者冒充公司員工製造一個提交,卻被公司其他員工認為是公司內部人士會有多可怕。目前基於 Git,業界的普遍做法是引入 GPG 簽名機制。
GPG 是基於非對稱加密演算法的一個應用,其原理是使用私鑰處理一段資訊,得到一段新的資訊,這段新的資訊只能由私鑰生成,而且可以使用對應的公鑰來識別這段新的資訊的生成來源,這段新的資訊就被稱為數字簽名。
簡單來說,資訊釋出者使用自己的私鑰(私人印章)對要釋出的資訊(待簽名檔案)進行簽名,並且把原始檔案和數字簽名一併傳送給使用方。使用方持有釋出方的公鑰,對收到的數字簽名和原始檔案進行校驗就可以確認確實是釋出方發出的,未被冒名頂替。這類似給要釋出的資訊蓋了個章。
如圖展示 Git 中某個提交被開發者新增 GPG 簽名的效果:
要點小結
- Git 本身的雜湊機制可確保內容不被篡改
- 使用 GPG 為提交簽名可防止冒名頂替
- 伺服器端要校驗 Git 提交郵箱宣告和 GPG 簽名
儲存安全
儲存安全是指當程式碼被檢入到程式碼倉庫後,如何保證資料的機密性,完整性和可用性。拋開基礎設施的安全性不談,對於程式碼儲存來說,資料往往由資料庫資料和程式碼庫檔案組成,這裡重點討論程式碼檔案儲存安全問題。
機密性
程式碼倉庫中的程式碼大多直接存放於作業系統的磁碟中,在伺服器軟體進行讀寫操作的時候,不涉及網路傳輸的機密性風險,但直接寫入磁碟上的檔案在未做控制的情況下,往往可以被作業系統上的很多不相關程式隨意讀寫,這些非預期的程式碼讀寫會造成額外的風險。
一種做法是去控制每一個檔案的讀寫許可權,如統一設定為 600,另一種做法是乾脆只允許伺服器上執行一個業務程式,實現作業系統級別隔離。
容器技術提供了一種良好的隔離程式方案:如在 Kubernetes 體系下,程式碼倉庫儲存在 PV 上,並只被掛載進程式碼倉庫的應用容器內讀寫,而且基於容器的排程和彈性特性可以較好的支援高可用並避免資源浪費。
完整性和可用性
我們知道 Git 本身會通過雜湊校驗機制來確保倉庫的完整性,但前提是倉庫檔案是完備的。如果倉庫的檔案丟失或者損壞,Git 的雜湊校驗也將無法工作。資料的完整性有很多種解決方案,最常見的冷備,半實時備份,實時備份,磁碟快照等方案都是為了確保檔案在丟失或者損壞的時候可以找回,來確保倉庫的完整性的。不過總的來說,備份往往是事後的恢復手段,無法實現即時的自愈,最終依據備份機制來進行資料修復往往會影響可用性。
雖然業界沒有針對程式碼倉庫的通用高可用方案,但資料庫主從策略和 RAID 機制是兩個可以參考的做法,這裡來做下簡要介紹。
資料庫主從策略,一種做法是資料寫入主庫,從庫自動增量同步資料。當主庫發生故障時,從庫自動替代。程式碼儲存類似,可以把儲存節點分為主節點和從節點。
RAID 機制是一種磁碟分片儲存的冗餘機制,有多種做法,如 RAID5,分片儲存,並儲存一份校驗資訊,當任意一塊磁碟壞掉,可以通過校驗資訊來複原資料。
騰訊雲 CODING DevOps 在這方面進行了深入研究,並結合了主從和 RAID 的思路,實現了針對程式碼倉庫的高可用策略,可妥善保障倉庫的完整性。
如圖所示,對於 D 倉庫來說,他的主倉庫 D(m) 存放於第二個節點,他的從倉庫 D(s) 存放於第一個節點(實質上還可以設定更多從倉庫,這裡為了圖示方便,只顯示了一個)。這樣的設計讓各個節點都可以不閒置計算資源,而且任意一個節點出現損壞都可以快速恢復。
檢出安全
程式碼檢出後才能使用,而檢出也涉及傳輸機密性問題,這點與檢入部分沒有區別。而對於 Git 倉庫來說,檢出環節的倉庫完整性會由 Git 的雜湊校驗機制保證,也不會有太大問題。檢出環節的安全問題往往是因為不合適的許可權策略和金鑰管理導致程式碼洩露。
企業內部程式碼通常有如下四個場景:
- 檢出開發
- 閱讀評審
- 自動執行(CI,自動化測試等)
- 管理審計
檢出開發許可權
需要區分開發者能讀寫的許可權範圍,保護好關鍵資源和金鑰,按如下原則:
- 按照業務、元件等進行分門別類的存放,倉庫隔離
- 根據所處的部門和組織關係配置倉庫的許可權
- 為分支設定讀寫許可權,只允許有許可權的成員寫入
- 使用檔案鎖定方式保護敏感檔案不被誤修改
- 統一傳輸協議,如只允許 HTTPS 或者 SSH
- 為個人密碼,令牌,公鑰等設定有限期
- 審計密碼,令牌,公鑰等的使用記錄
- 為目錄設定讀寫許可權,只允許指定開發者讀取或者寫入某些目錄
- 禁止強制推送策略,防止程式碼被回退
如圖所示,設定倉庫內的目錄許可權:
閱讀評審許可權
訴求是看原始碼和輔助資訊,並做出自己的評審結果,不涉及寫入程式碼,按如下原則:
- 區分讀寫和只讀成員群體,禁用後者的寫入許可權
- 區分深入評審和輕量級評審,禁用後者的程式碼檢出許可權,只允許其 Web 頁面檢視原始碼
- 使用 CODEOWNERS 機制自動指定評審成員
如圖所示,設定倉庫的 CODEOWNERS:
自動執行許可權
自動檢出,檢出行為背後不對應一個人,不涉及程式碼寫回,按如下原則:
- 禁止成員把自己的密碼,令牌,金鑰用於自動執行
- 使用專案/倉庫令牌,部署公鑰機制確保令牌和金鑰只對指定倉庫有許可權
- 為不同場景設定專用的令牌,不得混用,也不得用於其他用途
- 為令牌,公鑰等設定有效期
- 為令牌,公鑰等設定禁止寫入許可權
- 審計令牌,公鑰等的使用記錄
如圖所示,設定令牌的許可權和有效期:
管理審計許可權
這種場景是非技術人員希望瞭解倉庫統計資訊,活躍情況,瞭解研發過程進度等,按如下原則:
- 給成員開放所管轄的倉庫列表和倉庫詳情的 Web 頁訪問許可權
- 禁止成員使用 HTTPS/SSH 協議把原始碼檢出到本地
- 禁止成員在網頁端下載原始碼包
如圖所示,設定禁止倉庫寫入等許可權
總結
程式碼資產管理是個體系化的工程,這個過程中的安全性不是某個單點可以完全保障的,需要從檢入,儲存,檢出三個環節對全鏈條進行風險分析。很多企業在這些方面很重視,但聚焦錯了方向,可能付出了很大努力,但實質上依然冒著程式碼資產的丟失和洩露的巨大風險。希望此文可以幫助企業正視程式碼資產安全,為程式碼資產管理者提供一個審視安全的基本框架。