1. 程式人生 > >隔離 docker 容器中的用戶

隔離 docker 容器中的用戶

pre 然而 ping htm spa 用戶 nic exe 完美

筆者在前文《理解 docker 容器中的 uid 和 gid》介紹了 docker 容器中的用戶與宿主機上用戶的關系,得出的結論是:docker 默認沒有隔離宿主機用戶和容器中的用戶。如果你已經了解了 Linux 的 user namespace 技術(參考《Linux Namespace : User》),那麽自然會問:docker 為什麽不利用 Linux user namespace 實現用戶的隔離呢?事實上,docker 已經實現了相關的功能,只是默認沒有啟用而已。筆者將在本文中介紹如何配置 docker 來隔離容器中的用戶。
說明:本文的演示環境為 ubuntu 16.04。

了解 Linux user namespace

Linux user namespace 為正在運行的進程提供安全相關的隔離(其中包括 uid 和 gid),限制它們對系統資源的訪問,而這些進程卻感覺不到這些限制的存在。關於 Linux User Namespace 的介紹請參考筆者的《Linux Namespace : User》一文。

對於容器而言,阻止權限提升攻擊(privilege-escalation attacks)的最好方法就是使用普通用戶權限運行容器的應用程序。
然而有些應用必須在容器中以 root 用戶來運行,這就是我們使用 user namespace 的最佳場景。我們通過 user namespace 技術,把宿主機中的一個普通用戶(只有普通權限的用戶)映射到容器中的 root 用戶。在容器中,該用戶在自己的 user namespace 中認為自己就是 root,也具有 root 的各種權限,但是對於宿主機上的資源,它只有很有限的訪問權限(普通用戶)。

User namespace 的用戶映射

在配置 docker daemon 啟用 user namespace 前,我需要先來了解一些關於從屬(subordinate)用戶/組和映射(remapping)的概念。從屬用戶和組的映射由兩個配置文件來控制,分別是 /etc/subuid 和 /etc/subgid。看下它們的默認內容:在配置 docker daemon 啟用 user namespace 前,我需要先來了解一些關於從屬(subordinate)用戶/組和映射(remapping)的概念:

技術分享圖片

對於 subuid,這一行記錄的含義為:
用戶 nick,在當前的 user namespace 中具有 65536 個從屬用戶,用戶 ID 為 100000-165535,在一個子 user namespace 中,這些從屬用戶被映射成 ID 為 0-65535 的用戶。

subgid 的含義和 subuid 相同。

比如說用戶 nick 在宿主機上只是一個具有普通權限的用戶。我們可以把他的一個從屬 ID(比如 100000 )分配給容器所屬的 user namespace,並把 ID 100000 映射到該 user namespace 中的 uid 0。此時即便容器中的進程具有 root 權限,但也僅僅是在容器所在的 user namespace 中,一旦到了宿主機中,你頂多也就有 nick 用戶的權限而已。

當開啟 docker 對 user namespace 的支持時(docker 的 userns-remap 功能),我們可以指定不同的用戶映射到容器中。比如我們專門創建一個用戶 dockeruser,然後手動設置其 subuid 和 subgid:

nick:100000:65536
dockeruser:165536:65536

並把它指定給 docker daemon:

{
  "userns-remap": "dockeruser"
}

請註意 subuid 的設置信息,我們為 dockeruser 設置的從屬 ID 和 nick 用戶是不重疊的,實際上任何用戶的從屬 ID 設置都是不能重疊的。

或者一切從簡,讓 docker 為我們包辦這些繁瑣的事情,直接把 docker daemon 的 userns-rempa 參數指定為 "default":

{
  "userns-remap": "default"
}

這時,docker 會自動完成其它的配置。

配置 docker daemon 啟用用戶隔離

這裏筆者采取簡單的方式,讓 docker 創建默認的用戶用於 user namespace。我們需要先創建 /etc/docker/daemon.json 文件:

$ sudo touch /etc/docker/daemon.json

然後編輯其內容如下(如果該文件已經存在,僅添加下面的配置項即可),並重啟 docker 服務:

{
  "userns-remap": "default"
}
$ sudo systemctl restart docker.service

下面我們來驗證幾個關於用戶隔離的幾個點。

首先驗證 docker 創建了一個名為 dockremap 的用戶:

技術分享圖片

然後查看 /etc/subuid 和 /etc/subgid 文件中是否添加了新用戶 dockremap 相關的項:

技術分享圖片

接下來我們發現在 /var/lib/docker 目錄下新建了一個目錄: 165536.165536,查看該目錄的權限:

技術分享圖片

165536 是由用戶 dockremap 映射出來的一個 uid。查看 165536.165536 目錄的內容:

技術分享圖片

與 /var/lib/docker 目錄下的內容基本一致,說明啟用用戶隔離後文件相關的內容都會放在新建的 165536.165536 目錄下。

通過上面的檢查,我們可以確認 docker daemon 已經啟用了用戶隔離的功能。

宿主機中的 uid 與容器中 uid

在 docker daemon 啟用了用戶隔離的功能後,讓我們看看宿主機中的 uid 與容器中 uid 的變化。

$ docker run -d --name sleepme ubuntu sleep infinity

技術分享圖片

uid 165536 是用戶 dockremap 的一個從屬 ID,在宿主機中並沒有什麽特殊權限。然而容器中的用戶卻是 root,這樣的結果看上去很完美:

技術分享圖片

新創建的容器會創建 user namespace

在 docker daemon 啟用用戶隔離的功能前,新創建的容器進程和宿主機上的進程在相同的 user namespace 中。也就是說 docker 並沒有為容器創建新的 user namespace:

技術分享圖片

上圖中的容器進程 sleep 和宿主機上的進程在相同的 user namespace 中(沒有開啟用戶隔離功能的場景)。

在 docker daemon 啟用用戶隔離的功能後,讓我們查看容器中進程的 user namespace:

技術分享圖片

上圖中的 4404 就是我們剛啟動的容器中 sleep 進程的 PID。可以看出,docker 為容器創建了新的 user namespace。在這個 user namespace 中,容器中的用戶 root 就是天神,擁有至高無上的權力!

訪問數據卷中的文件

我們可以通過訪問數據卷中的文件來證明容器中 root 用戶究竟具有什麽樣的權限?創建四個文件,分別屬於用戶 root 、165536 和 nick。rootfile 只有 root 用戶可以讀寫,用戶 nick 具有 nickfile 的讀寫權限,uid 165536 具有文件 165536file 的讀寫權限,任何用戶都可以讀寫 testfile 文件:

技術分享圖片

下面把這幾個文件以數據卷的方式掛載到容器中,並檢查從容器中訪問它們的權限:

$ docker run -it --name test -w=/testv -v $(pwd)/testv:/testv ubuntu

技術分享圖片

容器中的 root 用戶只能訪問 165536file 和 testfile,說明這個用戶在宿主機中只有非常有限的權限。

在容器中禁用 user namespace

一旦為 docker daemon 設置了 "userns-remap" 參數,所有的容器默認都會啟用用戶隔離的功能(默認創建一個新的 user namespace)。有些情況下我們可能需要回到沒有開啟用戶隔離的場景,這時可以通過 --userns=host 參數為單個的容器禁用用戶隔離功能。--userns=host 參數主要給下面三個命令使用:

docker container create
docker container run
docker container exec

比如執行下的命令:

$ docker run -d --userns=host --name sleepme ubuntu sleep infinity

查看進程信息:

技術分享圖片

進程的有效用戶又成 root 了,並且也沒有為進程創建新的 user namespace:

技術分享圖片

已知問題

User namespace 屬於比較高級的功能,目前 docker 對它的支持還算不上完美,下面是已知的幾個和現有功能不兼容的問題:

  • 共享主機的 PID 或 NET namespace(--pid=host or --network=host)
  • 外部的存儲、數據卷驅動可能不兼容、不支持 user namespace
  • 使用 --privileged 而不指定 --userns=host

總結

Docker 是支持 user namespace 的,並且配置的方式也非常簡便。在開啟 user namespace 之後我們享受到了安全性的提升,但同時也會因為種種限制讓其它的個別功能出現問題。這時我們需要作出選擇,告別一刀切的決策,讓合適的功能出現的合適的場景中。

參考:
Understanding how uid and gid work in Docker containers
Introduction to User Namespaces in Docker Engine
Isolate containers with a user namespace

隔離 docker 容器中的用戶