Ubuntu 26.04 sudo-rs 变更:sudoers 配置指南
Ubuntu 26.04 默认启用 sudo-rs。本文解析 sudoers 文件中的关键差异,重点说明通配符参数匹配失效的问题,并提供正确的配置替代方案,助您平滑完成系统升级。
sudo-rs 在 Ubuntu 上的变更
Ubuntu 26.04 LTS 默认使用 sudo-rs 作为 sudo 的实现,因此在全新服务器上运行 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 时,或在新版本上部署服务器时。若您同时运行中间版本,LTS 与 Ubuntu 中间版本在服务器上的区别 一文解释了哪些机器会优先遇到此类变更。
检查服务器当前运行的 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 归档库的验证:dpkg -L sudo 列出了带有后缀的二进制文件,而 sudo-rs 则在它们旁边提供了 /usr/bin/sudo-rs。
为什么 Ubuntu 转向使用 sudo-rs
sudo 具有 setuid root 权限。系统上的任何用户均可启动它,且它启动时拥有完整权限,因此其内部的内存错误即为本地 root 提权漏洞。CVE-2021-3156 正是此类漏洞:任何本地用户均可触发堆缓冲区溢出,且该漏洞在已发布的代码中存在了约 10 年。Rust 在编译时即可捕获此类错误,这正是进行重写的核心理由。
第二个原因是作用域,这一点会直接影响您的配置。原始的 sudo 在过去 30 年中积累了大量功能,而每一项功能都意味着有更多代码以 root 权限运行。sudo-rs 有意仅实现了其中的一个子集。作者认为属于小众或具有潜在危害的功能均被剔除,因此您过去多年一直有效的 sudoers 配置规则可能会直接失效。您的通配符规则就是其中之一。
内存安全性消除了某一类错误。它并不能使程序完全无错,事实上,sudo-rs 自成为默认配置以来也发布过自身的安全补丁。请像对待其他软件一样对其进行补丁更新。
哪些 sudoers 规则仍然有效
该文件与原版一致。sudo-rs 会读取 /etc/sudoers 以及 /etc/sudoers.d/ 目录下的插件配置文件,并支持服务器管理员常用的配置项:
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 默认开启,因此命令会在其自身的伪终端中运行。
为什么您的通配符 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 拒绝使用这种结构,而不是试图使其变得安全,因为这种结构不存在通用的安全形式。
将通配符替换为明确的命令列表
大多数通配符规则的存在,仅仅是因为有人不想输入四行内容。请直接输入这四行。
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 拥有且仅 root 可写。如果 deploy 对该文件拥有写权限,deploy 即可替换其内容并以 root 身份运行任意命令,这比你移除的通配符规则危害更大。请使用 ls -l 检查权限模式;如果你看不懂输出结果,学习解读 drwxr-xr-x 权限字符串只需五分钟。同样的规则也适用于目录:/usr/local/sbin 也绝不能对该账户开放写权限,因为目录可写意味着其中的文件可以被整体替换。
为任务创建专用账户而非使用 sudo 规则
更好的做法通常是审视该命令为何需要 root 权限。如果服务以专用用户身份运行,则该用户可自行管理服务,无需配置 sudoers。对于 systemd 单元,systemd 会将权限决策委托给 polkit,因此规则可以指定特定的单元和操作员:
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
update-alternatives --config sudo
sudo update-alternatives --set sudo /usr/bin/sudo.ws请直接从 --config 的输出中复制确切路径,不要直接复制本页内容,因为该列表才是您系统所认可的路径。后续若要切回 sudo-rs,只需将替代项指向同一列表中对应的 sudo-rs 二进制文件路径即可。
在执行任何可能影响 sudo 的操作前,请务必保持第二个 SSH 会话处于已登录且空闲的状态。如果 sudoers 文件解析失败,或者替代项指向了一个未安装的二进制文件,您将无法在远程服务器上获取 root 权限。这一习惯应与您在 新购 VPS 后的前十分钟 内执行的所有操作一样,成为您的标准流程。
将切回操作视为一种缓冲手段,而非最终修复方案。这能为您争取一周时间来正确重写规则。重写本身非常有价值,因为您删除的每一条通配符规则,其权限范围往往都超出了原作者的预期。
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/ 下的附加配置文件,并支持相同的用户、组、别名、运行身份规范以及 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" 这样的行。env_reset 在 sudo-rs 中始终处于开启状态且无法禁用,因此所有未保留的变量都会被清除。