隨着容器及K8s的廣泛使用,越來越多的容器安全與隔離問題被暴露出來,如:容器逃逸、水平攻擊、DDos攻擊等嚴重威脅了辦公和生產環境的安全與穩定,影響了業務的正常運行。安全容器技術孕育而生,產生了kata、gVisor、unikernel等多種安全容器方案。本文旨在介紹各種安全容器方案,分析各方案特點,結合騰訊在容器安全領域的實踐,幫助讀者選擇適合自身特性的容器運行時。同時引入Rust-VMM項目,介紹 Rust-VMM 技術和生態,演示如何使用K8s調度和啟用Rust-VMM安全容器運行時,展望以Rust語言實現的容器運行時的廣闊前景。
容器安全與隔離
一個基於K8s集群構建的基礎設施中,內部存在不同層次的隔離,從容器到Pod再到節點最后到cluster,每一層隔離都有它的特點和特性,我們尤其關注Pod級別的隔離特性。
相比其他層次的安全隔離,Pod及容器級別的隔離對我們的挑戰非常大。容器在運行時使用root運行進程,盡管使用namespace技術為容器空間內的pid、uts、fib等進行了隔離,但由於各個容器共享系統內核,容器與內核間缺乏隔離保護,容易引發容器逃逸等安全問題,典型容器逃逸攻擊如:CVE-2018-14634、CVE-2016-5195、CVE-2019-5736 及 CVE-2019-14271等。
docker.vh.neargle.com:8888/?command_exec=python3 -c "import docker;client = docker.DockerClient(base_url='unix:///var/run/docker.sock');data = client.containers.run('alpine:latest', r'''sh -c \"echo 'ssh-rsa xxxxx root@620e839e9b02' >> /tmp/root/root/.ssh/authorized_keys\" ''', remove=True, volumes={'/': {'bind': '/tmp/root', 'mode': 'rw'}})"
上述腳本是一個簡單的例子,這段代碼會向docker.sock的端口發起請求,拉起一個alpine的容器,容器內進程會向所在主機注入一段SSH的公鑰。在容器里的惡意用戶或者攻擊者,就可以獲輕松得這個容器所在host主機的SSH的登錄權限,從而能夠非法查看同主機其他容器空間的信息,篡改關鍵文件或記錄,甚至以主機為跳板攻擊整個集群。
還有一個就是Noisy Neighbor,就是吵鬧鄰居問題。關於Noisy Neighbor,容器方面已經有了很多進展,比如對於CPU、memory、bandwidth甚至是buffer IO,基於Cgroup對這些資源已經有了一些隔離和限制,但是這些限制並不能完全解決Noisy Neighbor的問題,還是有一些吵鬧的進程會影響到正常的業務進程的運行。
# kubectl run --rm -it bb --image=busybox sh
/ # f(){ f|f& };f # WARNING: Don't try this!
上面是一個簡單的例子,啟動一個busybox的容器,在里面執行一個嵌套循環的指令,會把這台主機上所有的file descriptor全部耗盡,造成這台主機上正常運行的業務進程工作不正常,這個是Noisy Neighbor的風險和問題。
對於上述問題,建議用戶關注官方的漏洞報告,升級操作系統或docker的版本,根據安全指引配置容器環境,同時可以參考以下措施增強容器集群的安全防護級別。
- 在物理層面上隔離,為不同的租戶之間划分不同的Hardware Isolation域,讓不同的租戶使用不同的硬件空間,從物理上、網絡上以及存儲上徹底的隔離,這也是最直接最有效的方法。
- 利用一些Security Tools,包括經常用的SElinux或者Cgroup隔離,划分不同的資源訪問和安全規則,但是這些安全規則需要編寫大量的profile文件,實現起來難度頗大。
- 入侵檢測機制,主機防御的一種手段。入侵檢測的軟件或者進程會監控這台主機上有風險的進程或者有風險的文件,對於這些文件的讀寫操作都會有安全方面的記錄,會即時預警,即時響應。比如對於containerd-shim/busybox/docker-runc的關鍵進程,比如docker-runc對於bash、init或者對於fd的訪問都可以做到即時的預警和標記。對於上面提到的容器逃離漏洞,通過入侵檢測的機制,就可以有一個比較有效的防御。
- 定制Linux Kernel Patch,一些特殊的資源隔離或者安全漏洞,也可以為kernel打一些自身特有的patch來加以防范,但是這里的patch面臨的問題是多種多樣的,所以這里就不再展開細講了,如果有關注的同學可以參考一下騰訊對於Linux Kernel方面的一些文章和報道。
容器運行時
上述安全實踐方案和措施能夠很大程度的減少對外提供服務時受攻擊的范圍,提高容器服務的安全能力。但我們仍然想要尋找一種簡單的、有效的、統一的runtime解決方法,我們把眼光投入到CNCF runtime landscape,可以看到有多種解決方案。
簡單梳理一下這些解決方案,都是按照兩大標准划分的。一個就是CRI的標准,偏向於kubelet或者K8s這一側的標准。還有一側的標准就是OCI,就是偏向於容器底層基礎實現。
可以看到OCI這邊有很多種實現方案,簡單梳理一下,划分了一個表格:
OCI Solution | OCI Compatible | Dedicated Docker Image | Implementation Language | Open source | Hot plug | Direct access to HW | Required Hypervisors | Backed by |
---|---|---|---|---|---|---|---|---|
Runc | yes | yes | golang | yes | no | yes | None | Docker |
gVisor + runsc | yes | yes | golang | yes | no | no | none or kvm | |
Kata + qemu | yes | yes | golang,C | yes | yes | yes | kvm | Hyper |
Firecracker + Firecracker containerd | no | yes | Rust,golang | yes | no | no | kvm | Amazon |
Nabla + runnc | yes | no | C,golang | yes | no | no | None | IBM |
簡單介紹一下,比如有runc的方案,就是基於原生namespace的容器方案,docker使用的,containerd直接對接的runc的方案。GVisor是谷歌開源出來的一種基於用戶態的內核進程的安全沙箱技術的方案。Kata和qemu的基於虛擬化實現安全容器,這個現在也是比較熱門,使用比較廣泛的一種方案。Firecracker,由AWS開源出來的,Firecracker Containerd是對接OCI標准的組件,但現在還沒有完全對接OCI標准,所以這里是有所欠缺的。最后是Nabla, runnc是Nabla對接OCI實現的一個組件,Nabla是IBM開源的一套安全容器的方案,但是它跟上述的一些方案有所區別,它是一個基於unikernel的方案,把業務應用和內核完全編譯在了一起,直接運行在host上面,所以可以看到它跟其他的方案不一樣的就是它不能直接使用容器的鏡像,需要自己編譯一個帶着內核的特殊鏡像,所以用起來不太符合容器的標准。
比較下來,典型的三種安全容器方案就是runc,kata-runtime,以及 gvisor 方案。
三種方案各有優缺點,從架構來說,runc的方案最簡單,直接用原生的namespace的方案。kata是基於hypervisor的方案,就是基於虛擬化的方案,所以它的每一個容器或者它的容器是運行在一個一個guest kernel上面,就是guest的虛擬機里,然后會有一個kata agent負責runtime跟底層真正跑的container的進行通信。GVisor是介於runc和kata runtime之間,它不像Kata有一個虛擬化的Guest Kernel的存在,它是把Guest Kernel變成了一個運行於用戶態的一個內核的進程叫做Sentry,Gofer是它IO的組件,核心的sentry負責的就是攔截和劫持所有運行在它的沙箱里面容器的所有的Syscall,對系統調用進行過濾和保護。
Solution | Typical Software | Bullet Point |
---|---|---|
Rule-based Sandbox | RunC | Needs to work with SELinux, AppArmor, Seccomp, cgroup |
VM-based Sandbox | Kata-container | BareMetal Only, Heavy control logic |
Application kernel based Sandbox | gVisor | Compatibility problem, Bottleneck in Sentry |
最后匯總比較一下,runc的方案實現最簡單,而且它的性能也是最好的。但是一大缺點就是安全隔離的特性不夠安全,所以需要大量的外部Security Tools的保護。
基於kata的虛擬化的沙箱方案,它只能運行在支持KVM虛擬化的環境上面,因為他的虛擬層是基於KVM實現的。Kata的邏輯的鏈路很長,調用的路徑也非常長,會造成一些性能上的損耗,也會有一些問題。
GVisor介於兩者之間,但是GVisor現在的問題就是它的兼容性不夠,300多個系統調用只能兼容里面的一部分,並且Sentry的作為核心會有一些性能瓶頸。
騰訊雲在安全容器上融合和上述方案的優點,結合騰訊雲在虛擬化,存儲和網絡方面的優勢,
選擇使用mVMd + QEMU + EKLET的方案,實現一個彈性的Kubernetes的服務,即EKS,大家可以訪問一下騰訊雲的官網,看一下EKS的介紹。
EKS是基於Hypervisor的虛擬化的解決方案,不同於Kata,EKS使用的containerd + mVMd組件更加輕量,調用路徑更短。通過containrtd + mVMd來實現對於上層K8s調用的CRI的解析,並且把它轉化成真正對於底層一個一個Guest VM或者QEMU的控制指令,在guest VM里會啟動相應的containers容器。
在整個的架構中,在Runtime方面最大的瓶頸是QEMU,因為QEMU有幾十年的歷史了,所以存在着它比較臃腫,反應慢,占用的資源多等等問題。所以讓QEMU作為底層Runtime還不夠快,不夠安全。為了增強QEMU的性能和安全特性,我們很自然把眼光投向了Rust-Vmm。
Rust-Vmm 介紹
Rust-Vmm是一個開源工程,是一個可以自由定制的VMM(virtual machine monitor)虛擬機管理器,用戶可以按照自己的方式訂制它。它是基於Rust語言實現的VMM,有着Rust語言帶來的優點和特性。
首先,Rust語言一個內存安全的語言,相比於用C或者C++會頻繁遇到的各種內存的問題,比如內存的溢出、空指針、野指針、越界訪問等等,更進一步會造成安全的問題、性能的問題,以及各種崩潰的問題。Rust語言很好地解決了這一點,從它的語法、編譯規則等杜絕了內存級別訪問的漏洞以及風險,所以用Rust寫的Rust-Vmm天然的就是內存安全的。
第二,Rust-Vmm是不易被攻擊的,Rust-VMM是從零開始的,它是從最小的硬件虛擬化出發的,最小的硬件虛擬化意味着它有着最小的攻擊面,被攻擊的面就非常少,所以它會很安全。
第三,Rust-Vmm能夠很靈活的定制。Rust-VMM可以靈活定制它的每一個組件,所有的對於設備的模擬或者關鍵特性的處理都是封裝成了一個一個的Rust-Vmm crates包,比如有VCPU,有linuxloader,vm-virtIO等等。其中crates是Rust語言中的包管理工具,可以理解JAVA或golang里面的package,它是以發行不同的包或者庫的形式對外發布它的feature。
第四,Rust-Vmm有非常高的性能,基於Rust語言的without garbage collection特性,它是沒有GC回收檢查機制的,不像JAVA或者其他更高級的語言會有一個runtime,Rust-Vmm的性能上會更好,同時基於KVM實現的虛擬化方案也是性能的保證。
簡單介紹一下Rust-Vmm的一個歷史,它是由谷歌首先實現的,谷歌首先實現一個Rust based的輕量級的VMM,它叫做crosVM,大家也可以從鏈接里面看到,它是一個為chrome瀏覽器做的一個微內核。然后AWS,亞馬遜基於谷歌開源出來的crosVM,實現了自己的基於rust的VMM叫Firecracker。兩個項目的開發人員會發現做這兩個項目的時候,會有很多重復的重疊的通用的代碼,很自然的把可以開源的、通用的部分結合到一塊,就有了Rust-Vmm的項目。
從這個全景圖里面可以看到,Rust-Vmm應用在多個項目和產品中。從最開始的谷歌開源的crosVM里面會有Rust-Vmm,比如AWS的Firecracker、以及其它CSP雲服務器廠商,比如QEMU里面會把一些耗時的或者內存訪問的部分也用Rust-Vmm實現,還有開源的Cloud Hypervisor項目。
Cloud Hypervisor是Intel開源出來的一套框架,把Rust-Vmm的組件組合在一起,能夠對外提供一個VMM的完整服務。用戶可以配置使用已公開發布的crates包,或集成自己的開發的crates包,通過將各種crates包組合在一起,就可以生成一個訂制的安全的高性能的VMM。
基於 Rust-Vmm 實現 K8s Runtime
講到這里,基於Rust-VMM的一個實現,組件、原料和所有的知識都已經具備了,接下來就是如何對接K8s,基於Rust-VMM實現Kubernetes runtime,運行一個Kubernetes的Pod。
測試環境如圖所示,用到了包括K8s, containerd, Kata, Rust-Vmm, Cloud Hypervisor等開源項目,首先我們配置Kubernetes節點上的kubelet使用的runtime為containerd, 在containerd上配置了Kata-runtime作為容器的運行時,再配置了Kata-runtime使用的vmm為基於Rust-Vmm crates構建的Cloud Hypervisor,同時為了提升性能,這里也是用了virtos-fs技術來承載物理機上的容器鏡像到子機內容器的imagefs的共享,所以在物理機上需要額外開啟virtiofsd的進程,在guest os的內核上也是使用最新的包含virtio-fs支持的內核版本。
在K8s上創建一個Pod后,調度器調度這個Pod到我們這個環境上的一個worker節點,節點上的kubelet進程根據我們的配置調用containerd,containerd的runtime插件kata來創建Cloud Hypervisor的vmm(virtual machine monitor),在子機內預置的Guest OS操作系統啟動后,kata-agent也隨之啟動,成功建立母機與子機的通道,后續cri協議中的createcontainer,startcontainer以及停止容器等接口也由這個通道傳遞到子機內的kata-agent,由其來調用子機內真正的容器運行時,從而完成整個pod的生命周期。
我們來看一下最終我們達到的一個效果,如圖中第一條指令,我們執行kubectl get pod命令可以看到pod的狀態為運行中,同樣的,我們也可以通過crictl pods命令查詢到這個Pod的狀態。通過ps查詢進程列表,可以看到包含這個pod id的kata shim進程,virtiofsd進程以及cloud-hypervisor這個vmm的進程。最后我們也可以通過kubectl exec -it交互式的進入pod的container內,通過uname返回此容器使用的內核版本為5.6,物理機的內核版本為5.4。
總結與展望
最后是對未來的展望,基於Rust-Vmm我們想要做得更多。比如希望擴展一下Rust-Vmm在網絡以及存儲方面的一些特性,尤其是在騰訊雲的環境里面,通過結合CBS和VPC,提升Runtime的性能。
第二,會結合入侵檢測、主機安全、安全工具等,把它們融合在一起,構建起一個多維的、多重防護的一套全維度安全的容器服務平台,Rust-VMM會實現里面的一部分。
第三,擴展Rust Runtime,尤其在邊緣計算領域,在邊緣端能夠實現K8s以下完全由Rust接管,由Rust語言全部重寫,比如底層的Containerd、 shim等組件,變成一個Full Stack Rust的實現。
最后,因為Rust-VMM也是基於hypervisor的一個實現,所以基於hypervisor的一些特性,比如熱遷移、備份、回滾這些機制,未來都可以在Rust-Vmm上有所實現,這個也是我們的未來的一個設想。
最后大家如果想要了解更多關於我剛才介紹的K8s Runtime,Rust-Vmm以及EKS方面的知識,大家可以掃描下方的二維碼加入騰訊雲容器團隊的公眾號。
或者點擊下方的網址了解TKE的公有雲產品。騰訊雲對外也有一個開源出來的項目叫做TKEstack,大家可以從下方的github網址訪問得到,也希望大家多多支持。
-
Learn more about TKE:
-
Join us:
最后如果大家有興趣,立志於開源事業,立志於CNCF的雲原生事業,也歡迎大家加入騰訊的團隊。鏈接就在上方,歡迎大家加入我們。
Q&A
-
現在的Rust-VMM項目開源了,哪里可以看到項目相關的信息?
Rust-VMM是有谷歌,也有亞馬遜、英特爾,也有業內的廠商共同開源出來的一個項目,相關的信息大家可以從github(https://github.com/rust-vmm)上面找到它們開源的項目,另外google一下就可以找到這個項目更多的信息。
-
基於Rust-Vmm成熟度如何,基於Rust-Vmm構建的安全容器主要解決了哪些難題。
大家可以對比一下AWS開源的Firecracker,它就是基於Rust-Vmm實現的一套亞馬遜自己的方案,而且這套方案已經應用在了亞馬遜fargate以及lambda的項目,serverless的解決方案里面。所以基於Rust-Vmm這種項目的成熟度,亞馬遜已經把它做到商用了,這一點是有所保證的。
但是Rust-Vmm本身又是開源的項目,它是跟AWS的firecracker有不太一樣的地方,成熟度方面肯定是有所欠缺的,需要廠商自己維護和保證質量問題或者安全問題。
因此我想說基於Rust-Vmm實現的方案,亞馬遜已經幫我們做了前驅的工作,已經幫我們做了一個商業上的試水,所以這個是沒有問題的。
-
是否現在的公有雲容器運行時都不傾向於使用runc。
不是不使用runc,使用runc有一些隔離的問題。比如兩個租戶共用一個物理資源池,或者兩個租戶的容器運行在一個物理節點上,這個時候就帶來了容器逃離以及吵鬧鄰居的問題,多租戶的場景下或者serverless的場景下就會有這種問題。
所以大部分的公有雲廠商的解決方案其實就是物理隔離。一個租戶所在的物理主機都是屬於自己的,屬於同一個租戶的,所以你會發現某一個用戶假如從一個容器里面逃離出去之后,逃離出去的這台主機還是自己的,所以這樣逃出去,從物理上面還有安全防護的。
但是這種面對於serverless的場景,serverless天然的就是要脫離主機的管理、脫離主機的隔離,所以serverless場景下,跟公有雲的容器服務就有所區別,它就不能簡單靠物理隔離的方式實現的,它就要考慮沙箱(安全容器)的技術,比如像剛才我也介紹了多種多樣的沙箱隔離的技術,我們可能選擇的就是這種基於hypervisor的實現方案。
如果感興趣,您可以訪問一下騰訊雲的官網,看一下里面EKS相關的介紹,這個就是騰訊對於這一領域的解決方案和貢獻。
【騰訊雲原生】雲說新品、雲研新術、雲游新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!