了解如何使用 Podman 在單獨的使用者空間執行容器。
Podman 是 libpod 庫的一部分,使使用者能夠管理 pod、容器和容器映象。在我的上一篇文章中,我寫過將 。在這裡,我將解釋如何使用 Podman 在單獨的使用者名稱空間中執行容器。
作為分離容器的一個很棒的功能,我一直在思考使用者名稱空間,它主要是由 Red Hat 的 Eric Biederman 開發的。使用者名稱空間允許你指定用於執行容器的使用者識別符號(UID)和組識別符號(GID)對映。這意味著你可以在容器內以 UID 0 執行,在容器外以 UID 100000 執行。如果容器進程逃逸出了容器,核心會將它們視為以 UID 100000 執行。不僅如此,任何未對映到使用者名稱空間的 UID 所擁有的檔案物件都將被視為 nobody
所擁有(UID 是 65534
, 由 kernel.overflowuid
指定),並且不允許容器進程存取,除非該物件可由“其他人”存取(即世界可讀/可寫)。
如果你擁有一個許可權為 660 的屬主為“真實” root
的檔案,而當使用者名稱空間中的容器進程嘗試讀取它時,會阻止它們存取它,並且會將該檔案視為 nobody
所擁有。
以下是它是如何工作的。首先,我在 root
擁有的系統中建立一個檔案。
$ sudo bash -c "echo Test > /tmp/test"$ sudo chmod 600 /tmp/test$ sudo ls -l /tmp/test-rw-------. 1 root root 5 Dec 17 16:40 /tmp/test
接下來,我將該檔案卷掛載到一個使用使用者名稱空間對映 0:100000:5000
執行的容器中。
$ sudo podman run -ti -v /tmp/test:/tmp/test:Z --uidmap 0:100000:5000 fedora sh# iduid=0(root) gid=0(root) groups=0(root)# ls -l /tmp/test-rw-rw----. 1 nobody nobody 8 Nov 30 12:40 /tmp/test# cat /tmp/testcat: /tmp/test: Permission denied
上面的 --uidmap
設定告訴 Podman 在容器內對映一系列的 5000 個 UID,從容器外的 UID 100000 開始的範圍(100000-104999)對映到容器內 UID 0 開始的範圍(0-4999)。在容器內部,如果我的進程以 UID 1 執行,則它在主機上為 100001。
由於實際的 UID=0
未對映到容器中,因此 root
擁有的任何檔案都將被視為 nobody
所擁有。即使容器內的進程具有 CAP_DAC_OVERRIDE
能力,也無法覆蓋此種保護。DAC_OVERRIDE
能力使得 root 的進程能夠讀/寫系統上的任何檔案,即使進程不是 root
使用者擁有的,也不是全域性可讀或可寫的。
使用者名稱空間的功能與宿主機上的功能不同。它們是名稱空間的功能。這意味著我的容器的 root 只在容器內具有功能 —— 實際上只有該範圍內的 UID 對映到內使用者名稱空間。如果容器進程逃逸出了容器,則它將沒有任何非對映到使用者名稱空間的 UID 之外的功能,這包括 UID=0
。即使進程可能以某種方式進入另一個容器,如果容器使用不同範圍的 UID,它們也不具備這些功能。
請注意,SELinux 和其他技術還限制了容器進程破開容器時會發生的情況。
我們在 podman top
中新增了一些功能,允許你檢查容器內執行的進程的使用者名稱,並標識它們在宿主機上的真實 UID。
讓我們首先使用我們的 UID 對映執行一個 sleep
容器。
$ sudo podman run --uidmap 0:100000:5000 -d fedora sleep 1000
現在執行 podman top
:
$ sudo podman top --latest user huserUSER HUSERroot 100000$ ps -ef | grep sleep100000 21821 21809 0 08:04 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000
注意 podman top
報告使用者進程在容器內以 root
身份執行,但在宿主機(HUSER
)上以 UID 100000 執行。此外,ps
命令確認 sleep
過程以 UID 100000 執行。
現在讓我們執行第二個容器,但這次我們將選擇一個單獨的 UID 對映,從 200000 開始。
$ sudo podman run --uidmap 0:200000:5000 -d fedora sleep 1000$ sudo podman top --latest user huserUSER HUSERroot 200000$ ps -ef | grep sleep100000 21821 21809 0 08:04 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000200000 23644 23632 1 08:08 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000
請注意,podman top
報告第二個容器在容器內以 root
身份執行,但在宿主機上是 UID=200000。
另請參閱 ps
命令,它顯示兩個 sleep
進程都在執行:一個為 100000,另一個為 200000。
這意味著在單獨的使用者名稱空間內執行容器可以在進程之間進行傳統的 UID 分離,而這從一開始就是 Linux/Unix 的標準安全工具。
幾年來,我一直主張使用者名稱空間應該作為每個人應該有的安全工具,但幾乎沒有人使用過。原因是沒有任何檔案系統支援,也沒有一個移動檔案系統。
在容器中,你希望在許多容器之間共用基本映象。上面的每個範例中使用了 Fedora 基本映象。Fedora 映象中的大多數檔案都由真實的 UID=0
擁有。如果我在此映象上使用使用者名稱稱空間 0:100000:5000
執行容器,預設情況下它會將所有這些檔案視為 nobody
所擁有,因此我們需要移動所有這些 UID 以匹配使用者名稱稱空間。多年來,我想要一個掛載選項來告訴核心重新對映這些檔案 UID 以匹配使用者名稱空間。上游核心儲存開發人員還在繼續研究,在此功能上已經取得一些進展,但這是一個難題。
由於由 Nalin Dahyabhai 領導的團隊開發的自動 chown 內建於容器/儲存中,Podman 可以在同一映象上使用不同的使用者名稱稱空間。當 Podman 使用容器/儲存,並且 Podman 在新的使用者名稱空間中首次使用一個容器映象時,容器/儲存會 “chown”(如,更改所有權)映象中的所有檔案到使用者名稱空間中對映的 UID 並建立一個新映象。可以把它想象成一個 fedora:0:100000:5000
映象。
當 Podman 在具有相同 UID 對映的映象上執行另一個容器時,它使用“預先 chown”的映象。當我在0:200000:5000
上執行第二個容器時,容器/儲存會建立第二個映象,我們稱之為 fedora:0:200000:5000
。
請注意,如果你正在執行 podman build
或 podman commit
並將新建立的映象推播到容器註冊庫,Podman 將使用容器/儲存來反轉該移動,並將推播所有檔案屬主變回真實 UID=0 的映象。
這可能會導致在新的 UID 對映中建立容器時出現真正的減速,因為 chown
可能會很慢,具體取決於映象中的檔案數。此外,在普通的 OverlayFS 上,映象中的每個檔案都會被複製。普通的 Fedora 映象最多可能需要 30 秒才能完成 chown
並啟動容器。
幸運的是,Red Hat 核心儲存團隊(主要是 Vivek Goyal 和 Miklos Szeredi)在核心 4.19 中為 OverlayFS 新增了一項新功能。該功能稱為“僅複製後設資料”。如果使用 metacopy=on
選項來掛載層疊檔案系統,則在更改檔案屬性時,它不會複製較低層的內容;核心會建立新的 inode,其中包含參照指向較低階別資料的屬性。如果內容發生變化,它仍會複製內容。如果你想試用它,可以在 Red Hat Enterprise Linux 8 Beta 中使用此功能。
這意味著容器 chown
可能在兩秒鐘內發生,並且你不會倍增每個容器的儲存空間。
這使得像 Podman 這樣的工具在不同的使用者名稱空間中執行容器是可行的,大大提高了系統的安全性。
我想向 Podman 新增一個新選項,比如 --userns=auto
,它會為你執行的每個容器自動選擇一個唯一的使用者名稱空間。這類似於 SELinux 與單獨的多類別安全(MCS)標籤一起使用的方式。如果設定環境變數 PODMAN_USERNS=auto
,則甚至不需要設定該選項。
Podman 最終允許使用者在不同的使用者名稱稱空間中執行容器。像 Buildah 和 CRI-O 這樣的工具也可以利用使用者名稱空間。但是,對於 CRI-O,Kubernetes 需要了解哪個使用者名稱空間將執行容器引擎,上游正在開發這個功能。
在我的下一篇文章中,我將解釋如何在使用者名稱空間中將 Podman 作為非 root 使用者執行。