运维工作里有一个很现实的问题:服务器上的用户账号越建越多,密码却散落在聊天记录、Excel 表格、临时记事本和某个人的脑子里。等到需要交接、需要排查谁改过什么、需要给新人开账号的时候,才发现根本没有一条清晰的链路。本文围绕 Linux 用户管理与 KeePass 密码管理工具这两个环节,把"账号怎么建、密码怎么存、权限怎么收、出了问题怎么查"串成一套可以落地执行的流程。适合刚接触 Linux 运维的读者、需要管理多台服务器的后端开发,以及正在整理团队账号规范的负责人。读完之后你应该能够独立完成用户与组的创建调整、密码策略配置、KeePass 数据库的结构设计与命令行自动化调用,并且知道每个动作背后的风险和规避方式。
1. Linux 用户管理与密码管理的核心概念
1.1 为什么要把用户管理和密码管理放在一起看
很多教程把 Linux 用户管理单独讲一遍,把密码管理工具单独讲一遍,读者学完之后仍然是两套割裂的知识。但在真实场景中,这两件事是强耦合的:useradd创建出来的账号必须有一个可信的初始密码,这个密码必须安全地送到使用者手里,使用者第一次登录后必须强制改掉它,改完之后的新密码又必须能被审计和重置。只要中间任何一环靠"发微信告诉他"来完成,整条链路就断了。
从安全视角看,Linux 用户管理的本质是"身份与权限的边界划分",而密码管理的本质是"凭据的生成、存储、分发与轮换"。前者决定了谁能进系统、进来以后能做什么;后者决定了进来的人是不是本人、凭据有没有泄露。两者合在一起,才构成一套完整的访问控制体系。把 KeePass 这类密码库引入进来,并不是为了多装一个软件,而是为了给"凭据"这个容易失控的东西找一个有加密、有版本、有备份、可审计的落点。
本文选择的组合是:Linux 系统自带shadow工具链负责账号生命周期,KeePassXC 及其命令行工具keepassxc-cli负责密码库的存储与自动化读取。这个组合的好处是完全离线、数据库文件自己掌控、不需要依赖任何云端服务,同时又能通过命令行嵌入到 Shell 脚本里,不至于变成纯手工操作。
1.2 Linux 用户、组与 UID 的基本模型
在动手敲命令之前,先把几个基础概念对齐。Linux 系统中每一个可以登录的身份都是一个用户,用户用一个数字 UID 来标识,用户名只是给人看的别名。UID 为 0 的用户是 root,拥有系统的最高权限;UID 在 1 到 999 之间(不同发行版区间略有差异)通常留给系统服务账号,比如www-data、nginx、sshd;普通用户的 UID 一般从 1000 开始。这个区间划分很重要,因为很多程序会根据 UID 范围判断当前是"人类用户"还是"服务账号"。
组(GID)是用来批量授权的单位。每个用户至少有一个主组,登录时默认生效;还可以加入若干附加组,用来获得额外权限。比如把运维人员加入sudo组或wheel组,就相当于给了他提权入口。这里要特别注意:Linux 的权限判断是"用户 + 主组 + 附加组"三者取并集,只要任一身份满足条件就放行,所以附加组不是"额外赠送",而是"额外授权",授权时必须想清楚后果。
用户信息落在两个核心文件里:/etc/passwd存账号的基本属性,所有用户可读;/etc/shadow存密码哈希和过期策略,只有 root 可读。理解这两个文件的字段含义,是排查登录失败、密码过期、账号锁定这类问题的前提。
1.3 KeePass 是什么,它解决什么问题
KeePass 是一类开源密码管理方案的统称,核心形态是"一个加密数据库文件 + 一个主密码"。数据库文件本身是密文,即使被拷走,没有主密码也打不开。KeePassXC 是其中一个跨平台实现,支持 Windows、macOS、Linux,同时提供了 GUI 客户端和命令行工具keepassxc-cli。数据库文件常见扩展名是.kdbx,采用 AES 等算法加密,主密码只存在于内存中,不落盘。
它解决的问题可以归纳为三点。第一是"凭据集中化",把散落在各处的服务器密码、数据库口令、API Key、SSH 私钥口令统一放进一个加密文件,按分组管理,找起来有结构。第二是"可复制而不易泄露",因为文件是加密的,可以放心地放进加密盘、私有 Git 仓库或备份系统里做版本管理。第三是"可自动化",keepassxc-cli支持非交互式读取指定条目的密码字段,这让它能够被 Shell 脚本调用,实现"密码不写在脚本里"的自动化。
需要强调的是,KeePass 不是"免密登录工具",它管理的是"你用来登录别处的密码"。它自己也有一个主密码,这个主密码是你整套凭据体系的根,必须单独保护,不能和库里的内容放在一起。
1.4 几个容易混淆的概念
第一个容易混淆的是"密码哈希"和"密码库"。Linux 的/etc/shadow里存的是单向哈希,只能验证不能还原,丢了只能重置;KeePass 库里存的是可还原的明文(加密状态),因为它的用途是把密码取出来用。两者的安全假设完全不同,不要指望用 KeePass 去"备份"系统密码哈希,也不要指望用/etc/shadow去恢复忘记的服务器密码。
第二个容易混淆的是"主密码"和"密钥文件"。KeePassXC 支持主密码 + 密钥文件的双因素解锁,密钥文件可以放在 U 盘上,形成"知道什么 + 拥有什么"的组合。但很多人只配了主密码,然后把这个主密码写在便签上贴在显示器边框,等于白做。
第三个容易混淆的是"用户删除"和"用户禁用"。userdel会把账号从系统里抹掉,但如果文件的所有者 UID 还留在磁盘上,就会变成孤儿文件;而usermod -L或chage -E 0只是让账号不能登录,数据和归属都还在。人员离职时到底用哪种,取决于你要不要保留他的数据归属。
2. 环境准备与工具版本说明
2.1 实验环境说明
本文的示例在常见的 Linux 发行版上都可以复现,命令本身是 POSIX 通用的,差异主要集中在包管理器、PAM 配置文件路径和部分passwd的扩展参数上。示例环境以 Debian/Ubuntu 系和 RHEL/CentOS 系两条线并行说明,遇到差异会明确标注。具体的发行版版本、内核版本和软件版本请以你实际的环境为准,本文不锁定某个具体版本号,因为不同发行版的软件包更新节奏差异较大,写死版本反而容易误导读者。
需要提醒的是,用户管理相关的操作会直接修改系统认证数据,建议先在虚拟机或测试机上完整走一遍流程,确认无误后再在生产环境执行。涉及userdel、chage -E、修改/etc/sudoers这类操作时,务必先做好快照或备份,并且保留至少一个可用的 root 会话,防止把自己锁在门外。
2.2 安装用户管理相关工具
大部分用户管理命令由shadow工具包提供,主流发行版默认已安装。可以用下面的命令确认常用工具是否齐全,缺失时按发行版安装:
# 检查关键命令是否存在 for cmd in useradd usermod userdel groupadd gpasswd passwd chage chpasswd newusers id groups; do command -v "$cmd" >/dev/null 2>&1 && echo "OK $cmd" || echo "MISS $cmd" done如果出现MISS,在 Debian/Ubuntu 上执行:
sudo apt update sudo apt install -y passwd在 RHEL/CentOS/Fedora 上执行:
sudo dnf install -y shadow-utilspasswd包和shadow-utils包分别包含了上表中的大部分命令,具体包名以你所在发行版的软件源为准。安装完成后建议再跑一次上面的检查循环,确认全部为OK。
2.3 安装 KeePassXC 与命令行工具
KeePassXC 在主流发行版的官方源中都有提供。Debian/Ubuntu 系:
sudo apt update sudo apt install -y keepassxcRHEL/Fedora 系需要先启用 EPEL 仓库(Fedora 一般可直接安装):
sudo dnf install -y epel-release sudo dnf install -y keepassxc安装完成后,验证 GUI 和命令行工具是否都可用:
keepassxc --version keepassxc-cli --version keepassxc-cli --helpkeepassxc-cli --help会列出当前版本支持的所有子命令。不同版本的子命令集合和参数命名可能存在差异,本文示例使用写文章时较为通用的写法,实际执行前请以你的--help输出为准。如果只需要命令行而不需要图形界面,部分发行版可以把 GUI 拆掉只装核心包,具体拆分方式请查阅对应发行版的包信息。
如果你的团队更习惯用 Perl 写的kpcli,它也是一个可选项,通过 CPAN 或发行版包管理器安装。本文以keepassxc-cli为主线,因为它的参数更稳定、脚本化更友好,kpcli会在对比时简单提及。
2.4 目录与文件规划
动手之前先把目录结构定下来,后面所有操作都基于这个结构。建议在运维账号的家目录下建立独立目录,并严格控制权限:
# 以运维账号 ops 为例 sudo mkdir -p /home/ops/vault sudo chown -R ops:ops /home/ops/vault sudo chmod 700 /home/ops/vault这样规划的理由是:密码库文件、密钥文件、备份文件都集中在700权限的目录里,其他用户连目录都无法进入,即使库文件的权限配置有疏漏,也多了一层保护。后续所有.kdbx文件和密钥文件都放在这个目录下,备份可以考虑放到加密的外部介质或受控的备份系统中。
3. Linux 用户与组管理的核心操作
3.1 useradd 创建用户的关键参数
useradd是最常用的建号命令,但很多人只会用useradd 用户名,结果创建出来的账号没有家目录、shell 是/bin/sh、也不在预期的组里。下面这条命令把常用参数都覆盖到了:
sudo useradd \ -m \ -d /home/deploy \ -s /bin/bash \ -c "deploy account" \ -g deploy \ -G docker,sudo \ -u 1500 \ deploy逐个说明这些参数的作用。-m表示创建家目录,如果不加,账号登录后会没有可写的家目录,很多程序会直接报错。-d指定家目录路径,只有在你想偏离默认的/home/用户名时才需要。-s指定登录 shell,生产环境里如果账号只用来跑服务,建议设成/usr/sbin/nologin而不是/bin/bash。-c是注释字段,用来写清楚这个账号的用途,后面排查问题时非常有用。-g指定主组,这个组必须已经存在,否则命令会失败。-G指定附加组,多个组用逗号分隔,注意不要有空格。-u手动指定 UID,一般只在需要与旧系统保持一致或对接共享存储时才用。
这里有一个高频误区:直接useradd deploy而不指定-g,系统会尝试创建一个同名组作为主组,但如果/etc/login.defs里的USERGROUPS_ENAB被关闭,就不会自动创建,命令直接报错。所以稳妥的做法是先groupadd deploy,再建用户,顺序不要颠倒。
3.2 passwd 与 chage 控制密码生命周期
设置密码看起来简单,但不同发行版的差异很大。RHEL 系的passwd支持--stdin从标准输入读取密码,Debian/Ubuntu 系不支持这个参数,需要用chpasswd:
# RHEL / CentOS / Fedora echo 'Init@Passw0rd2024' | sudo passwd --stdin deploy # Debian / Ubuntu(通用写法) echo 'deploy:Init@Passw0rd2024' | sudo chpasswdchpasswd的输入格式是用户名:密码,一次可以传多行批量改密,这在后面批量建号的场景会用到。需要说明的是,这种写法会把明文密码短暂暴露在命令历史或进程列表里,生产环境应当通过文件重定向或直接从密码库读取来规避,本文第 5 节会给出更安全的做法。
密码设完之后,必须配置过期策略,否则初始密码可能被长期沿用。chage就是干这个的:
# 强制用户下次登录时修改密码 sudo chage -d 0 deploy # 密码最长使用 90 天,最短 1 天,提前 14 天开始提醒 sudo chage -M 90 -m 1 -W 14 deploy # 查看当前策略 sudo chage -l deploy-d 0的含义是把"上次修改日期"设为 1970-01-01,系统判断该日期早于今天,就会在下次登录时强制要求改密。这是交付初始密码的标准做法。-M是最长有效期,-m是最短有效期(防止用户改完立刻改回去),-W是提醒天数。这三项合起来构成了密码的时间边界。
3.3 usermod、gpasswd 与用户组调整
账号建好之后,调整是常态。usermod负责修改用户属性,gpasswd负责管理组成员,两者的区别在于:usermod -G是"覆盖式"设置附加组列表,而gpasswd -a是"追加式"添加成员。这个差异是很多人踩过的坑——用usermod -G docker ops本意是给 ops 加 docker 组,结果把他原有的 sudo 组覆盖掉了。
# 推荐:追加方式加入组,不会影响已有附加组 sudo gpasswd -a ops docker sudo gpasswd -a ops sudo # 从组中移除 sudo gpasswd -d ops docker # 查看用户当前所属的所有组 id ops groups ops如果确实需要用usermod修改附加组,务必把所有组一次性写全:
# 覆盖式设置,必须写全所有需要的附加组 sudo usermod -aG docker,sudo ops注意这里加了-a(append),-aG才等价于追加,单独-G是覆盖。这个-a建议永远带上,除非你明确知道自己要覆盖。改完组成员之后,用户需要重新登录才会生效,因为组的归属信息是在登录时写入进程凭证的,已在线的会话不会自动刷新。
3.4 userdel 删除用户与残留文件清理
删除用户是风险最高的操作之一。userdel默认只删/etc/passwd、/etc/shadow、/etc/group里的记录,家目录和邮件池会保留。加-r才会连家目录一起删:
# 只删账号记录,保留家目录 sudo userdel deploy # 连家目录和邮件池一起删除(危险操作,先确认数据已备份) sudo userdel -r deploy在执行userdel -r之前,务必先确认三件事:该用户是否还有正在运行的进程、家目录里是否有需要归档的产物、系统其他位置是否有该用户创建的文件。前两项可以直接检查:
# 检查是否有残留进程 ps -u deploy -o pid,cmd # 检查系统中属于该 UID 的文件(排除已挂载的临时目录) sudo find / -xdev -uid 1500 -print 2>/dev/null如果find查出来一堆文件但你又不想删,可以先把这些文件的所有权转移给一个保留账号,再删除用户,这样就不会产生孤儿文件。孤儿文件的危害在于:如果将来新建的账号恰好复用了同一个 UID,那个新用户会莫名其妙地拥有这些文件的读写权限,形成隐蔽的越权。
3.5 /etc/passwd 与 /etc/shadow 字段解析
排查登录问题时,读懂这两个文件是基本功。/etc/passwd一行七个字段,用冒号分隔:
deploy:x:1500:1500:deploy account:/home/deploy:/bin/bash依次是:用户名、密码占位符(x表示密码在 shadow 里)、UID、GID、注释(GECOS)、家目录、登录 shell。如果第二个字段不是x而是空,说明密码直接存在这个文件里,这是极不安全的配置,应当立即修正。如果最后一个字段是/usr/sbin/nologin或/bin/false,说明该账号不允许交互登录。
/etc/shadow一行九个字段,同样用冒号分隔:
deploy:$6$rounds=5000$abcdefgh$....:19600:1:90:14:::关键字段包括:用户名、密码哈希($6$表示 SHA-512,$y$表示 yescrypt,!或!!表示密码被锁定)、上次修改日期(从 1970-01-01 起算的天数)、最短使用天数、最长使用天数、提醒天数、宽限天数、账号失效日期(同样是从 1970 起算的天数,留空表示永不过期)、保留字段。很多"密码明明是对的却登不上"的问题,答案就在这里的第 4 到第 8 字段上。
知识点:锁定账号有两种常见方式,usermod -L会在哈希前加一个!,chage -E 0会把失效日期设为 1970-01-01。前者只锁密码,SSH 密钥登录仍然可用;后者锁的是整个账号,任何方式都无法登录。选择哪种取决于你要堵的是"密码泄露"还是"人员离场"。
4. KeePass 密码库的结构设计与基本操作
4.1 数据库文件、主密码与密钥文件
创建一个 KeePass 数据库有两种方式:图形界面里点"新建数据库",或者用命令行keepassxc-cli db-create。命令行方式适合在无图形界面的服务器上操作:
# 创建数据库,-p 表示之后会提示输入密码 keepassxc-cli db-create -p /home/ops/vault/ops.kdbx执行后会连续提示输入两遍主密码。主密码的强度直接决定了整个凭据体系的安全性,建议长度不少于 20 位,包含大小写字母、数字和符号,并且绝对不要与库里任何一条记录的密码相同。如果环境支持,更推荐再加一个密钥文件做双因素:
# 生成一个随机密钥文件 head -c 128 /dev/urandom > /home/ops/vault/ops.keyx chmod 600 /home/ops/vault/ops.keyx密钥文件生成后,需要在 KeePassXC 的"数据库设置 → 安全"里把它关联进去。关联之后,解锁数据库必须同时提供主密码和密钥文件,单独拿到.kdbx文件是打不开的。这里的关键是:密钥文件不能和数据库文件放在同一个备份里,否则双因素就退化成了单因素。常见做法是把密钥文件放在独立的 U 盘或另一台设备的受控目录中,只在需要时挂载。
4.2 分组设计:按环境还是按主机
分组结构决定了这个库半年后还能不能用。常见的两种思路是"按环境分层"和"按主机分层",实际项目中更推荐混合方式:第一层放环境,第二层放主机或服务,第三层放具体条目。例如:
ops.kdbx ├── prod │ ├── web01 │ │ ├── root │ │ ├── deploy │ │ └── mysql-root │ └── db01 │ ├── root │ └── app-user ├── staging │ └── web01 │ └── root └── shared ├── api-token-gitlab └── wifi-office这样设计的好处是权限边界清晰:当你要把 staging 环境的密码交给外包同学时,只需要导出一个子分组,而不是把整个生产库交出去。条目的标题建议统一成角色@主机或主机-角色的格式,保证用keepassxc-cli ls列出来时一眼能看懂。用户名、URL、备注字段也要填,备注里可以写"该账号仅用于部署,禁止交互登录"这类约束,这些信息在交接时价值极高。
4.3 keepassxc-cli 常用命令
keepassxc-cli的子命令覆盖了日常需要的增删改查。下面这组命令是最常用的,建议先在测试库上跑一遍熟悉手感:
DB=/home/ops/vault/ops.kdbx # 列出根分组 keepassxc-cli ls "$DB" # 列出某个分组下的条目 keepassxc-cli ls "$DB" prod/web01 # 新增条目,-u 指定用户名,-U 指定 URL keepassxc-cli add -u root -U "ssh://10.0.0.11" "$DB" "prod/web01/root" # 显示条目的密码字段(-s 表示显示密码,-a 指定属性) keepassxc-cli show -s -a password "$DB" "prod/web01/root" # 生成一个 24 位随机密码并写入条目 keepassxc-cli generate -L 24 -l -u -n "$DB" "prod/web01/root" # 修改已有条目的用户名 keepassxc-cli edit -u deploy "$DB" "prod/web01/root" # 删除条目 keepassxc-cli rm "$DB" "prod/web01/root"每个命令执行时都会提示输入数据库主密码(除非配置了密钥文件或使用了标准输入传入)。其中show -s -a password是脚本化时最关键的组合,它把密码以纯文本输出到标准输出,可以被管道或变量捕获。generate的参数含义是:-L指定长度,-l包含小写字母,-u包含大写字母,-n包含数字,如果需要符号还要加-s。
这里必须提醒一个安全边界:show -s的输出如果直接写进 Shell 变量,会短暂存在于进程内存和可能的历史记录中。脚本里应当用--no-password配合环境变量传入主密码,或者通过文件描述符传入,并且确保脚本本身权限为700、不被其他用户读取。
4.4 密码生成策略
随机生成长度足够的密码,是替代"人工想一个复杂密码"的正确做法。人工想的密码通常符合某种模式,而模式是可被字典攻击利用的。建议按用途区分强度:
| 用途 | 建议长度 | 字符集要求 |
|---|---|---|
| 普通系统登录账号 | 20 位以上 | 大小写 + 数字 + 符号 |
| 数据库账号 | 24 位以上 | 大小写 + 数字 + 符号 |
| API Token / 密钥 | 32 位以上 | 全字符集 |
| 数据库主密码 | 24 位以上,需人工记忆 | 建议用长口令短语 |
数据库主密码这一项比较特殊:它需要人来记忆,所以可以用keepassxc-cli diceware生成由多个单词组成的口令短语,比随机字符更容易记且熵值不低:
# 生成 6 个单词组成的口令短语 keepassxc-cli diceware -w 6口令短语的强度来自单词数量而非单词复杂度,6 个随机单词的熵已经相当可观。需要注意的是,生成之后要在一段时间内反复记忆,确认记牢再正式启用,否则出现"库打不开"的尴尬局面会非常麻烦。
5. 完整实战:为运维团队搭建账号与密码管理流程
5.1 需求与角色拆分
假设我们要为一台新上线的应用服务器配置账号体系,涉及四类角色:运维管理员opsadmin(需要 sudo 权限)、应用部署账号deploy(只能操作应用目录,不能交互登录)、只读审计账号auditor(能看日志但不能改)、以及临时的外包账号vendor(限时可用,到期自动失效)。同时要把这些账号的初始密码统一收进 KeePass 数据库,并且满足"首次登录必须改密"的要求。
这个需求拆解下来有三个动作:建组、建用户并设置初始密码、把初始密码入库。三步之间要保证顺序和一致性,所以我们用一个脚本串起来,避免手工操作时漏配某项。
5.2 批量创建用户与初始密码
先建组。组名和用户名保持一致,方便记忆和审计:
# 创建应用所需的分组 sudo groupadd -f appadmin sudo groupadd -f appdeploy sudo groupadd -f appaudit-f表示如果组已存在就静默跳过,这样脚本可以重复执行而不会报错。接着用一个脚本统一建用户:
#!/usr/bin/env bash # 文件路径:/home/ops/scripts/create_users.sh # 用途:批量创建运维相关账号并设置过期策略 set -euo pipefail # 用户名:主组:附加组:shell:注释 USERS=( "opsadmin:appadmin:sudo:/bin/bash:ops administrator" "deploy:appdeploy:appdeploy:/usr/sbin/nologin:app deploy account" "auditor:appaudit:appaudit:/bin/bash:read only auditor" "vendor:appdeploy:appdeploy:/bin/bash:temporary vendor account" ) for line in "${USERS[@]}"; do IFS=':' read -r name primary extra shell comment <<< "$line" if id "$name" >/dev/null 2>&1; then echo "[SKIP] user $name already exists" continue fi sudo useradd -m -g "$primary" -G "$extra" -s "$shell" -c "$comment" "$name" echo "[OK] user $name created, primary=$primary extra=$extra" done这段脚本有几个设计点值得说明。set -euo pipefail让脚本在任意命令失败时立刻退出,避免"建了一半就继续往下走"造成的状态不一致。用id判断用户是否已存在,让脚本具备幂等性,重复执行不会报错。把配置写成数组而不是散落在命令里,后面加账号只需要改数组,不用改逻辑。IFS=':' read -r是 Bash 里解析冒号分隔字段的常用写法,比cut更不容易出错。
建完用户之后设置初始密码并强制改密。这里我们直接从密码库生成随机密码,而不是人工指定,避免出现"所有账号初始密码都一样"的问题:
#!/usr/bin/env bash # 文件路径:/home/ops/scripts/init_passwords.sh # 用途:为指定账号生成随机初始密码,写入密码库,并强制首次登录改密 set -euo pipefail DB="${KEEPASS_DB:?KEEPASS_DB 未设置}" GROUP="prod/web01" ACCOUNTS=(opsadmin deploy auditor vendor) for user in "${ACCOUNTS[@]}"; do entry="$GROUP/$user" # 生成 24 位随机密码并写入数据库(交互式提示主密码) keepassxc-cli generate -L 24 -l -u -n "$DB" "$entry" # 从数据库取回密码,用于设置到系统账号 init_pwd="$(keepassxc-cli show -s -a password "$DB" "$entry")" # 设置密码(Debian/Ubuntu 写法,RHEL 系可换成 passwd --stdin) printf '%s:%s\n' "$user" "$init_pwd" | sudo chpasswd # 强制下次登录修改密码 sudo chage -d 0 "$user" # 设置最长 90 天、最短 1 天、提前 14 天提醒 sudo chage -M 90 -m 1 -W 14 "$user" unset init_pwd echo "[OK] $user 初始化完成,密码已入库:$entry" done执行前需要先把数据库路径导出到环境变量,这样脚本里不用硬编码路径:
export KEEPASS_DB=/home/ops/vault/ops.kdbx bash /home/ops/scripts/init_passwords.sh脚本执行过程中会多次提示输入主密码。如果嫌烦,可以先把主密码通过环境变量传入(keepassxc-cli支持从标准输入读取),但这会带来"主密码出现在环境变量里"的风险,只建议在受控的自动化环境中使用,并且用完立刻清空。脚本里unset init_pwd这一步是刻意加的,尽量缩短明文密码在 Shell 变量里停留的时间。
5.3 强制首次登录改密与账号状态确认
脚本跑完之后,必须逐项确认状态,不能只看输出是[OK]就完事。用chage -l检查每个账号的密码策略是否真的落到了系统里:
for user in opsadmin deploy auditor vendor; do echo "===== $user =====" sudo chage -l "$user" done预期输出中应当能看到Last password change为password must be changed,Maximum number of days between password change为 90,Number of days of warning before password expires为 14。如果某项是空的,说明对应参数没生效,需要回头检查脚本是否中途报错退出。
接着确认账号的 shell 和组归属:
id opsadmin # 预期:uid=... gid=...(appadmin) groups=...(appadmin),27(sudo),... id deploy # 预期:uid=... gid=...(appdeploy) groups=...(appdeploy) sudo getent passwd deploy # 预期末位为 /usr/sbin/nologindeploy账号的 shell 是/usr/sbin/nologin,这意味着它无法交互登录,但可以被sudo -u deploy或su -s /bin/bash deploy用来执行部署任务。这个设计是刻意的:部署账号只需要执行权限,不需要登录权限,减少了一个可被爆破的入口。
5.4 用脚本从密码库取密码完成远程登录
密码入库之后,日常操作就不应该再去问人要密码了。下面的脚本演示从库里取出密码并通过 SSH 登录远程主机。注意这里用到的是sshpass工具,仅用于"还没有配置密钥认证"的过渡阶段:
#!/usr/bin/env bash # 文件路径:/home/ops/scripts/ssh_login.sh # 用途:从密码库读取密码完成一次远程登录(过渡方案) set -euo pipefail DB="${KEEPASS_DB:?KEEPASS_DB 未设置}" ENTRY="${1:?用法: ssh_login.sh <分组/条目>}" HOST="${2:?用法: ssh_login.sh <分组/条目> <主机地址>}" SSH_USER="$(keepassxc-cli show -s -a username "$DB" "$ENTRY")" SSH_PASS="$(keepassxc-cli show -s -a password "$DB" "$ENTRY")" # 通过环境变量传给 sshpass,避免出现在进程列表中 SSHPASS="$SSH_PASS" sshpass -e ssh \ -o StrictHostKeyChecking=accept-new \ -o PreferredAuthentications=password \ -p 22 "${SSH_USER}@${HOST}" unset SSH_PASSsshpass -e表示从环境变量SSHPASS读取密码,相比-p直接写在命令行上更安全,因为命令行参数会被ps看到,环境变量则不会(虽然/proc/PID/environ仍可读,但权限受限)。StrictHostKeyChecking=accept-new表示首次连接自动接受主机密钥,后续连接则严格校验,这在自动化场景里比no更安全。
必须强调:这只是过渡方案。生产环境的正确做法是配置 SSH 密钥认证,密码登录只作为应急手段,并在sshd_config中明确设置PasswordAuthentication no、PermitRootLogin no。密码库的作用是保管密钥文件的口令和应急口令,而不是鼓励长期用密码登录。
5.5 结果说明与验收清单
整套流程跑完之后,可以按下面的清单做一次验收。每一项都对应具体命令,建议逐条执行并记录结果,而不是凭印象判断。
| 验收项 | 检查命令 | 期望结果 |
|---|---|---|
| 用户已创建 | id 用户名 | 输出 UID、GID 和组列表 |
| 主组正确 | getent passwd 用户名 | 第 4 列为预期 GID |
| 附加组正确 | groups 用户名 | 包含预期的附加组 |
| 密码策略生效 | sudo chage -l 用户名 | 最长 90 天,提醒 14 天 |
| 首次必须改密 | sudo chage -l 用户名 | Last password change 显示 must be changed |
| 服务账号不可登录 | getent passwd 用户名 | shell 为 nologin |
| 密码已入库 | keepassxc-cli ls 数据库 分组 | 能看到对应条目 |
| 库内密码可用 | keepassxc-cli show -s -a password ... | 输出非空且与系统密码一致 |
| 库文件权限 | ls -l 数据库文件 | 权限为 600,属主正确 |
其中"库内密码可用"这一项容易被忽略,但它非常关键。如果脚本在生成密码和写入系统之间出现了中断,库里的记录和系统里的实际密码就可能不一致,导致交接时拿到的是错误密码。建议在流程末尾固定做一次校验性的登录测试,确认从库里取出的密码确实能登录系统。
6. 常见问题与排查思路
用户管理和密码库这两块,问题往往出在细节上。下面这张表覆盖了实际工作中出现频率较高的情形,后面再对几个典型问题展开说明。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
useradd报错 group does not exist | 主组未提前创建,或USERGROUPS_ENAB被关闭 | 先groupadd建组,再建用户 |
| 新用户登录后没有家目录 | 建用户时漏了-m,或CREATE_HOME为 no | 补建目录并chown给该用户 |
| 用户登录提示密码错误但密码是对的 | 账号被usermod -L锁定,哈希前带! | usermod -U解锁,或重置密码 |
| 登录提示账号已过期 | chage -E设置的失效日期已过 | chage -E -1取消失效日期 |
| 改完组不生效 | 已登录会话不会刷新组凭证 | 用户重新登录,或用newgrp切换 |
chpasswd报无权限 | 未使用 root 或 sudo 执行 | 加sudo重新执行 |
keepassxc-cli提示 database not found | 路径写错或使用了相对路径 | 使用绝对路径,或用变量统一管理 |
keepassxc-cli提示 invalid credentials | 主密码错误,或密钥文件未提供 | 确认主密码,检查密钥文件是否挂载 |
| 脚本里读取密码总是失败 | 主密码走交互提示,脚本无法输入 | 通过标准输入或环境变量传入主密码 |
sshpass提示 command not found | 未安装该工具 | 按发行版安装,或改用密钥认证 |
对于"改完组不生效"这个问题,值得多说两句。Linux 在用户登录时会读取一次组信息并写入进程凭证,之后即使你改了/etc/group,当前会话也不会自动更新。所以给某人加了docker组之后,他必须退出重新登录才能使用docker命令。临时应急可以用newgrp docker在当前会话里切换主组,但这只对当前 shell 有效,新开窗口还是要重新登录。理解这一点,能避免很多"明明加了组却没权限"的困惑。
对于"keepassxc-cli提示 invalid credentials",排查顺序建议是:先确认数据库文件路径是否指向了正确的文件(同名文件放在不同目录是很常见的事故);再确认主密码有没有输错,特别是大小写和输入法状态;最后确认数据库是否关联了密钥文件,如果关联了但没提供,表现也会是认证失败。如果三样都排除了还是打不开,就要考虑数据库文件是否损坏,这时候备份的价值就体现出来了。
还有一个不常被提起但确实存在的问题:在 root 用户下运行的 cron 任务去读ops用户目录下的密码库,会因为没有权限而失败。这不是密码库的问题,而是文件权限设计的问题。解决办法要么把任务放到ops用户的 crontab 里执行,要么把库文件放在一个ops和 root 都能读的位置(但这会降低安全性),要么通过sudo -u ops切换身份执行。第一种做法最干净。
7. 最佳实践与工程建议
7.1 密码策略与 PAM 配置
系统层面的密码强度控制由 PAM 的pam_pwquality模块负责。配置文件路径因发行版而异,Debian/Ubuntu 一般是/etc/pam.d/common-password,RHEL 系是/etc/pam.d/system-auth(或/etc/security/pwquality.conf)。以pwquality.conf为例:
# 文件路径:/etc/security/pwquality.conf # 最短长度 minlen = 12 # 至少包含多少个大写字母(负数表示"最多允许",谨慎使用) ucredit = -1 # 至少包含多少个小写字母 lcredit = -1 # 至少包含多少个数字 dcredit = -1 # 至少包含多少个符号 ocredit = -1 # 与旧密码相同位置的最大字符数,0 表示允许全部不同 difok = 3 # 连续相同字符的最大数量 maxrepeat = 3 # 失败重试次数 retry = 3修改这些参数时要注意,过严的策略会导致用户把密码写在便签上,反而降低整体安全性。12 位以上的长度配合字符集要求是比较平衡的起点。改完配置后建议用pwscore命令测试几个样例密码,确认策略按预期生效,再整体推行,避免上线当天大量用户无法改密。
/etc/login.defs里的密码有效期默认值也建议一并调整:
PASS_MAX_DAYS 90 PASS_MIN_DAYS 1 PASS_WARN_AGE 14 CREATE_HOME yes UMASK 027这些是新建用户的默认值,对已存在的用户不会回溯生效,已存在的用户需要用chage逐个调整。UMASK 027表示新建文件默认权限为 640、新建目录为 750,比默认的 022 更严格,能减少"文件被同组或其他用户读到"的风险。
7.2 最小权限与 sudo 管理
sudo 权限必须通过visudo编辑,不要直接用编辑器改/etc/sudoers,因为visudo会在保存时做语法检查,语法错误会导致所有 sudo 失效,后果非常严重。推荐的授权方式是给组授权,而不是给个人授权:
# 文件路径:/etc/sudoers.d/appadmin # 注意:文件权限必须是 0440,属主 root:root %appadmin ALL=(ALL) ALL %appaudit ALL=(root) NOPASSWD: /usr/bin/journalctl, /usr/bin/tail第一行给appadmin组完整提权,适合运维管理员。第二行只给审计组两条只读命令的执行权限,并且免密码,这样审计人员可以看日志但不能做任何写操作。这种"白名单命令 + 精确路径"的写法比ALL安全得多,因为ALL意味着可以用sudo vim打开任意文件再提权,实质上等于给了 root。
/etc/sudoers.d/下的文件权限必须严格,否则 sudo 会拒绝加载:
sudo chmod 0440 /etc/sudoers.d/appadmin sudo chown root:root /etc/sudoers.d/appadmin sudo visudo -c最后一句visudo -c是语法检查,每次改完都跑一次,确认没有报错再去测试实际授权效果。另外建议开启 sudo 的操作日志,默认会记录到/var/log/auth.log(Debian 系)或/var/log/secure(RHEL 系),审计时按用户名和命令关键字检索即可。
7.3 密码库的备份、轮换与交接
密码库的备份策略和普通文件不一样,因为它是"单点"。一旦.kdbx文件损坏且没有备份,里面所有凭据就都没了。建议的做法是:每次批量变更后做一次带时间戳的快照,保留最近 N 份;快照放在加密的备份介质里,与密钥文件分开存放;定期做一次"从备份恢复并解锁"的演练,确认备份真的可用。
# 带时间戳备份,保留最近 30 份 BACKUP_DIR=/home/ops/vault/backup mkdir -p "$BACKUP_DIR" cp /home/ops/vault/ops.kdbx "$BACKUP_DIR/ops-$(date +%Y%m%d%H%M%S).kdbx" ls -1t "$BACKUP_DIR"/ops-*.kdbx | tail -n +31 | xargs -r rm -f密码轮换方面,建议把"密码库里的密码"和"系统里的密码"做定期对齐:每季度抽查若干条目,用库里的密码实际登录验证一次,发现不一致就及时修正。人员离场时,除了禁用账号,还要把他接触过的共享凭据全部轮换,并把新密码写入库中、在备注里记录轮换日期和操作人。这一步经常被省略,但恰恰是安全事故的高发点。
交接场景下,最忌讳的是"把整个库连同主密码一起发过去"。正确的做法是导出一个只包含相关分组的子库,设置独立的临时主密码,交付后要求对方在限定时间内改密,并记录交付清单。KeePassXC 支持导出子集,导出后同样要检查文件权限和传输渠道。
7.4 自动化脚本的安全边界
把密码库接进脚本之后,脚本本身就成了敏感资产。几条硬性要求:脚本文件权限设为700,属主为执行者;脚本里不得出现任何明文密码、主密码或密钥文件路径;主密码通过交互输入或受控的环境变量传入,用完立即unset;所有涉及密码的变量在使用后清空;脚本的临时输出不要重定向到全局可读的文件。
另外一个容易被忽略的点是命令历史。像echo 'user:pass' | chpasswd这种写法,密码会留在~/.bash_history里。规避方式是在命令前加一个空格(需要配置HISTCONTROL=ignorespace),或者在脚本里执行而不是在交互式 shell 里直接敲。如果确实在交互式 shell 里操作过,记得事后清理历史记录并检查是否有其他审计日志留存了明文。
最后是权限降级的思路。任何需要读密码库的自动化任务,都应该以最小必要身份运行。如果任务只需要读某一条记录,就不要给它整个库的访问权;如果任务只需要在特定主机上执行,就不要给它 sudo 全权限。这种"按需授权"的设计,能让一次凭据泄露的影响范围被限制在很小的区域内。
8. 总结与学习路线
回过头看,本文做的事情可以归纳成一条链:用groupadd、useradd、chage把身份和有效期建立起来,用pam_pwquality和/etc/login.defs把密码强度和时间边界约束住,用keepassxc-cli把初始密码和日常凭据收进加密数据库,最后用脚本把"生成、入库、下发、强制改密"串成可重复执行的流程。每一环单独看都不复杂,串起来才构成一套能通过审计的账号体系。
下一步可以继续深入的方向有几个。一是 PAM 栈的完整理解,包括pam_unix、pam_tally2(或pam_faillock)如何配合实现登录失败锁定;二是 SSH 密钥认证的完整落地,包括authorized_keys的权限要求、sshd_config的关键项、以及证书签发方式;三是集中式身份管理的思路,了解 LDAP、Kerberos 这类方案在多人多机场景下如何替代本地账号;四是密码库的团队协作模式,包括共享库的并发写入冲突、审计日志留存、以及与 CI/CD 系统的集成边界。
实际项目中优先关注的风险,我认为有三个:第一是孤儿文件导致的 UID 复用越权,删用户前一定要find -uid查一遍;第二是usermod -G覆盖附加组造成的权限意外丢失,养成带-a的习惯;第三是密码库成为单点故障,备份和恢复演练不能省。这三点都不需要高深技术,但都是真实事故的高频来源。
建议你在一台测试机上把第 5 节的脚本完整跑一遍,故意制造几个错误(比如先不建组就建用户、故意把主密码输错),观察系统给出的报错信息,再对照第 6 节的排查表验证自己的判断。这种"先制造问题再解决问题"的练习,比单纯照着命令敲一遍印象深得多。如果本文对你有帮助,可以收藏备用,后面配服务器的时候直接翻出来对照执行。