了解如何使用 Podman 在單獨(dú)的用戶空間運(yùn)行容器。
Podman 是 libpod 庫(kù)的一部分,使用戶能夠管理 pod、容器和容器鏡像。在我的上一篇文章中,我寫(xiě)過(guò)將 Podman 作為一種更安全的運(yùn)行容器的方式。在這里,我將解釋如何使用 Podman 在單獨(dú)的用戶命名空間中運(yùn)行容器。
作為分離容器的一個(gè)很棒的功能,我一直在思考用戶命名空間,它主要是由 Red Hat 的 Eric Biederman 開(kāi)發(fā)的。用戶命名空間允許你指定用于運(yùn)行容器的用戶標(biāo)識(shí)符(UID)和組標(biāo)識(shí)符(GID)映射。這意味著你可以在容器內(nèi)以 UID 0 運(yùn)行,在容器外以 UID 100000 運(yùn)行。如果容器進(jìn)程逃逸出了容器,內(nèi)核會(huì)將它們視為以 UID 100000 運(yùn)行。不僅如此,任何未映射到用戶命名空間的 UID 所擁有的文件對(duì)象都將被視為 nobody
所擁有(UID 是 65534
, 由 kernel.overflowuid
指定),并且不允許容器進(jìn)程訪問(wèn),除非該對(duì)象可由“其他人”訪問(wèn)(即世界可讀/可寫(xiě))。
如果你擁有一個(gè)權(quán)限為 660 的屬主為“真實(shí)” root
的文件,而當(dāng)用戶命名空間中的容器進(jìn)程嘗試讀取它時(shí),會(huì)阻止它們?cè)L問(wèn)它,并且會(huì)將該文件視為 nobody
所擁有。
示例
以下是它是如何工作的。首先,我在 root
擁有的系統(tǒng)中創(chuàng)建一個(gè)文件。
接下來(lái),我將該文件卷掛載到一個(gè)使用用戶命名空間映射 0:100000:5000
運(yùn)行的容器中。
上面的 --uidmap
設(shè)置告訴 Podman 在容器內(nèi)映射一系列的 5000 個(gè) UID,從容器外的 UID 100000 開(kāi)始的范圍(100000-104999)映射到容器內(nèi) UID 0 開(kāi)始的范圍(0-4999)。在容器內(nèi)部,如果我的進(jìn)程以 UID 1 運(yùn)行,則它在主機(jī)上為 100001。
由于實(shí)際的 UID=0
未映射到容器中,因此 root
擁有的任何文件都將被視為 nobody
所擁有。即使容器內(nèi)的進(jìn)程具有 CAP_DAC_OVERRIDE
能力,也無(wú)法覆蓋此種保護(hù)。DAC_OVERRIDE
能力使得 root 的進(jìn)程能夠讀/寫(xiě)系統(tǒng)上的任何文件,即使進(jìn)程不是 root
用戶擁有的,也不是全局可讀或可寫(xiě)的。
用戶命名空間的功能與宿主機(jī)上的功能不同。它們是命名空間的功能。這意味著我的容器的 root 只在容器內(nèi)具有功能 —— 實(shí)際上只有該范圍內(nèi)的 UID 映射到內(nèi)用戶命名空間。如果容器進(jìn)程逃逸出了容器,則它將沒(méi)有任何非映射到用戶命名空間的 UID 之外的功能,這包括 UID=0
。即使進(jìn)程可能以某種方式進(jìn)入另一個(gè)容器,如果容器使用不同范圍的 UID,它們也不具備這些功能。
請(qǐng)注意,SELinux 和其他技術(shù)還限制了容器進(jìn)程破開(kāi)容器時(shí)會(huì)發(fā)生的情況。
使用 podman top 來(lái)顯示用戶名字空間
我們?cè)?podman top
中添加了一些功能,允許你檢查容器內(nèi)運(yùn)行的進(jìn)程的用戶名,并標(biāo)識(shí)它們?cè)谒拗鳈C(jī)上的真實(shí) UID。
讓我們首先使用我們的 UID 映射運(yùn)行一個(gè) sleep
容器。
現(xiàn)在運(yùn)行 podman top
:
注意 podman top
報(bào)告用戶進(jìn)程在容器內(nèi)以 root
身份運(yùn)行,但在宿主機(jī)(HUSER
)上以 UID 100000 運(yùn)行。此外,ps
命令確認(rèn) sleep
過(guò)程以 UID 100000 運(yùn)行。
現(xiàn)在讓我們運(yùn)行第二個(gè)容器,但這次我們將選擇一個(gè)單獨(dú)的 UID 映射,從 200000 開(kāi)始。
請(qǐng)注意,podman top
報(bào)告第二個(gè)容器在容器內(nèi)以 root
身份運(yùn)行,但在宿主機(jī)上是 UID=200000。
另請(qǐng)參閱 ps
命令,它顯示兩個(gè) sleep
進(jìn)程都在運(yùn)行:一個(gè)為 100000,另一個(gè)為 200000。
這意味著在單獨(dú)的用戶命名空間內(nèi)運(yùn)行容器可以在進(jìn)程之間進(jìn)行傳統(tǒng)的 UID 分離,而這從一開(kāi)始就是 Linux/Unix 的標(biāo)準(zhǔn)安全工具。
用戶名字空間的問(wèn)題
幾年來(lái),我一直主張用戶命名空間應(yīng)該作為每個(gè)人應(yīng)該有的安全工具,但幾乎沒(méi)有人使用過(guò)。原因是沒(méi)有任何文件系統(tǒng)支持,也沒(méi)有一個(gè)移動(dòng)文件系統(tǒng)。
在容器中,你希望在許多容器之間共享基本鏡像。上面的每個(gè)示例中使用了 Fedora 基本鏡像。Fedora 鏡像中的大多數(shù)文件都由真實(shí)的 UID=0
擁有。如果我在此鏡像上使用用戶名稱空間 0:100000:5000
運(yùn)行容器,默認(rèn)情況下它會(huì)將所有這些文件視為 nobody
所擁有,因此我們需要移動(dòng)所有這些 UID 以匹配用戶名稱空間。多年來(lái),我想要一個(gè)掛載選項(xiàng)來(lái)告訴內(nèi)核重新映射這些文件 UID 以匹配用戶命名空間。上游內(nèi)核存儲(chǔ)開(kāi)發(fā)人員還在繼續(xù)研究,在此功能上已經(jīng)取得一些進(jìn)展,但這是一個(gè)難題。
由于由 Nalin Dahyabhai 領(lǐng)導(dǎo)的團(tuán)隊(duì)開(kāi)發(fā)的自動(dòng) chown 內(nèi)置于容器/存儲(chǔ)中,Podman 可以在同一鏡像上使用不同的用戶名稱空間。當(dāng) Podman 使用容器/存儲(chǔ),并且 Podman 在新的用戶命名空間中首次使用一個(gè)容器鏡像時(shí),容器/存儲(chǔ)會(huì) “chown”(如,更改所有權(quán))鏡像中的所有文件到用戶命名空間中映射的 UID 并創(chuàng)建一個(gè)新鏡像??梢园阉胂蟪梢粋€(gè) fedora:0:100000:5000
鏡像。
當(dāng) Podman 在具有相同 UID 映射的鏡像上運(yùn)行另一個(gè)容器時(shí),它使用“預(yù)先 chown”的鏡像。當(dāng)我在0:200000:5000
上運(yùn)行第二個(gè)容器時(shí),容器/存儲(chǔ)會(huì)創(chuàng)建第二個(gè)鏡像,我們稱之為 fedora:0:200000:5000
。
請(qǐng)注意,如果你正在執(zhí)行 podman build
或 podman commit
并將新創(chuàng)建的鏡像推送到容器注冊(cè)庫(kù),Podman 將使用容器/存儲(chǔ)來(lái)反轉(zhuǎn)該移動(dòng),并將推送所有文件屬主變回真實(shí) UID=0 的鏡像。
這可能會(huì)導(dǎo)致在新的 UID 映射中創(chuàng)建容器時(shí)出現(xiàn)真正的減速,因?yàn)?chown
可能會(huì)很慢,具體取決于鏡像中的文件數(shù)。此外,在普通的 OverlayFS 上,鏡像中的每個(gè)文件都會(huì)被復(fù)制。普通的 Fedora 鏡像最多可能需要 30 秒才能完成 chown
并啟動(dòng)容器。
幸運(yùn)的是,Red Hat 內(nèi)核存儲(chǔ)團(tuán)隊(duì)(主要是 Vivek Goyal 和 Miklos Szeredi)在內(nèi)核 4.19 中為 OverlayFS 添加了一項(xiàng)新功能。該功能稱為“僅復(fù)制元數(shù)據(jù)”。如果使用 metacopy=on
選項(xiàng)來(lái)掛載層疊文件系統(tǒng),則在更改文件屬性時(shí),它不會(huì)復(fù)制較低層的內(nèi)容;內(nèi)核會(huì)創(chuàng)建新的 inode,其中包含引用指向較低級(jí)別數(shù)據(jù)的屬性。如果內(nèi)容發(fā)生變化,它仍會(huì)復(fù)制內(nèi)容。如果你想試用它,可以在 Red Hat Enterprise Linux 8 Beta 中使用此功能。
這意味著容器 chown
可能在兩秒鐘內(nèi)發(fā)生,并且你不會(huì)倍增每個(gè)容器的存儲(chǔ)空間。
這使得像 Podman 這樣的工具在不同的用戶命名空間中運(yùn)行容器是可行的,大大提高了系統(tǒng)的安全性。
前瞻
我想向 Podman 添加一個(gè)新選項(xiàng),比如 --userns=auto
,它會(huì)為你運(yùn)行的每個(gè)容器自動(dòng)選擇一個(gè)唯一的用戶命名空間。這類似于 SELinux 與單獨(dú)的多類別安全(MCS)標(biāo)簽一起使用的方式。如果設(shè)置環(huán)境變量 PODMAN_USERNS=auto
,則甚至不需要設(shè)置該選項(xiàng)。
Podman 最終允許用戶在不同的用戶名稱空間中運(yùn)行容器。像 Buildah 和 CRI-O 這樣的工具也可以利用用戶命名空間。但是,對(duì)于 CRI-O,Kubernetes 需要了解哪個(gè)用戶命名空間將運(yùn)行容器引擎,上游正在開(kāi)發(fā)這個(gè)功能。
在我的下一篇文章中,我將解釋如何在用戶命名空間中將 Podman 作為非 root 用戶運(yùn)行。
【責(zé)任編輯:龐桂玉 TEL:(010)68476606】