用户管理
Linux 用户、组与权限管理
本文按以下顺序整理:用户操作 → 组操作 → 权限设置。每组操作都列出常用命令、参数、作用和安全注意事项。
命令前的
sudo表示需要 root 权限。删除用户、批量修改属主或权限前,先确认路径和业务影响。
目录
一、先理解用户、组和权限的关系
用户 --UID--> 文件属主
用户 --主组 GID--> 组(每个用户只有一个)
用户 --附加组--> 组(每个用户可以有多个)
组 --GID--> 文件属组
- 每个用户只有一个主组,记录在
/etc/passwd第 4 列。 - 用户可以属于多个附加组,记录在
/etc/group的成员列表中。 - 主组成员一般不出现在
/etc/group成员列表中,查看主组用id -gn 用户。 - 组本身不是权限。权限设置在文件、目录或 sudo 规则上,组用于批量归类用户。
- 文件访问按属主、属组、其他用户的匹配结果判断,不是把多类权限简单相加。
相关配置文件:
| 文件 | 作用 |
|---|---|
/etc/passwd |
用户名、UID、主组 GID、家目录、Shell |
/etc/shadow |
密码哈希、密码有效期、账号过期时间 |
/etc/group |
组名、GID、附加组成员 |
/etc/gshadow |
组密码、组管理员、组成员 |
/etc/login.defs |
UID/GID 范围、密码策略等默认值 |
/etc/skel/ |
新建用户家目录的模板 |
/etc/sudoers、/etc/sudoers.d/ |
sudo 授权规则 |
1.1 /etc/passwd 字段
格式:
用户名:密码占位符:UID:GID:GECOS:家目录:登录Shell
示例:
alice:x:2001:2001:Alice:/home/alice:/bin/bash
| 序号 | 字段 | 示例 | 含义 |
|---|---|---|---|
| 1 | 用户名 | alice |
登录名,通常使用小写 |
| 2 | 密码占位符 | x |
密码哈希在 /etc/shadow,这里只占位 |
| 3 | UID | 2001 |
用户 ID;root 的 UID 是 0 |
| 4 | GID | 2001 |
主组 GID,组必须存在于组数据库 |
| 5 | GECOS | Alice |
备注信息,通常为全名或用途 |
| 6 | 家目录 | /home/alice |
登录后的初始目录 |
| 7 | 登录 Shell | /bin/bash |
登录时执行的程序;nologin 表示禁止登录 |
字段注意:
- 密码列通常为
x,不要在这里存放明文密码。 - UID
0是 root。不要随意把普通用户改为 UID0。 - 第 4 列是主组 GID。附加组不在该文件里,而在
/etc/group。 - GECOS 字段可以包含逗号分隔的附加信息,但不同工具解释不完全一致。
- Shell 为空时,部分程序会回退到
/bin/sh。 - Shell 为
/usr/sbin/nologin或/sbin/nologin时,交互式登录会被拒绝。
1.2 /etc/shadow 字段
格式:
用户名:加密密码:上次改密日期:最小间隔:最大有效期:警告天数:宽限天数:账号过期日期:保留字段
示例:
alice:$6$...:20000:7:90:14:30:21000:
| 序号 | 字段 | 示例 | 含义 |
|---|---|---|---|
| 1 | 用户名 | alice |
与 /etc/passwd 对应 |
| 2 | 加密密码 | $6$... |
密码哈希;!、!!、* 表示不可用或锁定 |
| 3 | 上次改密日期 | 20000 |
从 1970-01-01 起的天数;0 表示下次登录必须改密 |
| 4 | 最小间隔 | 7 |
两次改密之间至少间隔的天数;0 表示不限制 |
| 5 | 最大有效期 | 90 |
密码有效天数;到期后要求改密 |
| 6 | 警告天数 | 14 |
密码到期前提前提醒的天数 |
| 7 | 宽限天数 | 30 |
密码过期后仍可登录改密的天数 |
| 8 | 账号过期日期 | 21000 |
从 1970-01-01 起的天数;空表示永不过期 |
| 9 | 保留字段 | 空 | 预留给未来使用 |
密码字段状态:
| 值 | 含义 |
|---|---|
$6$... |
通常为 SHA-512 密码哈希 |
! 或 !! |
密码被锁定或未设置 |
* |
无可用密码,通常表示不能密码登录 |
| 空 | 可能表示无需密码,风险高;是否允许登录还取决于 PAM 配置 |
字段注意:
- 日期按“UTC 1970-01-01 起的天数”计算,不是
YYYY-MM-DD文本。 - 账号过期与密码过期不同:账号过期后不能登录;密码过期后通常要求改密。
- 最大有效期小于最小间隔时,用户可能无法修改密码。
- 不要直接编辑
/etc/shadow。优先使用passwd、chage、usermod。 /etc/shadow通常只允许 root 读取。
为什么普通用户执行 getent shadow 自己 没有输出
这是正常现象。getent 只是通过 NSS 查询数据库,不会绕过 /etc/shadow 的文件权限:
ls -l /etc/shadow
典型权限:
---------- 1 root root /etc/shadow
普通用户没有读权限,因此查询结果为空,命令还可能返回非零状态:
getent shadow test1
echo "$?"
即使查看的是自己的记录,也一样不会返回密码哈希。密码哈希不应暴露给普通用户,否则离线破解风险会增加。
使用 root 或 sudo 才能查看:
sudo getent shadow test1
也可以查看非敏感的有效期信息:
sudo passwd -S test1
sudo chage -l test1
排查时注意:
getent passwd test1能返回,只说明用户可以查询/etc/passwd。getent shadow test1没有输出,不代表用户不存在,也不代表账号被删除。- 如果 NSS 配置了 LDAP 或 SSSD,
shadow查询还取决于对应的 NSS 服务是否支持。 - 不要为了“查得到”而修改
/etc/shadow权限,例如改成644或777。 /etc/shadow的属主和权限异常本身是安全问题,应使用发行版默认值或pwck、chmod正确修复。
1.3 /etc/group 字段
格式:
组名:组密码占位符:GID:附加组成员
示例:
dev:x:2000:alice,bob
| 序号 | 字段 | 示例 | 含义 |
|---|---|---|---|
| 1 | 组名 | dev |
组名称 |
| 2 | 组密码占位符 | x |
组密码通常在 /etc/gshadow |
| 3 | GID | 2000 |
组 ID,必须唯一 |
| 4 | 附加组成员 | alice,bob |
逗号分隔的用户名,不能有空格 |
字段注意:
- 第 4 列是附加组成员,不是主组成员。
- 用户的主组由
/etc/passwd第 4 列决定,不一定出现在这里。 - 成员列表为空表示没有显式的附加组成员。
- 组名和 GID 应保持唯一;
-o允许重复 GID,但一般不建议。 - 修改组成员后,已有会话不会自动刷新。
1.4 /etc/gshadow 字段
格式:
组名:加密组密码:组管理员:组成员
示例:
dev:$6$...:alice:bob,carol
| 序号 | 字段 | 示例 | 含义 |
|---|---|---|---|
| 1 | 组名 | dev |
与 /etc/group 对应 |
| 2 | 加密组密码 | $6$... |
组密码;! 开头表示锁定 |
| 3 | 组管理员 | alice |
可修改组密码和成员的用户,逗号分隔 |
| 4 | 组成员 | bob,carol |
组内成员,逗号分隔 |
字段注意:
/etc/gshadow中的密码优先于/etc/group中的组密码。- 组成员可以不用组密码访问组权限。
- 组管理员也可以修改组密码和成员,并拥有组员权限。
- 组成员列表通常应与
/etc/group保持一致。 /etc/gshadow通常只允许 root 读取。- 不要直接编辑这些文件,优先使用
groupadd、groupmod、gpasswd、grpconv等工具。
1.5 四个文件之间的关系
/etc/passwd -- 用户名/UID/主组GID --> /etc/group
/etc/shadow -- 用户密码和账号策略
/etc/group -- 组名/GID/附加组成员
/etc/gshadow -- 组密码/组管理员/组成员
不要只根据一个文件判断用户权限,建议使用:
getent passwd alice
getent group dev
id alice
sudo getent shadow alice
sudo getent gshadow dev
getent 会通过 NSS 查询本地文件、LDAP 等来源,比直接 cat 更适合排查系统中的实际用户和组信息。但 shadow 和 gshadow 包含敏感信息,普通用户查询不到时是正常的。
二、用户操作
2.1 useradd:创建用户
基本语法:
sudo useradd [参数] 用户名
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-m |
创建家目录 | 从 /etc/skel 复制模板文件 |
-M |
不创建家目录 | 与 -m 相反 |
-d 目录 |
指定家目录 | 不配合 -m 时只写入配置,不创建目录 |
-b 目录 |
指定家目录的上级目录 | 默认通常是 /home |
-s Shell |
指定登录 Shell | 如 /bin/bash、/usr/sbin/nologin |
-u UID |
指定用户 ID | 必须唯一,普通用户通常从 1000 开始 |
-g 组 |
指定主组 | 组必须已存在;可用组名或 GID |
-G 组1,组2 |
指定附加组 | 多个组用逗号分隔,不能有空格 |
-c "说明" |
设置备注 | 通常填写姓名或用途 |
-e 日期 |
设置账号过期日期 | 格式 YYYY-MM-DD |
-f 天数 |
密码过期后多少天禁用账号 | 0 立即禁用,-1 不启用 |
-r |
创建系统账号 | UID 通常在系统号段,默认不建家目录 |
-p 哈希值 |
直接写入加密密码 | 不推荐,容易泄露 |
-N |
不创建同名私有组 | 主组使用默认组 |
-U |
创建同名私有组 | 可覆盖默认配置 |
-D |
查看或修改默认配置 | 查看默认值:useradd -D |
创建可登录用户:
sudo useradd -m -s /bin/bash -c "Alice" alice
sudo passwd alice
创建指定主组、附加组和 UID 的用户:
sudo groupadd -g 2000 dev
sudo groupadd ops
sudo useradd -m -u 2001 -g dev -G ops -s /bin/bash alice
创建不能登录的服务账号:
sudo useradd -r -s /usr/sbin/nologin appuser
说明:
useradd默认不会设置密码,需要再执行passwd。- 不指定
-g时,很多发行版会创建同名私有组作为主组。 -G只设置附加组,不会把用户加入主组的成员列表。
2.2 passwd:设置和修改密码
基本语法:
sudo passwd [参数] 用户名 # root 修改指定用户
passwd # 普通用户修改自己
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-S |
查看密码状态 | 输出 PS 可用、LK 锁定、NP 无密码 |
-l |
锁定密码 | 在密码哈希前加 !,不禁止 SSH 密钥登录 |
-u |
解锁密码 | 去掉密码哈希前的 ! |
-d |
删除密码 | 账号变成无密码状态,风险高 |
-e |
强制下次登录改密码 | 等价于密码立即过期 |
-n 天数 |
最短改密间隔 | 期间不能再次改密 |
-x 天数 |
密码最大有效期 | 到期后必须修改密码 |
-w 天数 |
提前警告天数 | 密码到期前提醒 |
-i 天数 |
密码过期后宽限天数 | 超过后账号不可用 |
-f |
强制执行 | 配合 -u 等参数使用 |
示例:
sudo passwd alice # 设置或重置密码
sudo passwd -S alice # 查看状态
sudo passwd -l alice # 锁定密码
sudo passwd -u alice # 解锁密码
sudo passwd -e alice # 下次登录强制改密
sudo passwd -n 7 -x 90 -w 14 alice
注意:passwd -l 只锁定密码登录,SSH 公钥等方式可能仍可登录。要禁用整个账号,可使用 chage -E 0 alice 或 usermod -e 1 alice。
批量设置密码:
echo 'alice:StrongPassword' | sudo chpasswd
这种方法可能进入 Shell 历史,生产环境优先交互式 passwd,或使用权限受控的临时文件配合 chpasswd < 文件。
2.3 chage:密码和账号有效期
基本语法:
sudo chage [参数] 用户名
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-l |
查看有效期信息 | 显示上次改密、有效期等 |
-d 日期或天数 |
设置上次改密时间 | -d 0 强制下次登录改密 |
-m 天数 |
最短改密间隔 | 0 表示随时可改 |
-M 天数 |
密码最大有效期 | -1 表示不检查 |
-W 天数 |
到期前警告天数 | 提前提醒用户 |
-I 天数 |
密码过期后的宽限天数 | 超期后锁定账号 |
-E 日期或天数 |
账号过期日期 | -1 表示取消过期日期 |
示例:
sudo chage -l alice
sudo chage -m 7 -M 90 -W 14 alice
sudo chage -I 30 alice
sudo chage -E 2026-12-31 alice
sudo chage -d 0 alice
区别:
-M是密码有效期,过期后要求改密。-I是密码过期后的宽限天数,超过后账号被锁定。-E是整个账号的过期日期。-d 0只是要求下次登录改密。
2.4 usermod:修改用户
基本语法:
sudo usermod [参数] 用户名
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-l 新名 |
修改登录名 | 不会自动修改家目录和组名 |
-u UID |
修改 UID | 用户不应有运行中的进程 |
-g 组 |
修改主组 | 组必须存在 |
-G 组1,组2 |
设置附加组列表 | 会覆盖原有附加组 |
-aG 组1,组2 |
追加附加组 | 必须加 -a,否则会覆盖 |
-d 目录 |
修改家目录 | 配合 -m 可迁移旧目录内容 |
-s Shell |
修改登录 Shell | 如改为 /bin/bash 或 /usr/sbin/nologin |
-c "说明" |
修改备注 | 姓名或用途说明 |
-e 日期 |
修改账号过期日期 | 格式 YYYY-MM-DD;空值取消过期 |
-f 天数 |
修改密码过期后的宽限天数 | 0 立即禁用,-1 不启用 |
-L |
锁定密码 | 在密码哈希前加 ! |
-U |
解锁密码 | 去掉密码哈希前的 ! |
-p 哈希值 |
设置加密密码 | 不推荐,容易出现在进程列表中 |
示例:
sudo usermod -aG docker alice
sudo usermod -s /bin/bash alice
sudo usermod -g dev alice
sudo usermod -d /home/alice2 -m alice
sudo usermod -L alice
sudo usermod -U alice
注意:
- 追加附加组必须使用
-aG;单独使用-G会替换原有附加组列表。 - 修改 UID 后,家目录内的文件通常会自动调整,但家目录外的文件需要手动修正。
- 修改用户名后,还要检查家目录、同名组、sudoers、SSH、cron、systemd 和数据库授权。
修改 UID 后查找旧文件:
sudo find /path -uid 2001 -ls
sudo find /path -uid 2001 -exec chown 2002 {} \;
2.5 userdel:删除用户
基本语法:
sudo userdel [参数] 用户名
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-r |
删除家目录和邮件池 | 不会清理所有系统范围内的关联文件 |
-f |
强制删除 | 用户仍登录或文件归属异常时也可能删除,风险高 |
-Z |
删除 SELinux 用户映射 | 仅适用于启用 SELinux 的环境 |
示例:
# 先检查、备份
pgrep -u alice -a
sudo tar czf /backup/alice-home.tar.gz /home/alice
# 删除用户和家目录
sudo userdel -r alice
userdel -r 不保证清理以下内容:
/var/spool/cron/中的定时任务。/etc/sudoers.d/中的授权。- SSH 密钥和
authorized_keys。 - 数据库账号和应用配置。
- 家目录以外的用户文件。
删除后检查:
sudo find / -uid 2001 -ls 2>/dev/null
2.6 id、groups、getent:查看用户信息
| 命令 | 作用 |
|---|---|
id alice |
显示 UID、主组、附加组 |
id -u alice |
只显示 UID |
id -g alice |
只显示主组 GID |
id -gn alice |
只显示主组名称 |
id -G alice |
显示所有组 GID |
groups alice |
显示所有组名称 |
getent passwd alice |
从用户数据库查询用户 |
getent group dev |
从组数据库查询组 |
sudo getent shadow alice |
查询密码信息,必须使用 root 或 sudo |
用户数据库可能来自本地文件、LDAP 或其他 NSS 服务,因此优先使用 getent 查询,而不是只查看 /etc/passwd。
注意:普通用户查询自己的 shadow 记录也可能返回空,因为 /etc/shadow 默认不可读。getent 不会绕过文件权限:
ls -l /etc/shadow
sudo getent shadow alice
查不到通常不是用户不存在,而是权限限制。
2.7 su:切换用户
基本语法:
su [参数] [-] [用户名]
不带用户名时默认切换到 root:
su -
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
- 或 -l |
启动登录 Shell | 清理环境变量,切换到目标用户家目录,推荐使用 |
-c "命令" |
以目标用户执行一条命令 | 执行完返回,不进入交互式 Shell |
-s Shell |
指定 Shell | root 才可绕过 /etc/shells 限制 |
-m 或 -p |
保留当前环境变量 | 容易混用环境,一般不推荐 |
-g 组 |
指定主组 | 仅 root 可用 |
-G 组 |
指定附加组 | 仅 root 可用 |
-P |
创建独立伪终端 | 某些版本支持,增强终端隔离 |
-f |
传递 -f 给 Shell |
主要用于 csh/tcsh |
示例:
su - alice
su - alice -c 'id'
su - alice -s /bin/bash
注意:su 本身没有 -u 和 -H 参数。它们属于 sudo:
| 命令 | 参数 | 作用 |
|---|---|---|
sudo |
-u 用户 |
以指定用户身份运行命令 |
sudo |
-H |
将 HOME 设置为目标用户家目录 |
su |
无 -u |
目标用户直接写在命令后面,如 su - alice |
su |
无 -H |
使用 su - 启动登录 Shell 时,通常会切换到目标用户家目录 |
常见等价关系:
# su 切换登录 Shell
su - alice
# sudo 以 alice 身份启动登录 Shell
sudo -iu alice
# su 以 alice 身份执行单条命令
su - alice -c 'id'
# sudo 以 alice 身份执行单条命令,并使用 alice 的家目录
sudo -u alice -H id
说明:
sudo -iu alice中的-i表示模拟登录环境,-u alice指定目标用户。sudo -u alice -H command不会启动登录 Shell,适合脚本和单条命令。sudo -H只影响HOME;如果需要完整登录环境,应使用sudo -i。su -的-是登录 Shell 简写,不是sudo的-H。
切换用户后 SSH 连接其他主机
常见写法可以执行:
sudo su - alice -c 'ssh bob@192.0.2.10'
但更推荐直接使用 sudo -u,不需要启动登录 Shell:
sudo -u alice -H ssh bob@192.0.2.10
区别:
su - alice会以alice的登录环境启动 Shell;如果alice的 Shell 是nologin,会失败。sudo -u alice -H ssh ...直接以alice的 UID 执行ssh,即使alice是nologin服务账号也可以运行;-H让HOME指向alice的家目录。ssh bob@192.0.2.10中的bob是远端主机的登录用户,不是本机切换后的alice。- 如果写成
ssh 192.0.2.10,SSH 默认使用本机当前用户名alice作为远端用户名。 - 本机切换用户只影响使用哪个本地账号的
~/.ssh配置、私钥和 SSH agent,不会自动获得远端权限。
如果需要 alice 使用自己的密钥:
sudo -u alice -H ssh -i /home/alice/.ssh/id_ed25519 bob@192.0.2.10
目录和密钥权限通常应为:
chmod 700 /home/alice/.ssh
chmod 600 /home/alice/.ssh/id_ed25519
chmod 600 /home/alice/.ssh/authorized_keys
chown -R alice:alice /home/alice/.ssh
注意:
su的-c命令建议用单引号,避免本地 Shell 提前展开变量。- 首次连接会提示确认主机指纹,自动化脚本应提前配置
known_hosts或使用受控的StrictHostKeyChecking策略。 - 非交互式脚本应使用密钥认证和
BatchMode=yes,避免卡在密码输入。 - 不要把密码写在命令行中,命令行参数可能被其他用户通过进程列表看到。
- 如果
alice没有可用的 SSH 私钥,切换到alice后也无法使用 root 的私钥。此时应单独为alice配置密钥,或明确使用有权限的本地账号。
su 的注意事项
- 尽量使用
su - 用户,不要省略-。-会加载目标用户的登录环境;省略后可能继承当前目录和部分环境变量,导致命令行为不一致。 su默认切换到 root,生产环境应明确写出目标用户,避免误操作。su需要目标用户的密码,只有 root 切换时通常不需要密码。不要为了免密切换而滥用 PAM 或 sudoers 配置。- 普通用户能否
su到 root,取决于/etc/pam.d/su中的pam_wheel.so配置,不取决于用户是否在wheel组本身。 su -会按目标用户当前的用户和组数据库初始化身份,但已经打开的旧会话不会自动刷新。修改组成员后,新开的su -会话通常能读到新组信息,旧 Shell 仍保持原状态。su -c适合执行单条命令,但不要拼接不可信输入,避免 Shell 注入。su和sudo的认证对象不同:su通常验证目标用户密码,sudo通常验证当前用户密码,并按 sudoers 规则授权。- 在脚本中由 root 切换身份时,优先使用
runuser或setpriv,避免依赖 PAM 和密码交互。 - 修改过用户组、UID 或 GID 后,必须重新登录或重新
su -,旧会话不会自动刷新权限。 su -后环境变量、家目录和 PATH 会变化,排查“命令找不到”或“配置文件不生效”时要先确认当前身份和 Shell。- 操作完成后及时
exit,不要长期保留 root 或高权限会话。
检查当前身份和环境:
id
whoami
echo "$HOME"
echo "$PATH"
2.8 不能登录的用户如何启动服务
“不能登录”通常是指把用户的登录 Shell 设置为:
/usr/sbin/nologin
这只限制交互式登录,不等于该用户不能拥有进程或不能运行服务。
先区分两种场景
| 场景 | 说明 |
|---|---|
| 交互式登录 | 用户通过 SSH、终端或 su - 登录,nologin 会阻止 |
| 以该用户身份启动进程 | 由 root、systemd、容器运行时等主动切换 UID 后执行,不经过登录 Shell |
因此,服务账号可以使用 nologin,仍可被 systemd 等服务管理器以该 UID 启动。
方式一:systemd 服务(推荐)
创建服务账号:
sudo groupadd -r myapp
sudo useradd -r -g myapp -s /usr/sbin/nologin -d /opt/myapp -M myapp
创建应用目录并设置属主:
sudo mkdir -p /opt/myapp
sudo chown -R myapp:myapp /opt/myapp
sudo chmod 750 /opt/myapp
创建 unit 文件:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /opt/myapp/conf/myapp.conf
Restart=on-failure
RestartSec=3
# 可选的安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
[Install]
WantedBy=multi-user.target
加载并启动:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
检查进程实际运行身份:
ps -o pid,user,group,cmd -C myapp
关键点:
- systemd 以
User=myapp指定的 UID 启动进程,不要求myapp能登录。 User=中的服务账号应使用系统账号,Shell 设为nologin。Group=指定的组必须存在,并且应与服务目录的属组一致。- 服务需要的目录、端口、日志和配置文件必须授予
myapp或对应组权限。 - 不要让服务以 root 运行,除非确实需要绑定低端口或执行特权操作。
- 如果需要绑定 80/443,优先使用反向代理或
CAP_NET_BIND_SERVICE,而不是直接使用 root。
方式二:root 手动以服务账号执行命令
sudo -u myapp /opt/myapp/bin/myapp --config /opt/myapp/conf/myapp.conf
或者:
sudo runuser -u myapp -- /opt/myapp/bin/myapp
注意:
sudo -u myapp需要 root 或相应 sudoers 授权。runuser通常只允许 root 使用,适合脚本。- 直接用
su - myapp会被nologin阻止,不适合作为启动服务的方式。 - 用
su myapp -s /bin/bash强行绕过nologin会破坏服务账号的安全边界,不推荐。
方式三:容器或编排系统
Docker、Kubernetes 等运行时可以指定 UID,不依赖登录 Shell:
USER 1001:1001
或:
securityContext:
runAsUser: 1001
runAsGroup: 1001
容器镜像中即使没有 nologin 用户,也可以直接用数字 UID 运行。生产中应固定 UID/GID,并确保挂载目录权限匹配。
常见错误
| 错误做法 | 问题 |
|---|---|
su - myapp 启动服务 |
nologin 会拒绝登录,服务无法启动 |
把服务账号 Shell 改成 /bin/bash |
增加被登录利用的风险 |
| 给服务账号配置可登录密码 | 没有必要,且扩大攻击面 |
| 用 root 运行所有服务 | 服务被攻破后直接获得 root 权限 |
只改 User=,不改目录权限 |
服务启动失败,报权限不足 |
| 用共享账号运行多个服务 | 无法隔离故障和审计责任 |
推荐的服务账号流程
sudo groupadd -r myapp
sudo useradd -r -g myapp -s /usr/sbin/nologin -d /opt/myapp -M myapp
sudo install -d -o myapp -g myapp -m 0750 /opt/myapp
sudo install -d -o myapp -g myapp -m 0750 /opt/myapp/conf
sudo install -d -o myapp -g myapp -m 0750 /var/log/myapp
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
id myapp
ps -o pid,user,group,cmd -C myapp
服务账号原则:
- 使用系统 UID/GID,Shell 设置为
nologin。 - 不设置登录密码,或保持密码锁定。
- 每个服务使用独立账号,避免共享。
- 只授予必要目录和文件权限。
- 通过 systemd、容器运行时或 root 主动切换身份启动,不通过登录。
2.9 Docker 为什么需要 sudo
root 启动 Docker 时,Docker daemon 通常监听一个 Unix Socket:
/var/run/docker.sock
客户端执行 docker ps、docker run 时,并不是直接操作容器,而是通过这个 Socket 向 root 运行的 daemon 发送请求。因此,普通用户能否直接使用 Docker,取决于 Socket 的访问权限,而不是 docker 命令本身。
查看 Socket:
ls -l /var/run/docker.sock
stat -c '%U %G %a %A' /var/run/docker.sock
典型结果:
srw-rw---- 1 root docker 660 /var/run/docker.sock
含义:
| 字段 | 含义 |
|---|---|
s |
Socket 文件 |
root |
属主是 root |
docker |
属组是 docker |
660 |
属主和属组可读写,其他用户无权限 |
普通用户不在 docker 组时,访问 Socket 会被拒绝,所以只能使用 sudo docker ...。
方式一:加入 docker 组
sudo usermod -aG docker alice
然后让 alice 重新登录,使附加组生效:
id alice
验证:
docker ps
注意:
- 必须使用
-aG,不要用-G覆盖原有附加组。 - 组信息变更后,已有会话不会立即生效,需要重新登录。
- 加入
docker组后不需要sudo,但权限风险很高。
为什么 id 显示已加入 docker 组,docker 仍然报错
容易混淆的两个命令:
id test11 # 查询账号数据库,显示该用户“已配置”的组
id # 查询当前进程真正拥有的 UID/GID 和附加组
id 用户名 成功显示 docker,只证明 /etc/group 中已经记录了成员关系,不代表当前 Shell 进程已经获得该组权限。
验证当前会话是否真正生效:
id
docker ps
如果 id 用户名 有 docker,但 id 没有,说明当前会话还是旧权限。处理方式:
newgrp docker
id
docker ps
或者退出当前 SSH/终端会话,重新登录。仅执行 usermod 不会修改已经启动的 Shell 进程的附加组。
加入 docker 组后仍然不能访问的排查顺序
- 确认 Docker daemon 是否运行:
sudo systemctl status docker
- 确认
sudo docker ps是否正常:
sudo docker ps
如果 sudo docker ps 也失败,说明问题不在用户组,而在 daemon、Socket 路径或 Docker 配置。
- 查看 Socket 的属主、属组和权限:
ls -l /var/run/docker.sock
stat -c '%U %G %a %A' /var/run/docker.sock
getent group docker
有效条件是:
- Socket 属组确实是
docker。 - Socket 权限允许组读写,通常是
660。 - 当前进程确实拥有
docker组。
如果 Socket 属组不是 docker,加入 docker 组没有作用。应检查 Docker 的 --group 或 daemon 配置,不要直接把 Socket 改成 777。
- 确认 Docker 客户端连接的 Socket 路径是否正确:
echo "$DOCKER_HOST"
docker context ls
rootless Docker 或自定义 DOCKER_HOST 可能使用其他 Socket 路径。
.docker/config.json 的 permission denied
这类警告通常是 Docker 客户端读取自己的配置失败:
WARNING: Error loading config file:/home/user/.docker/config.json - permission denied
它与“能否连接 Docker daemon”是两件事。多数情况下该警告不会阻止连接,但应确认配置文件的权限:
ls -ld ~/.docker
ls -l ~/.docker/config.json
stat -c '%U %G %a %A' ~/.docker ~/.docker/config.json
namei -l ~/.docker/config.json
getfacl ~/.docker/config.json 2>/dev/null
常见原因:
- 曾经用
sudo docker或sudo -E docker运行,配置被 root 创建。 - 家目录或
.docker目录权限不足。 .docker是指向不可访问路径的符号链接。- ACL、挂载权限或网络文件系统权限异常。
如果确认文件应由当前用户拥有,可以修正:
sudo chown -R "$USER":"$(id -gn)" ~/.docker
chmod 700 ~/.docker
chmod 600 ~/.docker/config.json
如果不需要已有配置,也可以先备份后移走:
mv ~/.docker/config.json ~/.docker/config.json.bak
不要把整个家目录或 .docker 目录改成 777。
方式二:继续使用 sudo
sudo docker ps
sudo docker run --rm hello-world
可以为特定命令配置 sudoers:
%dev ALL=(root) /usr/bin/docker ps
但通常不建议细粒度授权 docker 命令,因为 Docker 权限本身很难安全收窄。
为什么 docker 组约等于 root 权限
Docker daemon 以 root 运行,Socket 实际上是一个高权限控制接口。能访问 Socket 的用户通常可以:
- 以 root 身份运行容器。
- 把宿主机根目录挂载进容器。
- 读取或修改宿主机文件。
- 通过特权容器或
--pid=host等方式影响宿主机。
例如:
docker run --rm -v /:/host -it alpine chroot /host
因此,加入 docker 组基本等同于获得 root 权限。安全要求高的环境应考虑:
- 使用 rootless Docker。
- 使用 Podman 等无守护进程方案。
- 通过受控的 CI/CD 或编排平台执行容器操作。
- 不给普通开发组直接授予
dockerSocket 访问权限。 - 使用
docker context、SSH 远程 Docker 或专用构建节点隔离权限。
常见现象与排查
| 现象 | 原因 | 处理 |
|---|---|---|
permission denied while trying to connect to the Docker daemon socket |
当前用户不能访问 Socket | 使用 sudo 或加入 docker 组 |
加入 docker 组后仍报错 |
当前会话未刷新组信息 | 重新登录 |
docker 命令找不到 |
PATH 或未安装客户端 | 检查 command -v docker 和软件包 |
Cannot connect to the Docker daemon |
daemon 未启动或 Socket 路径不同 | 检查 systemctl status docker 和 Socket 路径 |
sudo docker 可用但普通 docker 不可用 |
正常,说明当前用户没有 Socket 权限 | 按需加入组,不要直接改 Socket 为 777 |
不要用下面的方式绕过权限:
sudo chmod 777 /var/run/docker.sock
这会让任意本地用户获得 Docker 控制权,等同于把 root 权限开放给所有用户。
2.10 密码登录与 SSH 免密登录
SSH 登录必须先通过认证,常见方式有两种:
| 方式 | 认证凭据 | 远端保存什么 | 是否传输密码 |
|---|---|---|---|
| 密码登录 | 远端账号密码 | 密码哈希在 /etc/shadow |
是,但受 SSH 加密通道保护 |
| 公钥免密登录 | 客户端私钥 | 客户端公钥在 ~/.ssh/authorized_keys |
不传输密码,也不传输私钥 |
“免密”通常不是完全没有认证,而是用私钥认证代替密码认证。私钥仍然受本机文件权限保护。
密码登录流程
- 客户端连接 SSH 服务端。
- 服务端要求密码认证。
- 客户端输入远端账号密码。
- 服务端通过 PAM 等机制校验密码。
前提条件:
- 远端账号存在且未被锁定或过期。
- 账号有可用密码。
- 服务端允许密码认证:
PasswordAuthentication yes。 - 用户没有被
AllowUsers、DenyUsers、PAM、防火墙等策略拒绝。
检查服务端配置:
sudo sshd -T | grep -E '^(passwordauthentication|pubkeyauthentication|permitrootlogin|authorizedkeysfile)'
注意:sshd -T 需要 root,且显示的是当前生效配置,不一定是 /etc/ssh/sshd_config 中的原始文本。
SSH 公钥免密登录流程
- 客户端生成一对密钥:私钥和公钥。
- 客户端把公钥写入远端用户的
~/.ssh/authorized_keys。 - 客户端连接时,服务端检查该公钥是否被授权。
- 服务端向客户端发送挑战,客户端用私钥签名。
- 服务端用公钥验证签名,通过后允许登录。
生成密钥:
ssh-keygen -t ed25519 -C 'alice@laptop'
复制公钥到远端:
ssh-copy-id alice@192.0.2.10
也可以手动追加公钥:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cat id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
权限要求:
# 远端用户家目录不能对其他用户可写
chmod go-w ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# 客户端私钥必须仅自己可读
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
常见问题:
~/.ssh、authorized_keys或家目录权限过宽,sshd的StrictModes yes会拒绝使用公钥。- 公钥复制到了错误用户的家目录。
- 服务端
PubkeyAuthentication no。 - 私钥文件损坏、权限过宽或使用了错误的密钥。
- SELinux 上下文不正确,可检查
restorecon -Rv ~/.ssh。
两种方式的对比
| 对比项 | 密码登录 | SSH 公钥免密登录 |
|---|---|---|
| 是否输入密码 | 需要 | 通常不需要 |
| 凭据 | 账号密码 | 客户端私钥 |
| 密码是否通过网络传输 | 是,但受 SSH 加密通道保护 | 否 |
| 抗暴力破解 | 较弱,依赖密码强度和限速 | 较强,私钥难以暴力穷举 |
| 私钥泄露风险 | 无 | 有,私钥泄露即可登录 |
是否依赖 authorized_keys |
否 | 是 |
| 是否依赖账号密码 | 是 | 通常否 |
| 自动化友好程度 | 较差 | 较好 |
| 推荐场景 | 临时登录、首次配置 | 日常运维、自动化、CI/CD |
锁密码不等于禁止 SSH 登录
这是最容易误解的地方:
sudo passwd -l alice
这通常只是锁定密码认证,alice 仍然可能通过 SSH 公钥登录。类似地:
sudo usermod -L alice
也主要影响密码认证。
如果要彻底禁止某个账号登录,应结合多种措施:
# 设置账号过期,禁止所有登录方式
sudo chage -E 0 alice
同时按需:
- 设置
PasswordAuthentication no。 - 使用
DenyUsers alice或AllowUsers白名单。 - 移除远端
authorized_keys。 - 在 PAM 中配置账号锁定策略。
- 禁用账号或删除账号。
反过来,如果只把 Shell 设置为 nologin,也要注意:某些服务端配置或 ForceCommand 场景下仍可能执行非交互命令。安全边界应以 PAM、账号状态和 SSH 配置共同判断。
服务端常见配置
# /etc/ssh/sshd_config
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin prohibit-password
AuthorizedKeysFile .ssh/authorized_keys
StrictModes yes
含义:
| 配置 | 作用 |
|---|---|
PubkeyAuthentication yes |
允许公钥认证 |
PasswordAuthentication no |
禁止密码认证 |
PermitRootLogin prohibit-password |
root 禁止密码登录,仅允许密钥等方式 |
AuthorizedKeysFile |
指定公钥文件位置 |
StrictModes yes |
检查家目录和密钥文件权限 |
修改后先校验再重载:
sudo sshd -t
sudo systemctl reload sshd
不同发行版的服务名可能是 ssh 而不是 sshd:
sudo systemctl reload ssh
注意:重载前保留一个已登录的会话,避免配置错误导致无法远程登录。
排查命令
客户端使用详细日志:
ssh -vvv alice@192.0.2.10
只使用公钥认证:
ssh -o PreferredAuthentications=publickey alice@192.0.2.10
测试密码认证:
ssh -o PreferredAuthentications=password alice@192.0.2.10
服务端日志:
sudo journalctl -u sshd -n 100 --no-pager
sudo tail -n 100 /var/log/auth.log
排查时先判断失败发生在:
- 网络连接阶段。
- SSH 协议协商阶段。
- 用户认证阶段。
- 登录 Shell 或 PAM 会话阶段。
三、组操作
3.1 groupadd:创建组
基本语法:
sudo groupadd [参数] 组名
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-g GID |
指定 GID | 必须唯一 |
-r |
创建系统组 | GID 通常在系统号段 |
-f |
组已存在时按成功处理 | 常用于幂等脚本 |
-o |
允许非唯一 GID | 一般不推荐 |
-p 哈希值 |
设置组密码 | 不推荐,容易泄露 |
示例:
sudo groupadd dev
sudo groupadd -g 2000 ops
sudo groupadd -r appgroup
组分类:
- 主组:用户登录时默认生效,只有一个。
- 附加组:用户额外加入的组,可以有多个。
- 私有组:新用户创建的同名组,通常只包含该用户。
- 系统组:为服务或系统资源创建,GID 通常较小。
3.2 usermod 和 gpasswd:维护组成员
把用户加入附加组:
sudo usermod -aG dev alice
sudo gpasswd -a alice dev
从组中移除用户:
sudo gpasswd -d alice dev
gpasswd 常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-a 用户 |
把用户加入组 | 追加成员 |
-d 用户 |
从组中移除用户 | 不影响主组 |
-M 用户1,用户2 |
设置组成员列表 | 会覆盖原成员列表 |
-A 用户1,用户2 |
设置组管理员 | 组管理员可维护成员 |
-r |
删除组密码 | 仅成员可使用 newgrp |
-R |
限制组访问 | 组密码设为 ! |
注意:
sudo usermod -G dev,ops alice
会替换用户已有附加组,不会追加。追加必须使用 -aG。
组成员修改后,已有登录会话不会自动刷新附加组,用户通常需要重新登录。
3.3 groupmod:修改组
基本语法:
sudo groupmod [参数] 组名
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-n 新组名 |
修改组名 | 只改名称 |
-g 新GID |
修改 GID | 旧文件不会自动更新属组 |
-o |
允许非唯一 GID | 配合 -g 使用 |
-p 哈希值 |
修改组密码 | 不推荐 |
示例:
sudo groupmod -n newdev dev
sudo groupmod -g 3000 dev
修改 GID 后修正旧文件:
sudo find /path -gid 2000 -ls
sudo find /path -gid 2000 -exec chgrp 3000 {} \;
3.4 groupdel:删除组
sudo groupdel dev
常用参数:
| 参数 | 作用 | 说明 |
|---|---|---|
-f |
强制删除 | 部分发行版支持;可删除用户主组,风险高,不建议依赖 |
如果组是某个用户的主组,通常不能直接删除:
groupdel: cannot remove the primary group of user 'alice'
处理方式:
sudo usermod -g newgroup alice
sudo groupdel dev
删除组不会自动修改旧文件属组,需要检查残留:
sudo find / -gid 2000 -ls 2>/dev/null
3.5 newgrp:切换当前组
newgrp dev
作用:在当前终端临时切换到指定组,启动一个新的 Shell 会话。
| 参数 | 作用 |
|---|---|
- |
同时重新初始化登录环境 |
注意:newgrp 只影响当前 Shell 及其启动的进程,不会修改用户的组成员配置。长期生效仍应使用 usermod -aG 或 gpasswd -a。
查看当前会话的组:
id
四、权限设置
权限模型
属主 user u
属组 group g
其他 other o
| 权限 | 文件 | 目录 |
|---|---|---|
r |
读取文件内容 | 列出目录中的文件名 |
w |
修改文件内容 | 创建、删除、重命名目录项 |
x |
执行程序 | 进入目录、访问目录内文件 |
目录的 w 权限通常需要配合 x 才能正常使用。
chown 和 chgrp:修改属主和属组
sudo chown alice file.txt
sudo chown alice:dev file.txt
sudo chgrp dev file.txt
| 命令或参数 | 作用 |
|---|---|
chown 用户 文件 |
修改文件属主 |
chown 用户:组 文件 |
同时修改属主和属组 |
chown :组 文件 |
只修改属组 |
chgrp 组 文件 |
只修改属组 |
-R |
递归处理目录内容 |
--reference=文件 |
参照已有文件设置属主和属组 |
chmod:修改权限
符号方式:
chmod u+rwx,g+rx,o-rwx /srv/project
chmod g+r file.txt
chmod o-rwx file.txt
| 对象 | 含义 |
|---|---|
u |
属主 |
g |
属组 |
o |
其他用户 |
a |
所有用户,等价于 ugo |
| 操作 | 含义 |
|---|---|
+ |
增加权限 |
- |
移除权限 |
= |
设置为指定权限 |
数字方式:
r = 4
w = 2
x = 1
7 = rwx
6 = rw-
5 = r-x
4 = r--
0 = ---
示例:
chmod 750 /srv/project
chmod 640 file.txt
chmod -R g+r /srv/project
常用参数:
| 参数 | 作用 |
|---|---|
-R |
递归修改目录及其内容 |
--reference=文件 |
参照已有文件设置权限 |
-v |
显示处理过程 |
特殊权限
| 数值 | 名称 | 常见用途 |
|---|---|---|
4 |
setuid | 程序以文件属主身份运行,风险高 |
2 |
setgid | 文件以属组身份运行;目录中新文件继承目录属组 |
1 |
sticky | 目录中的文件通常只有文件属主、目录属主或 root 能删除 |
chmod 1777 /tmp
chmod 4755 /path/to/program
chmod 2770 /srv/project
setuid 应谨慎使用,不建议给脚本或普通程序随意设置。
默认权限 umask
umask
umask 027
umask 027 时:
- 普通文件最大权限
666,实际通常为640。 - 目录最大权限
777,实际通常为750。
权限排查
| 命令 | 作用 |
|---|---|
ls -ld /path |
查看目录本身的权限和属主 |
ls -l /path/file |
查看文件权限和属主 |
id 用户 |
查看用户 UID、主组和附加组 |
namei -l /path/file |
逐级查看路径上每一层目录权限 |
getfacl /path |
查看 ACL 权限 |
五、共享目录与 ACL
共享目录
团队共享目录推荐使用“属组 + setgid + 合适 umask”:
sudo groupadd project
sudo usermod -aG project alice
sudo mkdir -p /srv/project
sudo chown root:project /srv/project
sudo chmod 2770 /srv/project
2770中的2是 setgid 位。- 目录下新建文件和子目录会自动继承属组
project。 - 组内用户可读写和进入,其他用户无权限。
- 用户需要重新登录,确保新附加组生效。
ACL 精细授权
ACL 适合给额外用户或组授权,而不改变文件的基本属主和属组:
sudo setfacl -m u:bob:rw /srv/project/file.txt
sudo setfacl -m g:ops:rx /srv/project
sudo setfacl -d -m g:project:rwx /srv/project
getfacl /srv/project
| 参数 | 作用 |
|---|---|
-m |
修改 ACL 规则 |
-x 用户或组 |
删除指定 ACL 规则 |
-b |
删除所有扩展 ACL |
-d |
设置目录默认 ACL,新建内容自动继承 |
查看和清理:
getfacl /srv/project
sudo setfacl -x u:bob /srv/project/file.txt
sudo setfacl -b /srv/project/file.txt
ACL 的 mask 会限制组和命名用户的权限范围,排查权限时不应只看 ls -l。
六、sudo 权限
不建议直接把用户写入 /etc/sudoers,优先使用组和 visudo。
sudoers 规则的基本结构
一条 sudoers 规则通常写成:
用户或组 主机 = (运行身份) 命令
完整语法可以理解为:
User_List Host_List = (Runas_List) Tag_Spec: Cmnd_List
例如:
%dev ALL=(root) /usr/bin/systemctl restart myapp
这条规则的字段含义:
| 字段 | 示例 | 含义 |
|---|---|---|
| 用户或组 | %dev |
% 开头表示组 dev;不加 % 表示用户 |
| 主机 | ALL |
规则适用于所有主机 |
= |
= |
分隔“谁/在哪”与“能做什么” |
| 运行身份 | (root) |
允许通过 sudo -u root 以 root 身份执行 |
| 命令 | /usr/bin/systemctl restart myapp |
只允许执行这条具体命令 |
因此该规则表示:
dev 组中的用户,可以在任意主机上,
以 root 身份执行:
sudo /usr/bin/systemctl restart myapp
注意:
%dev是组;dev才是用户。ALL在主机位置表示所有主机,在命令位置表示所有命令,在运行身份位置表示所有用户,含义随字段位置变化。(root)只限定可以“以谁的身份”运行,不是“只能运行 root 的命令”。- 写了具体命令和参数后,用户通常只能按该参数形式执行;不要误以为可以执行
systemctl的任意子命令。 systemctl应使用实际绝对路径,建议通过command -v systemctl确认。systemctl restart myapp中的服务名和参数要按实际服务名调整。- 如果服务名和参数可被用户控制,仍可能形成越权风险。
常用 sudoers 字段
| 写法 | 含义 |
|---|---|
alice ALL=(ALL) ALL |
alice 可在所有主机上以任意用户身份执行任意命令 |
%dev ALL=(ALL) ALL |
dev 组所有成员拥有上述权限 |
%dev ALL=(root) ALL |
dev 组只能以 root 身份执行任意命令 |
%dev ALL=(root) /usr/bin/systemctl restart myapp |
只允许执行指定命令 |
%dev ALL=(root) NOPASSWD: /usr/bin/systemctl status myapp |
指定命令免密码,但仍需是 dev 组成员 |
alice ALL=(root) /usr/bin/systemctl restart myapp |
只授权单个用户 |
NOPASSWD: ALL 表示什么
%dev ALL=(ALL) NOPASSWD: ALL
含义:
| 字段 | 含义 |
|---|---|
%dev |
dev 组的所有成员 |
ALL |
在任意主机上 |
(ALL) |
可以以任意用户身份运行,包括 root |
NOPASSWD: |
执行 sudo 时不需要输入该用户自己的密码 |
ALL |
可以执行任意命令 |
等价于:
dev 组中的任何成员,都能免密获得完整的 root 权限,
可以执行任意命令、读取任意文件、修改系统配置。
这不是“免密执行某一条命令”,而是“免密执行所有命令”。因此:
- 只应在完全可信、受控的运维组中使用。
- 不应授予普通开发组、临时账号或共享账号。
- 用户一旦进入该组,基本等同于拥有 root 权限。
- 应优先改为收窄命令、收窄运行身份,例如:
%dev ALL=(root) NOPASSWD: /usr/bin/systemctl status myapp
PASSWD: 与 NOPASSWD: 相反,表示执行该命令前需要认证。标签会影响同一命令列表中后续命令,使用 PASSWD: 可以恢复密码验证:
%dev ALL=(root) NOPASSWD: /usr/bin/systemctl status myapp, PASSWD: /usr/bin/systemctl restart myapp
命令别名与排除规则
实际 sudoers 配置中常见下面这种写法:
%tbdba ALL=(ALL) ALL,SUPERMIT,!SHELLS,SUDOEXEC,!VIETC
%tbnoc ALL=(ALL) ALL,SUPERMIT,!SHELLS,SUDOEXEC,!VIETC,!USERADD
%tbscm ALL=(ALL) ALL,SUPERMIT,!SHELLS,SUDOEXEC,!VIETC,!USERADD
%tbdev ALL=(ALL) ALL,SUPERMIT,!SHELLS,SUDOEXEC,!VIETC,!USERADD
%tbtest ALL=(ALL) ALL,SUPERMIT,!SHELLS,SUDOEXEC,!VIETC,!USERADD
逐段理解:
| 片段 | 含义 |
|---|---|
%tbdba 等 |
组 tbdba 等组的所有成员 |
ALL |
在任意主机上 |
(ALL) |
可以以任意用户身份运行,包括 root |
ALL |
允许运行所有命令 |
SUPERMIT |
引用名为 SUPERMIT 的 sudoers 别名 |
!SHELLS |
排除名为 SHELLS 的命令别名 |
SUDOEXEC |
引用名为 SUDOEXEC 的别名 |
!VIETC |
排除名为 VIETC 的命令别名 |
!USERADD |
排除名为 USERADD 的命令别名 |
这些大写名称不是 sudo 内置命令,而是当前 sudoers 文件中通过 Cmnd_Alias 定义的可复用命令集合,例如:
Cmnd_Alias SHELLS = /bin/sh, /bin/bash, /bin/csh
Cmnd_Alias USERADD = /usr/sbin/useradd, /usr/sbin/usermod, /usr/sbin/userdel
Cmnd_Alias VIETC = /usr/bin/vi /etc/passwd
Cmnd_Alias SUDOEXEC = /usr/bin/sudo
以上只是结构示例,不代表本机实际定义。要确认真实含义,需要查看:
sudo grep -R 'Cmnd_Alias.*\(SUPERMIT\|SHELLS\|SUDOEXEC\|VIETC\|USERADD\)' /etc/sudoers /etc/sudoers.d
或者查看某用户最终解析后的权限:
sudo -l -U alice
需要特别理解 ALL 与 !别名 的组合:
ALL先表示允许所有命令。!SHELLS、!VIETC、!USERADD表示从命令集合中排除这些命令。- 但这类排除通常不能形成可靠的安全边界。sudo 手册明确指出:从
ALL中使用!减去少量命令,一般不会按预期生效。 - 用户可以复制被排除的命令到另一个文件名后执行,也可以通过编辑器、分页器等程序的 Shell 逃逸绕过。
- 如果用户同时拥有
ALL,实际上很难阻止其创建自己的程序来获得 root Shell。 - 因此这些
!SHELLS、!VIETC、!USERADD更适合视为“提示性限制”或审计标记,不能当作真正的隔离措施。 - 需要严格限制时,不要先给
ALL;应只白名单列出允许的具体命令,并配合NOEXEC、sudoedit等机制。
五个组的差异:
tbdba没有!USERADD,因此其规则没有显式排除USERADD别名中的命令,但仍受ALL和其他排除规则影响。tbnoc、tbscm、tbdev、tbtest都包含!USERADD,显式排除了USERADD别名对应的用户管理命令。- 五条规则都包含
SUPERMIT、!SHELLS、SUDOEXEC、!VIETC,具体权限仍由这些别名定义决定。 - 由于五条规则都已经包含
ALL,这些差异不能理解为严格的安全隔离;拥有这些组权限的用户仍可能通过其他命令或自建程序绕过限制。
这类规则常见于“受限运维组”的设计:希望授予大部分 root 能力,同时用别名表达不希望使用的 Shell、用户管理、关键配置编辑等操作。但必须注意:ALL 之后的 ! 排除通常只是管理约定,不是可靠的技术隔离。实际评估时既要查看完整别名定义,也要通过 sudo -l -U 用户 和实际测试确认权限边界。
推荐写法
使用组授权,并放到独立配置文件中:
sudo groupadd ops
sudo usermod -aG ops alice
sudo visudo -f /etc/sudoers.d/ops
文件中写入:
%ops ALL=(root) /usr/bin/systemctl restart myapp
检查配置和用户权限:
sudo visudo -c
sudo -l -U alice
Debian/Ubuntu 常见 sudo 组:
sudo usermod -aG sudo alice
RHEL/CentOS 常见 sudo 组:
sudo usermod -aG wheel alice
注意:sudo 组成员和 wheel 组成员的具体权限由发行版默认 sudoers 配置决定,加入组后仍应使用 sudo -l -U 用户 核实实际权限。
七、完整流程与排错
创建用户完整流程
sudo groupadd dev
sudo useradd -m -u 2001 -g dev -s /bin/bash alice
sudo passwd alice
sudo chage -d 0 alice
sudo usermod -aG sudo alice # Debian/Ubuntu,按需
id alice
修改用户完整流程
sudo usermod -aG docker alice
sudo usermod -s /bin/bash alice
sudo chage -M 90 alice
id alice
删除用户完整流程
pgrep -u alice -a
sudo tar czf /backup/alice-home.tar.gz /home/alice
sudo userdel -r alice
sudo find / -uid 2001 -ls 2>/dev/null
常见问题
| 现象 | 排查方向 |
|---|---|
| 用户创建后不能登录 | 是否设置密码、Shell 是否为 nologin、家目录和账号是否正常 |
| 加入组后权限没生效 | 是否重新登录,当前会话是否仍使用旧组信息 |
| 附加组丢失 | 是否误用了 usermod -G,应使用 -aG |
| 修改 UID/GID 后文件异常 | 旧文件仍保留旧 UID/GID,需要手动修正 |
groupdel 失败 |
该组可能是某个用户的主组,先修改主组 |
| 删除用户后有残留 | 检查 cron、sudoers、SSH、数据库、systemd 和家目录外文件 |
| 共享目录文件属组不对 | 检查目录 setgid、用户附加组和 umask |
| sudo 不生效 | 使用 visudo 检查语法,确认用户已重新登录 |

浙公网安备 33010602011771号