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)"的匹配策略,不是"首次命中就返回"。
匹配过程:
- 执行
sudo 任意命令; - sudo 扫描
/etc/sudoers:- 匹配到第 1 条用户规则:
rian ALL=(ALL) NOPASSWD: ALL→ 临时标记为"免密"; - 继续往下扫;
- 匹配到第 2 条组规则:
%sudo ALL=(ALL:ALL) ALL(rian 在 sudo 组中 gid=27)→ 覆盖前一条,标记为"需要密码";
- 匹配到第 1 条用户规则:
- 最终结果:需要密码。
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同步,请勿在站点侧修改)