SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-09-08

Ubuntu 26.04 sudo-rs:sudoers 怎麼改?

Ubuntu 26.04 預設改用 sudo-rs。sudoers 中引數含萬用字元的規則將不再匹配,請改寫規則,避免升級或新建伺服器後授權失效。

Ubuntu 上 sudo-rs 的變更

Ubuntu 26.04 LTS 預設提供 sudo-rs,因此在新安裝的伺服器上執行 sudo 命令時,會使用 Rust 重新實作版本,而不是原本的 C 程式。大多數 sudoers 檔案都能照常運作。會失效的是在命令引數中包含萬用字元的規則,因為 sudo-rs 不會比對引數文字中的 glob 模式。

Ubuntu 25.10 首先完成切換,Ubuntu 26.04 LTS 延續這項變更。Ubuntu 24.04 LTS 不受影響,因為除非手動安裝 sudo-rs,否則仍會使用原本的 sudo。這項差異會在 從 Ubuntu 24.04 升級至 26.04 時生效,也會在較新版的 Ubuntu 上建立新伺服器時生效。如果你也執行中期版本,Ubuntu LTS 與中期版本在伺服器上的差異會說明哪台機器會先遇到這類變更。

確認伺服器實際執行的 sudo

不要根據發布版本號推斷。請直接向機器查詢。

sudo --version
update-alternatives --config sudo
dpkg -l 'sudo*'

請以自己伺服器上的 sudo --version 為準,不要相信網際網路上的任何版本表格,包括本頁。update-alternatives --config sudo 是另一半答案:它會列出 /usr/bin/sudo 的所有已安裝提供者,並標示目前選取的提供者。套件已安裝,不代表已被選取,因此請查看選取結果,而不是套件清單。

轉換期間會同時封裝這兩種實作。Rust 版本是 sudo-rs,截至 2026 年 8 月,26.04 中的版本為 0.2.13。由 Todd C. Miller 維護的原始版本仍是 sudo 套件;變更之處在於其程式會加上 .ws 尾碼,因此兩者可以同時安裝:/usr/bin/sudo.ws 和 /usr/bin/visudo.ws,以及 cvtsudoers.ws 和 sudoreplay.ws。我們已在 2026 年 9 月對照 26.04 archive 驗證:dpkg -L sudo 會列出帶尾碼的二進位檔,而 sudo-rs 會在其中一併提供 /usr/bin/sudo-rs。

為什麼 Ubuntu 改用 sudo-rs

sudo 採用 setuid root。系統上的任何使用者都能啟動它,而它會以完整權限啟動,因此其中的記憶體錯誤可能成為本機 root 漏洞。CVE-2021-3156 正是這種情況:任何本機使用者都能觸發的 heap buffer overflow,而且存在於已發布的程式碼中約十年。Rust 能在編譯時攔截這類錯誤,這就是重寫的主要理由。

第二個原因是功能範圍,而且這正是會影響設定的部分。原始 sudo 在 30 多年間累積了大量功能,而每項功能都代表更多以 root 執行的程式碼。sudo-rs 刻意只實作其中一部分。其作者認為過於偏門或可能造成實際危害的功能都未納入,因此原本運作多年的 sudoers 語法可能完全不存在。你的萬用字元規則就是其中之一。

記憶體安全只能排除一類錯誤。它不會讓程式完全沒有錯誤;sudo-rs 在成為預設工具後,也曾發布自身的安全性修正。請像處理其他軟體一樣套用修補程式。

仍可使用哪些 sudoers 規則

檔案本身不變。sudo-rs 會讀取 /etc/sudoers 以及 /etc/sudoers.d/ 中的 drop-in 檔案,伺服器管理員通常會撰寫的規則都受到支援:

  • deploy ALL=(ALL:ALL) ALL,以及 %sudo ALL=(ALL:ALL) ALL 等群組格式
  • NOPASSWD: 與 PASSWD: 標籤
  • User_Alias、Runas_Alias、Host_Alias 與 Cmnd_Alias
  • 指定完整引數清單的命令,例如 /usr/bin/systemctl restart app-api
  • 命令後接 "",表示只允許不帶任何引數執行該命令
  • 命令最後一個引數使用 *,表示允許任意後續引數
  • 以 / 結尾的目錄路徑,表示允許該目錄中的任何命令
  • 使用 ! 從清單中排除命令
  • Defaults 的實用子集,包括 secure_path、env_keep、env_check、timestamp_timeout、passwd_tries、editor、umask、targetpw、rootpw 與 use_pty

有兩個預設值的行為不同,容易造成誤解。env_reset 在 sudo-rs 中無法關閉,永遠會啟用。use_pty 預設會啟用,因此命令會在自己的 pseudo-terminal 中執行。

為何您的萬用字元 sudoers 規則不再相符

萬用字元仍可用於一個位置:命令的檔案名稱。%ops ALL = /sbin/fsck* 規則仍允許執行 sudo fsck 和 sudo fsck_exfat,因為 * 是要與檔案系統比對的路徑之一部分。

在引數清單中,sudo-rs 只接受兩種特殊形式,而且兩者都不是模式。"" 表示不接受任何引數。結尾的 * 表示可接受任意後續引數。其他所有引數都會以純文字逐字比對。因此,%ops ALL = /sbin/service ntp * 沒有問題,因為 ntp 是純文字,而 * 位於最後。不過,以下這類規則不會授予您原本想授予的任何權限:

deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app-*

app-* 是位於引數中間的模式。sudo-rs 不會展開它,因此該規則不涵蓋 systemctl restart app-api,sudo 也會拒絕執行該命令。以下兩個命令可如實顯示您自己伺服器上的規則狀態:以 root 身分執行 sudo -l -U deploy,可列出該帳號實際能執行的命令;sudo visudo -c 則可確認檔案是否能成功解析。開始任意修改前,先執行這兩個命令。

萬用字元規則一直都是漏洞

在原始的 sudo 中,您輸入的引數會先串接成一個字串,再與規則中的引數字串進行 glob 比對。glob 會比對空白字元。這正是幾乎所有人都會忽略的部分。

sudo-rs 文件提供了最清楚的示範。規則 /bin/rm *.txt 也會允許 sudo rm -rf /home .txt,因為單一的 * 會吞掉 -rf /home ,而串接後的字串仍以 .txt 結尾。這條規則表面上是「只能處理文字檔」。實際上卻代表「只要該行以 .txt 結尾,任何引數都可以」。

systemctl 範例也是相同情況。由於引數會以一個串接後的字串進行比對,結尾的模式也會比對您在其後附加的內容。因此,restart app-* 不只涵蓋 restart app-api,也涵蓋呼叫端加入的任何後續引數。引數中的模式會放寬其周圍的引數限制,而命令的權限正是由引數決定。sudo-rs 不會嘗試將這種寫法變得安全,而是直接拒絕,因為不存在通用且安全的寫法。

將萬用字元替換為明確的命令清單

大多數萬用字元規則的存在原因,是有人不想輸入 4 行。請直接輸入這 4 行。

Cmnd_Alias APP_RESTART = /usr/bin/systemctl restart app-api, /usr/bin/systemctl restart app-worker
Cmnd_Alias APP_STATUS = /usr/bin/systemctl status app-api, /usr/bin/systemctl status app-worker
deploy ALL=(root) NOPASSWD: APP_RESTART, APP_STATUS

確認路徑正確。若系統上的二進位檔位於 /usr/bin/systemctl,規則卻指定 /bin/systemctl,規則永遠不會比對成功,且失敗情況看起來與權限問題完全相同。請使用 command -v systemctl 確認,並貼上其輸出內容。

請將規則放在獨立的 drop-in 檔案中,不要放在 /etc/sudoers,如此套件升級就不會覆寫你的修改:

sudo visudo -f /etc/sudoers.d/90-deploy
sudo visudo -c
sudo -l -U deploy

檔名不要包含句點,也不要以波浪號結尾。原始的 sudo 會忽略 sudoers.d 中檔名含有句點的檔案,因此 90-deploy.conf 是常見的靜默無效設定。遵循這項慣例不會增加任何成本。

清單過長時使用 root 擁有的包裝程式

允許清單過大而無法直接列出時,請將判斷邏輯移出 sudoers,改由 root 擁有的小型程式處理。

sudo tee /usr/local/sbin/app-restart >/dev/null <<'EOF'
#!/bin/sh
set -eu
case "${1:-}" in
  app-api|app-worker) ;;
  *) echo "app-restart: not allowed: ${1:-}" >&2; exit 1 ;;
esac
exec /usr/bin/systemctl restart "$1"
EOF
sudo chown root:root /usr/local/sbin/app-restart
sudo chmod 0755 /usr/local/sbin/app-restart
ls -l /usr/local/sbin/app-restart

接著,sudoers 端只需指定一個命令:

deploy ALL=(root) NOPASSWD: /usr/local/sbin/app-restart *

此處保留結尾的 * 是可接受的,因為決定允許內容的是該腳本,而不是 sudo。這只有在腳本由 root 擁有,且其他帳號無法寫入時才成立。若 deploy 能寫入該檔案,deploy 就能替換其內容,並以 root 身分執行任何操作,這比你移除的萬用字元規則更危險。請使用 ls -l 檢查模式;如果輸出對你而言不夠直觀,閱讀 drwxr-xr-x 權限字串只需五分鐘即可學會。同一項規則也適用於目錄:/usr/local/sbin 不得由該帳號寫入,因為可寫入的目錄代表檔案可以被整個替換。

讓工作使用專用帳號,而不是 sudo 規則

更好的問題通常是:該命令為何需要 root 權限?以專用使用者身分執行的服務,可以由該使用者管理,不需要加入 sudoers 規則。對於 system unit,systemd 已將這項權限決策交由 polkit 處理,因此規則可以指定單一 unit 與單一操作人員:

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units" &&
        action.lookup("unit") == "app-api.service" &&
        subject.user == "deploy") {
        return polkit.Result.YES;
    }
});

將此內容儲存為 /etc/polkit-1/rules.d/50-app-api.rules,如此 deploy 即可完全不使用 sudo 執行 systemctl restart app-api。請從實際使用該權限的確切執行環境進行測試,因為在 SSH 工作階段中有效的規則,仍應先從 cron 確認後再依賴。無論採用哪種方式,執行工作的帳號都應只為該項工作而存在;這也是 VPS 上採用最小權限使用者帳號 所依據的原則。

sudo-rs 未實作的其他功能

sudo -E 尚未實作。請改用 Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY" 指定所需的變數,並記住 env_reset 一律啟用,因此未保留的內容都會被清除。

集中式 LDAP sudoers 儲存功能已移除。sudoers.ldap 與 cvtsudoers 尚未實作,sudo-ldap 套件也已在 26.04 移除。透過 PAM 或 SSSD 進行 LDAP 驗證仍可運作。超出支援範圍的是將政策儲存在目錄中的功能。

INTERCEPT 原本用於阻止已允許的命令啟動 shell escape,但目前尚未實作。即使過去也無法真正防住有意突破限制的使用者。如果規則允許某人以 root 身分執行編輯器或直譯器,該使用者就已具備 root 權限;任何 sudo 選項都無法改變這點。

工作階段錄製功能尚未實作,因此沒有 I/O 日誌,也沒有 sudoreplay。日誌只會寫入 syslog,且沒有 logfile 選項可將其重新導向到其他位置,因此 sudo 訊息會出現在系統原本設定的 syslog 目的地。

是否應切回 sudo.ws?

可以。在 26.04 週期內,原始版本會基於這個原因繼續提供套件。

sudo apt install sudo
update-alternatives --config sudo
sudo update-alternatives --set sudo /usr/bin/sudo.ws

請從 --config 的輸出複製確切路徑,不要從本頁複製,因為該輸出列出的是您自己的系統會接受的項目。日後若要切回 sudo-rs,請將替代項目設定為同一份清單中的 sudo-rs 二進位檔路徑。

在變更任何會影響 sudo 的設定前,請保持第二個 SSH 工作階段開啟,並以已登入且閒置的狀態保留。如果 sudoers 檔案無法解析,或替代項目指向未安裝的二進位檔,您可能無法在遠端主機上切換為 root。這項習慣應與您在新 VPS 的前 10 分鐘內執行的其他工作一併遵守。

請將切回視為期限,而不是修正方案。這能讓您多出一週,正確重寫規則。即使沒有這項期限,重寫規則本身也值得進行,因為刪除的每一條萬用字元規則,原本授予的權限都可能超出撰寫者所理解的範圍。

FAQ

我的 sudoers 萬用字元規則為何在 Ubuntu 26.04 中停止運作?

因為 Ubuntu 26.04 LTS 將 sudo-rs 設為預設的 sudo,而 sudo-rs 不會比對命令引數中的萬用字元模式。它允許在命令檔名中使用萬用字元、使用 "" 表示不接受引數,以及將單一 * 作為最後一個引數。像 /usr/bin/systemctl restart app-* 這類規則會在引數中間放入模式,因此不會授予任何權限,命令也會遭到拒絕。以 root 身分執行 sudo -l -U deploy,確認該帳戶實際擁有的權限,再將規則替換為精確命令或由 root 擁有的包裝腳本。

如何在 Ubuntu 26.04 切回原本的 sudo?

原本的 sudo 位於 sudo 套件中,其二進位檔帶有 .ws 尾碼。使用 sudo apt install sudo 安裝,然後使用 sudo update-alternatives --set sudo /usr/bin/sudo.ws 將替代項目指向該版本。先執行 update-alternatives --config sudo,查看系統提供的確切路徑;變更設定時,請保持第二個 SSH 工作階段連線。無論選用哪個實作,這都不會恢復 sudo-ldap,因為它已從 26.04 移除。

sudo-rs 會讀取相同的 /etc/sudoers 檔案嗎?

會。sudo-rs 會讀取 /etc/sudoers 以及 /etc/sudoers.d/ 下的 drop-in 檔案,並支援使用者、群組、別名、執行身分規格及 NOPASSWD 標籤的相同語法。它只實作 sudoers 語言的子集,因此差異表現為缺少某些語法結構,而不是既有結構的行為不同。使用 sudo visudo 編輯,然後以 sudo visudo -c 驗證,再關閉工作階段。

sudo-rs 中的 sudo -E 有什麼替代方案?

sudo -E 尚未實作;在原本的 sudo 中也已不建議使用,因為將由呼叫端控制的環境交給 root 程序,是改變該程序行為的已知方式。請改在 sudoers 中列出實際需要的變數,例如 Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY"。sudo-rs 會一律啟用 env_reset,且無法停用,因此所有未保留的變數都會被清除。

#sudo#sudo-rs#ubuntu#sudoers#permissions