"容器是隔離的空間,因此在內部使用 root 幹嘛都可以吧?"

"非要創建 USER 只會增加層數,太麻煩了..."

"要讓容器 USER 使用綁定到主機的目錄,處理權限也很麻煩..."

許多開發者就是這樣認為,因而直接使用 Dockerfile 的默認值 root 用戶。

但這是 非常危險的安全實踐。容器的“隔離”並不是完美的防火牆,以 root 權限運行容器可能會使整個伺服器面臨風險。

這篇文章將明確解釋為什麼不應以 root 身份運行容器,以及如何防止這種情況的發生。


1. 最壞的情況: "容器逃脫" (Container Escape)



避免使用 root 的最重要原因。

  • 基本映射: Docker 容器內的 root 用戶 (UID 0) 基本上就是 主機伺服器的 root 用戶 (UID 0)

  • 當隔離被突破: 假設攻擊者利用應用程序的漏洞或 Docker、甚至是 Linux 核心本身的漏洞成功“逃脫”了容器的隔離環境。

  • 結果: 如果此時容器是以 root 權限運行的,攻擊者將立即獲取主機伺服器的 root 權限。整個伺服器將完全被掌控。

比喻:root 身份運行容器就像是讓“一個擁有酒店鑰匙的大客戶”住進房間。當這位客戶打開自己房間(容器)的門後,就可以隨心所欲地在整個酒店(主機)內走動。

反之,如果容器是以 appuser (UID 1001) 這樣的無權限普通用戶運行的話,情況會怎樣呢?就算攻擊者成功逃脫,他也只會擁有appuser這一無權限的用戶的權限,從而將損害降到最低。


2. 最小權限原則 (Principle of Least Privilege)

安全的最基本原則是“所有程序和用戶僅應具備執行任務所需的最低權限”。

運行網頁應用程序根本不需要 root 權限。

  • 當是 root 時: 如果攻擊者進入到容器內(就算沒有逃脫),他仍然是容器內部的 root

    • 可以隨意安裝惡意的掃描器、加密挖掘工具等。

    • 可以修改和刪除所有文件,包括包含數據庫連接信息的配置文件(如 settings.py 等)。

  • 當是 appuser 時: 就算攻擊者成功入侵,他也只不過是 appuser

    • apt-get install? 權限被拒絕。

    • 要修改系統設置文件? 權限被拒絕。

    • 損害範圍絕對限制在 appuser 擁有的應用源代碼目錄內。


3. 澄清常見誤解



🤔 "USER 命令不只是增加映像層而已嗎?"

不是。這是最大的誤解。

Dockerfile 的 USER 命令並不會生成文件系統層。該命令只是向映像的元數據(Metadata) 添加了 “當這個容器啟動時,默認用戶設定為 'appuser'” 的指令

映像大小不會增加 1KB,生成速度也不會受到影響。

當然,RUN useradd... 命令則會創建用戶並增加非常微小的層,但這是一個為了安全而必須付出的成本

🤔 "要使用 80 號端口還是必須是 root 吧?"

正確。在 Linux 中,1024 以下的端口(例如 80,443)只能由 root 開放。然而,這不再是以 root 身份運行容器的理由。

現代的做法如下。

  1. 容器內部以 appuser 權限運行應用,使用類似 8000 的高端口

  2. 外部通過 Docker 映射端口 (docker run -p 80:8000),或者使用 Nginx 等反向代理將外部 80 請求轉發至內部8000 端口

容器內部並不需要 root 權限。


4. 如何: Dockerfile 的最佳實踐

那麼,應該怎麼適用呢?在Dockerfile 的最後添加幾行,這是最基本且重要的安全規則,而不是浪費。

Dockerfile

FROM python:3.12-slim

WORKDIR /app

# ... (apt-get install, pip install 等必要操作)

# 1. 創建無系統權限的(non-privileged)組和用戶
# -r: 以系統用戶/組的身份創建, --no-create-home: 不創建主目錄
RUN groupadd -r appgroup && useradd -r -g appgroup --no-create-home appuser

# 2. 在複製應用文件時指定所有權為 appuser
# (之後 WORKDIR 的文件也會遵循此所有權)
COPY --chown=appuser:appgroup . .

# 3. 之後的命令 будет выполнять пользователь appuser
USER appuser

# 4. 以 8000 之類的高端口啟動應用
EXPOSE 8000
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]

總結

root 身份運行容器就是放棄“深度防禦(Defense in Depth)”的概念。在隔離這道第一道防線破裂之時,自行拆除第二道防線(USER)。

現在就檢查一下你的 Dockerfile吧。