Ubuntu 26.04 sudo-rs 的 sudoers 規則怎麼改
Ubuntu 26.04 預設改用 sudo-rs,sudoers 中含萬用字元的命令引數規則將不再匹配。了解 Ubuntu 24.04、25.10 與 26.04 的差異,以及應改寫成什麼。
Ubuntu 上 sudo-rs 的變更
Ubuntu 26.04 LTS 預設提供 sudo-rs,因此在全新伺服器上執行 sudo 命令時,會改用 Rust 重寫版本,而非原本的 C 程式。大多數 sudoers 檔案仍可照常運作。會失效的是命令引數中含有萬用字元的規則,因為 sudo-rs 不會比對引數文字中的 glob 模式。
Ubuntu 25.10 首先切換至 sudo-rs,Ubuntu 26.04 LTS 延續這項變更。Ubuntu 24.04 LTS 不受影響,因為除非手動安裝 sudo-rs,否則仍會選用原本的 sudo。這項變更會在你 將 Ubuntu 24.04 升級至 26.04,或在較新版本上建立新伺服器時產生影響。如果你也使用 interim release,請參閱Ubuntu LTS 與 interim release 在伺服器上的差異,了解哪台機器會先遇到這類變更。
確認伺服器實際執行的 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,其程式名稱帶有 .ws 尾碼:sudo.ws 和 visudo.ws。
Ubuntu 為何改用 sudo-rs
sudo 具有 setuid root 權限。系統上的任何使用者都能啟動它,而且它會以完整權限執行,因此其中的記憶體錯誤可能造成在地 root 權限提升漏洞。CVE-2021-3156 正是這種問題:任何在地使用者都能觸發堆積緩衝區溢位,而該錯誤存在於已發布的程式碼中約十年。Rust 能在編譯期間攔截這類錯誤,這就是重寫的主要理由。
第二個理由是功能範圍,而且這會直接影響你的設定。原始 sudo 在三十年間累積了大量功能,每項功能都代表更多以 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
有兩個預設值的行為不同,容易造成誤解。sudo-rs 無法關閉 env_reset:它永遠啟用。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 也會拒絕此命令。以下兩個命令可確認您自己伺服器上的規則實際允許哪些操作:sudo -l -U deploy 以 root 身分執行時,會列出該帳號實際可執行的命令;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確認路徑正確。如果規則指定 /bin/systemctl,但系統上的二進位檔位於 /usr/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 擁有的 wrapper
允許的項目過多而無法逐一列出時,請將判斷邏輯移出 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-restartsudoers 端只需指定一個命令:
deploy ALL=(root) NOPASSWD: /usr/local/sbin/app-restart *這裡保留結尾的 * 沒有問題,因為允許哪些操作是由 script 判斷,而不是由 sudo 判斷。這項作法只有在 script 由 root 擁有,且其他帳號無法寫入時才成立。若 deploy 可以寫入該檔案,deploy 就能替換檔案內容,並以 root 身分執行任何操作;這比你原本移除的萬用字元規則更危險。請使用 ls -l 檢查權限模式;如果輸出內容不易判讀,讀懂 drwxr-xr-x 權限字串只需花五分鐘學習。同樣的規則也適用於目錄:/usr/local/sbin 也不得由該帳號寫入,因為可寫入的目錄代表整個檔案都能被替換。
讓工作使用專用帳號,而不是 sudo 規則
更值得先問的問題,通常是該命令為何需要 root 權限。以專用使用者身分執行的服務,可由該使用者管理,不需要設定 sudoers 規則。對於 systemd 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,但尚未實作。即使實作,也無法防止有決心的使用者。若規則允許使用者以 root 身分執行編輯器或直譯器,該使用者就已取得 root 權限,任何 sudo 選項都無法改變這點。
工作階段錄製尚未實作,因此沒有 I/O 日誌,也沒有 sudoreplay。日誌只會寫入 syslog,且沒有 logfile 選項可將日誌重新導向到其他位置。因此,sudo 訊息會出現在系統現有的 syslog 目的地。
是否應切換回 sudo.ws?
可以。在 26.04 週期內,原本的套件仍會保留,原因正是如此。
sudo apt install sudo.ws
update-alternatives --config sudo
sudo update-alternatives --set sudo /usr/bin/sudo.ws請從 --config 輸出複製確切路徑,不要從本頁複製。該輸出列出的路徑才是你自己的系統會接受的內容。之後若要切回 sudo-rs,只需將 alternative 設定為同一份清單中的 sudo-rs binary 路徑。
在修改任何會影響 sudo 的設定前,請保持第 2 個 SSH 工作階段開啟,並以已登入且閒置的狀態待命。若 sudoers 檔案解析失敗,或 alternative 指向尚未安裝的 binary,遠端主機可能會失去切換為 root 的方法。這項習慣應與 新 VPS 上線後前 10 分鐘的其他工作一併遵守。
請將切回視為期限,而不是修正方案。這能為你爭取 1 週時間,妥善重寫規則。即使沒有這個期限,重寫仍值得進行,因為你刪除的每一條萬用字元規則,原本授予的權限都可能超出撰寫者實際理解的範圍。
FAQ
為什麼我的 sudoers 萬用字元規則在 Ubuntu 26.04 上停止運作?
因為 Ubuntu 26.04 LTS 預設使用 sudo-rs,而 sudo-rs 不會比對命令引數中的萬用字元模式。它允許在命令檔名中使用萬用字元、使用 "" 表示不接受引數,以及使用單一 * 作為最後一個引數。像 /usr/bin/systemctl restart app-* 這類規則會在引數中間放置模式,因此不會授予任何權限,命令也會遭到拒絕。以 root 身分執行 sudo -l -U deploy,查看該帳號實際具備的權限,然後將規則改成精確指定命令,或改用由 root 擁有的 wrapper script。
如何在 Ubuntu 26.04 上切回原本的 sudo?
原本的 sudo 套件名稱是 sudo.ws。使用 sudo apt install sudo.ws 安裝,接著使用 sudo update-alternatives --set sudo /usr/bin/sudo.ws 將 alternative 指向它。先執行 update-alternatives --config sudo,查看系統提供的確切路徑;變更設定時,請保留另一個 SSH 工作階段。無論選用哪個實作,這都不會恢復 sudo-ldap,因為該功能已從 26.04 移除。
sudo-rs 會讀取相同的 /etc/sudoers 檔案嗎?
會。sudo-rs 會讀取 /etc/sudoers 以及 /etc/sudoers.d/ 下的 drop-in 檔案,並使用相同的使用者、群組、別名、run-as 規格及 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,且無法停用,因此所有未保留的變數都會被清除。