第17章:sudo 权限管理

第十七章:sudo 权限管理

想象一下这个场景:你是公司大楼的物业经理,但不是老板。你有一把"万能钥匙",能在紧急情况下打开任何门——但平时你只能用自己家的钥匙。

这就是sudo!普通用户用它临时"升级"成root,干完活又自动降级回普通用户。

这一章,我们来聊聊为什么不要直接用root、怎么用sudo、以及sudo的配置大法。


17.1 为什么要用 sudo?直接用 root 的危险

直接用root有多危险?这么说吧——root权限就像一把上了膛的枪,走火就是命。

root 的三大原罪

1. 杀伤力太大

root可以rm -rf /——没错,这就是著名的"删库跑路"命令。如果你用root敲了这个,眼睛一闭,服务器就成空壳了。

1
2
3
# 当你以root身份运行时:
rm -rf /     # 这是真的在删系统!不是演习!
# 系统:完蛋,告辞

2. 没有问责机制

root做的任何操作,系统都记录为"root干的"。如果团队里有多个人用root,你根本不知道是谁误删了文件。

1
2
3
4
5
# root的操作日志里只写:
# root : 删库了
# root : 又删库了
# root : 删库真爽
# 分不清是谁!

3. 远程入侵等于全盘沦陷

如果有人破解了你的root密码,你的整个系统就像大门敞开的金库——想拿什么拿什么。

sudo 的四大好处

sudo的好处解释
最小权限原则只给需要的命令授权,不多不少
操作可追溯每次sudo都有日志,记录谁在什么时候干了什么
防误操作sudo通常要求输入自己的密码,不是root密码
团队协作友好不同用户有不同的sudo权限,互不干扰

📊 root vs sudo 对比图

graph LR
    A[root用户] --> B["🔴 危险<br/>权限太大<br/>无法追溯<br/>一失足成千古恨"]
    
    C[普通用户 + sudo] --> D["🟢 安全<br/>按需授权<br/>操作日志<br/>密码保护"]
    
    style A fill:#ff6b6b
    style C fill:#51cf66

17.2 sudo 命令使用

sudo的基本语法超级简单:sudo + 你想执行的命令

17.2.1 sudo + 命令 —— 最基本用法

1
2
3
4
5
6
# 普通用户想查看系统日志,但日志只有root能读
sudo cat /var/log/auth.log

# 系统会提示输入当前用户的密码(不是root密码!)
# [sudo] password for longx:   # 输入longx的密码
# ... 日志内容输出 ...

17.2.2 sudo -i —— 切换到 root

如果你要连续执行多个root命令,用sudo 命令一个一个敲太麻烦了,可以切换到root shell:

1
2
3
4
5
6
7
8
# 切换到root shell(会要求输入当前用户密码)
sudo -i

# 现在你是root了,提示符会变成 # 
# root@server:/home/longx#

# 干完活,输入 exit 退回到普通用户
exit

17.2.3 sudo -s —— 保持当前用户环境

1
2
3
4
5
6
# -s 会启动一个root shell,但保持当前用户的环境变量
sudo -s

# 跟 -i 的区别:
# -i : 以login shell启动,环境变量重置为root的
# -s : 以non-login shell启动,保持当前用户的环境
1
2
3
4
# 实际对比
sudo -i     # login shell:HOME=/root,并自动 cd 到 /root
sudo -s     # non-login shell:HOME 仍是你自己的家目录,当前目录不变
sudo -H 命令 # 只把这一条命令的 HOME 设为目标用户的家目录

最直观的区别:sudo -i 之后敲 pwd 会看到 /root、echo $HOME 也是 /root;sudo -s 之后 HOME 往往还是 /home/你的名字。所以想让 root 的命令找不到你的个人配置文件(更"干净"、更接近真正登录 root),用 -i;想保留当前环境(比如沿用你的 proxy、PATH 设置),用 -s。 安全提醒:sudo -s 保留环境变量有风险——如果 PATH 里混入了当前用户可写的目录,root 执行命令时可能被"劫持"(这就是经典的 PATH 提权)。要精确控制保留哪些变量,用 /etc/sudoers 里的 Defaults env_keep += "..."。

17.2.4 sudo 常用选项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# -u 用户名:以指定用户的身份运行命令
sudo -u www-data whoami
# 输出:
# www-data

# -l:查看当前用户可以用sudo运行哪些命令
sudo -l

# 输出大概是:
# [sudo] password for longx:
# User longx may run the following commands on this host:
#     (ALL : ALL) ALL

# -k:清除sudo缓存,下次使用需要重新输入密码
sudo -k

# -v:延长sudo验证时间(刷新缓存)
sudo -v

# 查看sudo版本
sudo -V
1
2
3
4
5
6
7
8
9
# 实战例子:安装软件(Debian/Ubuntu)
sudo apt update
sudo apt install nginx

# 实战例子:重启服务
sudo systemctl restart nginx

# 实战例子:以www-data用户身份创建文件
sudo -u www-data touch /var/www/html/test.txt

17.3 /etc/sudoers 文件:sudo 配置

sudo的配置都存在/etc/sudoers文件里。这个文件极其重要,配置错了可能导致:

  • 所有人都不能用sudo(系统管理灾难)
  • 所有人都变成root(安全灾难)

17.3.1 格式:用户 主机=(用户) 命令

sudoers的基本格式是:

1
用户 主机=(目标用户:目标组) 命令

解释一下:

  • 用户:哪个用户可以使用sudo
  • 主机:在哪台主机上可以用(通常用ALL表示所有主机)
  • 目标用户:以哪个用户身份运行命令(通常用ALL表示任意用户)
  • 目标组:以哪个组身份运行命令(通常省略)
  • 命令:可以运行哪些命令(通常用ALL表示所有命令)

17.3.2 %组名:用户组

用%组名表示一个用户组的成员:

1
2
3
4
5
6
7
# sudoers文件里的配置示例:

# 允许wheel组的成员以root身份运行所有命令
%wheel  ALL=(ALL)  ALL

# 允许developers组的成员以任意用户身份运行某些命令
%developers  ALL=(ALL)  /usr/bin/systemctl restart, /usr/bin/apt

wheel 还是 sudo? 两个名字都是"管理员组",只是发行版传统不同:RHEL/CentOS/Fedora 用 %wheel,Debian/Ubuntu 用 %sudo。所以看到教程里写 usermod -aG wheel 用户 却在自己的 Ubuntu 上没反应,就是因为它该用 sudo 组。想知道自己系统上默认是哪个:grep -E '^%(wheel|sudo)' /etc/sudoers。

17.3.3 ALL=(ALL) ALL:所有命令

最常见的配置:

1
2
# 让用户longx可以在任何主机上以任何用户身份运行任何命令
longx  ALL=(ALL)  ALL

这个配置的意思是:

  • longx —— 用户longx
  • ALL=(ALL) —— 可以在任何主机上,以任何用户身份
  • ALL —— 运行任何命令

17.3.4 NOPASSWD:无密码

有些场景下,你可能想让某些命令不需要输入密码:

1
2
3
4
5
# 让longx在本地不需要密码就能sudo
longx  ALL=(ALL)  NOPASSWD: ALL

# 或者只对特定命令免密
longx  ALL=(ALL)  NOPASSWD: /usr/bin/systemctl restart nginx

17.3.5 sudoers 配置示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 1. 给单个用户完全sudo权限
longx  ALL=(ALL)  ALL

# 2. 给用户组完全sudo权限
%sudo  ALL=(ALL)  ALL

# 3. 让运维用户可以运行所有命令,但需要密码
%ops  ALL=(ALL)  ALL

# 4. 让开发用户只能重启nginx服务
%developers  ALL=(ALL)  /usr/bin/systemctl restart nginx

# 5. 让数据库管理员可以管理mysql
%dbadmin  ALL=(mysql)  ALL

# 6. 限制用户只能以root身份运行特定命令
john  ALL=(root)  /usr/bin/systemctl restart httpd, /usr/bin/systemctl stop httpd

# 7. 限制用户只能在特定主机上使用sudo
alice  webserver01=(ALL)  ALL

17.3.6 Defaults:全局行为调整

除了"谁能干什么",sudoers 里还有一类以 Defaults 开头的行,用来调整 sudo 的全局行为。几个最常用的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /etc/sudoers 或 /etc/sudoers.d/ 下的配置

# 免密时长:默认 15 分钟,改成 5 分钟
Defaults    timestamp_timeout=5
# 设为 0 表示"每次都问密码",-1 表示"本会话内永远不问"

# 记录 sudo 命令的输出到日志(审计用,file 目录需先存在)
Defaults    log_output

# 强制在安全 PATH 下执行,避免用户用自己 PATH 里的假命令冒充系统命令
Defaults    secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# 每次 sudo 都重新验证密码(最严格的审计环境)
Defaults    timestamp_timeout=0

# 针对单个用户单独设置
Defaults:zhangsan    timestamp_timeout=0

timestamp_timeout 是安全与便利的平衡点:默认 15 分钟内免密,意味着只要你的终端被别人接管 15 分钟内的 sudo 就无需再输密码(这也是很多人不理解"我明明锁屏了怎么还被提权"的原因)。对高安全环境建议调小或设为 0。 secure_path 是默认开启的,它保证 sudo 命令 走的是系统标准路径;但注意它并不保护你在 sudo 命令行里主动写的相对路径(比如 sudo ./script.sh 依然会执行当前目录的脚本,而当前目录可能被普通用户写过)。


17.4 visudo 安全的编辑 sudoers

绝对不要直接用文本编辑器打开/etc/sudoers! 如果你保存的时候语法错误了,下次谁都可能用不了sudo——包括你自己!

正确做法是用visudo命令。

17.4.1 语法检查

1
2
# 用visudo编辑sudoers(会锁定文件防止其他人同时编辑)
sudo visudo

visudo会在你保存前检查语法:

  • 如果语法正确,正常保存退出
  • 如果语法错误,会报错并让你重新编辑:
1
2
3
4
5
# 错误示例:忘了写ALL
longx  ALL=      # visudo会说:>>> /etc/sudoers: syntax error, line 25 <<

# 正确写法:
longx  ALL=(ALL)  ALL

17.4.2 锁定文件

visudo 编辑时会自动给 sudoers 文件加锁,防止两个人同时编辑造成文件损坏(第一个人退出前,第二个人会看到"This file is busy, still try to open?“之类的提示)。

下面这条命令则和"锁定"无关——它是用来检查所有 sudoers 文件的语法:

1
2
3
4
5
6
# 检查 /etc/sudoers 及 /etc/sudoers.d/ 下所有配置的语法
sudo visudo -c

# 输出大概是:
# /etc/sudoers: parsed OK
# /etc/sudoers.d/README: parsed OK

-c 是 check(检查),不是查看锁定状态。改完 sudoers 想确认有没有写错,或者怀疑队友改坏了,跑一条 sudo visudo -c 就能一眼看出哪个文件第几行有问题。第 17.4 节前面说的"visudo 保存前自动检查语法"和这个是同一套检查逻辑。

17.4.3 在 sudoers.d 目录下添加独立配置

/etc/sudoers.d/目录是存放额外sudo配置的地方。好处是:

  • 不污染主sudoers文件
  • 可以单独管理不同用户/组的配置
  • 出问题了可以快速禁用(移动文件即可)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 在 /etc/sudoers.d/ 下创建一个配置文件
sudo visudo -f /etc/sudoers.d/zhangsan

# 添加内容:
# zhangsan  ALL=(ALL)  ALL

# 保存退出

# 目录下的文件需要设置正确的权限
sudo chmod 0440 /etc/sudoers.d/zhangsan

# 查看所有sudoers配置
sudo visudo -c
# /etc/sudoers: parsed OK
# /etc/sudoers.d/zhangsan: parsed OK
1
2
3
4
5
6
# 推荐的sudoers.d目录结构:
/etc/sudoers.d/
├── README              # 说明文件(会被忽略)
├── admin               # 管理员组配置
├── developers          # 开发者配置
└── zhangsan            # 某个用户的单独配置

17.5 sudo -l 查看当前用户权限

想知道当前用户能sudo哪些命令?用-l选项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 查看当前用户的sudo权限
sudo -l

# 输出示例1(普通用户):
# User longx may run the following commands on this host:
#     (ALL : ALL) ALL

# 输出示例2(受限用户):
# User zhangsan may run the following commands on this host:
#     (root) /usr/bin/systemctl restart nginx
1
2
3
4
5
6
7
# 查看"另一个用户"有哪些 sudo 权限(注意是大写 -U)
sudo -l -U zhangsan

# 以指定用户身份运行whoami
sudo -u zhangsan whoami
# 输出:
# zhangsan

-u 和 -U 别搞反:小写 -u 用户 是"以这个用户的身份去运行命令”;大写 -U 用户 是"去看看这个用户被授权了哪些命令"。写成 sudo -l -u zhangsan 会变成"以 zhangsan 的身份列出权限"(普通用户下多半报权限不足)。

1
2
# 查看某个特定命令是否可以用sudo运行
sudo -l | grep systemctl

17.6 sudo 日志:/var/log/auth.log

sudo的每次使用都会被记录下来。日志文件通常是:

  • Debian/Ubuntu: /var/log/auth.log
  • RHEL/CentOS/Fedora: /var/log/secure
  • Arch Linux: /var/log/auth.log或journalctl

查看sudo操作记录

1
2
3
4
5
6
# 查看最近的sudo操作
sudo tail -50 /var/log/auth.log | grep sudo

# 输出大概是:
# Mar 23 12:00:00 server sudo: longx : TTY=pts/0 ; PWD=/home/longx ; USER=root ; COMMAND=/bin/cat /var/log/auth.log
# Mar 23 12:05:00 server sudo: longx : TTY=pts/0 ; PWD=/home/longx ; USER=root ; COMMAND=/usr/bin/apt update

每条日志都记录了:

  • 用户:谁执行的sudo
  • 时间:什么时候
  • TTY:从哪个终端
  • PWD:执行时在哪个目录
  • USER:以哪个用户身份运行
  • COMMAND:执行了什么命令

journalctl 查看sudo日志(systemd系统)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 查看 sudo 的日志(journalctl 方式)
# 注意:sudo 不是一个 systemd 服务,-u sudo 查不到任何东西!
# 要用 -t(按 syslog 的 tag 过滤):
sudo journalctl -t sudo

# 查看最近100条 sudo 记录
sudo journalctl -t sudo -n 100

# 实时查看 sudo 日志
sudo journalctl -t sudo -f

# 也可以按命令名过滤(适用于日志来自 audit 的情况)
sudo journalctl _COMM=sudo -n 50

为什么 -u sudo 是错的:-u 过滤的是 systemd 服务单元(unit),而 sudo 只是普通命令,运行时并不存在 sudo.service,所以 journalctl -u sudo 永远返回空。按"日志标签"过滤要用 -t,按"产生的进程名"过滤用 _COMM=。 另外在 logind/sudo 较新的版本里,Debian/Ubuntu 的 sudo 日志也可能已经进入 journal 而不再写 /var/log/auth.log,所以两种查法都试一下。

📊 sudo日志分析示例

graph LR
    A["sudo日志记录的内容"] --> B["谁?<br/>longx"]
    A --> C["什么时候?<br/>Mar 23 12:00"]
    A --> D["从哪登录?<br/>pts/0"]
    A --> E["用什么身份?<br/>root"]
    A --> F["干了什么?<br/>cat /var/log/auth.log"]
    
    style A fill:#dfe6e9
    style B fill:#ffeaa7
    style C fill:#ffeaa7
    style D fill:#ffeaa7
    style E fill:#ff6b6b
    style F fill:#51cf66

17.7 sudo 实战配置案例

案例1:创建一个"不能删库跑路"的管理员

1
2
3
# 用visudo添加以下配置:
# 允许 zhangsan 查看服务状态和日志,但不允许他重启/停止服务、更不能 rm
zhangsan  ALL=(root)  /usr/bin/systemctl status *, /usr/bin/journalctl

⚠️ sudoers 的命令匹配是"按整行精确匹配"的:写 /usr/bin/systemctl restart 意味着"只允许恰好执行这一条、且不带任何参数的命令",实际上根本没法用(真实命令总是带服务名)。所以要配合 通配符:/usr/bin/systemctl restart *。 但通配符也要小心——systemctl 参数里的 * 覆盖面很广,而且 systemctl 能做的事远不止重启服务(它还能改单元文件、跑任意命令),放行 systemctl 几乎等价于放行 root。给运维人员开权限时,宁可放行具体的服务脚本(比如封装好的 /usr/local/bin/restart-nginx.sh),也不要直接放行整个 systemctl。

案例2:限制Web开发人员只能操作nginx

1
2
# 添加配置:
%webdev  ALL=(root)  NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx

为什么把 (www-data) 改成了 (root):systemctl 是通过 D-Bus/Polkit 去指挥 PID 1 的,普通身份跑不起来。用 (www-data) 运行 systemctl restart nginx 通常会报 Interactive authentication required 或 Access denied。要让 systemctl 真正生效,必须是以 root 身份执行。 这也带来一个现实问题:一旦放行 systemctl restart nginx,就等于给了对方"以 root 身份执行 systemctl"的能力。更安全的替代方案是写一个只能干这一件事的脚本(里面硬编码好要重启的服务),然后只放行这个脚本。

案例3:sudo免密码的合理使用场景

1
2
3
4
5
# 在sudoers.d/下创建自动化脚本的配置
# 比如监控脚本需要sudo权限但不应该要求密码

# /etc/sudoers.d/monitoring
monitor  ALL=(root)  NOPASSWD: /usr/local/bin/monitor.sh, /usr/local/bin/backup.sh

📊 sudo配置格式速查表

graph LR
    S["一条 sudoers 规则"] --> U["谁:用户 / %组名"]
    S --> H["在哪台机器:主机"]
    S --> T["以谁的身份:(目标用户 : 目标组)"]
    S --> C["能干什么:命令列表"]
    S --> O["附加选项:NOPASSWD: / SETENV: / PASSWD:"]
    style S fill:#ffeaa7
    style O fill:#dfe6e9
组成部分含义示例
用户被授权的用户(或%组名表示组)longx 或 %sudo
主机允许使用的主机ALL、192.168.1.100
= 目标用户以哪个用户身份运行(ALL)、(root)、(www-data)
:目标组以哪个组身份运行:wheel(可选)
命令允许执行的命令ALL、/usr/bin/apt

本章小结

本章我们学习了sudo权限管理:

🔑 核心知识点

  1. 为什么不用root:

    • 杀伤力太大,误操作可能毁掉整个系统
    • 没有操作追溯,出问题找不到责任人
    • 远程入侵直接全盘沦陷
  2. sudo基本用法:

    • sudo 命令:以root运行命令
    • sudo -i:切换到root shell
    • sudo -s:保持当前用户环境的root shell
    • sudo -u 用户:以指定用户身份运行
  3. sudoers配置格式:

    • 基本格式:用户 主机=(目标用户:目标组) 命令
    • %组名表示用户组
    • ALL表示所有
    • NOPASSWD表示免密(危险!慎用)
  4. 安全编辑sudoers:

    • 必须用visudo,不能用普通编辑器
    • 改完用 sudo visudo -c 检查语法
    • 配置放在/etc/sudoers.d/下更安全
    • 命令匹配是"整行精确匹配",要放参数就得用 *;别轻易放行 systemctl、apt 这类"万能命令"
  5. 日志查看:

    • Debian/Ubuntu: /var/log/auth.log
    • RHEL/CentOS: /var/log/secure
    • journalctl 用 -t sudo 过滤(-u sudo 是查不到的)
  6. 常用 Defaults:

    • timestamp_timeout=N:N 分钟内免密(默认 15 分钟)
    • log_output:记录 sudo 执行的命令输出,便于审计
    • secure_path:强制使用系统标准 PATH

💡 记住这个原则

永远不要直接登录root。 用普通用户做事,需要权限时sudo一下,这不仅是最佳实践,更是一种"操作系统礼仪"。

最后修改 September 19, 2026: 更新 (3489033b1)