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




