
1. 从一次深夜告警说起为什么密码过期策略会让人头疼凌晨两点手机突然震动监控告警提示生产服务器上的一个关键服务账号登录失败。睡眼惺忪地爬起来用个人账号SSH连上去一看发现是那个跑定时任务的系统账号密码过期了。这已经不是第一次了团队里新来的运维同事按照公司安全基线给所有Linux用户都设置了90天密码过期策略却忘了给这些“非人类”的服务账号做例外处理。结果就是每隔三个月总有几个脚本会悄无声息地挂掉直到业务受到影响才被发现。这个场景相信很多运维和开发朋友都遇到过。Linux系统的密码过期策略Password Aging是一把双刃剑。对于需要交互登录的真人用户定期更换密码是基本的安全卫生习惯能有效降低密码被长期窃取的风险。但对于大量的服务账号、自动化脚本、CI/CD流水线中的机器账号频繁的密码过期简直就是灾难。想象一下成百上千台服务器上那些用于应用部署、日志收集、监控检测的账号如果每90天就要手动更新一次密码并且同步到所有相关的配置文件和脚本中这其中的运维成本和出错风险是难以承受的。所以“修改密码永不过期”这个操作绝不是教你偷懒或者降低安全标准而是一次精准的安全策略调整。它的核心目标是在保障安全的前提下提升系统的可维护性和稳定性。我们需要区分“人”和“机器”对它们应用不同的安全规则。今天我们就来彻底搞懂Linux用户密码的生命周期管理特别是如何将那些不该过期的密码“锁死”让你再也不用为半夜的密码过期告警而烦恼。2. 理解密码时效管理的核心/etc/shadow文件与chage命令要修改密码过期策略我们得先知道Linux系统把密码信息藏在了哪里。对于现代Linux发行版如CentOS/RHEL 7, Ubuntu 16.04用户密码的哈希值以及相关的时效信息默认都存储在/etc/shadow这个文件里。这个文件对普通用户是不可读的只有root用户才有权限查看和修改这本身也是一项安全措施。我们可以用sudo cat /etc/shadow命令查看一下这个文件的内容。每一行代表一个系统用户字段之间用冒号:分隔。我们重点关注后面几个与密码时效相关的字段username:$6$random_salt$hashed_password:last_change:min:max:warn:inactive:expire:reserved为了更直观我们来看一个具体的例子比如用户deploy的这一行deploy:$6$xyz...$abc...:19236:0:90:7:::这里的几个数字字段含义如下第三个字段 (19236): 表示密码最后一次修改的天数。这个天数是从1970年1月1日Unix纪元开始计算的。19236天大约对应2022年9月左右。第四个字段 (0): 密码最短有效期。0表示密码修改后可以立即再次修改没有限制。如果设为7则表示修改密码后7天内不能再次修改。第五字段 (90):密码最长有效期也就是我们标题里提到的“90天过期”。这是核心参数表示密码在90天后会失效用户必须更改密码。第六字段 (7): 密码过期前的警告期。7表示在密码过期前7天用户登录时会收到警告信息。第七字段 (空): 密码过期后的宽限期Inactive。如果设为10则表示密码过期后账户还能登录10天之后账户才会被完全禁用。这里是空的表示密码一过期账户立即不可用。第八字段 (空): 账户的绝对过期日期Expire date同样以从1970年1月1日以来的天数表示。如果设置到了这个日期账户会被禁用与密码是否更改无关。这里为空表示账户不会绝对过期。直接编辑/etc/shadow文件是危险的容易因格式错误导致用户无法登录。因此系统提供了专门的工具chage(change age) 命令来安全地管理这些时效信息。chage命令的-l选项可以清晰地列出某个用户的所有时效信息比直接看/etc/shadow友好得多sudo chage -l deploy输出会是这样Last password change : Sep 01, 2022 Password expires : Nov 30, 2022 Password inactive : never Account expires : never Minimum number of days between password change : 0 Maximum number of days between password change : 90 Number of days of warning before password expires : 7看一目了然。密码将在2022年11月30日过期这正是基于“最后修改日期Sep 01”加上“最大有效期90天”计算出来的。我们的目标就是把那个Maximum number of days between password change从90变成never。3. 实战操作将用户密码策略修改为永不过期知道了原理操作就很简单了。我们有两种主流方法来实现“密码永不过期”一种是使用chage命令另一种是直接修改/etc/login.defs系统默认配置。前者针对特定用户后者影响后续新建的所有用户。3.1 方法一使用chage命令修改指定用户这是最常用、最直接的方法用于修改已存在的用户。让密码永不过期的关键是将“最大密码有效期”设置为-1。执行以下命令sudo chage -M -1 username请将username替换为你的目标用户名例如deploy、nginx、jenkins等。命令拆解与原理sudo: 因为修改用户密码策略需要root权限。chage: 管理密码时效的命令。-M: 选项代表设置“密码最大有效期”Maximum password age。-1: 这个参数是精髓。在chage命令的语境下将最大有效期设置为-1具有特殊含义它代表“永不过期”。系统内部会将其解释为一个非常大的值或者一个特殊标志从而禁用密码过期检查。修改后立即使用sudo chage -l username验证一下Maximum number of days between password change : never看到never就表示成功了。这个用户的密码再也不会因为时间到期而失效了。注意chage命令非常强大除了-M还有其他常用选项需要了解避免误操作-m 天数: 设置密码最短有效期。如果设为7用户改密后7天内不能再次修改。对于服务账号通常设为0。-W 天数: 设置过期前警告天数。对于服务账号可以设为0或-1来禁用警告。-I 天数: 设置密码过期后的宽限期Inactive。-1表示禁用此功能。这里有个大坑如果这个值被设置成一个正数比如30即使密码最大有效期-M是-1在密码最后一次修改后经过“最大有效期宽限期”天账户仍然会被锁定所以对于永不过期的服务账号建议也执行sudo chage -I -1 username。-E 日期: 设置账户的绝对过期日期。格式可以是 YYYY-MM-DD或者从1970-1-1起的天数。设为-1表示永不过期。因此一个完整的、确保服务账号“一劳永逸”的命令组合是sudo chage -M -1 -m 0 -I -1 -E -1 username这条命令一次性将账号的密码策略设置为密码永不过期、修改后无等待时间、无过期宽限期、账户本身永不过期。3.2 方法二修改系统默认配置/etc/login.defs如果你负责管理一个服务器集群希望所有新创建的用户默认就是密码永不过期那么修改全局配置更高效。这个配置文件是/etc/login.defs。使用vim或nano编辑这个文件sudo vim /etc/login.defs找到关于密码老化的配置部分通常会有以下行PASS_MAX_DAYS 90 PASS_MIN_DAYS 0 PASS_WARN_AGE 7将PASS_MAX_DAYS的值从90修改为-1PASS_MAX_DAYS -1保存并退出。重要提示这个修改只对之后新创建的用户生效。之前已经存在的用户其密码策略仍然保持原来的设置需要使用方法一的chage命令逐个修改。3.3 方法三在创建用户时指定策略useradd在创建新用户的瞬间就指定其密码永不过期也是一个好习惯。这可以通过useradd命令的-K大写K选项来实现。sudo useradd -m -s /bin/bash -K PASS_MAX_DAYS-1 new_service_account命令解释-m: 创建用户的家目录。-s /bin/bash: 指定用户的默认shell。-K PASS_MAX_DAYS-1: 这是关键。-K选项允许你直接覆盖/etc/login.defs中的默认值这里我们指定密码最大有效期为-1。这样用户new_service_account在创建之初密码就是永不过期的。你可以用chage -l命令立刻验证。4. 深入排查为什么改了策略还是提示密码过期有时候明明用chage -M -1设置了永不过期但一些自动化工具或者特定的登录方式比如通过Ansible、Jenkins SSH密钥验证仍然报告密码过期错误。这很可能触及了Linux PAMPluggable Authentication Modules可插拔认证模块的深层机制。PAM是Linux系统中处理认证登录、改密等的灵活框架。密码过期策略除了记录在/etc/shadow还可能被PAM模块强制执行。一个常见的“幕后黑手”是pam_unix.so模块。4.1 检查PAM配置PAM的配置目录通常在/etc/pam.d/。我们需要检查与密码认证相关的配置文件比如system-authRHEL/CentOS系列或common-authDebian/Ubuntu系列。查看一下这个文件sudo cat /etc/pam.d/system-auth寻找包含pam_unix.so的行特别是带有shadow和nullok参数的那一行。它的样子可能如下auth sufficient pam_unix.so nullok try_first_pass password sufficient pam_unix.so sha512 shadow nullok try_first_pass在有些严格的安全策略下即使shadow文件里标记为永不过期-1PAM模块也可能因为其他原因比如密码强度、历史密码检查等在认证时返回“密码需要更改”的信号。某些应用程序尤其是一些老的或自定义的软件可能会错误地解读这个信号认为是密码过期。4.2 服务账号的特殊情况密码锁定与空密码对于根本不需要密码登录的服务账号完全依靠SSH密钥认证一个更彻底的做法是锁定密码。这比设置永不过期更绝对。使用passwd命令的-l(lock) 选项可以锁定用户密码sudo passwd -l username锁定后该用户的密码字段前会被加上!或!!使得任何密码都无法验证通过。因为登录完全依赖SSH密钥所以这没有任何影响。同时一个被锁定的密码自然也就没有“过期”一说了。如果你想检查用户密码状态可以看/etc/shadow文件里对应用户的密码哈希字段。如果前面有!就表示被锁定。deploy:!$6$xyz...$abc...:19236:0:-1:7:::重要警告对于需要密码认证的服务比如某些FTP服务、数据库本地登录等绝对不能锁定密码否则会导致服务无法连接。这种情况下只能使用chage -M -1设置为永不过期。4.3 账户过期 vs 密码过期另一个容易混淆的概念是“账户过期”Account Expiry。它由/etc/shadow的第八个字段控制使用chage -E设置。即使密码永不过期如果账户设置了一个过去的绝对过期日期用户依然无法登录。检查命令是chage -l username中的Account expires一行。确保它是never。如果需要修改可以使用sudo chage -E -1 username # 设置为永不过期或者sudo chage -E YYYY-MM-DD username # 设置为一个未来的具体日期5. 自动化与批量管理在成百上千台服务器上操作当服务器数量庞大时手动登录每台机器执行chage命令是不现实的。这时就需要借助自动化工具。5.1 使用Ansible批量修改Ansible是运维自动化的利器。下面是一个简单的Playbook示例用于将指定列表中的用户密码策略修改为永不过期并确保账户本身也永不过期。创建一个YAML文件例如disable_password_expiry.yml--- - name: Disable password expiration for service accounts hosts: all # 针对清单中的所有主机可以替换为具体的组名 become: yes # 使用sudo权限 vars: service_accounts: - deploy - jenkins - monitoring_agent tasks: - name: Set password never expires and account never expires ansible.builtin.user: name: {{ item }} password_expire_max: -1 password_expire_min: 0 loop: {{ service_accounts }}运行这个Playbookansible-playbook -i your_inventory_file disable_password_expiry.ymlAnsible的user模块抽象得很好password_expire_max: -1就对应了chage -M -1。5.2 使用Shell脚本与SSH密钥批量执行如果没有Ansible可以使用传统的Shell脚本配合SSH密钥免密登录。假设你有一个服务器IP列表文件server_list.txt并且已经配置了从管理机到所有目标机的SSH密钥信任。创建一个脚本batch_chage.sh#!/bin/bash # 要操作的用户名 USERNAMEdeploy # 服务器列表文件 SERVERS_FILEserver_list.txt # 读取服务器列表逐台执行命令 while read SERVER; do echo Processing $SERVER... # 使用ssh执行远程命令 ssh -o ConnectTimeout5 -o StrictHostKeyCheckingno root$SERVER echo Current status on $SERVER:; chage -l $USERNAME 2/dev/null | grep -E (Maximum|Account expires); echo Setting to never expire...; chage -M -1 -E -1 $USERNAME; echo New status:; chage -l $USERNAME 2/dev/null | grep -E (Maximum|Account expires); echo ------------------------------------- || echo Failed to connect to $SERVER done $SERVERS_FILE echo Batch operation completed.给脚本添加执行权限并运行chmod x batch_chage.sh ./batch_chage.sh安全提示在生产环境中尽量避免直接使用root密钥。应该创建一个具有sudo权限的专用运维账号并限制其可执行的命令通过sudoers文件。在脚本中可以使用ssh ops_userserver sudo chage ...的方式并在目标机上配置ops_user可以无密码执行特定的sudo chage命令。6. 安全最佳实践永不过期不等于无限风险将服务账号密码设置为永不过期绝不意味着可以忽视其安全。相反正因为密码长期不变我们更需要其他强有力的安全措施来补偿。6.1 原则最小权限与角色分离这是安全的第一道防线。每个服务账号应该只拥有完成其本职工作所必需的最小权限。不要滥用root绝大多数服务不需要root权限。为Jenkins创建一个jenkins用户为Web服务创建一个www-data或nginx用户。精细的文件权限使用chown和chmod严格控制服务账号能访问的文件和目录。例如一个只负责读取日志的账号不应该有写入应用程序目录的权限。使用sudo限制如果服务账号偶尔需要执行特权命令通过sudoers文件精确授权而不是直接给root密码。例如jenkins ALL(ALL) NOPASSWD: /usr/bin/systemctl restart myapp。6.2 强制使用SSH密钥认证禁用密码登录对于所有需要远程登录SSH的服务账号必须禁用密码认证强制使用SSH密钥。这是防止暴力破解的最有效手段。编辑SSH服务端配置/etc/ssh/sshd_configPasswordAuthentication no PubkeyAuthentication yes修改后重启sshd服务sudo systemctl restart sshd然后将对应的公钥.pub文件内容添加到服务账号家目录下的~/.ssh/authorized_keys文件中。确保~/.ssh目录权限为700authorized_keys文件权限为600。6.3 定期审计与监控即使密码永不过期定期的安全审计也必不可少。检查账号列表定期查看/etc/passwd和/etc/shadow确认没有未知的、可疑的用户。关注UID为0root的用户除了root本身不应有其他。检查登录记录使用last、lastb查看失败登录、journalctl -u sshd等命令监控异常登录行为。监控特权操作配置审计规则如使用auditd或日志集中管理监控su、sudo的使用情况以及关键文件的修改。轮换SSH密钥虽然比轮换密码麻烦但定期如每年更换服务账号的SSH密钥对也是一个好习惯尤其是在有成员离职或密钥可能泄露的情况下。6.4 使用集中化认证与管理进阶在大型企业环境中更佳的做法是避免在每台服务器上管理本地用户密码。可以采用LDAP / Active Directory将所有服务器账号统一到中央目录服务中管理。密码策略包括过期策略在中央统一设置可以针对“用户”和“服务账号”设置不同的策略组。配置管理数据库CMDB与堡垒机所有服务器的访问通过堡垒机跳板机进行堡垒机与CMDB联动实现账号的自动创建、授权、回收和审计。服务账号的密钥由系统自动生成和管理人工不接触私钥。将密码设为永不过期是为了运维的便利和系统的稳定。但随之而来的是我们必须用更严格的访问控制、更可靠的认证方式密钥和更主动的监控审计来构建新的、更坚固的安全防线。这从来不是一个“偷懒”的操作而是一个要求更高安全素养的“精细化管理”操作。