跳转至

Ubuntu sudo 免密配置不生效问题排查与修复记录

文档类型:故障排查与修复报告
故障时间:2026-07-29
修复时间:2026-07-29
影响主机:rian-Ubt(192.168.0.11 本地开发机)
影响用户:rian (uid=1000)
修复等级:P2(导致自动化脚本中 echo 密码管道方式间歇性失败,影响 crontab 与批量运维)


1. 问题现象与复现步骤

1.1 现象描述

用户 rian 在 /etc/sudoers 中明确配置了 NOPASSWD: ALL,但执行 sudo 命令时仍被要求输入密码:

rian@rian-Ubt:~$ sudo whoami
[sudo] password for rian:

这导致: 1. 项目脚本中 echo "密码" | sudo -S 命令 的方式虽然能绕过去,但不符合"免密"预期; 2. crontab / Ansible / 无人值守运维脚本执行 sudo 命令时报错或行为异常; 3. 与用户此前设置的"rian 拥有免密 sudo 权限"配置意图不符。

1.2 复现方式

# 方式一:直接执行(稳定复现)
sudo -n true   # -n = non-interactive,不弹密码,免密成功则 exit 0,失败 exit 1
echo $?        # 修复前返回非 0

# 方式二:查看实际生效的 sudo 规则
sudo -l
# 修复前输出中会同时出现 NOPASSWD: ALL 和 需要密码的 ALL,
# 最终执行时以需要密码那条为准

2. 系统环境与基础信息采集

2.1 主机与用户信息

项目 采集结果
操作系统 Ubuntu 22.04 LTS (rian-Ubt)
当前用户 rian (uid=1000, gid=1000)
用户从属组 rian adm cdrom sudo dip plugdev lpadmin lxd sambashare
sudo 版本 Sudo version 1.9.9 / policy plugin 1.9.9 / grammar 48

2.2 sudo 关键文件权限与属主排查

ls -la /etc/sudo.conf /etc/sudoers /usr/bin/sudo

采集结果:

文件 权限 属主 结论
/etc/sudo.conf -rw-r--r-- root:root ✅ 属主为 root,未被篡改
/etc/sudoers -r--r----- (0440) root:root ✅ 权限正确(必须 0440)
/usr/bin/sudo -rwsr-xr-x (setuid) root:root ✅ setuid 位正常(否则无法提权)

经验点:当以上 3 个文件中有任何一个属主不是 root(例如出现 65534 nobody/nogroup),或 sudo 二进制 setuid 位丢失,都会导致 sudo 直接报错 "sudo.conf is owned by uid X, should be 0" 或 "no new privileges"。本次故障不属于此类。


3. 根因分析:sudoers 规则匹配的 LAST MATCH WINS 机制

3.1 采集到的 /etc/sudoers 关键片段

# User privilege specification
root    ALL=(ALL:ALL) ALL
rian    ALL=(ALL) NOPASSWD: ALL        # ← 用户级免密,行号约 56

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL              # ← 组规则,行号约 65,需要密码

@includedir /etc/sudoers.d              # ← 最后加载 sudoers.d 下的文件

3.2 为什么 rian 的 NOPASSWD 没生效

关键原理:sudoers 是"多轮扫描 + 最后匹配优先(LAST MATCH WINS)"的匹配策略,不是"首次命中就返回"。

匹配过程:

  1. 执行 sudo 任意命令;
  2. sudo 扫描 /etc/sudoers:
    • 匹配到第 1 条用户规则:rian ALL=(ALL) NOPASSWD: ALL → 临时标记为"免密";
    • 继续往下扫;
    • 匹配到第 2 条组规则:%sudo ALL=(ALL:ALL) ALL(rian 在 sudo 组中 gid=27)→ 覆盖前一条,标记为"需要密码";
  3. 最终结果:需要密码。

3.3 用 sudo -l 交叉验证

修复前 sudo -l 输出的规则顺序:

User rian may run the following commands on rian-Ubt:
    (ALL) NOPASSWD: ALL          # 先匹配
    (ALL : ALL) ALL              # 后匹配(无 NOPASSWD)→ 覆盖上面那条,最终要密码

因此无论前面写了多少个 NOPASSWD,只要在它之后还有一条更宽松的同用户/同组规则不带 NOPASSWD,就一定会被覆盖。


4. 修复方案对比与选型

方案 做法 优点 缺点
A. 调整主 sudoers 顺序 将 rian NOPASSWD: ALL 行移动到 %sudo ALL 行之后 改动集中在主文件 后续 apt upgrade sudo 或系统更新可能覆盖;多人协作容易误改
B. 修改 %sudo 规则 给 %sudo ALL=(ALL:ALL) ALL 加上 NOPASSWD: sudo 组成员全部免密 范围过大,任何加入 sudo 组的账号都免密,违反最小权限
C. 在 sudoers.d 下建独立文件 新建 /etc/sudoers.d/90-rian-nopasswd,仅写入 rian 的免密规则 ✅ 在 @includedir 处最后加载,天然 LAST MATCH ✅ 不污染主 sudoers ✅ 系统升级不覆盖 ✅ 最小权限(仅 rian 一人) 无

最终选型:方案 C。


5. 修复操作全流程(可直接复现)

以下所有命令均已在故障机实际执行,标注 # 已执行。

5.1 步骤一:备份(高危操作强制要求)

# 备份 /etc/sudoers,命名带时间戳,避免多次备份互相覆盖
sudo cp /etc/sudoers /etc/sudoers.bak.$(date +%Y%m%d_%H%M%S)
# 已执行 → 生成 /etc/sudoers.bak.20260729_001249

5.2 步骤二:创建 /etc/sudoers.d/90-rian-nopasswd

不直接用 echo 重定向写入 /etc(普通用户没权限),正确姿势是:先写 /tmp,再用 install 命令以 root:root 0440 安装到位。

# ① 先写入 /tmp 临时文件(避免权限问题)
echo 'rian ALL=(ALL:ALL) NOPASSWD: ALL' > /tmp/rian-sudoers

# ② 用 install 命令复制到位,自动设置正确属主和权限
sudo install -o root -g root -m 0440 /tmp/rian-sudoers /etc/sudoers.d/90-rian-nopasswd

# ③ 清理临时文件
rm -f /tmp/rian-sudoers
# 已执行

最终生成文件:

/etc/sudoers.d/90-rian-nopasswd
权限: -r--r----- (0440)
属主: root:root
内容: rian ALL=(ALL:ALL) NOPASSWD: ALL

5.3 步骤三:语法校验(必做!否则 sudo 全废)

sudo visudo -c

必须全部显示 parsed OK,才说明语法正确。本次修复执行结果:

/etc/sudoers: parsed OK
/etc/sudoers.d/90-rian-nopasswd: parsed OK
/etc/sudoers.d/README: parsed OK

⚠️ 如果这一步出现 any parse error,务必立即回滚(见第 7 节),不要退出当前终端!
否则新开的所有 shell 都将无法再使用 sudo,只能进入单用户模式抢救。


6. 验证结果

6.1 免密执行验证

sudo whoami
# 修复后输出:root      (不再出现 [sudo] password for rian: 提示)

sudo -n true
echo $?
# 修复后输出:0         (-n 非交互模式执行成功)

6.2 生效规则顺序验证

sudo -l

修复后:

User rian may run the following commands on rian-Ubt:
    (ALL) NOPASSWD: ALL
    (ALL : ALL) ALL
    (ALL : ALL) NOPASSWD: ALL    # ← 新增的 sudoers.d 规则,最后一条匹配 → 免密生效

6.3 项目脚本兼容性验证

/home/rian/Documents/electricity_monitoring/ele_monitoring_venv/bin/python \
  /home/rian/Documents/electricity_monitoring/entry_electric_task.py
# 脚本中所有 sudo 操作均免密通过,不再需要 echo 密码管道。

7. 回滚与撤销方案

如果出现意外情况(例如发现范围过大、新规则语法错误),按以下顺序回滚:

7.1 温和回滚(只撤销本次新增文件)

sudo rm /etc/sudoers.d/90-rian-nopasswd
sudo visudo -c   # 再次校验语法

→ 回到修复前的行为(rian 需要输入密码才能 sudo)。

7.2 完全回滚(主 sudoers 也被改坏时)

sudo cp /etc/sudoers.bak.20260729_001249 /etc/sudoers
sudo chmod 0440 /etc/sudoers
sudo chown root:root /etc/sudoers
sudo visudo -c

备份文件路径:/etc/sudoers.bak.20260729_001249(请保留至少 30 天)。


8. 经验总结与最佳实践(面向后续运维)

8.1 sudoers 配置三条铁律

铁律 说明
优先用 /etc/sudoers.d/ 独立文件 不要在主 /etc/sudoers 里写业务用户的规则,放到 sudoers.d 下独立文件,文件名加数字前缀(如 90-)确保加载顺序可控
LAST MATCH WINS 牢记在心 同一个用户既匹配用户规则又匹配组规则时,写在文件最下面的那条才是最终生效的;用 sudo -l 随时检查
改完必须 visudo -c 语法错误会让整个系统 sudo 失效;绝对禁止用 vi /etc/sudoers 直接编辑,必须走 visudo 或至少 visudo -c 校验

8.2 命名建议

  • 用户级规则:/etc/sudoers.d/90-<username>-nopasswd
  • 服务/批量运维规则:/etc/sudoers.d/90-ansible、/etc/sudoers.d/90-monitoring
  • 组规则:/etc/sudoers.d/90-group-<groupname>

数字前缀参考: - 00-10:系统默认(README 等) - 10-50:通用 / 组规则 - 80-99:用户级覆盖规则(NOPASSWD、命令白名单等),确保最后加载。

8.3 检查清单(以后给任何用户开 sudo 免密都走一遍)

□ 用 id <username> 确认该用户是否已在 sudo/admin 组中
□ 配置写入 /etc/sudoers.d/90-xxx 而不是主 sudoers
□ 配置内容显式写 (ALL:ALL) 而非省略 host/cmd 字段
□ 执行 sudo visudo -c 返回全部 parsed OK
□ 执行 sudo -n whoami 直接返回 root(不开新终端测试)
□ 执行 sudo -l 确认 NOPASSWD 行在最后一条匹配位

9. 相关文件清单

文件/路径 变更说明 权限/属主
/etc/sudoers 仅备份,未修改 0440 / root:root
/etc/sudoers.bak.20260729_001249 备份(修复前的 sudoers) 0440 / root:root
/etc/sudoers.d/90-rian-nopasswd ✅ 新增,rian 免密规则 0440 / root:root
/home/rian/Documents/electricity_monitoring/docs/sudo_nopasswd_troubleshooting_20260729.md ✅ 本报告 0644 / rian:rian

文档结束 — 若后续再次遇到"配置了 NOPASSWD 但仍要密码"的情况,请直接跳至 第 3 节根因分析 + 第 5 节步骤三执行,99% 的情形都能解决。


来源:/home/rain/Documents/electricity_monitoring/docs/sudo_nopasswd_troubleshooting_20260729.md | 最后更新:2026-07-29 00:17(由 build/sync.py 同步,请勿在站点侧修改)