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

Linux 命名空間提供執行中程序的隔離,限制它們對系統資源的存取,而執行中程序不會察覺到這些限制。如需 Linux 命名空間的詳細資訊,請參閱 Linux 命名空間

防止容器內權限提升攻擊的最佳方法是將容器的應用程式設定為以非特權使用者身分執行。對於程序必須以容器內部的 root 使用者身分執行的容器,您可以將此使用者重新映射到 Docker 主機上權限較低的使用者。映射的使用者會被指派一個 UID 範圍,在命名空間內的功能與從 0 到 65536 的一般 UID 相同,但在主機上本身沒有任何權限。

關於重新映射和次要使用者和群組 ID

重新映射本身由兩個檔案處理:/etc/subuid/etc/subgid。每個檔案的運作方式都相同,但一個檔案與使用者 ID 範圍有關,另一個檔案與群組 ID 範圍有關。請考慮 /etc/subuid 中的以下項目

testuser:231072:65536

這表示 testuser 被指派了 231072 的次要使用者 ID 範圍,以及接下來依序的 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 安裝上啟用此功能,而不是現有的 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,請使用冒號 (:) 字元分隔它們。假設 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 為最高指派的 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 不會使用它們。

停用容器的命名空間重新映射

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

若要停用特定容器的使用者命名空間,請將 --userns=host 旗標新增至 docker container createdocker container rundocker container exec 命令。

使用此旗標時會有一個副作用:該容器將不會啟用使用者重新映射,但由於唯讀(映像檔)層會在容器之間共用,因此容器檔案系統的所有權仍會被重新映射。

這表示整個容器檔案系統將屬於 --userns-remap 守護行程設定中指定的使用者(在上述範例中為 231072)。這可能會導致容器內程式出現意外行為。例如,sudo(會檢查其二進位檔是否屬於使用者 0)或具有 setuid 旗標的二進位檔。

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

以下標準 Docker 功能與在啟用使用者命名空間的情況下執行 Docker 守護行程不相容

  • 與主機共用 PID 或 NET 命名空間 (--pid=host--network=host)。
  • 不知道或無法使用守護行程使用者映射的外部(磁碟區或儲存裝置)驅動程式。
  • docker run 上使用 --privileged 模式旗標,但未同時指定 --userns=host

使用者命名空間是一項進階功能,需要與其他功能協調。例如,如果從主機掛載磁碟區,如果您需要讀取或寫入磁碟區內容,則必須事先安排檔案所有權。

雖然使用者命名空間容器處理程序中的 root 使用者擁有超級使用者在容器內許多預期的權限,但 Linux 核心會根據內部認知(這是一個使用者命名空間處理程序)施加限制。一個值得注意的限制是無法使用 mknod 命令。當由 root 使用者執行時,會拒絕在容器內建立裝置的權限。