Linux服务器如何限制用户权限?账号管理、sudo与安全配置

多人共用一台服务器时,最危险的做法就是"大家都用 root"。一旦有人误执行 rm -rf,或者某个账号的密码泄露,整台机器就直接交出去了。规范的做法是:每人一个普通账号 → 按需授予最小 sudo 权限 → 关键操作留痕可审计。本文从账号生命周期管理讲起,覆盖 sudoers 的准确写法(含 NOPASSWD)、用户组与文件权限配合、以及登录安全加固,所有配置都给可直接落地的命令。

一、账号管理:创建、修改、锁定与清理

Linux 用户信息的存放在四个文件里:/etc/passwd(账号基本信息)、/etc/shadow(密码哈希与过期策略)、/etc/group(组)、/etc/gshadow(组密码)。UID 0 即为 root,1-999 为系统账号,普通用户通常从 1000 开始(CentOS/RHEL)或 1000 起(Ubuntu)。

# 创建用户(同时创建家目录、指定 shell)
useradd -m -s /bin/bash zhangsan

# 创建并指定 UID、主组、附加组、家目录
useradd -m -u 2001 -g devops -G docker,wheel -d /data/zhangsan -s /bin/bash zhangsan

# 设置/修改密码
passwd zhangsan
echo "Zhang@2024!" | passwd --stdin zhangsan    # 仅 RHEL 系支持 --stdin

# 修改属性
usermod -s /sbin/nologin zhangsan    # 禁止交互登录,仅用于服务账号
usermod -L zhangsan                  # 锁定账号(shadow 密码前加 !)
usermod -U zhangsan                  # 解锁
usermod -aG docker zhangsan          # 追加附加组(-a 必须加,否则覆盖原组)
gpasswd -d zhangsan docker           # 从组中移除

# 删除用户(-r 同时删除家目录和邮件池)
userdel -r zhangsan

几个必须注意的点:usermod -G 是覆盖附加组列表,漏写 -a 会把用户从其他所有附加组踢掉,这是极常见的误操作;userdel 不加 -r 会留下属主已消失的孤儿文件,用 find / -nouser -o -nogroup 可以找出来清理。

1.1 密码与有效期策略

# 查看账号密码状态
passwd -S zhangsan
chage -l zhangsan

# 强制首次登录改密码
chage -d 0 zhangsan

# 设置密码 90 天过期、提前 7 天提醒、过期后 14 天锁定
chage -M 90 -W 7 -I 14 zhangsan

# 设置账号绝对过期时间
chage -E 2025-12-31 zhangsan

# 全局默认策略(/etc/login.defs)
grep -E 'PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_WARN_AGE|UMASK' /etc/login.defs

判断标准:生产环境建议 PASS_MAX_DAYS 90、PASS_MIN_LEN 与复杂度交给 pam_pwquality(RHEL 系配置文件 /etc/security/pwquality.conf,常见项 minlen=12、dcredit=-1、ucredit=-1、ocredit=-1、lcredit=-1)。服务账号(如 nginx、redis)应设置 /sbin/nologin shell 并锁定密码。

二、sudo:把 root 权限切成小块发出去

sudo 的价值在于授权粒度可控 + 操作留痕。与直接给 root 密码相比,sudo 能精确到"允许用户 A 以 root 身份执行 /usr/bin/systemctl restart nginx 这一条命令"。

2.1 基本语法与 visudo

永远用 visudo 编辑 /etc/sudoers,它会在保存时做语法检查,避免写错导致所有 sudo 失效(到那时只能进单用户模式救场)。

# 编辑主配置(推荐)
visudo

# 在 /etc/sudoers.d/ 下放独立文件,便于管理(推荐做法)
visudo -f /etc/sudoers.d/devops

# 检查配置语法
visudo -c

# 查看当前用户能执行哪些 sudo 命令
sudo -l
sudo -l -U zhangsan

sudoers 的基本格式是:用户/组 主机=(可切换的身份) 命令列表。

# 示例 1:允许 devops 组在所有主机上以任意身份执行任意命令
%devops  ALL=(ALL)  ALL

# 示例 2:允许 zhangsan 免密执行任意命令
zhangsan  ALL=(ALL)  NOPASSWD: ALL

# 示例 3:只允许重启 nginx 和查看日志,且免密
zhangsan  ALL=(root)  NOPASSWD: /usr/bin/systemctl restart nginx, \
                                 /usr/bin/systemctl status nginx, \
                                 /usr/bin/tail /var/log/nginx/error.log

# 示例 4:只允许以 www 用户身份执行,不能提权到 root
zhangsan  ALL=(www)  /usr/bin/php /data/www/artisan *

# 示例 5:禁止某用户执行特定命令(黑名单,注意顺序:后面的规则覆盖前面的)
lisi  ALL=(ALL)  ALL, !/usr/bin/passwd root, !/bin/rm -rf /

关键规则逐条解释:

  • ALL 出现在三个位置含义不同:第一个 ALL 是"允许从哪些主机登录"(一般就写 ALL);括号里的 (ALL) 是"可以切换成哪个用户身份";最后的 ALL 是"可以执行哪些命令"。
  • % 前缀表示用户组,如 %wheel、%sudo。
  • NOPASSWD: 是标签,必须紧跟在身份括号之后、命令列表之前,只对它后面列出的命令生效,不是全局开关。写成 zhangsan ALL=(ALL:ALL) NOPASSWD:ALL 才是全免密。
  • 命令必须写绝对路径,且路径要与 sudo 实际解析的一致(用 which systemctl 确认)。
  • 命令末尾的 * 与空参数:写成 /usr/bin/tail /var/log/nginx/error.log 表示只能带这一组参数;写成 /usr/bin/tail /var/log/nginx/* 表示可接受该目录下任意文件;写成 /usr/bin/tail ""(空字符串)表示不允许带任何参数。这个细节常被忽略,导致权限被放大。
  • 规则冲突时后写的生效,所以更具体的授权应放在通用授权之后。

2.2 用别名组织复杂授权

# 定义命令别名
Cmnd_Alias WEBCTL = /usr/bin/systemctl start nginx, /usr/bin/systemctl stop nginx, \
                    /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx
Cmnd_Alias LOGVIEW = /usr/bin/tail /var/log/nginx/*, /usr/bin/less /var/log/nginx/*, /usr/bin/journalctl -u nginx

# 定义用户别名
User_Alias OPS = zhangsan, lisi, wangwu

# 组合使用
OPS  ALL=(root)  NOPASSWD: WEBCTL, LOGVIEW

2.3 sudo 的安全选项

# /etc/sudoers 常用安全项
Defaults    logfile="/var/log/sudo.log"        # 独立日志文件,便于审计
Defaults    log_input, log_output              # 记录会话输入输出(iolog,敏感环境慎用)
Defaults    iolog_dir="/var/log/sudo-io"
Defaults    requiretty                         # 要求有 tty,可阻断部分脚本提权(cron 场景需关闭)
Defaults    !visiblepw
Defaults    always_set_home
Defaults    secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults    timestamp_timeout=5                # 密码缓存 5 分钟,0 表示每次都要输

secure_path 很重要:它决定了 sudo 执行时的 PATH,能防止用户用自定义 PATH 里的同名恶意程序劫持。审计时直接 grep sudo /var/log/secure(RHEL)或 /var/log/auth.log(Debian)即可看到"谁在什么时间以什么身份执行了什么"。

2.4 授权之后的验证与回收

写完 sudoers 不代表权限就是对的,必须验证。验证分三步:一是用目标账号执行 sudo -l,确认列出的命令集合与预期完全一致,没多也没少;二是实际执行一条被允许的命令,确认成功;三是故意执行一条不该被允许的命令(如 sudo cat /etc/shadow),确认被拒绝并留下审计记录。第三步最常被跳过,而恰恰是它能发现"白名单写宽了"的问题。

# 验证:查看权限集合
sudo -l -U zhangsan

# 验证:尝试越权操作,应被拒绝
sudo -u zhangsan sudo cat /etc/shadow

# 回收权限:删除对应片段文件即可
rm -f /etc/sudoers.d/zhangsan
visudo -c

# 全量搜索某用户出现在哪些授权文件里(离职交接必做)
grep -rn 'zhangsan' /etc/sudoers /etc/sudoers.d/

回收权限时,直接删除 /etc/sudoers.d/ 下对应的片段文件最安全,比在主配置里找行删除要可靠得多。人员离职或转岗时除了锁定账号,还要用 grep 全量搜索他是否出现在多个 sudoers 片段里——实践中一个人被三四个片段分别授权是很常见的,漏掉一处就等于权限没收回。

最后提醒一类隐性的权限放大:如果允许某用户以 root 身份执行 tee、cp、vi、find -exec、python、bash 这类"能写任意文件或能启动子进程"的命令,那么白名单形同虚设,他可以覆盖 /etc/passwd 或反弹一个 root shell。正确做法是把操作封装成一个参数固定的专用脚本,脚本本身由 root 所有且普通用户不可写(权限 755、属主 root),然后只授权执行这一个脚本。

三、用户组与文件权限:权限控制的第二道防线

sudo 管的是"能不能提权",文件权限管的是"不提权时能看到什么"。两者配合才能真正做到最小权限。

# 查看权限
ls -l /data/www
stat -c '%A %U:%G %n' /data/www/index.php

# 修改属主属组
chown -R www:www /data/www
chgrp -R devops /data/scripts

# 修改权限
chmod 750 /data/scripts          # 属主rwx,同组r-x,其他无权限
chmod 640 /data/conf/db.conf     # 配置文件不可被执行
chmod -R g+w /data/shared
chmod -R o-rwx /data/secret      # 收回其他用户所有权限

# 设置默认 umask(/etc/profile 或 /etc/bashrc)
umask 027    # 新建文件 640,目录 750

权限数字速查:r=4 w=2 x=1。目录必须有 x 权限才能进入,所以共享目录通常给 750 或 770;文件一般不给 x,配置文件建议 640 或 600(私钥、数据库密码文件必须 600 且属主正确)。

3.1 特殊位与 ACL

# setgid:目录内新建文件自动继承目录属组(团队协作目录必备)
chmod g+s /data/team

# sticky bit:只有文件属主能删除(/tmp 就是 1777)
chmod o+t /data/upload

# ACL 精细授权:给单个用户加权限,不影响其他
setfacl -m u:zhangsan:rwx /data/project
setfacl -m g:devops:rx /data/project
setfacl -R -m u:zhangsan:rwx /data/project    # 递归
setfacl -x u:zhangsan /data/project           # 删除单条
setfacl -b /data/project                      # 清除所有 ACL
getfacl /data/project                         # 查看

注意:setgid 只对新建文件生效,已有文件需要 chgrp -R 补一次。使用 ACL 后 ls -l 权限位末尾会出现 + 号,备份时用 tar 需要加 --acls 才能保留。

四、登录安全加固

4.1 禁止 root 直接 SSH 登录

这是最基础也最重要的一条。先确保有一个 sudo 权限的普通账号,再改 /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no        # 强制密钥登录
PubkeyAuthentication yes
AllowUsers zhangsan lisi         # 白名单,只允许这些用户登录
AllowGroups sshusers             # 或按组白名单
MaxAuthTries 3
LoginGraceTime 30
PermitEmptyPasswords no
X11Forwarding no
# 改完检查语法并重载(不要 restart,避免断连后无法恢复)
sshd -t
systemctl reload sshd

操作纪律:改 SSH 配置时保持当前连接不退出,另开一个终端验证新配置能登录成功,再断开旧连接。否则配置错误会把自己锁在门外,只能去控制台救。

4.2 登录审计与失败锁定

# 查看登录历史
last -n 20
lastb -n 20            # 失败登录尝试
lastlog                # 各用户最后一次登录时间

# 查看失败次数
faillog -u zhangsan
pam_tally2 --user=zhangsan        # RHEL 6/7
faillog -r -u zhangsan            # 清零

# 配置失败锁定(RHEL 8+/Rocky:/etc/security/faillock.conf)
deny=5
fail_interval=900
unlock_time=600

判断标准:lastb 里出现来自同一 IP 的大量 root 尝试,基本可以确认在暴力破解,应立即用 fail2ban 或防火墙封禁该 IP,并确认 PasswordAuthentication 已关闭。

五、落地步骤清单

  1. 清点现有账号:awk -F: '$3>=1000 && $3<65534 {print $1, $3, $7}' /etc/passwd,列出所有普通账号及其 shell,识别长期不用的僵尸账号。
  2. 清理与收敛:离职人员账号先 usermod -L 锁定并备份家目录,观察 30 天无异常再 userdel -r;服务账号改为 /sbin/nologin。
  3. 建组并归类:按角色建 devops、develop、readonly 等组,用 usermod -aG 把人加进去,避免逐个授权。
  4. 配置 sudoers:在 /etc/sudoers.d/ 下按角色建文件,遵循"命令绝对路径 + 最小集合 + 必要时 NOPASSWD",保存前 visudo -c 校验。
  5. 设置目录权限:业务目录 750/770 配 setgid,密钥与配置文件 600/640,共享目录加 sticky bit 视需求。
  6. 加固 SSH:禁 root 登录、禁密码登录、配 AllowUsers 白名单,改完 sshd -t 后 reload。
  7. 开启审计:配置 sudo logfile,定期 last/lastb 巡检,密码策略写入 /etc/login.defs 与 pwquality.conf。
  8. 定期复核:每季度跑一次账号清点和 sudo 权限复核,形成记录。

六、方案对比:三种授权方式怎么选

方式适用场景优点缺点
直接给 root 密码无省事无法审计、无法回收、一次泄露全盘失守
sudo 全量授权(ALL=(ALL) ALL)核心运维人员、可信度高的小团队保留审计日志,可随时收回粒度粗,误操作风险仍高
sudo 命令级白名单开发/测试人员、外包、需要值班重启的岗位最小权限、误操作面小、留痕清晰配置维护成本高,业务变更时需同步更新
用户组 + 文件权限(不提权)只读查看日志、上传部署包等场景无提权风险无法执行需要 root 的操作

小结

限制用户权限的完整思路是"三层夹逼":账号层保证一人一号、僵尸账号及时清理、密码有过期策略;提权层用 sudoers 做命令级白名单,杜绝 ALL=(ALL) NOPASSWD: ALL 这种图省事的写法;文件层用属主属组 + 750/640 + setgid/ACL 把数据访问范围框住。再配合 SSH 禁 root、禁密码、白名单登录与 sudo 日志审计,一台多人共用的服务器就能达到可管、可控、可追溯的状态。

避坑清单

  • 永远用 visudo 改 sudoers,直接 vi 写错会让整台机器的 sudo 失效。
  • usermod -G 会覆盖附加组,追加必须写 -aG。
  • NOPASSWD 只作用于其后列出的命令,别以为写在开头就全局生效;同时它意味着"拿到该账号即获得对应 root 能力",只给必要的命令。
  • 命令白名单要防参数逃逸:授权 /usr/bin/tail 而不限定参数,等于允许读任意文件;授权 vim、less、find、python 这类带 shell 逃逸能力的程序,等同于给了完整 root。
  • 改 sshd_config 前保留一个已登录会话,用 reload 而非 restart,验证通过再断开。
  • 删除用户后清理孤儿文件:find / -xdev -nouser -o -nogroup,否则残留文件属主会变成数字 UID,可能带来越权隐患。
  • 服务账号锁定后可能影响 cron,需要跑定时任务的服务账号保持密码状态正常但 shell 设为 nologin。
  • sudo 日志默认在系统日志里,建议单独指定 logfile 并纳入日志轮转,避免被清理或被篡改。