跳转至

sudo 权限管理最佳实践:最小权限原则

前言

sudo 让普通用户可以执行特权命令——但「能 sudo 一切」和「只能 sudo 该做的事」之间有天壤之别。很多安全问题源于「为了省事,给了 ALL 权限」。

本文从 sudoers 语法讲到生产环境的权限设计模式,帮你实现谁、在哪台机器、能执行什么命令、是否需要密码的细粒度控制。

永远用 visudo 编辑 sudoers

visudo 在保存前会检查语法错误。直接 vim /etc/sudoers 写错一个字符可能导致所有 sudo 权限失效,只能进单用户模式修复。visudo 是唯一安全的方式。


sudoers 语法速成

基本格式

用户  主机=(以谁的身份)  命令
%组   主机=(以谁的身份)  命令
# 允许 alice 在所有主机上以 root 身份执行所有命令
alice ALL=(ALL:ALL) ALL

# 允许 developers 组成员在所有主机上以 root 身份重启 nginx
%developers ALL=(ALL) /usr/bin/systemctl restart nginx

# 允许 bob 无需密码执行特定命令
bob ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx

字段详解

字段 含义 常用值
用户 谁拥有权限 alice%developers(组前加 %
主机 从哪台机器执行 ALLweb-01192.168.1.0/24
RunAs 以谁的身份执行 (ALL)(root)(www-data)
命令 可以执行什么 绝对路径,逗号分隔,支持通配符

权限设计模式

模式一:运维组(经典)

# /etc/sudoers.d/ops
# 运维人员可以管理服务、查看日志、重启
Cmnd_Alias SERVICES = /usr/bin/systemctl start *, \
                       /usr/bin/systemctl stop *, \
                       /usr/bin/systemctl restart *, \
                       /usr/bin/systemctl reload *, \
                       /usr/bin/systemctl status *

Cmnd_Alias LOGS = /usr/bin/journalctl, \
                   /usr/bin/tail -f /var/log/*, \
                   /usr/bin/less /var/log/*

Cmnd_Alias FILES = /usr/bin/cat /etc/*, \
                    /usr/bin/ls /etc/*

%ops ALL=(ALL) SERVICES, LOGS, FILES

模式二:应用管理员(按应用分离)

# /etc/sudoers.d/app-nginx
# Nginx 管理员只能管理 Nginx
%nginx-admins ALL=(ALL) NOPASSWD: /usr/bin/systemctl * nginx
%nginx-admins ALL=(ALL) /usr/bin/nginx -t, \
                         /usr/bin/nginx -s reload

模式三:开发自服务(安全)

# /etc/sudoers.d/dev
# 开发者可以查看自己应用的日志,但不能改系统配置
%developers ALL=(ALL) /usr/bin/journalctl -u myapp*
%developers ALL=(www-data) /usr/bin/tail -f /var/log/myapp/*

常见陷阱

1. ALL 的通配陷阱

# 危险:允许改任何文件
bob ALL=(ALL) /usr/bin/vim /etc/*

# bob 可以 vim /etc/sudoers → 给自己加权限 → 提权成功

原则:命令别名中不要包含编辑器、shell、cat/less + 重定向等可被利用的命令。

2. NOPASSWD 的滥用

# 危险
alice ALL=(ALL) NOPASSWD: ALL
# 任何能登录 alice 账号的人都能直接 root

原则:生产环境 NOPASSWD 应仅用于自动化脚本调用的特定命令。

3. 命令路径绕过

# 如果允许 /usr/bin/cat
# 实际上可以通过 /usr/bin/cat /etc/shadow 读取敏感文件

原则:考虑「被允许的命令能做什么」,而不仅是「限制了什么」。


Defaults 安全配置

# /etc/sudoers.d/security-defaults
Defaults        env_reset                    # 重置环境变量(安全)
Defaults        mail_badpass                 # 密码错误时发邮件
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults        logfile="/var/log/sudo.log"  # 独立的 sudo 日志
Defaults        lecture="always"             # 每次显示警告
Defaults        passwd_timeout=1             # 密码缓存 1 分钟

审计 sudo 使用

# 查看 sudo 日志(独立日志文件)
sudo tail -f /var/log/sudo.log
# Jul 20 14:30:00 alice : TTY=pts/0 ; PWD=/home/alice ; USER=root ; COMMAND=/usr/bin/systemctl restart nginx

# 查看 journal 中的 sudo 记录
journalctl -u sudo -f       # Debian/Ubuntu 可能不存在此 unit
sudo journalctl _COMM=sudo  # 按命令过滤
sudo ausearch -c sudo -i    # auditd 视角

sudo 与容器

在容器中,sudo 通常不是必需的——容器以 root 运行,或通过 USER 指令切换用户。如果容器中需要部分特权:

# 不推荐在容器中安装 sudo
# 而是用 USER 指令 + 最小权限原则
FROM alpine
RUN addgroup -S app && adduser -S app -G app
USER app
CMD ["/app/server"]

对于确实需要特权的场景(如 init 容器修改内核参数),用 Kubernetes securityContext 替代 sudo。


总结

sudo 权限管理的核心是最小权限原则

  1. 按角色分组:运维组、开发组、DBA 组——每组只给必要的命令
  2. 精确指定命令:用绝对路径 + 命令别名,避免 ALL
  3. 避免编辑器/shellvim/less/cat 可被利用来提权
  4. NOPASSWD 谨慎:仅用于自动化脚本,人类操作需要密码
  5. 日志审计/var/log/sudo.log + journalctl + auditd 三重保障

权限管理没有「一步到位」——随着团队和基础设施的成长,持续审视和收紧权限。 🚀