Kubernetes v1.33:使用者名稱字空間預設啟用!

在 Kubernetes v1.33 中,對使用者名稱字空間的支援已預設啟用。這意味著,當滿足技術棧要求時,Pod 可以選擇性地使用使用者名稱字空間。使用該功能不再需要啟用任何 Kubernetes 特性門控!

在這篇部落格文章中,我們回答了一些關於使用者名稱字空間的常見問題。但在我們深入探討之前,讓我們先回顧一下什麼是使用者名稱字空間以及它們為何重要。

什麼是使用者名稱字空間?

注意:Linux 使用者名稱字空間與 Kubernetes 名字空間 是不同的概念。前者是 Linux 核心的一個特性;後者是 Kubernetes 的一個特性。

Linux 提供了不同的名字空間來隔離程序。例如,一個典型的 Kubernetes Pod 在一個網路名字空間中執行以隔離網路身份,在一個 PID 名字空間中執行以隔離程序。

一個被遺忘的 Linux 名字空間是使用者名稱字空間。它將容器的 UID 和 GID 與主機上的 UID 和 GID 隔離開來。容器中的識別符號可以對映到主機上的識別符號,使得主機和容器的 UID/GID 永遠不會重疊。此外,這些識別符號可以對映到主機上非特權的、不重疊的 UID 和 GID。這帶來了三個關鍵好處:

  • 防止橫向移動:由於不同容器的 UID 和 GID 對映到主機上不同的 UID 和 GID,即使容器逃逸出容器邊界,它們也很難互相攻擊。例如,假設容器 A 在主機上執行的 UID 和 GID 與容器 B 不同。在這種情況下,它對容器 B 的檔案和程序可以執行的操作是有限的:只能讀/寫檔案允許其他人操作的部分,因為它永遠不會擁有所有者或組許可權(主機上的 UID/GID 保證對不同容器是不同的)。

  • 增強主機隔離:由於 UID 和 GID 對映到主機上的非特權使用者,如果一個容器逃逸出容器邊界,即使它在容器內以 root 身份執行,它在主機上也沒有特權。這極大地保護了它能讀/寫的主機檔案、能傳送訊號的程序等。此外,授予的能力僅在使用者名稱字空間內有效,而在主機上無效,從而限制了容器逃逸可能造成的影響。

  • 啟用新的使用場景:使用者名稱字空間允許容器在自己的使用者名稱字空間內獲得某些能力,而不會影響主機。這開啟了新的可能性,例如執行需要特權操作的應用程式,而無需在主機上授予完全的 root 訪問許可權。這對於執行巢狀容器特別有用。

Image showing IDs 0-65535 are reserved to the host, pods use higher IDs

使用者名稱字空間 ID 分配

如果一個沒有使用者名稱字空間的 Pod 以 root 使用者身份執行併成功逃逸,它將在節點上擁有 root 許可權。如果容器被授予了某些能力,這些能力在主機上也有效。當使用使用者名稱字空間時,這些情況都不會發生(當然,不考慮 bug 🙂)。

演示

Rodrigo 建立了一些演示,以幫助理解使用使用者名稱字空間時如何緩解某些 CVE。我們之前在這裡展示過(參見這裡這裡),如果你還沒看過,可以看看:

使用使用者名稱字空間緩解 CVE-2024-21626

使用使用者名稱字空間緩解 CVE-2022-0492

關於 Kubernetes 中使用者名稱字空間你想知道的一切

在這裡,我們嘗試回答一些關於 Kubernetes 中使用者名稱字空間支援的常見問題。

1. 使用它有什麼要求?

要求已在這裡詳細說明。但我們將在接下來的問題中進一步闡述。

請注意,這是一個僅限 Linux 的功能。

2. 我該如何配置一個 Pod 來選擇性啟用它?

一個完整的步驟指南可以在這裡找到。但簡而言之,你需要在 Pod 規約中設定 hostUsers: false 欄位。例如像這樣:

apiVersion: v1
kind: Pod
metadata:
  name: userns
spec:
  hostUsers: false
  containers:
  - name: shell
    command: ["sleep", "infinity"]
    image: debian

是的,就是這麼簡單。應用程式將正常執行,無需任何其他更改(除非你的應用程式需要特權)。

使用者名稱字空間允許你在容器內以 root 身份執行,但在主機上沒有特權。然而,如果你的應用程式需要在主機上擁有特權,例如一個需要載入核心模組的應用,那麼你不能使用使用者名稱字空間。

3. 什麼是 idmap 掛載?為什麼使用的檔案系統需要支援它?

Idmap 掛載是 Linux 核心的一個特性,它在訪問掛載點時使用 UID/GID 的對映。當與使用者名稱字空間結合使用時,它極大地簡化了對卷的支援,因為你可以不用關心使用者名稱字空間正在使用的主機 UID/GID。

特別地,感謝 idmap 掛載,我們可以:

  • 讓每個 Pod 在主機上以不同的 UID/GID 執行。這對於我們之前提到的防止橫向移動至關重要。
  • 與不使用使用者名稱字空間的 Pod 共享卷。
  • 啟用/停用使用者名稱字空間,而無需對 Pod 的捲進行 chown 操作。

核心中對 idmap 掛載的支援是按檔案系統進行的,不同的核心版本為不同的檔案系統添加了對 idmap 掛載的支援。

要了解哪個核心版本為每個檔案系統添加了支援,你可以檢視 mount_setattr 的 man 手冊頁,或其線上版本這裡

大多數流行的檔案系統都已支援,一個顯著的例外是 NFS 尚未支援。

4. 你能具體說明哪些檔案系統需要支援 idmap 掛載嗎?

需要支援 idmap 掛載的檔案系統是 Pod 在 pod.spec.volumes 欄位中使用的所有檔案系統。

這意味著:對於 PV/PVC 卷,PV 中使用的檔案系統需要支援 idmap 掛載;對於 hostPath 卷,hostPath 中使用的檔案系統需要支援 idmap 掛載。

這對 secrets/configmaps/projected/downwardAPI 卷意味著什麼?對於這些卷,kubelet 會建立一個 tmpfs 檔案系統。所以,你需要 6.3 版本的核心才能使用這些卷(請注意,如果你將它們用作環境變數則沒有問題)。

那麼 emptyDir 卷呢?這些卷預設由 kubelet 在 /var/lib/kubelet/pods/ 中建立。你也可以為此使用自定義目錄。但需要支援 idmap 掛載的是該目錄中使用的檔案系統。

kubelet 還會為容器建立一些其他檔案,如 /etc/hostname/etc/resolv.conf/dev/termination-log/etc/hosts 等。這些檔案預設也建立在 /var/lib/kubelet/pods/ 中,所以該目錄中使用的檔案系統支援 idmap 掛載非常重要。

此外,一些容器執行時可能會將這些臨時卷放在一個 tmpfs 檔案系統中,在這種情況下,你需要 tmpfs 支援 idmap 掛載。

5. 我可以使用低於 6.3 版本的核心嗎?

是的,但你需要確保你沒有使用 tmpfs 檔案系統。如果你避免使用它,你可以輕鬆地使用 5.19 版本(如果所有你使用的其他檔案系統在該核心中支援 idmap 掛載)。

不過,要避免使用 tmpfs 可能有點棘手,正如我們上面所描述的。除了必須避免那些卷型別,你還必須避免掛載服務賬號令牌。每個 Pod 預設都會掛載它,並且它使用了一個 projected 卷,正如我們提到的,它使用了一個 tmpfs 檔案系統。

你甚至可以使用比 5.19 更低的版本,一直到 5.12。然而,你的容器根檔案系統(rootfs)可能使用 overlayfs 檔案系統,而對 overlayfs 的支援是在 5.19 版本中新增的。我們不建議使用低於 5.19 版本的核心,因為無法在 rootfs 上使用 idmap 掛載是一個很大的限制。如果你絕對需要這樣做,可以檢視 Rodrigo 幾年前寫的這篇部落格文章,其中介紹了在 rootfs 不支援 idmap 掛載時使用使用者名稱字空間的技巧。

6. 如果我的技術棧支援使用者名稱字空間,我還需要配置其他東西嗎?

不,如果你的技術棧支援它並且你正在使用 Kubernetes v1.33,你*不需要*配置任何東西。你應該能夠按照任務文件操作:為 Pod 使用使用者名稱字空間

然而,如果你有特定的需求,你可以配置各種選項。你可以在這裡找到更多資訊。你還可以啟用一個特性門控來放寬 PSS 規則

7. 那些演示很不錯,但這個功能還能緩解更多的 CVE 嗎?

是的,實際上相當多!除了演示中的那些,KEP 中還有更多你可以檢視的 CVE。那個列表並不詳盡,還有很多其他的。

8. 你能總結一下為什麼使用者名稱字空間很重要嗎?

想象一下以 root 身份執行一個程序,甚至可能是一個不受信任的程序。你認為這安全嗎?如果我們透過新增 seccomp 和 apparmor 來限制它,遮蔽 /proc 中的一些檔案(這樣它就不會使節點崩潰等),以及一些其他的調整呢?

如果我們從一開始就不給它特權,而不是試圖玩“打地鼠”遊戲來應對 root 可能逃逸的所有方式,那不是更好嗎?

這就是使用者名稱字空間所做的,外加一些其他的好處:

  • 在主機上以非特權使用者身份執行,而無需對你的應用程式進行任何更改。Greg 和 Vinayak 在一次精彩的演講中談到了在沒有使用者名稱字空間的情況下嘗試以非特權方式執行時可能遇到的痛苦。關於痛苦的部分從這個時間點開始

  • 所有 Pod 都以不同的 UID/GID 執行,我們顯著改善了橫向移動的防護。這透過使用者名稱字空間得到了保證(kubelet 會為你選擇)。在同一次演講中,Greg 和 Vinayak 展示了為了在沒有使用者名稱字空間的情況下實現同樣的目標,他們經歷了一個相當複雜的自定義解決方案。這部分從這個時間點開始

  • 授予的能力僅在使用者名稱字空間內有效。這意味著如果一個 Pod 從容器中逃逸出來,這些能力在主機上是無效的。沒有使用者名稱字空間我們無法提供這一點。

  • 它以一種*安全*的方式啟用了新的使用場景。你可以執行 Docker in Docker、非特權容器構建、Kubernetes in Kubernetes 等,所有這些都**以一種安全的方式**。以前的大多數解決方案都需要特權容器或將節點置於高度的被攻破風險中。

9. 有關於使用者名稱字空間的容器執行時文件嗎?

是的,我們有 containerd 的文件。這解釋了 containerd 1.7 的不同限制以及如何在沒有 Kubernetes Pod 的情況下在 containerd 中使用使用者名稱字空間(使用 ctr)。請注意,如果你使用 containerd,你需要 containerd 2.0 或更高版本才能在 Kubernetes 中使用使用者名稱字空間。

CRI-O 沒有專門的使用者名稱字空間文件,它可以直接開箱即用。

10. 其他容器執行時呢?

據我們所知,沒有其他容器執行時支援在 Kubernetes 中使用使用者名稱字空間。遺憾的是,這其中也包括 cri-dockerd

11. 我想了解更多,你有什麼推薦?

Rodrigo 在 KubeCon 2022 上對使用者名稱字空間做了一個介紹:

此外,前面提到的在 KubeCon 2023 上的這個演講也可以作為使用者名稱字空間的動機:

請注意,這些演講是幾年前的,從那時起有些事情已經發生了變化。請以 Kubernetes 文件作為事實的來源。

如果你想了解更多關於使用者名稱字空間的底層細節,可以檢視 man 7 user_namespacesman 1 unshare。你可以輕鬆地建立名字空間並實驗它們的行為。請注意,unshare 工具有很大的靈活性,因此也有建立不完整設定的選項。

如果你想了解更多關於 idmap 掛載的資訊,可以檢視其 Linux 核心文件

結論

以 root 身份執行 Pod 並不理想,而在容器中以非 root 身份執行也很困難,因為它可能需要對應用程式進行大量更改。使用者名稱字空間是一個獨特的特性,讓你能夠兩全其美:以非 root 身份執行,而無需對你的應用程式進行任何更改。

這篇文章涵蓋了:什麼是使用者名稱字空間,它們為什麼重要,一些由使用者名稱字空間緩解的真實 CVE 示例,以及一些常見問題。希望這篇文章能幫助你消除最後的疑慮,現在你會去嘗試使用者名稱字空間(如果你還沒有的話!)。

我如何參與?

你可以透過多種方式聯絡 SIG Node

你也可以直接聯絡我們:

  • GitHub: @rata @giuseppe @saschagrunert
  • Slack: @rata @giuseppe @sascha