Skip to content

用户与组

Linux 的登录认证、文件权限、服务进程身份,全都是围绕用户和组展开的。排查权限问题时,要同时确认几件事:这个账号存不存在、它属于哪些组、用的是什么 Shell、进程以什么身份在跑——这些信息散落在不同的文件和命令里,得知道去哪找。

一、UID 和 GID

这里有个反直觉但很重要的点:Linux 内核识别的是数字 ID,不是用户名。用户名只是给人看的标签,内核做权限判断时看的是 UID 和 GID。理解这一点,后面很多奇怪现象就解释得通了。

名称含义
UID用户 ID。0 是 root,拥有完整权限
GID主组 ID。每个用户都属于一个主组
附加组用户额外加入的其他组,一个用户可以同时属于多个组

查看当前用户的身份:

bash
whoami       # 当前用户名
id           # uid、gid、所有附加组
groups       # 所属组列表

排查权限问题时,id 比看 whoami 有价值得多。一个用户能不能访问某个文件,取决于他的 uid 和所有 gid,光看用户名判断不了完整权限。比如 whoami 显示你是 deploy,但 id 里显示你在 docker 组,那你其实能免 sudo 用 docker——这种权限光看用户名是看不出来的。

跨机器复制文件时会踩到一个坑:文件系统里记录的 owner 和 group,本质上是 UID 和 GID 的数字,不是用户名。如果两台机器上同一个 UID 对应的用户名不一样,文件拷过去之后 owner 就"变了"。比如源机器上 UID 1001 是 nginx,目标机器上 UID 1001 是 deploy,把文件拷到目标机器后 ls -l 会显示 owner 是 deploy——其实 UID 没变,变的只是 UID 到用户名的映射。

二、用户和组的关键文件

Linux 的用户和组信息存在几个固定文件里。直接 cat 这些文件也行,但getent 更可靠——它会走系统的名称服务(NSS),LDAP、NIS 这种集中认证环境也能正确查到,而 cat 只能看本地文件。

文件装什么
/etc/passwd用户名、UID、GID、家目录、Shell
/etc/shadow密码哈希、密码过期策略(只有 root 能读)
/etc/group组名、GID、组成员
/etc/gshadow组密码(基本用不到)
bash
getent passwd nginx
getent group wheel

/etc/passwd 一行的结构,以 root 为例:

root:x:0:0:root:/root:/bin/bash
字段说明
用户名root登录时用的名字
密码占位x真正的密码哈希在 /etc/shadow 里,这里只是占位
UID0用户 ID
GID0主组 ID
GECOSroot注释字段,通常放全名或说明
家目录/root登录后的起始目录
Shell/bin/bash登录后启动的 Shell

三、创建用户

bash
useradd app          # 创建用户
passwd app           # 设置密码

useradd 的默认行为受 /etc/login.defs/etc/default/useradd 两个配置影响——会不会自动创建家目录、默认 Shell 是什么、UID 从哪个范围分配,不同发行版的默认值不一样。所以生产环境创建服务账号时,关键参数最好显式指定,别依赖默认值,换台机器可能行为就变了。

指定家目录和 Shell:

bash
useradd -m -d /home/deploy -s /bin/bash deploy

-m 创建家目录、-d 指定家目录路径、-s 指定登录 Shell。

创建不需要登录的服务用户(系统用户),比如给 Nginx 这种服务用的:

bash
useradd -r -s /sbin/nologin nginx

-r 创建系统用户(分配的 UID 在系统用户范围内,通常小于 1000)。/sbin/nologin(或者 /usr/sbin/nologin)作为 Shell 时,这个用户不能交互登录系统——但服务进程照样能以这个用户身份运行,两者不冲突。给服务配专用用户是安全实践,就算服务被攻破,攻击者拿到的也只是这个低权限用户,不是 root。

四、修改和删除用户

bash
usermod -aG wheel deploy         # 把 deploy 追加到 wheel 附加组
usermod -s /bin/bash deploy      # 修改登录 Shell
usermod -L deploy                # 锁定用户(禁止登录)
usermod -U deploy                # 解锁用户

这里有个极其容易踩的坑:usermod -G 的默认行为是替换所有附加组,不是追加。要追加必须加 -a(append)。把用户加到 docker 组或者 sudo 组的时候,如果忘了 -a,这个用户会从原来的所有附加组里被踢出去——本来能用的权限全没了,排查起来非常费劲。记住:加组永远用 -aG,不要用 -G

删除用户:

bash
userdel app
userdel -r app        # 同时删除家目录和邮件目录

正式环境里删用户别太干脆,通常先锁定、观察一段时间,确认没有遗留的定时任务或服务依赖这个账号,再真正删除:

bash
usermod -L olduser
chage -E 0 olduser    # 把账号过期时间设为 1970-01-01,立即失效

直接 userdel 删掉之后,如果发现某个定时任务或者某个 systemd 服务还配置着这个用户,服务起不来,再想恢复就麻烦了。

五、su 和 sudo

su 切换用户身份,完全切换到目标用户:

bash
su - app

-(横杠)这个参数很关键:带横杠会加载目标用户的完整登录环境(环境变量、PATH、工作目录),不带横杠只切换用户身份,环境还是原来的。排查"为什么我 su 过去之后命令找不到"这种问题,十有八九是忘了加横杠,PATH 没切过来。

sudo 以其他用户身份执行单条命令(默认 root),更常用也更安全:

bash
sudo systemctl restart nginx

sudo 的配置文件 /etc/sudoers 必须用 visudo 编辑,不能直接 vim:

bash
visudo

visudo 在保存前会做语法检查,防止你写错之后 sudo 全部不可用——直接编辑 /etc/sudoers 一旦写错语法,所有人都没法 sudo 了,只能进单用户模式救,非常惨。

RHEL 系的管理员组叫 wheel,Ubuntu 叫 sudo,把用户加进去就能 sudo:

bash
usermod -aG wheel deploy     # RHEL 系
usermod -aG sudo deploy      # Ubuntu / Debian

需要细粒度授权时,在 sudoers 里针对特定命令授权,比如只允许 deploy 用户重启 nginx:

sudoers
deploy ALL=(root) /usr/bin/systemctl restart nginx

字段从左到右:(1) 授权哪个用户或组(组前面加 %);(2) 从哪些主机登录时生效;(3) 以谁的身份执行;(4) 允许执行的具体命令,必须写绝对路径。

sudo 权限的粒度越细越好。把所有人都配成 ALL=(ALL) NOPASSWD:ALL 看着方便,出事的时候就是灾难——事后看 sudo 日志,根本分不清是谁执行了哪条高危命令,审计完全失效。生产环境里,NOPASSWD 只该配在必要的特定命令上,不该全开。

六、查看登录状态

bash
w                # 当前有哪些用户登录,在做什么
who              # 简化的在线用户列表
last             # 历史登录记录(读 /var/log/wtmp)
lastb            # 失败登录记录(读 /var/log/btmp)

SSH 端口暴露在公网的时候,lastb 的输出通常长得吓人——全是各种暴力破解的失败记录。改 SSH 端口能减少日志噪音(扫描器默认扫 22),但改端口不是真正的安全措施,只是降低被扫到的概率。真正的防护要靠密钥认证(禁用密码登录)、限制来源 IP(只允许堡垒机 IP)、或者干脆走堡垒机统一入口,把 SSH 端口完全藏起来。