默認情況下,容器中的進程以 root 用戶許可權運行,並且這個 root 用戶和宿主機中的 root 是同一個用戶。聽起來是不是很可怕,因為這就意味著一旦容器中的進程有了適當的機會,它就可以控制宿主機上的一切!本文我們將嘗試瞭解用戶名、組名、用戶 id(uid)和組 id(gid)如何在容器內的進程和主機系統之間映射,這對於系統的安全來說是非常重要的。說明:本文的演示環境為 ubuntu 16.04(下圖來自互聯網)。
uid 和 gid 由 Linux 內核負責管理,並通過內核級別的系統調用來決定是否應該為某個請求授予特權。比如當進程試圖寫入文件時,內核會檢查創建進程的 uid 和 gid,以確定它是否有足夠的許可權修改文件。注意,內核使用的是 uid 和 gid,而不是用戶名和組名。
很多同學簡單地把 docker 容器理解為輕量的虛擬機,雖然這簡化了理解容器技術的難度但是也容易帶來很多的誤解。事實上,與虛擬機技術不同:同一主機上運行的所有容器共享同一個內核(主機的內核)。容器化帶來的巨大價值在於所有這些獨立的容器(其實是進程)可以共享一個內核。這意味著即使由成百上千的容器運行在 docker 宿主機上,但內核控制的 uid 和 gid 則仍然只有一套。所以同一個 uid 在宿主機和容器中代表的是同一個用戶(即便在不同的地方顯示了不同的用戶名)。
注意,由於普通的用來顯示用戶名的 Linux 工具並不屬於內核(比如 id 等命令),所以我們可能會看到同一個 uid 在不同的容器中顯示為不同的用戶名。但是對於相同的 uid 不能有不同的特權,即使在不同的容器中也是如此。
如果你已經瞭解了 Linux 的 user namespace 技術,參考《Linux Namespace : User》,你需要注意的是到目前為止,docker 默認並沒有啟用 user namesapce,這也是本文討論的情況。筆者會在接下來的文章中介紹如何配置 docker 啟用 user namespace。
如果不做相關的設置,容器中的進程默認以 root 用戶許可權啟動,下面的 demo 使用 ubuntu 鏡像運行 sleep 程序:
$ docker run -d --name sleepme ubuntu sleep infinity0
注意上面的命令中並沒有使用 sudo。筆者在宿主機中的登錄用戶是 nick,uid 為 1000:
在宿主機中查看 sleep 進程的信息:
$ ps aux | grep sleep
sleep 進程的有效用戶名稱是 root,也就是說 sleep 進程具有 root 許可權。
其實我們可以通過數據捲來簡單的驗證上面的結論。在宿主機上創建一個只有 root 用戶可以讀寫的文件
然後掛載到容器中:
$ docker run --rm -it -w=/testv -v $(pwd)/testv:/testv ubuntu
在容器中可以讀寫該文件
我們可以在 Dockerfile 中添加一個用戶 appuser,並使用 USER 命令指定以該用戶的身份運行程序,Dockerfile 的內容如下:
FROM ubuntu RUN useradd -r -u 1000 -g appuser USER appuser ENTRYPOINT ["sleep", "infinity"]
編譯成名稱為 test 的鏡像:
$ docker build -t test
用 test 鏡像啟動一個容器:
$ docker run -d --name sleepme test.
這次顯示的有效用戶是 nick,這是因為在宿主機中,uid 為 1000 的用戶的名稱為 nick。再進入到容器中看看:
$ docker exec -it sleepme bash
讓我們再創建一個只有用戶 nick 可以讀寫的文件:
$ docker run -d --name sleepme -w=/testv -v $(pwd)/testv:/testv test
在容器中 testfile 的所有者居然變成了 appuser,當然 appuser 也就有許可權讀寫該文件。
這裡到底發生了什麼?而這些又這說明瞭什麼?
我們還可以通過 docker run 命令的 --user 參數指定容器中進程的用戶身份。比如執行下面的命令:
$ docker run -d --user 1000 --name sleepme ubuntu sleep infinity
因為我們在命令行上指令了參數 --user 1000,所以這裡 sleep 進程的有效用戶顯示為 nick。進入到容器內部看一下:
這是個什麼情況?用戶名稱居然顯示為 "I have no name!"!去查看 /etc/passwd 文件,裡面果然沒有 uid 為 1000 的用戶。即便沒有用戶名稱,也絲毫不影響該用戶身份的許可權,它依然可以讀寫只有 nick 用戶才能讀寫的文件,並且用戶信息也由 uid 代替了用戶名:
需要注意的是,在創建容器時通過 docker run --user 指定的用戶身份會覆蓋掉 Dockerfile 中指定的值。
$ docker run -d test
查看 sleep 進程信息:
$ docker run --user 0 -d test
再次查看 sleep 進程信息:
指定了 --urser 0 參數的進程顯示有效用戶為 root,說明命令行參數 --user 0 覆蓋掉了 Dockerfile 中 USER 命令的設置。
從本文中的示例我們可以瞭解到,容器中運行的進程同樣具有訪問主機資源的許可權(docker 默認並沒有對用戶進行隔離),當然一般情況下容器技術會把容器中進程的可見資源封鎖在容器中。但是通過我們演示的對數據卷中文件的操作可以看出,一旦容器中的進程有機會訪問到宿主機的資源,它的許可權和宿主機上用戶的許可權是一樣的。所以比較安全的做法是為容器中的進程指定一個具有合適許可權的用戶,而不要使用默認的 root 用戶。當然還有更好的方案,就是應用 Linux 的 user namespace 技術隔離用戶,筆者會在接下來的文章中介紹如何配置 docker 開啟 user namespace 的支持。
參考:
作者:sparkdev出處:http://t.cn/Eflqt47
作者:sparkdev
51Reboot 2019 最新課程招生信息
Python 零基礎入門課程此課程為面授班和網路班,一共 15 個課時,每週上一個全天,歷時4個月。附加:錄播視頻+筆記+答疑2019-6月份開課
Python 自動化運維進階課程此課程為面授班和網路班,一共 15 個課時,每週上一個全天,歷時4個月。附加:錄播視頻+筆記+答疑2019-4月份開課
Golang 課程
現在報名即可享受早鳥價
對此感興趣的朋友可以留言或私信我
推薦閱讀: