在我待过的几个运维群里,每隔一段时间就会有人贴出一张截图:sudo突然报错说当前用户不存在于密码数据库中,或者某个服务起不来、日志里写着找不到指定账号。顺着线索查下去,八成会落到同一个文件上——/etc/passwd。这个文件在 Linux 系统里活得非常低调,权限是所有人可读,体积小得可怜,几十行到几百行而已,很多人装了几年系统都没正眼瞧过它。可它偏偏是账号体系的户口本,谁是谁、谁是超级用户、谁能登录、登录后落到哪个目录、用哪个 shell,全在这一行行的冒号分隔文本里写着。这篇内容就围绕Linux /etc/passwd这一个文件展开,把它拆到字段级别,再把查看、解读、新增、修改、校验这条路走一遍。不管你是刚在虚拟机里装完系统、连useradd和adduser区别都还没分清的新手,还是天天跟服务器打交道的运维,这里面的坑大概率你都踩过或者即将踩到。
1. /etc/passwd 到底是个什么东西
打开这个文件之前,有个反常识的事实得先说清楚:/etc/passwd里面并没有密码。这个名字是四十多年前留下来的历史包袱,早期 Unix 确实把加密后的口令哈希直接写在第二个字段里,后来因为安全审计的需要,哈希被搬去了/etc/shadow,文件名却一直没改。所以第一次cat出来看到满屏的x,不用怀疑自己是不是把文件改坏了,那是正常状态。
1.1 文件名里的历史误会与它现在的定位
passwd这个词在英文里既是"密码"也是"密码文件",早期它名副其实。当年系统管理员把哈希写在所有人可读的文件里,任何登录上来的普通用户都能把整份哈希拷贝走慢慢离线破解,这在多用户主机时代是个巨大的隐患。于是系统把哈希迁移到了只有 root 能读的/etc/shadow,原文件第二个字段统一用x占位,表示"真正的密码在别处查"。文件名没改的原因很简单:那个年代已经有大量程序、脚本、文档依赖这个路径,改名带来的兼容成本远大于收益,索性就一直留着。
现在它的定位很清晰:一份账号索引,负责把用户名映射到一个数字 UID,并附带这个账号的基本登录环境信息。系统内部几乎所有的权限判断都以 UID 这个数字为准,而不是以用户名。你在ls -l里看到的文件属主名字,是显示工具反向查这张表翻译出来的;ps、top、sudo、ssh、psql、docker ps这些命令想把 UID 变成人能看懂的名字,也都要走这张表。
我见过有人在容器里手动改了文件属主,结果ls -l里那行显示成一串裸数字,第一反应是文件系统损坏,其实只是容器内的/etc/passwd里没有对应的 UID 条目,翻译不出来而已。理解这一点,后面很多"诡异现象"都能自己解释通。
1.2 它在登录链路里被谁读取
一次典型的 SSH 登录,背后会依次经过几个环节:sshd接到连接请求,先从/etc/passwd里查这个用户名对应的 UID、家目录和登录 shell,然后交给 PAM 做认证,PAM 的pam_unix模块去/etc/shadow取哈希做比对,认证通过后按 passwd 里记录的家目录切换过去,再启动记录的那个 shell。
这条链路意味着两个结论。第一,/etc/passwd的内容一旦有误,认证根本走不到密码比对那一步,直接拒绝。第二,登录 shell 字段并不是"建议值",而是真正被执行的程序路径——sshd会去执行它。所以把系统服务账号的 shell 从/usr/sbin/nologin改成/bin/bash,等于给这个账号开了后门,只要密码或者密钥对得上就能进系统。这一点在安全审计里是高频扣分项。
同样地,sudo在做权限提升前,也会确认当前调用者的 UID 能在 passwd 库里解析到,这也是那类"you do not exist in the passwd database"报错的直接来源。
1.3 为什么密码一定要待在 /etc/shadow
/etc/passwd的权限是-rw-r--r--,也就是 644,root 可写、所有人可读。这个权限是必须的,因为普通用户执行ls -l、id、whoami都需要读它。反过来说,任何写进这个文件的内容都等于公开信息。
/etc/shadow的权限通常是000或者640且属于root:shadow,普通用户根本读不到。再加上 shadow 文件里的哈希经过了加盐处理,虽然理论上依然可以被离线暴力破解,但门槛被抬高了很多。这两份文件的分工就这么定下来了:passwd 管"你是谁、你住哪、你用什么壳",shadow 管"你的密码哈希是什么、多久过期、是否被锁定"。
提示:有些老系统上你可能会看到 passwd 第二个字段是
*或者!,那表示该账号被禁止通过密码登录,只能走其它认证方式,或者干脆就是个不能登录的占位账号。
2. 一行记录七个字段,逐个咬碎
/etc/passwd的每一行代表一个账号,字段之间用英文冒号:分隔,一行固定七个字段,顺序不能乱,也不能少。拿一行最典型的出来看:
root:x:0:0:root:/root:/bin/bash拆开就是:
| 序号 | 字段名 | 这一行的值 | 含义 |
|---|---|---|---|
| 1 | 用户名 | root | 登录时输入的名字 |
| 2 | 密码占位 | x | 哈希在 /etc/shadow 里 |
| 3 | UID | 0 | 用户数字标识 |
| 4 | GID | 0 | 主组数字标识 |
| 5 | GECOS | root | 备注/全名信息 |
| 6 | 家目录 | /root | 登录后的落脚点 |
| 7 | 登录 shell | /bin/bash | 登录后执行的程序 |
七个字段看着简单,但是每一个都有值得展开的细节,尤其是 UID 和登录 shell 这两个。
2.1 用户名与密码占位符
用户名字段在同一个文件里必须唯一,长度一般限制在 32 个字符以内,允许字母、数字、下划线、短横线,通常不建议以数字开头。这里有个容易忽略的点:用户名里不能出现冒号,因为冒号是字段分隔符,一旦出现就会把一条记录拆成多余的字段,解析器会直接报错或者丢弃这一行。同理,用户名里也不建议出现空格和中文,虽然某些发行版能容忍,但在跨系统同步账号(比如 LDAP、NIS、集中认证)时极易出问题。
第二个字段现在的标准值是x,表示密码哈希存放在/etc/shadow中对应的同名条目里。除了x之外,你可能还会看到几种取值:
- 空值(两个冒号贴在一起):表示这个账号不需要密码就能登录。这在安全上非常危险,某些 PAM 配置会直接允许空密码通过。
*:一个不可能与任何哈希匹配的占位符,效果是该账号无法用密码登录。!或!!:通常出现在 shadow 文件里,表示账号被锁定。- 一串看起来像乱码的字符:极老系统上遗留的哈希,现在基本见不到了。
我在做服务器加固的时候,习惯性会扫一遍这个字段,凡是空值或者不是x的,都要拎出来单独确认用途,很多历史遗留的测试账号就是这么被翻出来的。
2.2 UID 的三段划分与它背后的权限逻辑
UID 是整份文件里最核心的字段,因为系统的权限判断只看这个数字,不看用户名。这里必须强调一句话:UID 等于 0 的账号,就是超级用户,不管它叫什么名字。所以如果你发现文件里有两个 UID 为 0 的条目,比如除了 root 之外还有一个叫admin或者别的什么名字的,那基本可以判定是留了后门,必须立刻处理。
UID 的取值范围在实践中被切成了三段,不同发行版的边界略有差异:
| 区段 | 用途 | 典型例子 | 备注 |
|---|---|---|---|
| 0 | 超级用户 | root | 有且应当只有一个 |
| 1 - 999 | 系统账号/服务账号 | daemon、bin、sshd、nginx | RHEL 系常用 1-999,Debian 系常用 100-999 |
| 1000 及以上 | 普通登录用户 | zhangsan、devops | 具体下限由 login.defs 控制 |
这三段的划分不是摆设,useradd在创建账号时会根据/etc/login.defs里的UID_MIN、UID_MAX、SYS_UID_MIN、SYS_UID_MAX来自动决定分配哪一段的数字。你执行useradd nginx或者带-r参数时,系统会从系统账号区间里挑一个没被占用的数字;执行useradd zhangsan时,则从普通用户区间挑。
还有一个特殊值值得单独记一下:65534,通常对应nobody这个账号。它的作用是"不属于任何人的身份",NFS 在开启 root squash 之后,客户端的 root 请求会被映射成这个 UID,避免远程 root 直接拿到服务器上的超级权限。有些系统里还会见到nfsnobody,也是类似用途。
再说一个非常实用的经验:改 UID 是一个危险操作。因为文件系统里记录属主用的是数字,你把某个用户的 UID 从 1001 改成 1005,那么这个用户原来拥有的所有文件在新 UID 下就变成了"陌生人",ls -l显示成一串数字。usermod -u 1005 -m zhangsan这个命令在改 UID 的同时会递归修正家目录内文件的属主,但家目录之外的、散落在/data、/opt里的文件它管不到,得自己find / -uid 1001 -exec chown zhangsan {} \;去补。这种操作建议在维护窗口做,别在业务高峰上手。
2.3 GID 与 GECOS 字段的实际用途
第四个字段 GID 是这个用户的主组编号。注意这里写的是主组,不是"所属的所有组"。一个用户除了主组之外,还可以加入若干个附加组,那些信息记录在/etc/group里,不在这个文件里。所以想完整看清一个用户的组关系,正确做法是跑id zhangsan,它会同时把主组和附加组都列出来。
主组的设计初衷是方便文件共享:同组的人创建的文件默认就归这个组所有,方便协作。实际使用中,很多人创建用户时会顺手建一个跟用户同名的组(useradd默认行为就是这样),这样每个用户都有自己独立的主组,避免所有人共享一个组导致的权限混乱。
第五个字段 GECOS 的名字来源挺有意思,它来自早期与通用电气公司合作的操作系统项目,当时这个字段是用来给finger命令展示用户信息的。它的内容可以再细分成用逗号隔开的五个子字段,分别是全名、房间号、工作电话、家庭电话、其它备注。现在基本只有第一个子字段(全名)还有实际意义,后面的房间号和电话在现代环境里早就没用了。
写的时候要注意:GECOS字段里不要出现冒号,但可以出现逗号。如果你想写完整的五个子字段,格式是张三,研发部,8021,13800000000,备注。实际运维里常见做法是只写全名或者干脆留空,写不写都不影响登录。
2.4 家目录与登录 shell,两个最容易出问题的字段
第六个字段是家目录的绝对路径。登录成功后,shell 的当前目录会切换到这儿;用户的个人配置文件.bashrc、.ssh/authorized_keys、.profile也都放在这里。如果这个路径指向的目录不存在,登录过程本身可能成功,但你会直接落在根目录/或者得到一个刺眼的警告。useradd加上-m参数才会自动创建家目录,不加的话系统只写一条记录、不建目录,这是新手很容易忽略的一个差异。
需要注意的是,系统账号的家目录通常是个不太一样的路径。比如nginx可能指向/var/lib/nginx或者/nonexistent,sshd可能指向/var/empty/sshd。这些目录往往权限收紧、内容几乎为空,因为它们本来就不承担"个人工作区"的角色。
第七个字段是登录 shell,它决定了账号能做什么。常见取值和含义如下:
| shell 路径 | 含义 | 适用场景 |
|---|---|---|
| /bin/bash | Bash 交互式登录 | 普通用户、管理员 |
| /bin/zsh | Zsh | 追求体验的开发者 |
| /bin/sh | 通常是 dash 或 bash 的软链 | 脚本兼容 |
| /usr/sbin/nologin | 拒绝登录,打印提示后断开 | 系统服务账号 |
| /sbin/nologin | 同上的老路径 | 老系统 |
| /bin/false | 静默退出,不打印任何信息 | 需要完全静默的场合 |
这里有个细节:/usr/sbin/nologin和/bin/false虽然都能阻止交互登录,但行为不同。前者会打印一段类似 "This account is currently not available." 的提示,后者什么都不说直接返回失败状态。做加固时我一般统一用nologin,因为出问题时能给人一点线索,不至于对着黑屏发呆。
另外,nologin只是阻止了交互式 shell 登录,并不阻止这个账号被su、sudo -u或者服务进程以其它身份使用。如果你要的是彻底禁用,还得配合锁定密码或者把账号本身删掉。
3. 动手实操:从查看到改动的完整流程
理论讲完,接下来走一遍真实操作。这一节的所有命令都在一台干净的虚拟机上跑过,你可以照着复现。装虚拟机的过程这里就不展开了,随便一个主流发行版都行,我用的是一台最小化安装的系统。
3.1 查看与过滤的正确姿势
最直接的方式当然是cat,但真正干活的时候我更推荐getent:
# 查看指定用户 getent passwd root # 查看全部(等价于 cat,但走的是统一的名称服务接口) getent passwd # 只看普通用户,过滤掉系统账号 awk -F: '$3 >= 1000 && $3 < 65534 {print $1"\t"$3"\t"$6"\t"$7}' /etc/passwd为什么优先用getent而不是cat?因为getent会按照/etc/nsswitch.conf里配置的顺序去查所有数据源。在只有本地账号的机器上,两者结果一样;但一旦接入了集中认证(LDAP、SSSD、Winbind 之类),/etc/passwd里只有本地账号,getent却能把远程账号也查出来。如果你习惯用grep xxx /etc/passwd来判断一个账号是否存在,在集中认证环境里会得到完全错误的结论——明明账号存在,你却以为没有。
提示:
nsswitch.conf里passwd:那一行的顺序决定了查找优先级,常见的写法是files sss,意思是先查本地文件、再查 SSSD。排查账号问题的第一步,永远是getent passwd 用户名。
再补充两个常用组合。想看某个 UID 对应哪个账号,用getent passwd 1001;想看某个用户到底属于哪些组,用id 用户名。这两个命令配合起来,账号的基本面貌就清楚了。
3.2 新建一个用户并逐字段验证
我用useradd手工造一个账号,把常用参数都用上:
useradd -m \ -s /bin/bash \ -u 1600 \ -c "Zhang San,R&D,8021,13800000000" \ -G developers \ zhangsan逐个解释这些参数在做什么:
-m:创建家目录,路径默认/home/zhangsan。不加这个参数,家目录字段照样会写进/etc/passwd,但目录不存在。-s:指定登录 shell。不指定的话会取/etc/default/useradd里SHELL的值,很多发行版默认就是/bin/bash。-u:手工指定 UID。不指定就自动分配,在普通用户区间里挑第一个空位。-c:写 GECOS 备注。注意内容里有逗号是可以的。-G:加入附加组developers。这个组必须已经存在,否则命令直接报错,不会自动创建。- 最后那个
zhangsan是用户名,同时默认会创建一个同名的组作为主组。
执行完之后立刻验证:
grep zhangsan /etc/passwd # 输出:zhangsan:x:1600:1600:Zhang San,R&D,8021,13800000000:/home/zhangsan:/bin/bash id zhangsan # 输出:uid=1600(zhangsan) gid=1600(zhangsan) groups=1600(zhangsan),1001(developers)对着输出核对一遍:UID 是不是 1600,GID 是不是同名组,家目录存不存在,shell 对不对。这里有个我踩过的坑值得说一下——-G指定的附加组在 GECOS 里是看不到的,很多人对着 passwd 那一行反复看,发现不了自己加错组。验证组关系必须用id。
新账号建出来还没有密码,这时候它其实处于一个"能用但登不进去"的状态。设置密码:
passwd zhangsan交互式输入两遍。想脚本化批量设置,可以用echo "密码" | passwd --stdin zhangsan,不过--stdin是 RHEL 系passwd的特有参数,Debian/Ubuntu 上不支持,跨发行版的写法是用chpasswd:
echo "zhangsan:密码" | chpasswdchpasswd从标准输入读取"用户名:密码"格式的文本,这个命令在主流发行版上都一致,写自动化脚本时更省心。
3.3 改 shell、改家目录、改备注的实操
账号建好之后改动需求很常见,usermod是主力工具。
把账号禁止登录(服务账号加固的标配操作):
usermod -s /usr/sbin/nologin zhangsan把家目录整体挪到数据盘:
usermod -d /data/home/zhangsan -m zhangsan-d改路径,-m表示把老目录的内容一起搬过去。不加-m只改记录、不搬文件,这是很多人迁移完发现个人配置全丢了的真实原因。另外,搬家过程中原目录里的隐藏文件也会一起过去,因为搬的是整个目录,不是目录里的可见文件。
改 GECOS 备注:
usermod -c "Li Si,Operations" zhangsan改 UID(慎用,前面说过原因):
usermod -u 1700 zhangsan这个命令会顺带修正家目录内文件的属主。执行完记得用find / -uid 1600 -ls扫一遍,看看还有没有散落在别处的孤儿文件需要手动处理。
改主组:
usermod -g newgroup zhangsan这里要特别注意,-g是改主组,-G是设置附加组列表。而且-G的行为是覆盖而不是追加:usermod -G a,b zhangsan会把 zhangsan 的附加组整体替换成 a 和 b,原来在 c 组里的身份会丢掉。想追加一个组而不影响已有的,得用:
usermod -aG newgroup zhangsan这个-a参数漏掉,是运维里最常见的"权限怎么突然没了"的来源之一,我自己也犯过。
3.4 用 vipw 和 pwck 安全地编辑与校验
有人图省事直接用vim /etc/passwd改,我强烈不推荐,原因有三个。一是这个文件没有锁机制,你这么编辑的同时,别人(或者某个自动化脚本)执行useradd,两边同时写就可能互相覆盖,少一条记录;二是手写容易出格式错误,冒号多一个少一个整行就废了;三是改完之后无法自动同步/etc/shadow和/etc/group。
正确做法是用专门的编辑器:
vipw它做的事情是:给文件加锁、创建临时副本给你编辑、保存时做语法检查、确认无误后原子替换回去。配套的还有vigr用来编辑组文件,vipw -s用来编辑 shadow 文件。
编辑完或者批量导入账号之后,做一次一致性检查:
pwckpwck会逐行检查 passwd 的字段数量、UID 唯一性、家目录是否存在、shell 是否在/etc/shells白名单里等等,把可疑的行报出来。组文件对应的检查工具是grpck。这两个命令在系统加固和故障排查时都非常好用,我一般在做完批量账号操作后都会跑一遍。
还有一组命令值得记住:
# 查看账号的密码状态(是否设置、是否锁定、上次修改时间) passwd -S zhangsan # 查看密码策略:有效期、最短使用天数、警告天数等 chage -l zhangsanpasswd -S的输出第二列如果是P表示密码已设置,L表示锁定,NP表示无密码。这几个状态跟 passwd 文件的第二个字段是联动的,理解了就能快速判断账号到底能不能用密码登录。
4. 跟 /etc/passwd 绑在一起的那几个文件
单独看/etc/passwd容易只见树木不见森林,实际运维里它总是和一个文件家族一起出现。搞清楚它们之间的分工,很多问题的定位速度会快一个量级。
4.1 /etc/shadow 与 /etc/group 的分工
/etc/shadow的每一行有九个字段,跟 passwd 一一对应同一个账号,内容包括密码哈希、上次修改时间、最短使用天数、最长使用天数、警告天数、宽限天数、账号失效日期、保留字段。字段多了之后,密码策略就能做得非常细,比如强制 90 天换一次密码、过期前 7 天开始提醒。
/etc/group的每一行是四个字段:组名、组密码占位、GID、成员列表。注意这里有个容易混淆的地方——用户的主组信息不在这个文件的成员列表里。比如 zhangsan 的主组是 zhangsan(GID 1600),在/etc/group里那一行是zhangsan:x:1600:,冒号后面是空的。只有附加组的成员列表里才会出现用户名。这就是为什么id命令比翻 group 文件更可靠。
这三个文件的关系可以用一句话概括:passwd 定义身份和登录环境,shadow 保存凭证和时效策略,group 管理组关系和成员。改动其中一个,往往需要同步考虑另外两个。
4.2 /etc/login.defs 与 /etc/default/useradd 决定默认行为
useradd不带参数时凭什么决定 UID 从哪开始分配、家目录建在哪、默认 shell 是什么?答案在这两个配置文件里。
/etc/login.defs里跟账号相关的关键项:
| 配置项 | 典型值 | 作用 |
|---|---|---|
| UID_MIN | 1000 | 普通用户 UID 下限 |
| UID_MAX | 60000 | 普通用户 UID 上限 |
| SYS_UID_MIN | 201 | 系统账号 UID 下限 |
| SYS_UID_MAX | 999 | 系统账号 UID 上限 |
| CREATE_HOME | yes | 是否默认创建家目录 |
| PASS_MAX_DAYS | 99999 | 密码最长有效期 |
/etc/default/useradd里则定义了家目录模板路径、默认 shell、默认所属组策略、是否创建邮箱文件等。有一段时间我在做等保相关的整改,需要统一全公司服务器的 UID 分配区间和密码有效期,改的就是这两个文件,改完再配合配置管理工具推到所有机器上。
提示:这两个文件在不同发行版里的默认值差异相当大。Debian/Ubuntu 的系统账号区间习惯从 100 起,RHEL 系从 201 起,跨发行版做统一规范时如果不注意,很容易出现 UID 冲突。
4.3 nsswitch.conf 与 getent 的配合关系
/etc/nsswitch.conf决定了系统去哪些地方找账号信息。典型的passwd:行可能是files systemd、files sss、或者compat。这一行的顺序就是查询顺序,找到即止。
理解这层关系能解释很多"灵异现象"。举个例子:某台机器接入了集中认证,本地/etc/passwd里也有一个同名账号,那么登录时到底用哪一份?答案是看 nsswitch 的顺序。如果files在前,本地那份优先,集中认证里的密码策略就被绕过了,这在安全合规检查里是个典型的失分点。反过来说,如果sss在前,本地账号被遮住,你用passwd改本地密码会发现改了个寂寞。
我的习惯是排查账号问题时的固定三步:先getent passwd 用户名确认系统视角下这个账号存在且信息正确;再getent passwd | wc -l看看总条目数是否合理,有没有爆炸或者空掉;最后才去看/etc/passwd这个文件本身。顺序反过来容易走弯路。
5. 踩坑实录与常见故障速查
这一节是我这些年攒下来的真实故障和应对方式,大部分在网上搜不到现成答案,但一旦遇上会很耽误时间。
5.1 账号类故障速查表
| 现象 | 大概率原因 | 第一步排查命令 |
|---|---|---|
| sudo 报当前用户不存在于密码数据库 | 账号条目被删或 UID 解析不到 | id $(whoami) |
| 登录成功但落在 / 目录 | 家目录字段指向的路径不存在 | getent passwd 用户名然后ls -ld 路径 |
| useradd 报 UID 已存在 | 手工指定了重复 UID | awk -F: '$3==<UID>' /etc/passwd |
| 服务账号能 SSH 登录进来 | shell 被误改成 /bin/bash | getent passwd 服务名查第七字段 |
| ls -l 显示一串数字而不是用户名 | 文件的 UID 在系统里没有对应条目 | getent passwd <数字> |
| 集中认证用户改了本地密码不生效 | nsswitch 顺序导致查的是远程源 | grep ^passwd /etc/nsswitch.conf |
| 某账号加了附加组但权限没变化 | 用了 -G 覆盖而不是 -aG 追加 | id 用户名 |
| 手工编辑后某行账号消失 | 行格式错误或末尾缺换行符 | pwck |
| 容器内应用读用户信息报错 | 容器内 passwd 缺对应 UID 条目 | docker exec 容器 getent passwd |
这张表里的每一条我都真实遇到过,其中两个值得展开讲。
5.2 三个真实场景的排查过程
场景一:一行漏掉的换行符引发的连环事故。有台老服务器在做账号清理时,同事用手工方式编辑了/etc/passwd,删掉了中间的一行,保存时编辑器没有在文件末尾补上换行符。之后新增账号,useradd把新记录直接追加到了最后一行末尾,结果最后两个账号粘成了一行,字段数直接超标。现象是这个账号能建出来但登不进去,而且前面那个没被删的账号也莫名失效了。定位过程很快:pwck一跑就把那行揪出来了。教训是永远不要用普通编辑器碰这个文件,用vipw。
场景二:NFS 共享目录的权限集体错乱。一套集群里,计算节点挂载了存储节点的数据目录,某天做完账号规范化之后,所有计算结果文件都变成了nobody所有,应用直接写不进去。原因很直接:存储节点上某个用户的 UID 从 1003 被改成了 1005,而计算节点上还是 1003,网络文件系统只认数字不认名字,两边对不上号,落到nobody上了。解决办法是把两边 UID 强制对齐,并且在/etc/login.defs里把分配区间锁死,避免以后再出现类似的漂移。这也是为什么我坚持在集群环境里手工指定 UID,而不是让系统自动分配。
场景三:容器内 Java 应用启动报找不到用户。有个基于精简镜像打包的 Java 服务,启动日志里一直有一行关于无法获取用户信息的警告,功能上不影响,但看着难受。根源是精简镜像里/etc/passwd只保留了极少数条目,容器以某个 UID 运行时,这个 UID 在文件里查不到名字,System.getProperty("user.name")拿到的是空值。处理方式是在构建镜像时用useradd显式建一个用户,让记录写进/etc/passwd,而不是在docker run时用--user指定一个裸数字。
5.3 改动这个文件时必须守住的几条线
做了这么多年账号维护,我给自己定的规矩是这几条,分享出来供参考。
第一,UID 一旦确定就不要轻易改。文件属主、定时任务、服务配置、数据库连接串里都可能硬编码或者间接依赖这个数字,改动的连锁反应远超预期。确实需要改的时候,改完必须扫全盘找孤儿文件。
第二,系统服务账号的 shell 一律设成 nologin,并且定期复查。这个检查很容易自动化,一行awk -F: '$3<1000 && $7!~/nologin|false/ {print $1,$7}' /etc/passwd就能把所有违规的列出来,塞进巡检脚本里,每周跑一次。
第三,杜绝 UID 为 0 的第二个账号。定期执行awk -F: '$3==0 {print $1}' /etc/passwd,输出应当只有root一个。
第四,批量操作前先备份。cp -a /etc/passwd /etc/passwd.bak.$(date +%F)这一行花费不到一秒,但能在关键时刻救回一台机器。如果是虚拟机或者物理机,做账号大规模调整之前打个快照更稳妥,出问题直接回滚,比逐行修复快得多。
第五,密码空值的账号必须清零。检查命令是awk -F: '$2=="" {print $1}' /etc/passwd,输出为空才安心。这类账号在某些宽松的 PAM 配置下真的能空密码登进去。
第六,改完之后永远跑一次 pwck 和 grpck。这两个工具花不了两秒钟,但能挡掉绝大多数低级错误。养成习惯之后,账号相关的线上事故率会明显下降。
关于这个文件的后续扩展方向,我个人觉得有两块值得深挖。一块是集中认证体系下的账号统一管理,本地 passwd 文件逐渐退化成"最后兜底"的角色,怎么设计这套兜底策略、怎么保证本地和远程账号不打架,是个挺有嚼头的题目。另一块是容器和不可变基础设施场景下的账号模型,传统主机的账号管理思路在那里基本失效,需要另一套做法。这两个方向我后面会另外整理。