很多运维和后端同学第一次碰 Linux 用户管理,都是从一条useradd开始的。命令敲下去,用户建好了,看似一切正常;等到某天发现这个账号登不上去、sudo 不给权限、或者把某个用户从附加组里"顺手踢掉"了,才回头去翻/etc/passwd和/etc/shadow,一脸茫然。更麻烦的是,改完这些系统文件之后,很多人根本没有意识去确认"我到底改了哪几行、改坏没有"。
这篇是 Linux 基础系列里比较硬的一节,主题是两个看起来不搭界、实际上经常一起出现的东西:用户管理和MD5 校验。前者是账户体系的骨架,后者是改动前后的"指纹比对"手段。把它们放在一起讲,不是为了凑章节,而是因为真实运维场景里,这两件事几乎是绑定的——批量建号、批量改密、批量分发配置文件之后,你需要一个低成本、可复现的方式确认结果是否符合预期。
先说结论:Linux 用户管理的难点从来不在命令有多少个参数,而在于你是否理解账户信息被拆成了哪几个文件、密码到底以什么形式存放、权限的最小边界在哪里;MD5 校验的价值也不在"加密"或"安全",它只是一个摘要比对工具,用来快速判断两份内容是否一致。把 MD5 当成密码保护手段,是这一节里最容易踩、后果也最严重的坑。
读完这篇,你应该能做到几件事:独立完成用户与用户组的增删改查并解释每个关键参数的作用;看懂/etc/shadow里那串密码哈希的前缀含义;用md5sum对关键配置文件做改动前后的完整性比对;写出一个可复用的批量建号脚本;并且清楚知道什么场景下必须放弃 MD5 改用更强的算法。
1. 这篇文章真正要解决的问题
先还原一个很典型的现场。假设你接手了一台应用服务器,需要为三个新同事开账号,同时要求他们只能访问/data/app目录、只能用密钥登录、并且 90 天必须改一次密码。这件事听起来是"建三个用户",实际涉及 UID 分配、主组与附加组的区别、家目录权限、shell 指定、密码老化策略、sudo 授权范围这几层内容。任何一层漏掉,最后都会以"某某登不上"或"某某权限过大"的形式暴露出来。
第二个现场更隐蔽。你在做配置变更,改了/etc/ssh/sshd_config、/etc/sudoers、/etc/login.defs三个文件,改完之后服务正常,你以为万事大吉。两周后某个用户投诉登不上,你回头排查,发现自己根本不记得当时改了哪几行,diff也没有基线可对比。这时候如果当初在改动前执行过一次md5sum,情况会完全不同——至少你能立刻定位到是哪个文件被动了。
所以在实际项目中,用户管理解决的是"谁能进来说了算"的问题,MD5 校验解决的是"东西有没有被改"的问题。前者是边界定义,后者是变更证据。它们共同服务于同一个目标:让系统状态可解释、可追溯。这也是为什么很多老运维会养成一个习惯——动/etc下的任何文件之前,先算一遍摘要存起来。
需要提前说明的是,这篇文章更适合已经能在 Linux 上完成基本操作、但账户体系还没完全理顺的读者;如果你是纯应用开发,平时只接触容器里的只读文件系统,那么第 2 章和第 4 章仍然值得看,因为容器里的 UID 映射、Dockerfile 里的USER指令,底层逻辑完全一致。
2. Linux 用户与用户组的核心概念
理解用户管理,第一步是接受一个事实:Linux 里的"用户"不是一个对象,而是散落在多个文件里的一组记录。任何一个文件出问题,都会导致账户行为异常。
/etc/passwd是账户的"名片",全局可读,存储的是用户名、UID、GID、描述、家目录、登录 shell 这七段信息。注意它不存密码,早期确实存在第二字段里,后来因为安全原因被迁走了。你在里面看到的x只是一个占位符。
/etc/shadow才是密码真正待的地方,默认权限通常是000或400,只有 root 可读。它的字段比 passwd 多,包括密码哈希、上次修改时间(从 1970-01-01 起算的天数)、最小间隔、最大有效期、警告天数、失效天数、账户过期时间、保留字段。很多人做密码策略配置时只改/etc/login.defs,却不知道已经存在的用户不会自动继承新策略,需要用chage单独调整。
/etc/group存组信息和附加成员列表,/etc/gshadow存组密码(基本不用,但存在)。此外还有/etc/login.defs控制 UID 分配范围和默认密码老化策略,/etc/default/useradd控制useradd的默认行为,/etc/skel决定新用户家目录里会复制哪些初始文件。
用户类型上,UID 为 0 的是 root;UID 1 到 999(不同发行版边界略有差异)通常是系统用户,给服务进程用,一般不分配登录 shell;UID 1000 起是普通用户。这个划分不是形式主义,它直接决定了某些服务在启动时会不会因为"UID 太小"而拒绝运行。
| 文件 | 主要作用 | 默认权限 | 常见误操作 |
|---|---|---|---|
/etc/passwd | 账户基本信息 | 644 | 手改字段错位导致登录失败 |
/etc/shadow | 密码哈希与老化策略 | 000/400 | 直接编辑覆盖哈希,用户被锁死 |
/etc/group | 组与附加成员 | 644 | 误删组导致文件属组悬空 |
/etc/login.defs | 默认 UID 范围与密码策略 | 644 | 改了但未对存量用户生效 |
/etc/skel | 新用户家目录模板 | 755 | 模板里有敏感文件被批量复制 |
再往下一层是认证链路。当你输入密码登录时,真正做校验的不是login程序本身,而是 PAM(可插拔认证模块)。/etc/pam.d/system-auth(RHEL 系)或/etc/pam.d/common-password(Debian 系)里配置了密码复杂度、失败锁定、哈希算法等规则。这意味着你在/etc/shadow里看到的哈希形式,是由 PAM 和 libxcrypt 的配置共同决定的,不完全是passwd命令自己的选择。理解这一点,后面看$1$、$6$这类前缀时才不会困惑。
3. 环境准备与前置条件
这一节的所有操作都建议在测试环境或本地虚拟机、容器中完成,不要直接在生产服务器上边学边试。用户与权限的变更影响面很大,一个错误的usermod -G就可能让某个服务账号失去必要的组权限,进而导致服务启动失败。
环境方面,主流发行版都可以,命令基本通用。RHEL/CentOS/Rocky 系和 Debian/Ubuntu 系在 PAM 配置文件路径、默认哈希算法、useradd默认行为上有差异,文中会分别标注。具体版本请以你实际使用的环境为准,本文重点演示通用思路,不绑定某个特定版本号。
需要的前置条件:
- 一个具备
sudo权限或 root 权限的账号。本文所有示例默认以 root 身份执行,如果是普通用户,请在命令前加sudo。 - 系统已安装
shadow-utils(提供useradd、usermod、userdel、chage、passwd)和coreutils(提供md5sum、sha256sum)。这两个包在绝大多数发行版中是默认安装的。 - 如果要演示密码哈希生成,可能需要
openssl命令行工具,通常也已预装。 - 建议先做一次快照或备份,尤其是
/etc目录。
# 确认关键工具是否存在,以及系统身份 whoami id which useradd usermod userdel chage passwd md5sum sha256sum openssl # 备份关键账户文件,这是动手前的必要动作 mkdir -p /root/backup-$(date +%F) cp -a /etc/passwd /etc/shadow /etc/group /etc/gshadow /root/backup-$(date +%F)/ ls -l /root/backup-$(date +%F)/这里有一个容易被忽略的细节:备份/etc/shadow时务必用cp -a或cp -p保留权限,否则备份文件的权限会变成默认的644,相当于把全系统的密码哈希暴露给所有本地用户。顺手用ls -l确认一下权限,是很值得养成的习惯。
4. 用户管理核心操作全流程
4.1 创建用户:useradd 的关键参数
useradd是最容易"看起来会了、实际用错"的命令。它的默认行为由/etc/default/useradd和/etc/login.defs共同决定,在不同发行版上可能不一样,比如是否自动创建家目录。所以更稳妥的做法是显式指定你需要的每一个参数,而不是依赖默认值。
# 创建一个完整配置的普通用户 # -m 创建家目录,-s 指定登录 shell,-u 指定 UID # -g 指定主组,-G 指定附加组,-c 添加注释,-d 指定家目录路径 useradd -m -s /bin/bash -u 1500 -g developers -G wheel, docker \ -c "Zhang San, backend team" -d /home/zhangsan zhangsan # 设置初始密码(交互式,推荐,避免密码进入命令历史) passwd zhangsan # 要求用户首次登录必须改密码 passwd -e zhangsan # 查看结果:账户信息、UID/GID、组成员关系 id zhangsan getent passwd zhangsan getent group developers参数里最需要解释的是-g和-G的区别。-g指定的是主组,每个文件在创建时默认继承这个组;-G是附加组,决定用户还能额外获得哪些组的权限。一个用户只有一个主组,但可以有多个附加组。这个区别在多团队协作的服务器上至关重要——主组错了,新建文件的属组就错了;附加组漏了,docker、wheel这类权限就全都拿不到。
另一个高频坑是usermod -G。很多人的直觉是"加到某个组里",于是写成usermod -G docker zhangsan。这条命令的实际语义是"把用户的附加组设置为只有 docker",原来在wheel组里的身份会被直接抹掉。正确的写法必须带-a:
# 错误写法:会把用户的附加组覆盖成只有 docker # usermod -G docker zhangsan # 正确写法:追加到附加组,保留原有组 usermod -aG docker zhangsan # 验证是否追加成功 id zhangsan4.2 修改与删除:usermod、userdel
usermod能改的东西很多,包括用户名、UID、家目录、shell、注释、锁定状态。改用户名和改 UID 都属于高风险操作,前者会影响家目录路径和部分应用配置,后者会导致原本属于旧 UID 的文件变成"无主文件"。如果确实要改 UID,需要配合find / -uid 旧UID -exec chown 新UID {} \;一起处理,而且要先停掉该用户的所有进程。
# 修改登录 shell 和注释 usermod -s /bin/zsh -c "Zhang San, infra team" zhangsan # 锁定账户(在 shadow 密码字段前加 !,不删除密码) usermod -L zhangsan # 解锁 usermod -U zhangsan # 修改家目录并迁移内容 usermod -d /data/home/zhangsan -m zhangsan # 删除用户,-r 同时删除家目录和邮件队列 userdel -r zhangsanuserdel -r的-r参数必须慎用。它会连家目录一起删掉,如果这个目录里恰好挂了数据盘或者有业务文件,后果不可逆。更保守的做法是先userdel(只删账户,保留家目录),确认没有需要保留的内容后,再手动rm -rf。删除用户前,务必先用ps -u 用户名或pgrep -u 用户名确认没有残留进程,否则会出现"用户删了但进程还在跑、文件属主显示为 UID 数字"的尴尬状态。
4.3 密码策略:chage 与 login.defs
密码老化策略分为两层:/etc/login.defs是全局默认值,只对之后新建的用户生效;chage是针对单个存量用户的调整工具。很多人改完login.defs就以为全系统生效了,实际上老账号完全没有变化。
# 查看某个用户的密码策略详情 chage -l zhangsan # 设置:最短间隔 1 天,最长有效期 90 天,提前 7 天警告 # 密码过期后 30 天内仍可登录但强制改密,账户在指定日期后彻底失效 chage -m 1 -M 90 -W 7 -I 30 -E 2027-12-31 zhangsan # 强制下次登录改密 chage -d 0 zhangsan/etc/login.defs里值得关注的是PASS_MAX_DAYS、PASS_MIN_DAYS、PASS_WARN_AGE、UID_MIN、UID_MAX、CREATE_HOME这几项。修改前建议先备份,并注意不同发行版对UID_MIN的默认值不一样,随意调小可能和已有系统用户冲突。
5. MD5 校验的原理与两类真实用途
5.1 MD5 到底在做什么
MD5 是一种消息摘要算法,输入任意长度的数据,输出固定 128 位、也就是 32 个十六进制字符的结果。它的核心特性是:同样的输入永远得到同样的输出;输入改动一个字节,输出会产生巨大变化;从输出反推输入在计算上不可行。
请特别注意这段话里没有出现"加密"两个字。MD5 是单向摘要,不是加密算法,它没有密钥,也无法解密还原。你能做的只有一件事:对同样的内容再算一次,比对两个摘要是否一致。这就是校验的全部含义。
这也是本节最需要纠正的认知偏差。很多初学者看到/etc/shadow里一长串字符,就认为"密码被 MD5 加密了,很安全"。实际上那串东西确实可能是 MD5 的变体(crypt家族的 MD5 版本),但它早就不该被用于密码存储了。
5.2 用途一:文件完整性校验
这是 MD5 最正当、最常用的场景。下载一个安装包、分发一份配置、备份一批文件之后,用md5sum生成基线摘要,之后任何时候重新计算并比对,就能快速判断内容是否发生变化。它的优势是速度快、输出短、对比直观、几乎所有 Linux 系统自带。
局限也很明确:MD5 已被证明存在碰撞攻击,也就是说攻击者可以构造两个内容不同但 MD5 相同的文件。所以它适合用来发现意外改动(比如误编辑、传输损坏、磁盘错误),不适合用来抵御恶意篡改。对抗主动攻击者,需要用 SHA-256 以上的摘要,或者更彻底地用 GPG 签名验证来源。
5.3 用途二:shadow 中的密码哈希(历史场景)
/etc/shadow第二字段的格式通常是$算法标识$盐值$哈希值。常见的标识有:
| 前缀 | 算法 | 现状 |
|---|---|---|
$1$ | MD5 crypt | 已不推荐,仅存在于老系统 |
$5$ | SHA-256 crypt | 可用,但逐渐被替代 |
$6$ | SHA-512 crypt | 目前较常见 |
$y$ | yescrypt | 部分新版本发行版采用 |
较老的发行版默认使用$1$,较新的发行版一般默认$6$,一些新版本已开始引入更强的算法。你可以直接查看自己的系统:
# 查看指定用户密码哈希的前缀,注意不要把完整哈希贴到公开渠道 getent shadow zhangsan | cut -d: -f2 | cut -c1-3 # 查看系统默认的加密方法配置(不同发行版路径不同) grep -i "^ENCRYPT_METHOD" /etc/login.defs 2>/dev/null grep -i "password.*pam_unix" /etc/pam.d/system-auth 2>/dev/null如果发现存量账号还是$1$,比较稳妥的做法不是手动去改 shadow 文件,而是让用户下次改密时自动升级,或者由管理员用passwd 用户名重设一次密码,让系统按当前配置重新生成哈希。手改 shadow 的风险极高,一个字符错位就可能导致该账号永久无法登录,而且难以回滚。
6. 完整示例与代码实现
下面用三个可复制的示例,把用户管理和 MD5 校验串起来。
6.1 示例一:带校验的批量建号脚本
这个脚本做四件事:读取用户清单、逐个创建用户、设置初始密码、在操作前后对账户文件做摘要比对。这样即使中途出错,你也能立刻知道哪些文件被改动过。
#!/bin/bash # 文件路径:/usr/local/bin/batch_add_users.sh # 用途:从清单文件批量创建用户,并在操作前后对账户文件做 MD5 摘要比对 # 注意:请在测试环境验证后再用于生产,执行前确保已备份 /etc 下的账户文件 set -euo pipefail USER_LIST="${1:-/root/userlist.txt}" BACKUP_DIR="/root/account-backup-$(date +%Y%m%d%H%M%S)" TARGET_FILES="/etc/passwd /etc/shadow /etc/group /etc/gshadow" if [[ ! -f "${USER_LIST}" ]]; then echo "用户清单不存在: ${USER_LIST}" exit 1 fi mkdir -p "${BACKUP_DIR}" echo "=== 操作前摘要 ===" md5sum ${TARGET_FILES} | tee "${BACKUP_DIR}/before.md5" while IFS=: read -r username comment group; do # 跳过空行和注释行 [[ -z "${username}" || "${username}" =~ ^# ]] && continue if id "${username}" &>/dev/null; then echo "[跳过] 用户 ${username} 已存在" continue fi useradd -m -s /bin/bash -g "${group}" -c "${comment}" "${username}" echo "[创建] ${username} 完成,主组 ${group}" done < "${USER_LIST}" echo "=== 操作后摘要 ===" md5sum ${TARGET_FILES} | tee "${BACKUP_DIR}/after.md5" echo "=== 差异对比 ===" diff "${BACKUP_DIR}/before.md5" "${BACKUP_DIR}/after.md5" || true echo "备份目录: ${BACKUP_DIR}"配套的用户清单文件格式如下,冒号分隔:
# 文件路径:/root/userlist.txt # 格式:用户名:注释:主组 zhangsan:Zhang San backend:developers lisi:Li Si frontend:developers wangwu:Wang Wu ops:ops这个脚本里有两个设计值得说明。第一是set -euo pipefail,它让脚本在任意命令失败时立即退出,避免"报错了还继续往下建号"的半成品状态。第二是用diff对比前后摘要,输出会直接告诉你哪几行不同,配合md5sum的"文件内容变化则摘要必变"特性,能快速锁定被修改的文件范围。
6.2 示例二:配置文件的完整性巡检
这个场景更贴近日常运维:把关键配置的摘要存成基线文件,定期巡检时比对,发现异常就报警。
#!/bin/bash # 文件路径:/usr/local/bin/config_integrity_check.sh # 用途:对关键配置文件做摘要巡检,与基线比对 # 建议配合 cron 定期执行,基线文件需妥善保管 set -uo pipefail BASELINE="/var/lib/config-baseline.md5" WATCH_FILES="/etc/passwd /etc/shadow /etc/group /etc/ssh/sshd_config /etc/sudoers /etc/login.defs" if [[ "${1:-}" == "init" ]]; then md5sum ${WATCH_FILES} > "${BASELINE}" chmod 600 "${BASELINE}" echo "基线已生成: ${BASELINE}" exit 0 fi if [[ ! -f "${BASELINE}" ]]; then echo "基线文件不存在,请先执行: $0 init" exit 2 fi echo "=== 开始巡检 $(date '+%F %T') ===" if md5sum -c "${BASELINE}" 2>&1; then echo "结果:全部一致" else echo "结果:存在差异,请立即排查变更来源" exit 1 fimd5sum -c是这里的关键。它会逐行读取摘要文件,重新计算每个文件的摘要并比对,输出文件名: OK或文件名: FAILED,只要有任意一项失败,命令的退出码就是非 0。这个退出码语义让它可以很方便地接入监控系统或 CI 流程。
6.3 示例三:用 Python 对比 MD5 与 SHA-256
如果要在应用层做校验,或者想直观理解不同算法输出长度和计算耗时的差异,可以看下面这段代码。
# 文件路径:/root/scripts/hash_compare.py # 用途:对比 MD5 与 SHA-256 的摘要长度和计算耗时 # 运行:python3 /root/scripts/hash_compare.py /etc/passwd import hashlib import sys import time def digest(path: str, algo: str, chunk_size: int = 8192) -> tuple[str, float]: """分块读取文件并计算摘要,避免大文件占满内存。""" h = hashlib.new(algo) start = time.perf_counter() with open(path, "rb") as f: while True: chunk = f.read(chunk_size) if not chunk: break h.update(chunk) cost = time.perf_counter() - start return h.hexdigest(), cost def main() -> None: if len(sys.argv) != 2: print("用法: python3 hash_compare.py <文件路径>") sys.exit(1) target = sys.argv[1] for algo in ("md5", "sha256"): value, cost = digest(target, algo) print(f"{algo:>6} | 长度 {len(value):>3} | 耗时 {cost * 1000:.3f} ms | {value}") if __name__ == "__main__": main()运行后你会看到 MD5 输出 32 个字符、SHA-256 输出 64 个字符。对于普通大小的配置文件,两者的耗时差异在日常场景里完全可以忽略,这也说明"因为性能所以选 MD5"在现代硬件上基本不成立。既然代价差不多,就没有理由在安全相关的场景里退而求其次。
7. 运行结果与效果验证
先验证用户是否创建成功。正确的输出应该包含用户 ID 和组信息:
id zhangsan预期输出类似uid=1500(zhangsan) gid=1001(developers) groups=1001(developers),10(wheel),999(docker)。如果groups里少了预期的附加组,说明-G参数写错或者用了usermod -G覆盖掉了。如果gid对不上,说明主组指定有误。
再验证密码哈希是否按预期生成:
getent shadow zhangsan | cut -d: -f2 | cut -c1-3输出$6$说明使用了 SHA-512,输出$1$则说明沿用了老配置。如果输出是!!或!,说明账号被锁定或还没有设置密码,需要排查是不是passwd那一步没执行成功。
然后验证摘要比对是否生效。故意改一个文件,再跑一次巡检:
# 增加一行无害注释,模拟配置变更 echo "# test change $(date)" >> /etc/login.defs # 执行巡检,应该报告 FAILED /usr/local/bin/config_integrity_check.sh # 验证完毕后恢复(生产环境请从备份还原,不要手写) sed -i '/# test change/d' /etc/login.defs预期能看到/etc/login.defs: FAILED和最终的md5sum: WARNING: 1 computed checksum did NOT match。如果命令返回 0 且显示全部 OK,说明基线文件里可能没有包含这个路径,需要检查WATCH_FILES变量的内容。
如果任何一步失败,排查顺序建议是:先看命令的退出码(echo $?),再看 stderr 的报错原文,最后检查/var/log/secure或/var/log/auth.log里的认证日志。用户管理相关的失败,九成以上能在认证日志里找到直接原因。
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
useradd成功但无法登录 | shell 被设为/sbin/nologin,或账户被锁定 | getent passwd 用户名看第七字段;passwd -S 用户名看状态 | 用usermod -s /bin/bash修正,或用passwd -u解锁 |
| 用户登不上且提示密码错误 | shadow 中密码字段为!!或!开头 | getent shadow 用户名 | cut -c1-3 | 用passwd 用户名重设,避免手改 shadow |
| 附加组权限不生效 | 用了usermod -G覆盖了原有组,或用户会话未刷新 | id 用户名对比预期组列表 | 用usermod -aG追加,用户需重新登录才生效 |
md5sum -c全部 FAILED | 基线文件里的路径与当前工作目录不匹配 | 打开基线文件看路径是相对还是绝对 | 生成基线时使用绝对路径 |
md5sum报No such file | 文件被移动或删除,或路径含空格未转义 | 逐个ls -l检查路径 | 修正路径,含空格时加引号 |
| 删除用户后文件属主变成数字 | userdel后残留文件,属主 UID 已无对应账户 | find / -nouser -print | 确认无用后chown给其他账号或归档删除 |
改了login.defs但老用户策略没变 | 该文件只影响新建用户 | chage -l 用户名查看实际策略 | 对存量用户用chage逐个调整 |
passwd提示密码过于简单 | PAM 的 pwquality 模块拒绝 | 查看/etc/security/pwquality.conf | 提高密码复杂度,而不是关掉策略 |
这里要特别强调第一条和第二条的区别。很多人把"登不上"笼统归因为"密码错了",实际上 shell 是nologin、账户被passwd -l锁定、账户已过chage -E指定日期、密码已过期未改,这四种情况的表现都是"登不上",但修复方式完全不同。passwd -S 用户名会直接给出状态(如PS表示已设密码、LK表示锁定、NP表示空密码),是排查这类问题的第一条命令。
9. 最佳实践与工程建议
关于用户管理。生产环境里更推荐"一人一号 + 组授权 + sudo 精细化"的组合,而不是共享账号。共享账号的问题不是安全教条,而是出事后日志里全是同一个用户名,根本追不到人。sudo 授权也不要图省事写ALL=(ALL) ALL,应该按命令白名单配置,并用visudo编辑(它会做语法检查,直接vi /etc/sudoers写错一个字符就可能让所有人失去 sudo 权限)。
关于 UID 规划。有多个节点的集群环境,最好提前规划 UID 段,让同一批人在不同机器上的 UID 一致。否则 NFS 共享目录会出现"文件属主错乱"的问题——在 A 机器上是 zhangsan,挂到 B 机器上就变成了别人的用户名,因为 UID 1500 在 B 机器上对应的是另一个人。这个坑在没有统一认证体系(如 LDAP)的小规模环境里非常常见。
关于哈希算法选择。新系统在装机时就应确认默认哈希算法是$6$或更强,而不是沿用老镜像里的$1$。如果存量系统里还有 MD5 哈希,制定一个渐进式迁移计划:优先处理有 sudo 权限和高权限服务的账号,通过强制改密自然升级,不要批量脚本直接改写 shadow。
关于摘要校验的定位。用 MD5 做完整性巡检是有价值的,但要清楚它的边界。建议的做法是:内部变更发现用 SHA-256(代价几乎一样),对外部下载的软件包同时校验摘要和签名,对/etc下的关键文件建立基线并纳入定期巡检,基线文件本身要设成600权限并单独备份。不要把摘要文件和数据文件放在同一个可写目录里,否则攻击者改完文件顺手改摘要,校验就形同虚设。
关于操作纪律。动/etc下的账户文件之前先备份、先算摘要、先在测试环境验证;删除用户前先确认无进程、无关键数据;修改组成员关系后提醒用户重新登录;所有这些动作都建议写进团队的运维手册,而不是靠个人记忆。这些不是流程负担,而是把"不可逆操作"变成"可回滚操作"的最低成本手段。
10. 收尾:把两件事真正连起来
回到文章开头那两个现场。第一个现场的核心不是命令用错,而是没有理解"主组决定文件属组、附加组决定额外权限"这条基本规则;第二个现场的核心不是记性差,而是缺少一个廉价、可靠、可自动化的变更证据机制。
用户管理让你定义清楚系统的访问边界,MD5 校验让你在边界被触碰时能第一时间发现。前者是设计,后者是验证,缺了任何一个,运维工作都只能靠"感觉没出问题"来推进。
如果这篇文章只能留下一句话,那应该是:下次动/etc/shadow之前,先md5sum一份备份,确认清楚你要改的是哪一行、改完之后怎么验证、出问题怎么回滚,再动手。这一步花掉的三十秒,往往能省下后面三个小时的排查。