FR-193 · 需求討論稿 · 2026-11-10

Docker 引擎改以非 root 帳號執行

主系統與 Agent 兩邊的安裝包都改成 rootless Docker;程式與 image 不動

FR-180 把容器裡的程式全部改成非 root,這案把開容器的 Docker 引擎本身也改成非 root。要動的全在安裝程式、維運指令與客戶主機的系統設定,後端與 Agent 程式碼不改。這份給 PM 看:做什麼、客戶會感覺到什麼、哪些事要先決定。

D1–D4 全部定案 Agent 先行 程式碼不動
§1

這案在做什麼

客戶主機上的 Docker 引擎(開容器、接網路、掛磁碟的那支常駐程式)現在是用主機最高權限 root 跑。這案把它改成用一個專用的普通帳號 guidant 跑,九顆主系統容器與 Agent 端三顆容器都由這個帳號開。

這是 Docker 官方正式支援的執行模式,官方名稱就叫 Rootless mode。不是外掛、不是自製方案。

為什麼要做:FR-180 做完後,容器裡的程式已經不是 root,但萬一有人靠核心漏洞從容器逃出來,碰到的 Docker 引擎還是 root,等於拿到整台主機。改成 rootless 後,逃出來拿到的只是 guidant 這個普通帳號,動不了主機其他東西。資安稽核問「Docker 有沒有 rootless」時,答案從「沒有、理由如下」變成「有」。

不做什麼:

  • 容器裡面不動。FR-180 做好的 USER 1000、cap_drop、no-new-privileges 全部沿用。
  • 後端與 Agent 的程式碼不動(查過:兩邊都沒有程式在叫 docker、沒有依賴主機 uid)。
  • 不保留「root Docker」的舊安裝路徑。照 FR-180 慣例,沒有舊客戶就不留相容。
  • 不用 userns-remap(Docker 另一個官方設定,引擎仍是 root 只對映容器身分)。決策者 2026-11-10 裁示走完整 rootless。
§2

做完之後客戶會感覺到什麼

這一節是 PM 最需要知道的。

不變的

客戶打 https://主機 進站,不帶 port。登入、上傳、掃描、報表全部一樣。升級照樣 sudo ./install.sh --upgrade。

會變的

主機上多一個 guidant 系統帳號。客戶 IT 以 root 下 docker ps 會是空的(容器屬於 guidant 的 Docker),要改用 guidantai status/guidantai logs,或切到 guidant 帳號再下 docker 指令。手冊要明寫,否則客戶第一反應是「服務沒起來」。

裝機改了哪些主機設定

安裝程式會動六項主機 OS 設定(見 §4),客戶 IT 會問。全部自動做、移除時自動還原,但要列在手冊讓他們知道。

效能

rootless 的容器網路走使用者態 TCP/IP 堆疊,Docker 官方自己標「慢」。大檔上傳、Agent 回傳掃描結果的吞吐會降。第一棒要量實際數字,看是否要換官方列的較快驅動。

§3

技術上要動哪些地方

程式碼與 image 零改動。要動的分四層。

3.1 客戶主機 OS 設定(安裝程式自動做)

# 設定 為了什麼 不做會怎樣
1 建 guidant 系統帳號,並在 /etc/subuid、/etc/subgid 配一段子 uid(65536 個) rootless Docker 的前提,容器內身分對映到這段 rootless Docker 裝不起來
2 開啟 guidant 的 linger(loginctl enable-linger) 沒人登入時這個帳號的 Docker 也要開機自起 重開機後服務不回來,要有人登入才起
3 給 rootlesskit 執行檔加 CAP_NET_BIND_SERVICE 讓 guidant 的 Docker 能綁 443 容器起不來,報 cannot expose privileged port
4 sysctl net.ipv4.ping_group_range=0 2147483647 讓容器裡的 ping 能開 ICMP socket Agent 的 ping 永遠「無法連線」,網路診斷誤判
5 cgroup v2 委派檔 /etc/systemd/system/user@.service.d/delegate.conf compose 的記憶體與行程數上限才生效 上限全部靜默失效,吃爆記憶體拖垮主機
6 裝 uidmap、slirp4netns(或 pasta)、dbus-user-session 套件 對映工具與使用者態網路 rootless Docker 裝不起來

第 3 項有兩個官方做法:改 sysctl ip_unprivileged_port_start(全機所有帳號都能綁低 port)或只給 rootlesskit 這支執行檔加能力。建議後者,影響範圍只有我們自己,客戶資安問起好答。第 4 項放寬的只是「核心代填的 ICMP echo」,無法偽造來源、無法嗅探,且 Ubuntu 與 RHEL 預設本來就是全開,等於把 Debian 對齊到其他發行版的預設。

這六項在 Agent 端的被掃主機上要再做一次。

3.2 主系統安裝程式與維運指令(BE repo)

  • 建帳號、裝 rootless Docker、設開機自起:新增一整段,現在完全沒有。離線安裝包預帶 Docker Engine 完整一組加 rootless 附加套件(D4 定案,照 tpm2-tools 的先例,每個支援 OS 版本一份),install.sh 依主機有無 Docker 三段式處理。
  • 137 處 docker 呼叫改身分:安裝程式 83 處、guidantai 54 處,每處改成以 guidant 帳號、指向它的 socket 執行。做法是改一支共用函式讓所有呼叫走它,不逐行改。
  • require_root 拆段:建目錄、裝 systemd 單元、碰 TPM 仍要 root;起容器、看狀態、看 log 切到 guidant。
  • /srv/guidant-ai 底下目錄 owner 換算:現在 chown 給 999(資料庫)與 1000(應用),rootless 下要改成「guidant 的子 uid 起點 + 999/1000」,每台主機的數字可能不同,安裝程式要讀 /etc/subuid 算。算錯的症狀是容器起不來或寫入靜默失敗、log 只有 permission denied。
  • /run/guidant 解密設定目錄:現在 root 700,guidant 的 Docker 要讀得到才能把 guidant.env 掛進容器。密碼檔仍只有 root 與 guidant 能讀。
  • 三支 systemd 單元:guidant-secrets(要碰 TPM,留 root,但解完的檔要給 guidant)、guidant-upgrade-boot-check、guidant-upgrade-order(兩支都叫 docker,要能叫到 guidant 的 Docker)。
  • 升級與回滾流程整條重驗:邏輯不改,但每一步叫 docker 的身分變了,要從舊版帶資料升到新版跑一遍。
  • guidantai uninstall 補還原:sysctl 檔、委派檔、linger、帳號、subuid 一起清掉。

3.3 Agent 安裝程式(Agent repo)

  • 同 3.2 的建帳號、裝 rootless Docker、106 處 docker 呼叫、require_root 拆段、owner 換算、/run/guidant-agent。
  • compose 的 cap_add: [NET_RAW] 拿掉:rootless 下 cap_add 只在容器自己的 namespace 內有效(官方文件明寫),這行不會報錯但沒有作用。ping 改靠主機 sysctl 第 4 項。
  • 程式唯一要看的一處:網路診斷的 traceroute 在某情況下會加 -I 走 ICMP 模式,那需要 raw socket。rootless 下要實測會不會壞,壞就只走預設 UDP 模式。

3.4 測試、文件、資安報告

  • 190 測試機整台重裝成 rootless 跑 e2e 回歸(test repo)。
  • 安裝手冊與維運手冊:新增「裝機改了哪些主機設定」「客戶 IT 怎麼看容器」兩節。
  • 資安報告 SR-09.5「不採用 rootless docker」整段改寫成「已採用」。
§4

主機系統需求

rootless 要 cgroup v2 加 systemd 才有資源上限。目前支援清單 Ubuntu 22.04 以上、Debian 12 以上、RHEL 9 以上剛好都符合,門檻不用改。

§5

決策定案

四項全部定案(2026-11-10)。Agent 先出安裝包、主系統後續;D1 只有主系統要面對。

D1 主系統對外一律 8443(PM 2026-11-10 定案,取代同日稍早的路三)

不貼 rootlesskit 能力、不改 sysctl,compose 對外只映射 8443;80 的 http 轉址拿掉。客戶網址 https://<主機>:8443。理由:還沒出給客戶,改預設沒有相容成本;省掉 capability 升級重貼與主機掃描解釋。被排除:預設 443 貼能力(路三);全機 sysctl。只影響主系統(FR-193.1),Agent 對外本來就是 8444。

D2 ping sysctl 全開 0 2147483647(決策者 2026-11-10 定案)

與 Ubuntu、RHEL 出廠預設相同,等於把 Debian 對齊主流。放寬的只有核心代填的 ICMP echo,無法偽造來源、無法嗅探。被排除:只開 guidant 子 gid 區段——每台數字不同、客戶日後動 sysctl 可能蓋掉;客戶明確要求時再改。

D3 網路驅動用預設 slirp4netns(決策者 2026-11-10 定案)

Agent 流量是單向上傳掃描結果,對吞吐不敏感。第一棒在乾淨機量上傳 100 MB 的秒數留紀錄,真的影響到才換 pasta。bypass4netns 要自訂 seccomp,不考慮。

D4 離線安裝包連 Docker Engine 本體一起包(決策者 2026-11-10 定案)

離線包內含 Docker Engine 完整一組(docker-ce、cli、containerd、compose plugin、rootless extras、uidmap、slirp4netns 與相依),每個支援的 OS 版本一份,照 tpm2-tools 先例由 fetch_docker_debs.sh 抓。安裝包約多 400 到 500 MB。

install.sh 三段式處理:①主機沒有 Docker → 從離線包裝完整一組;②主機已有 Docker 且版本不低於我們釘的最低版 → 沿用,只補裝 rootless 附加套件;③主機 Docker 太舊 → 停下印訊息請客戶自行升級或移除,不代客戶動既有 Docker。

定案理由:客戶拿到包就能裝、不必先自己裝 Docker,這個理由比體積重要。釘版對落地版客戶是優點,底層不會被自動更新。代價是 SBOM 與弱點掃描範圍多了 Docker 本體,有 CVE 要跟著出包。

被排除:只包 rootless 附加三支、Docker 本體客戶自備——省 500 MB,但客戶要先查怎麼裝 Docker 官方套件庫,離線主機更麻煩。

§6

怎麼開始

順序:Agent 先出、主系統後續(決策者 2026-11-10 裁示)。Agent 安裝程式較小、沒有資料庫、沒有 443 問題,先拿它把 rootless 的坑踩完;主系統照抄做法。Agent 的拆卡在 design.md。

第一棒不寫程式。乾淨的 Ubuntu 24.04 一台,手動照官方文件裝 rootless Docker,把現有安裝包用 guidant 帳號裝一次,記錄每一步壞在哪、怎麼修好的,順便量網路吞吐、驗 D1 到 D3。產出是一份「實際壞了什麼」的清單。

有了清單再開後面的卡。這樣卡是按實測開,不是按這份文件的推測開。預估整案 15 到 20 張卡,跨 BE、Agent、test 三個 repo,Agent 端的卡等主系統那套做法定了再開。

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
  A[第一棒<br>乾淨機手動裝<br>列壞掉清單] --> B[主系統<br>安裝程式改 rootless]
  B --> C[主系統<br>升級與回滾重驗]
  B --> D[Agent<br>安裝程式改 rootless]
  C --> E[190 重裝<br>e2e 回歸]
  D --> E
  E --> F[手冊<br>資安報告]
圖 1 — 第一棒之後的大致順序