1 前言
隨著容器技術的發展,越來越多業務甚至核心業務開始采用這一輕量級虛擬化方案。作為一項依然處于發展階段的新技術,容器的安全性在不斷提高,也在不斷地受到挑戰。天翼云云容器引擎于去年 11 月底上線,目前已經在 22 個自研資源池部署上線。天翼云云容器引擎使用 ebpf 技術實現了細粒度容器安全,對主機和容器異常行為進行檢測,對有問題的節點和容器進行自動隔離,保證了多租戶容器平臺容器運行時安全。
BPF 是一項革命性的技術,可在無需編譯內核或加載內核模塊的情況下,安全地高效地附加到內核的各種事件上,對內核事件進行監控、跟蹤和可觀測性。BPF 可用于多種用途,如:開發性能分析工具、軟件定義網絡和安全等。我很榮幸獲得今年 openEuler Summit 大會的演講資格,做 BPF 技術知識和實踐經驗的分享。本文將作為技術分享,從 BPF 技術由來、架構演變、BPF 跟蹤、以及容器安全面對新挑戰,如何基于 BPF 技術實現容器運行時安全等方面進行介紹。
2 初出茅廬:BPF 只是一種數據包過濾技術
BPF 全稱是「Berkeley Packet Filter」,中文翻譯為「伯克利包過濾器」。它源于 1992 年伯克利實驗室,Steven McCanne 和 Van Jacobson 寫得一篇名為《The BSD Packet Filter: A New Architecture for User-level Packet Capture》的論文。該論文描述是在 BSD 系統上設計了一種新的用戶級的數據包過濾架構。在性能上,新的架構比當時基于棧過濾器的 CSPF 快 20 倍,比之前 Unix 的數據包過濾器,例如:SunOS 的 NIT(The Network Interface Tap )快 100 倍。
BPF 在數據包過濾上引入了兩大革新來提高性能:
BPF 是基于寄存器的過濾器,可以有效地工作在基于寄存器結構的 CPU 之上。
BPF 使用簡單無共享的緩存模型。數據包會先經過 BPF 過濾再拷貝到緩存,緩存不會拷貝所有數據包數據,這樣可以最大程度地減少了處理的數據量從而提高性能。
3 Linux 超能力終于到來了:eBPF 架構演變
3.1 eBPF 介紹
2013 年 BPF 技術沉默了 20 年之后,Alexei Starovoitov 提出了對 BPF 進行重大改寫。2013 年 9 月 Alexei 發布了補丁,名為「extended BPF」。eBPF 實現的最初目標是針對現代硬件進行優化。eBPF 增加了寄存器數量,將原有的 2 個 32 位寄存器增加到 10 個 64 位寄存器。由于寄存器數量和寬度的增加,函數參數可以交換更多的信息,編寫更復雜的程序。eBPF 生成的指令集比舊的 BPF 解釋器生成的機器碼執行速度提高了 4 倍。
當時 BPF 程序仍然限于內核空間使用,只有少數用戶空間程序可以編寫內核的 BPF 過濾器,例如:tcpdump 和 seccomp 。2014 年 3 月, 經過 Alexei Starovoitov 和 Daniel Borkmann 的進一步開發, Daniel 將 eBPF 提交到 Linux 內核中。2014 年 6 月 BPF JIT 組件提交到 Linux 3.15 中。2014 年 12 月 系統調用 bpf 提交到 Linux 3.18 中。隨后,Linux 4.x 加入了 BPF 對 kprobes、uprobe、tracepoints 和 perf_evnets 支持。至此,eBPF 完成了架構演變,eBPF 擴展到用戶空間成為了 BPF 技術的轉折點。正如 Alexei 在提交補丁的注釋中寫到:“這個補丁展示了 eBPF 的潛力”。當前 eBPF 不再局限于網絡棧,成為內核頂級的子系統。
后來,Alexei 將 eBPF 改為 BPF。原來的 BPF 就被稱為 cBPF「classic BPF」。現在 cBPF 已經基本廢棄,Linux 內核只運行 eBPF,內核會將加載的 cBPF 字節碼透明地轉換成 eBPF 再執行。
下面是 cBPF 和 eBPF 的對比:
緯度 | cBPF | eBPF |
---|---|---|
內核版本 | Linux 2.1.75(1997 年) | Linux 3.18(2014 年)[4.x for kprobe/uprobe/tracepoint/perf-event](注:雖然 eBPF 在 Linux 3.18 版本以后引入,并不代表只能在內核 3.18+ 版本上運行,低版本的內核升級到最新也可以使用 eBPF 能力,只是可能部分功能受限。) |
寄存器數目 | 2 個:A, X | 10 個:R0–R9, 另外 R10 是一個只讀的幀指針* R0 - eBPF 中內核函數的返回值和退出值* R1 - R5 - eBF 程序在內核中的參數值* R6 - R9 - 內核函數將保存的被調用者 callee 保存的寄存器* R10 -一個只讀的堆棧幀指針 |
寄存器寬度 | 32 位 | 64 位 |
存儲 | 16 個內存位: M[0–15] | 512 字節堆棧,無限制大小的 “map” 存儲 |
限制的內核調用 | 非常有限,僅限于 JIT 特定 | 有限,通過 bpf_call 指令調用 |
目標事件 | 數據包、 seccomp-BPF | 數據包、內核函數、用戶函數、跟蹤點 PMCs 等 |
接下來,讓我們來看看演變后的 BPF 架構。
3.2 eBPF 架構演變
BPF 是一個通用執行引擎,能夠高效地安全地執行基于系統事件的特定代碼。BPF 內部由字節碼指令,存儲對象和幫助函數組成。從某種意義上看,BPF 和 Java 虛擬機功能類似。對于 Java 開發人員而言,可以使用 javac 將高級編程語言編譯成機器代碼,Java 虛擬機是運行該機器代碼的專用程序。相應地,BPF 開發人員可以使用編譯器 LLVM 將 C 代碼編譯成 BPF 字節碼,字節碼指令在內核執行前必須通過 BPF 驗證器進行驗證,同時使用內核中的 BPF JIT 模塊,將字節碼指令直接轉成內核可執行的本地指令。編譯后的程序附加到內核的各種事件上,以便在 Linux 內核中運行該 BPF 程序。下圖是 BPF 架構圖:
BPF 使內核具有可編程性。BPF 程序是運行在各種內核事件上的小型程序。這與 JavaScript 程序有一些相似之處:JavaScript 是允許在瀏覽器事件,例如:鼠標單擊上運行的微型 Web 程序。BPF 是允許內核在系統和應用程序事件,例如:磁盤 I/O 上運行的微型程序。內核運行 BPF 程序之前,需要知道程序附加的執行點。程序執行點是由 BPF 程序類型確定。通過查看/kernel-src/sample/bpf/bpf_load.c 可以查看 BPF 程序類型。下面是定義在 bpf 頭文件中的 bpf 程序類型:
BPF 映射提供了內核和用戶空間雙向數據共享,允許用戶從內核和用戶空間讀取和寫入數據。BPF 映射的數據結構類型可以從簡單數組、哈希映射到自定義類型映射。下面是定義在 bpf 頭文件中的 bpf 映射類型:
3.3.BPF 與傳統 Linux 內核模塊的對比
BPF 看上去更像內核模塊,所以總是會拿來與 Linux 內核模塊方式進行對比,但 BPF 與內核模塊不同。BPF 在安全性、入門門檻上及高性能上比內核模塊都有優勢。
傳統 Linux 內核模塊開發,內核開發工程師通過直接修改內核代碼,每次功能的更新都需要重新編譯打包內核代碼。內核工程師可以開發即時加載的內核模塊,在運行時加載到 Linux 內核中,從而實現擴展內核功能的目的。然而每次內核版本的官方更新,可能會引起內核 API 的變化,因此你編寫的內核模塊可能會隨著每一個內核版本的發布而不可用,這樣就必須得為每次的內核版本更新調整你的模塊代碼,并且,錯誤的代碼會造成內核直接崩潰。
BPF 具有強安全性。BPF 程序不需要重新編譯內核,并且 BPF 驗證器會保證每個程序能夠安全運行,確保內核本身不會崩潰。BPF 虛擬機會使用 BPF JIT 編譯器將 BPF 字節碼生成本地機器字節碼,從而能獲得本地編譯后的程序運行速度。
下面是 BPF 與 Linux 內核模塊的對比:
維度 | Linux 內核模塊 | BPF |
---|---|---|
kprobes、tracepoints | 支持 | 支持 |
安全性 | 可能引入安全漏洞或導致內核 Panic | 通過驗證器進行檢查,可以保障內核安全 |
內核函數 | 可以調用內核函數 | 只能通過 BPF Helper 函數調用 |
編譯性 | 需要編譯內核 | 不需要編譯內核,引入頭文件即可 |
運行 | 基于相同內核運行 | 基于穩定 ABI 的 BPF 程序可以編譯一次,各處運行 |
與應用程序交互 | 打印日志或文件 | 通過 perf_event 或 map 結構 |
數據結構豐富性 | 一般 | 豐富 |
入門門檻 | 高 | 低 |
升級 | 需要卸載和加載,可能導致處理流程中斷 | 原子替換升級,不會造成處理流程中斷 |
內核內置 | 視情況而定 | 內核內置支持 |
4 BPF 實踐中的第一公民:BPF 跟蹤
BPF 跟蹤是 Linux 可觀測性的新方法。在 BPF 技術的眾多應用場景中,BPF 跟蹤是應用最廣泛的。2013 年 12 月 Alexei 已將 eBPF 用于跟蹤。BPF 跟蹤支持的各種內核事件包括:kprobes、uprobes、tracepoint 、USDT 和 perf_events:
kprobes:實現內核動態跟蹤。kprobes 可以跟蹤到 Linux 內核中的函數入口或返回點,但是不是穩定 ABI 接口,可能會因為內核版本變化導致,導致跟蹤失效。
uprobes:用戶級別的動態跟蹤。與 kprobes 類似,只是跟蹤的函數為用戶程序中的函數。
tracepoints:內核靜態跟蹤。tracepoints 是內核開發人員維護的跟蹤點,能夠提供穩定的 ABI 接口,但是由于是研發人員維護,數量和場景可能受限。
USDT:為用戶空間的應用程序提供了靜態跟蹤點。
perf_events:定時采樣和 PMC。
5 容器安全
5.1 容器生態鏈帶來新挑戰
虛擬機(VM)是一個物理硬件層抽象,用于將一臺服務器變成多臺服務器。管理程序允許多個 VM 在一臺機器上運行。每個 VM 都包含一整套操作系統、一個或多個應用、必要的二進制文件和庫資源,因此占用大量空間。VM 啟動也較慢。
容器是一種應用層抽象,用于將代碼和依賴資源打包在一起。多個容器可以在同一臺機器上運行,共享操作系統內核,但各自作為獨立的進程在用戶空間中運行。與虛擬機相比,容器占用的空間比較少(容器鏡像大小通常只有幾十兆),瞬間就能完成啟動。
容器技術面臨的新挑戰:
容器共享宿主機內核,隔離性相對較弱!
有 root 權限的用戶可以訪問所有容器資源!某容器提權后可能影響全局!
容器在主機網絡之上構建了一層 Overlay 網絡,使容器間的互訪避開了傳統網絡安全的防護!
容器的彈性伸縮性,使有些容器只是短暫運行,短暫運行的容器行為異常不容易被發現!
容器和容器編排給系統增加了新的元素,帶來新的風險!
5.2 容器安全事故:容器逃逸
在容器安全問題中,容器逃逸是最為嚴重,它直接影響到了承載容器的底層基礎設施的保密性、完整性和可用性。下面的情況會導致容器逃逸:
? 危險配置導致容器逃逸。在這些年的迭代中,容器社區一直在努力將「縱深防御」、「最小權限」等理念和原則落地。例如,Docker 已經將容器運行時的 Capabilities 黑名單機制改為如今的默認禁止所有 Capabilities,再以白名單方式賦予容器運行所需的最小權限。如果容器啟動,配置危險能力,或特權模式容器,或容器以 root 用戶權限運行都會導致容器逃逸。下面是容器運行時默認的最小權限。
? 危險掛載導致容器逃逸。Docker Socket 是 Docker 守護進程監聽的 Unix 域套接字,用來與守護進程通信——查詢信息或下發命令。如果在攻擊者可控的容器內掛載了該套接字文件(/var/run/docker.sock),容器逃逸就相當容易了,除非有進一步的權限限制。
下面通過一個小實驗來展示這種逃逸可能性:
1.準備 dockertest 鏡像,該鏡像是基于 ubuntu 鏡像安裝 docker,通過 docker commit 生成
2.創建一個容器并掛載/var/run/docker.sock
[root@bpftest~]#dockerrun-itd--namewith_docker_sock-v/var/run/docker.sock:/var/run/docker.sockdockertest
3.接著使用該客戶端通過 Docker Socket 與 Docker 守護進程通信,發送命令創建并運行一個新的容器,將宿主機的根目錄掛載到新創建的容器內部;
[root@bpftest~]#dockerexec-it/bin/bash [root@bpftest~]#dockerps [root@bpftest~]#dockerrun-it-v/:/hostdockertest/bin/bash
? 相關程序漏洞導致容器逃逸,例如:runc 漏洞 CVE-2019-5736 導致容器逃逸。
? 內核漏洞導致容器逃逸,例如:Copy_on_Write 臟牛漏洞,向 vDSO 內寫入 shellcode 并劫持正常函數的調用過程,導致容器逃逸。
下面是 2019 年排名最高的容器安全事故列表:
6 容器安全主控引擎
6.1 主機和容器異常活動的檢測
確保容器運行時安全的關鍵點:
? 降低容器的攻擊面,每個容器以最小權限運行,包括容器掛載的文件系統、網絡控制、運行命令等。
? 確保每個用戶對不同的容器擁有合適的權限。
? 使用安全容器控制容器之間的訪問。當前,Linux 的 Docker 容器技術隔離技術采用 Namespace 和 Cgroups 無法阻止提權攻擊或打破沙箱的攻擊。
? 使用 ebpf 跟蹤技術自動生成容器訪問控制權限。包括:容器對文件的可疑訪問,容器對系統的可疑調用,容器之間的可疑互訪,檢測容器的異常進程,對可疑行為進行取證。例如:
? 檢測容器運行時是否創建其他進程。
? 檢測容器運行時是否存在文件系統讀取和寫入的異常行為,例如在運行的容器中安裝了新軟件包或者更新配置。
? 檢測容器運行時是否打開了新的監聽端口或者建立意外連接的異常網絡活動。
? 檢測容器中用戶操作及可疑的 shell 腳本的執行。
6.2 隔離有問題的容器和節點
如果檢測到有問題的容器或節點,可以將節點設置為維護狀態,或使用網絡策略隔離有問題的容器,或將 deployment 的副本數設置為 0,刪除有問題的容器。同時,使用 sidecar WAF,進行虛擬補丁等進行容器應用安全防范。
6.3 大型公有云容器平臺安全主控引擎
下面是天翼云云容器引擎產品為了保證容器運行時安全實現的安全主控引擎:
? Pod 通過 sidecar 注入 WAF 組件對容器進行深度攻擊防御。
? 容器安全代理 Sage 組件以 Daemonset 形式部署在各個節點上,用來收集容器和主機異常行為,并通過自己的 sidecar 推送到消息隊列中。
? 安全主控引擎組件 jasmine 從消息隊列中拉取事件,對數據進行分析,對有故障的容器和主機進行隔離。并將事件推送給 SIEM 安全信息事件管理平臺進行管理。
-
編譯
+關注
關注
0文章
672瀏覽量
33459 -
BPF
+關注
關注
0文章
25瀏覽量
4188 -
容器技術
+關注
關注
1文章
21瀏覽量
5659
原文標題:基于 eBPF 實現容器運行時安全
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論