为什么不能给所有人root密码
我刚工作那会儿,公司服务器的 root 密码写在一个共享文档里,每个人去查。直到有一天,有人误操作 rm -rf /usr/local/,半个系统瘫了。事后复盘发现:他只想执行一条需要 root 的命令,但习惯性 su - 切到了 root,然后打错了路径。
从那以后,公司规定:root 密码只有两个人知道。其他人用 sudo 执行特定命令,scope(范围)严格控制。
sudo 的核心思想
sudo(superuser do,超级用户执行)的精髓是 "最小权限原则"。你是一个普通用户,暂时需要 root 能力去完成某个任务。sudo 让你在执行特定命令时获得临时提升的权限,而不是把整个 root 身份交给你。
sudo 基础用法
# 以root身份执行命令
sudo apt update
# 以其他用户身份执行命令
sudo -u www-data whoami
# 以root身份打开shell
sudo -s
# 模拟root的登录shell(加载root的环境变量)
sudo -i
-u(user,指定用户)很实用。调试 Nginx 时我会用 sudo -u www-data touch /var/www/test.html,确保 web 用户对目录有写权限。不需要切到 www-data 账户去测试。
visudo:安全编辑sudoers文件
/etc/sudoers(sudo 的配置文件)语法非常严格。写错一行可能导致整个 sudo 系统失效,所有人(包括管理员)都无法提权。所以不要直接编辑,用 visudo(visual sudo,安全编辑sudo配置)。
# 安全编辑 sudoers 文件
sudo visudo
# 检查语法是否有误而不打开编辑器
sudo visudo -c
visudo 会在保存前做语法检查。语法不对就拒绝保存,这是保命设计。
推荐:在独立文件中配置
# 在 /etc/sudoers.d/ 下创建独立配置
sudo visudo -f /etc/sudoers.d/developers
用独立文件管理的好处:出错了删掉这个文件就行,不影响主 sudoers 文件。我在生产环境从来不改主文件,全部用小文件管理。
sudoers 语法详解
最简单的授权
# 用户 zhangsan 可以执行所有命令
zhangsan ALL=(ALL:ALL) ALL
格式解析(从左到右):
- ·
zhangsan:用户名或 %组名(%group) - ·第一个
ALL:适用于所有主机 - ·
(ALL:ALL):可以以任何用户和任何组的身份执行 - ·最后一个
ALL:可以执行所有命令
授权执行特定命令
# zhangsan 只能用 apt 安装和更新软件
zhangsan ALL=(root) /usr/bin/apt update, /usr/bin/apt upgrade
# devops 组的成员只能管理 systemd 服务
%devops ALL=(root) /usr/bin/systemctl restart *, /usr/bin/systemctl status *, /usr/bin/systemctl start *, /usr/bin/systemctl stop *
这是 sudo 最有价值的地方。开发人员需要重启服务,给他重启服务的权限就好。不需要给他 root shell。
NOPASSWD:免密码执行
# zhangsan 执行这些命令不需要输入密码
zhangsan ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx
NOPASSWD(no password,免密码)要慎用。如果攻击者拿下了 zhangsan 的账户,可以直接重启 Nginx 服务。权衡是:频繁操作(如服务启停)可以加 NOPASSWD,高危操作(如改密码、装包)一定要密码。
限制命令参数
# 只能执行命令,不能传任意参数
zhangsan ALL=(root) /usr/sbin/useradd
# 精确到参数
zhangsan ALL=(root) /usr/bin/passwd [A-Za-z]*, !/usr/bin/passwd root
感叹号 ! 是取反。上面这条权限让 zhangsan 可以改任何人的密码,除了 root。这个设计很精妙:给运维人员改普通用户密码的权限,但 root 密码只有管理员自己能改。
# 更严格的限制:只能重启特定的服务
zhangsan ALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl restart php*-fpm
* 通配符在这里匹配版本号,php7.4-fpm 和 php8.1-fpm 都匹配。
命令别名
# 在 /etc/sudoers 中定义别名
Cmnd_Alias SERVICES = /usr/bin/systemctl restart *, /usr/bin/systemctl status *, /usr/bin/systemctl start *, /usr/bin/systemctl stop *
# 然后给组授权
%devops ALL=(root) SERVICES
Cmnd_Alias(command alias,命令别名)让配置更清晰。多条相关命令组合成一个有意义的别名。
用户别名和主机别名
# 用户别名
User_Alias DEVELOPERS = zhangsan, lisi, wangwu
# 主机别名(多服务器场景)
Host_Alias WEBSERVERS = web01, web02, web03
# 组合使用
DEVELOPERS WEBSERVERS=(root) /usr/bin/systemctl restart nginx
日志审计:谁用sudo干了什么
sudo 默认记录所有执行日志。排查谁干了什么坏事时,这就是铁证。
# 查看 sudo 日志
sudo journalctl -t sudo --since "1 day ago"
# 或者查看 /var/log/auth.log
sudo grep sudo /var/log/auth.log | tail -20
# 或者配置专门的 sudo 日志文件
# 在 /etc/sudoers 中设置:
Defaults logfile=/var/log/sudo.log
有一次排查事故,发现有人凌晨3点执行了 sudo rm -rf /tmp/*。查看 sudo 日志,锁定是某位同事的 session 过期后自动重播了旧命令。有了日志才能快速定位问题。
常见踩坑
1. 用户不在 sudo 组
# 把用户加入 sudo 组(Debian/Ubuntu)
sudo usermod -aG sudo zhangsan
# 把用户加入 wheel 组(CentOS/RHEL)
sudo usermod -aG wheel zhangsan
Ubuntu 和 CentOS 用的 sudo 组名不同,这是跨平台运维的常见陷阱。
2. 环境变量不继承
# sudo 默认会重置 PATH,你的自定义命令可能找不到
sudo echo $PATH
# 输出简化的安全PATH
# 保留环境变量
sudo -E env "PATH=$PATH" your_command
-E(preserve environment,保留环境变量)在你需要特定环境变量时很有用。但注意这可能有安全隐患。
3. 通配符安全风险
# 看似安全的通配符,其实攻击者可以传入 --help 之类的参数
zhangsan ALL=(root) /usr/bin/rm /var/log/*.log
# 更好的做法:用 sudo 包装脚本
zhangsan ALL=(root) /usr/local/sbin/cleanup-logs.sh
通配符看似优雅,但容易被绕过。安全做法是把需要执行的操作封装成 shell 脚本,然后只授权执行这个脚本。
安全提醒
sudo 不是日志审计的全部。 虽然 sudo 会记录执行记录,但如果攻击者用 sudo -i 拿到 root shell,后续操作都不会被 sudo 记录。要配合 auditd(linux审计系统)一起用。
别把 NOPASSWD 给 ALL 命令。 直接配置 zhangsan ALL=(ALL) NOPASSWD: ALL 等于把 root 密码告诉了他但写成了别名。他可以用 sudo su - 直接变成 root。
定期审计 sudoers 文件。 检查是否有离职人员的 sudo 权限没回收,是否有过于宽泛的授权。
# 列出所有用户可以执行的sudo命令
for user in $(cut -d: -f1 /etc/passwd); do echo "=== $user ===" && sudo -l -U $user 2>/dev/null; done
sudo 不是万能的。 CAP_SETUID(设置用户ID的能力)权限相关的问题,sudo 处理不了。复杂的权限场景考虑用 SELinux(安全增强式Linux)或 AppArmor。
sudo 的精髓是给用户刚刚好的权限,不多也不少。配置好的 sudo 规则能让开发人员和运维人员各司其职,不用共享 root 密码,出事也能追溯到人。