Linux ACL 与 sudo 细粒度权限管理实战
在 chmod/chown 之上深入讲解 Linux 扩展访问控制列表(ACL)的配置与排查,以及 sudoers 文件的精细化授权——包括按用户、按命令、按时间窗口的最小权限配置,适用于多人协作的生产服务器。
标准的 chmod/chown 只支持“属主、属组、其他”三级权限,满足不了“A 用户只读、B 用户读写、C 用户无权限”这类多用户细粒度需求。Linux ACL(Access Control List)正是为此而生。与此同时,sudo 的 sudoers 配置如果只会写 ALL=(ALL) ALL,就等于把根钥匙交给了每一个运维——本文讲清楚这两个工具的生产级用法。
一、ACL 基础
检查文件系统是否支持 ACL
# 查看挂载选项
mount | grep "on / "
# /dev/sda1 on / type ext4 (rw,relatime,acl)
# 看到 acl 表示已支持
# 如果没有 acl 选项,临时挂载启用
mount -o remount,acl /
# 永久生效:编辑 /etc/fstab
# /dev/sda1 / ext4 defaults,acl 0 1
# XFS 文件系统默认支持 ACL,无需额外配置
ACL 核心命令
# 查看文件 ACL
getfacl /data/project/
# 典型输出:
# file: data/project/
# owner: root
# group: devteam
# user::rwx ← 属主权限(等同 chmod u)
# group::r-x ← 属组权限(等同 chmod g)
# other::--- ← 其他权限(等同 chmod o)
# 设置 ACL
setfacl -m u:alice:rwx /data/project/ # alice 读写执行
setfacl -m u:bob:r-x /data/project/ # bob 只读
setfacl -m u:carol:--- /data/project/ # carol 无权限(显式拒绝)
setfacl -m g:ops:r-x /data/project/ # ops 组只读
# 递归设置(-R)
setfacl -R -m u:alice:rwx /data/project/
# 删除某用户的 ACL 条目
setfacl -x u:bob /data/project/
# 清除所有 ACL(回到纯 chmod 状态)
setfacl -b /data/project/
默认 ACL(新建文件自动继承)
普通 ACL 只作用于已有文件,新建的文件不会自动继承。默认 ACL 解决这个问题:
# 为目录设置默认 ACL
setfacl -d -m u:alice:rwx /data/project/
setfacl -d -m g:ops:r-x /data/project/
setfacl -d -m o::--- /data/project/
# 验证:查看 default 部分
getfacl /data/project/
# default:user::rwx
# default:user:alice:rwx
# default:group::r-x
# default:group:ops:r-x
# default:other::---
# 测试:新建文件自动继承
touch /data/project/newfile.txt
getfacl /data/project/newfile.txt
# user:alice:rwx ← 自动继承
mask:ACL 的上限控制
mask 是 ACL 中最容易被忽略的概念——它是“命名用户/命名组的权限上限”:
# 当 mask 为 r-x 时,即使 alice 被设置了 rwx,实际生效权限是 rwx & r-x = r-x
setfacl -m m::r-x /data/project/
getfacl /data/project/
# user:alice:rwx #effective:r-x ← 实际权限被 mask 限制为 r-x
# group::r-x #effective:r-x
# mask::r-x
# chmod 修改属组权限会同步更新 mask!
chmod g+w /data/project/ # 这会把 mask 变为 rwx,间接放开了所有 ACL 用户的写权限
# 所以:启用 ACL 后,不要用 chmod g± 来控制权限,而应该直接设置 mask
setfacl -m m::r-x /data/project/ # 强制恢复 mask
实战:多团队共享目录
# 场景:/data/web 由 frontend 团队读写,backend 团队只读,audit 用户全权
# 1. 基础权限(属组 frontend)
chown root:frontend /data/web
chmod 750 /data/web
# 2. 设置 ACL
setfacl -m g:backend:r-x /data/web # backend 组只读
setfacl -m u:audit:rwx /data/web # audit 用户全权
# 3. 设置默认 ACL(新文件继承)
setfacl -d -m g:frontend:rwx /data/web
setfacl -d -m g:backend:r-x /data/web
setfacl -d -m u:audit:rwx /data/web
setfacl -d -m o::--- /data/web
# 验证
getfacl /data/web
二、sudo 精细化授权
sudoers 文件结构
# 永远用 visudo 编辑(有语法检查)
visudo
# 文件位置:/etc/sudoers
# 推荐将自定义配置放到 /etc/sudoers.d/ 目录下(避免系统更新覆盖)
visudo -f /etc/sudoers.d/ops-team
基本语法
# 格式:who where=(as_whom) what
# 用户 主机=(切换为谁) 命令
alice ALL=(ALL) ALL # alice 可以在任何主机,以任何用户,执行任何命令
%ops ALL=(root) /bin/systemctl restart nginx # ops 组只能以 root 重启 nginx
最小权限原则实战
# /etc/sudoers.d/web-ops
# ===========================
# Web 运维组:只能管理 Nginx 和查看日志
# 定义命令别名
Cmnd_Alias NGINX_CMDS = \
/bin/systemctl start nginx, \
/bin/systemctl stop nginx, \
/bin/systemctl restart nginx, \
/bin/systemctl reload nginx, \
/bin/systemctl status nginx, \
/usr/sbin/nginx -t
Cmnd_Alias LOG_CMDS = \
/usr/bin/tail -f /var/log/nginx/*, \
/usr/bin/less /var/log/nginx/*, \
/usr/bin/cat /var/log/nginx/*
# 授权:web-ops 组可以无密码执行以上命令
%web-ops ALL=(root) NOPASSWD: NGINX_CMDS, LOG_CMDS
# 禁止:绝对禁止 shell 逃逸
# 注意:如果上面授权了 less,用户可以在 less 里按 !bash 获取 shell
# 更安全的做法是不给 less,改为只给 cat/tail
# /etc/sudoers.d/dba-team
# ===========================
# 数据库运维:只能管理 MySQL 服务,不能直接 mysql 命令行
Cmnd_Alias MYSQL_SERVICE = \
/bin/systemctl start mysqld, \
/bin/systemctl stop mysqld, \
/bin/systemctl restart mysqld, \
/bin/systemctl status mysqld
# 明确拒绝危险命令(即使 ALL 规则里包含)
Cmnd_Alias DANGEROUS = /usr/bin/mysql, /usr/bin/mysqladmin
%dba-team ALL=(root) NOPASSWD: MYSQL_SERVICE
%dba-team ALL=(root) !DANGEROUS # ! 表示拒绝
密码要求与超时
# /etc/sudoers.d/security-policy
# 修改 sudo 密码缓存时间(默认 15 分钟)
Defaults timestamp_timeout=5 # 5 分钟后再次要求输入密码
# 对特定用户关闭缓存(每次都要输密码)
Defaults:audit timestamp_timeout=0
# 对高危命令始终要求密码(覆盖 NOPASSWD)
Cmnd_Alias HIGH_RISK = /usr/bin/rm -rf *, /usr/sbin/userdel, /usr/sbin/passwd
%ops ALL=(root) NOPASSWD: /bin/systemctl
%ops ALL=(root) PASSWD: HIGH_RISK # 即使组里有 NOPASSWD,高危命令仍要密码
记录 sudo 日志
# /etc/sudoers.d/logging
# 记录所有 sudo 命令(需要 sudo 版本 >= 1.9.0)
Defaults log_output # 记录命令输出
Defaults iolog_dir=/var/log/sudo-io/%{user} # 按用户分目录
Defaults iolog_file=%{seq} # 文件名为序号
# 更简单:通过 syslog 记录(所有版本可用)
Defaults syslog=auth # 记录到 /var/log/auth.log 或 journald
# 查看 sudo 日志
grep sudo /var/log/auth.log
journalctl -u sudo
# 查看命令回放(需要 iolog)
sudoreplay /var/log/sudo-io/alice/
常见陷阱
# 陷阱 1:别名中的路径必须是绝对路径
# ❌ 错误
Cmnd_Alias BAD = systemctl restart nginx
# ✅ 正确
Cmnd_Alias GOOD = /bin/systemctl restart nginx
# 陷阱 2:带参数的命令要精确匹配
# 下面只允许 restart nginx,不允许 restart sshd
Cmnd_Alias NGINX_RESTART = /bin/systemctl restart nginx
# 但 "systemctl restart nginx --now" 不匹配!参数不同就不匹配
# 陷阱 3:shell 逃逸漏洞
# 不要授权 vim/less/more/man/find/awk 等可以执行子命令的工具
# vim 里可以 :!bash 获取 root shell
# 陷阱 4:语法错误导致 sudo 完全不可用
# 永远用 visudo,不要直接 vi /etc/sudoers
# 如果已经锁死,用单用户模式(recovery mode)修复
验证 sudo 配置
# 以某用户身份测试(不实际执行)
sudo -l -U alice
# 输出:
# User alice may run the following commands on server01:
# (root) NOPASSWD: /bin/systemctl start nginx
# (root) NOPASSWD: /bin/systemctl stop nginx
# 检查 sudoers 语法
visudo -c
# /etc/sudoers: parsed OK
# /etc/sudoers.d/ops-team: parsed OK
# 测试特定命令是否可以执行(不用输密码验证)
sudo -l -U bob /bin/systemctl restart nginx
三、ACL + sudo 联合使用场景
# 场景:CI/CD 系统账号(ci-bot)需要:
# 1. 读写 /data/deploy 目录(ACL 控制)
# 2. 重启应用服务(sudo 控制)
# 3. 不能做任何其他 root 操作
# Step1:ACL 设置部署目录权限
setfacl -R -m u:ci-bot:rwx /data/deploy/
setfacl -d -m u:ci-bot:rwx /data/deploy/ # 新文件也继承
# Step2:sudo 设置服务重启权限
cat > /etc/sudoers.d/ci-bot << 'EOF'
Cmnd_Alias CIBOT_CMDS = \
/bin/systemctl restart myapp, \
/bin/systemctl start myapp, \
/bin/systemctl stop myapp, \
/bin/systemctl status myapp
ci-bot ALL=(root) NOPASSWD: CIBOT_CMDS
EOF
# Step3:验证
sudo -l -U ci-bot
getfacl /data/deploy/
小结
ACL 和 sudo 是 Linux 权限体系中“超越 chmod 三元组”的两大工具。ACL 解决“同一目录给不同用户不同权限”的问题,关键记住 mask 的上限作用和默认 ACL 的继承机制;sudo 解决“运维人员需要部分 root 权限”的问题,关键是最小权限原则、避免 shell 逃逸、强制日志记录。两者结合使用,可以在不给任何人 root 密码的前提下,精细化控制每个用户在服务器上能做什么。
