使用使用者名稱空間隔離容器

Linux 名稱空間為正在執行的程序提供了隔離,限制了它們對系統資源的訪問,而程序本身並不知道這些限制。有關 Linux 名稱空間的更多資訊,請參閱 Linux 名稱空間

防止容器內部發生許可權提升攻擊的最佳方法是,將容器的應用程式配置為以非特權使用者身份執行。對於那些程序必須在容器內以 root 使用者身份執行的容器,您可以將此使用者重新對映到 Docker 主機上的一個許可權較低的使用者。該對映使用者被分配了一個 UID 範圍,這個範圍在名稱空間內作為從 0 到 65536 的普通 UID 來使用,但在主機本身上沒有任何許可權。

關於重對映和附屬使用者及組 ID

重對映本身由兩個檔案處理:/etc/subuid/etc/subgid。每個檔案的工作方式相同,但一個涉及使用者 ID 範圍,另一個涉及組 ID 範圍。請看 /etc/subuid 中的以下條目

testuser:231072:65536

這意味著 testuser 被分配了一個從屬使用者 ID 範圍,起始於 231072,幷包含其後連續的 65536 個整數。UID 231072 在名稱空間內(在本例中是容器內)被對映為 UID 0 (root)。UID 231073 被對映為 UID 1,以此類推。如果一個程序試圖在名稱空間之外提升許可權,該程序在主機上將以一個非特權的高數值 UID 執行,這個 UID 甚至不對應任何真實使用者。這意味著該程序在主機系統上完全沒有任何許可權。

注意

透過在 /etc/subuid/etc/subgid 檔案中為同一使用者或組新增多個不重疊的對映,可以為給定的使用者或組分配多個從屬範圍。在這種情況下,Docker 僅使用前五個對映,這符合核心對 /proc/self/uid_map/proc/self/gid_map 中僅有五個條目的限制。

當您配置 Docker 使用 userns-remap 功能時,您可以選擇指定一個現有的使用者和/或組,或者您可以指定 default。如果您指定 default,則會為此目的建立並使用一個名為 dockremap 的使用者和組。

警告

某些發行版不會自動將新組新增到 /etc/subuid/etc/subgid 檔案中。如果是這種情況,您可能需要手動編輯這些檔案並分配不重疊的範圍。這一步在先決條件中有所介紹。

非常重要的一點是,這些範圍不能重疊,這樣程序就無法在不同的名稱空間中獲得訪問許可權。在大多數 Linux 發行版中,當您新增或刪除使用者時,系統工具會為您管理這些範圍。

這種重對映對容器來說是透明的,但在容器需要訪問 Docker 主機上的資源(例如,繫結掛載到系統使用者無法寫入的檔案系統區域)的情況下,會引入一些配置複雜性。從安全形度來看,最好避免這些情況。

先決條件

  1. 從屬 UID 和 GID 範圍必須與現有使用者關聯,即使這種關聯是一個實現細節。該使用者擁有 /var/lib/docker/ 下的名稱空間儲存目錄。如果您不想使用現有使用者,Docker 可以為您建立一個並使用它。如果您想使用現有的使用者名稱或使用者 ID,它必須已經存在。通常,這意味著相關條目需要存在於 /etc/passwd/etc/group 中,但如果您使用不同的身份驗證後端,此要求可能會有所不同。

    要驗證這一點,請使用 id 命令

    $ id testuser
    
    uid=1001(testuser) gid=1001(testuser) groups=1001(testuser)
    
  2. 在主機上處理名稱空間重對映的方式是使用兩個檔案:/etc/subuid/etc/subgid。這些檔案通常在您新增或刪除使用者或組時自動管理,但在某些發行版上,您可能需要手動管理這些檔案。

    每個檔案包含三個欄位:使用者的使用者名稱或 ID,後跟一個起始 UID 或 GID(在名稱空間內被視為 UID 或 GID 0)以及該使用者可用的最大 UID 或 GID 數量。例如,給定以下條目:

    testuser:231072:65536

    這意味著由 testuser 啟動的使用者名稱空間程序由主機 UID 231072(在名稱空間內看起來像 UID 0)到 296607 (231072 + 65536 - 1) 所擁有。這些範圍不應重疊,以確保名稱空間程序無法訪問彼此的名稱空間。

    新增使用者後,檢查 /etc/subuid/etc/subgid,看您的使用者是否在每個檔案中都有一個條目。如果沒有,您需要新增它,注意避免重疊。

    如果您想使用由 Docker 自動建立的 dockremap 使用者,請在配置並重啟 Docker 後檢查這些檔案中是否有 dockremap 條目。

  3. 如果 Docker 主機上有任何位置需要非特權使用者寫入,請相應地調整這些位置的許可權。如果您想使用 Docker 自動建立的 dockremap 使用者,情況也是如此,但在配置並重啟 Docker 之後才能修改許可權。

  4. 啟用 userns-remap 會有效地遮蔽 /var/lib/docker/ 中現有的映象和容器層以及其他 Docker 物件。這是因為 Docker 需要調整這些資源的所有權,並實際上將它們儲存在 /var/lib/docker/ 內的一個子目錄中。最好在一個新的 Docker 安裝上啟用此功能,而不是在現有的安裝上。

    同樣地,如果您停用了 userns-remap,您將無法訪問在它啟用時建立的任何資源。

  5. 請檢視使用者名稱空間的限制,以確保您的用例是可行的。

在守護程序上啟用 userns-remap

您可以使用 --userns-remap 標誌啟動 dockerd,或者按照此過程使用 daemon.json 配置檔案來配置守護程序。推薦使用 daemon.json 方法。如果您使用該標誌,請使用以下命令作為模型

$ dockerd --userns-remap="testuser:testuser"
  1. 編輯 /etc/docker/daemon.json。假設該檔案之前為空,以下條目使用名為 testuser 的使用者和組來啟用 userns-remap。您可以透過 ID 或名稱來指定使用者和組。僅當組名或 ID 與使用者名稱或 ID 不同時,才需要指定組名或 ID。如果您同時提供使用者名稱或 ID 和組名或 ID,請用冒號 (:) 字元分隔它們。以下格式都適用於該值,假設 testuser 的 UID 和 GID 都是 1001

    • testuser
    • testuser:testuser
    • 1001
    • 1001:1001
    • testuser:1001
    • 1001:testuser
    {
      "userns-remap": "testuser"
    }
    注意

    要使用 dockremap 使用者並讓 Docker 為您建立它,請將值設定為 default 而不是 testuser

    儲存檔案並重啟 Docker。

  2. 如果您正在使用 dockremap 使用者,請使用 id 命令驗證 Docker 是否已建立該使用者。

    $ id dockremap
    
    uid=112(dockremap) gid=116(dockremap) groups=116(dockremap)
    

    驗證該條目是否已新增到 /etc/subuid/etc/subgid

    $ grep dockremap /etc/subuid
    
    dockremap:231072:65536
    
    $ grep dockremap /etc/subgid
    
    dockremap:231072:65536
    

    如果這些條目不存在,請以 root 使用者身份編輯檔案,並分配一個起始 UID 和 GID,該值是已分配的最高值加上偏移量(在本例中為 65536)。請注意不要讓範圍有任何重疊。

  3. 使用 docker image ls 命令驗證以前的映象是否不可用。輸出應該為空。

  4. hello-world 映象啟動一個容器。

    $ docker run hello-world
    
  5. 驗證 /var/lib/docker/ 中是否存在一個以名稱空間使用者的 UID 和 GID 命名的名稱空間目錄,該目錄由該 UID 和 GID 擁有,並且不可被組或其他使用者讀取。一些子目錄仍然由 root 擁有,並具有不同的許可權。

    $ sudo ls -ld /var/lib/docker/231072.231072/
    
    drwx------ 11 231072 231072 11 Jun 21 21:19 /var/lib/docker/231072.231072/
    
    $ sudo ls -l /var/lib/docker/231072.231072/
    
    total 14
    drwx------ 5 231072 231072 5 Jun 21 21:19 aufs
    drwx------ 3 231072 231072 3 Jun 21 21:21 containers
    drwx------ 3 root   root   3 Jun 21 21:19 image
    drwxr-x--- 3 root   root   3 Jun 21 21:19 network
    drwx------ 4 root   root   4 Jun 21 21:19 plugins
    drwx------ 2 root   root   2 Jun 21 21:19 swarm
    drwx------ 2 231072 231072 2 Jun 21 21:21 tmp
    drwx------ 2 root   root   2 Jun 21 21:19 trust
    drwx------ 2 231072 231072 3 Jun 21 21:19 volumes
    

    您的目錄列表可能會有一些差異,特別是如果您使用的容器儲存驅動程式不是 aufs

    由重對映使用者擁有的目錄將替代 /var/lib/docker/ 下的同名目錄,而未使用的版本(例如本例中的 /var/lib/docker/tmp/)可以被移除。在啟用 userns-remap 時,Docker 不會使用它們。

為容器停用名稱空間重對映

如果您在守護程序上啟用使用者名稱空間,則所有容器預設都會以啟用使用者名稱空間的方式啟動。在某些情況下,例如特權容器,您可能需要為特定容器停用使用者名稱空間。有關其中一些限制,請參閱使用者名稱空間的已知限制

要為特定容器停用使用者名稱空間,請向 docker container createdocker container rundocker container exec 命令新增 --userns=host 標誌。

使用此標誌會有一個副作用:使用者重對映不會為該容器啟用,但是,由於只讀(映象)層在容器之間共享,容器檔案系統的所有權仍將被重對映。

這意味著整個容器檔案系統將屬於 --userns-remap 守護程序配置中指定的使用者(在上面的示例中為 231072)。這可能會導致容器內程式的意外行為。例如 sudo(它會檢查其二進位制檔案是否屬於使用者 0)或帶有 setuid 標誌的二進位制檔案。

使用者名稱空間的已知限制

以下標準的 Docker 功能與啟用使用者名稱空間的 Docker 守護程序不相容:

  • 與主機共享 PID 或 NET 名稱空間 (--pid=host--network=host)。
  • 不瞭解或無法使用守護程序使用者對映的外部(卷或儲存)驅動程式。
  • docker run 上使用 --privileged 模式標誌,而沒有同時指定 --userns=host

使用者名稱空間是一項高階功能,需要與其他功能協調。例如,如果從主機掛載卷,則如果需要對卷內容進行讀或寫訪問,檔案所有權必須預先安排好。

雖然使用者名稱空間容器程序內的 root 使用者擁有許多預期的超級使用者許可權,但 Linux 核心會根據其內部知識(這是一個使用者名稱空間程序)施加限制。一個顯著的限制是無法使用 mknod 命令。當由 root 使用者在容器內執行時,建立裝置的許可權將被拒絕。