基于PAM的Linux用户权限精细化管控实践
一、概述
1.1 背景介绍
去年我们团队接手一个客户的系统,发现所有运维人员共用一个root账号登录服务器,sudoers文件里写的是 ALL=(ALL) NOPASSWD: ALL,出了问题根本查不到是谁操作的。更离谱的是有个离职半年的人还能用原来的密码登录,因为没人改过密码。
这种粗放的权限管理在中小团队很常见。多数运维只会改 /etc/sudoers,对底层的PAM机制了解不深。PAM(Pluggable Authentication Modules)是Linux统一认证框架,控制用户登录、su切换、sudo提权、密码策略等所有认证行为。掌握PAM配置,才能实现真正的权限精细化管控。
本篇从PAM架构原理讲起,覆盖密码复杂度策略、登录失败锁定、资源限制、访问控制、时间段限制、sudo集成等完整配置链路。所有配置在CentOS 7/8/9和Ubuntu 20.04/22.04上实际验证过。
1.2 技术特点
- 模块化可插拔架构:PAM把认证逻辑拆成独立模块(.so文件),每个模块负责一个功能。需要密码复杂度检查就加pam_pwquality,需要登录锁定就加pam_faillock,像搭积木一样组合。新增功能不用改代码,加个模块配置就行。
- 四种管理类型覆盖认证全流程:auth(身份验证)、account(账户检查)、password(密码修改)、session(会话管理)四个阶段各司其职。一次SSH登录会依次经过auth验证身份、account检查账户是否过期、session建立会话环境,每个阶段都可以插入不同的控制逻辑。
- 控制标志实现灵活的策略组合:required(必须通过但继续检查后续模块)、requisite(失败立即返回)、sufficient(通过则跳过后续)、optional(结果不影响最终判断)四种控制标志,可以实现"必须满足A且满足B"、"满足A或满足B即可"等复杂逻辑。
1.3 适用场景
- 场景一:密码安全策略强制执行。等保2.0要求密码长度不少于8位、包含大小写字母+数字+特殊字符、90天强制更换。靠口头通知用户设强密码不现实,用pam_pwquality从系统层面强制执行,不符合策略的密码直接拒绝。
- 场景二:防暴力破解的登录锁定。SSH加固只是外层防护,如果攻击者通过其他途径(比如Web应用漏洞)拿到了shell,还能在本地尝试su提权。pam_faillock在PAM层面做锁定,不管从哪个入口尝试认证,失败5次就锁定账户15分钟。
- 场景三:多角色权限分层管控。一个系统上有普通开发、运维、DBA、安全审计等不同角色,每个角色能做的事不一样。通过PAM的pam_access控制谁能从哪里登录,pam_limits控制资源配额,配合sudoers实现命令级权限控制。
1.4 环境要求
| | |
|---|
| | |
| | 系统自带,CentOS 7是1.1.8,CentOS 8+是1.3.1 |
| | 密码复杂度检查模块,替代旧的pam_cracklib |
| | 登录失败锁定模块,替代已废弃的pam_tally2 |
| | |
二、详细步骤
2.1 准备工作
2.1.1 系统检查
# 检查系统版本
cat /etc/os-release
# 检查PAM版本
rpm -q pam 2>/dev/null || dpkg -l libpam-runtime 2>/dev/null
# 查看已安装的PAM模块
ls /usr/lib64/security/pam_*.so 2>/dev/null || ls /usr/lib/x86_64-linux-gnu/security/pam_*.so 2>/dev/null
# 检查当前PAM配置文件
ls -la /etc/pam.d/
# 查看当前密码策略
grep -v "^#" /etc/security/pwquality.conf 2>/dev/null
# 查看当前资源限制
cat /etc/security/limits.conf | grep -v "^#" | grep -v "^$"
PAM配置改错了会导致所有用户无法登录,包括root。 改之前必须保留一个root的rescue终端(物理控制台、VNC、串口),确认有不经过PAM认证的恢复通道。我们团队的规矩是:改PAM配置前先开一个root的tmux会话挂着,万一改错了还能恢复。
2.1.2 安装依赖
# CentOS/RHEL
sudo yum install -y pam pam-devel libpwquality
# CentOS 8+/RHEL 8+
sudo dnf install -y pam pam-devel libpwquality
# Ubuntu/Debian
sudo apt install -y libpam-modules libpam-pwquality libpam-modules-extra
# 验证关键模块是否存在
# CentOS/RHEL
ls -la /usr/lib64/security/pam_pwquality.so
ls -la /usr/lib64/security/pam_faillock.so
ls -la /usr/lib64/security/pam_access.so
ls -la /usr/lib64/security/pam_limits.so
ls -la /usr/lib64/security/pam_time.so
ls -la /usr/lib64/security/pam_exec.so
# Ubuntu/Debian
ls -la /usr/lib/x86_64-linux-gnu/security/pam_pwquality.so
ls -la /usr/lib/x86_64-linux-gnu/security/pam_faillock.so
2.1.3 备份原始配置
# 备份整个PAM配置目录
sudo cp -a /etc/pam.d /etc/pam.d.bak.$(date +%Y%m%d)
# 备份安全相关配置文件
sudo cp /etc/security/pwquality.conf /etc/security/pwquality.conf.bak.$(date +%Y%m%d)
sudo cp /etc/security/limits.conf /etc/security/limits.conf.bak.$(date +%Y%m%d)
sudo cp /etc/security/access.conf /etc/security/access.conf.bak.$(date +%Y%m%d)
# 验证备份
ls -la /etc/pam.d.bak.*
ls -la /etc/security/*.bak.*
2.2 核心配置
2.2.1 PAM配置文件结构
PAM的配置文件在 /etc/pam.d/ 目录下,每个服务一个文件。SSH登录看 /etc/pam.d/sshd,su切换看 /etc/pam.d/su,sudo提权看 /etc/pam.d/sudo。
# 查看PAM配置目录
ls /etc/pam.d/
# 常见文件:
# system-auth 系统认证主配置(CentOS/RHEL)
# common-auth 系统认证主配置(Ubuntu/Debian)
# password-auth 密码认证配置(CentOS/RHEL)
# common-password 密码认证配置(Ubuntu/Debian)
# sshd SSH登录认证
# su su切换用户
# sudo sudo提权
# login 本地终端登录
# postlogin 登录后处理
PAM配置文件格式:
类型 控制标志 模块路径 模块参数
auth required pam_faillock.so preauth deny=5 unlock_time=900
四列含义:
- 类型:auth(验证身份)、account(检查账户状态)、password(修改密码)、session(会话管理)
- 控制标志:required(必须通过)、requisite(失败立即返回)、sufficient(通过则跳过后续)、optional(可选)
- 模块路径:.so模块文件名,系统会自动在
/usr/lib64/security/ 下查找
控制标志详解:
| | | |
|---|
| | | |
| | | |
| | | 提供"快速通过"路径,比如本地密码通过就不查LDAP |
| | | |
理解这四个标志是掌握PAM的关键。举个例子:
auth required pam_env.so
auth required pam_faillock.so preauth
auth sufficient pam_unix.so try_first_pass
auth required pam_deny.so
执行流程:
pam_env.so(required):设置环境变量,基本不会失败pam_faillock.so preauth(required):检查账户是否被锁定,被锁定则记录失败pam_unix.so(sufficient):验证密码,密码正确且前面没有required失败则立即返回成功pam_deny.so(required):如果走到这一步说明密码验证失败,pam_deny无条件返回失败
2.2.2 PAM模块类型和控制标志实践
# 查看某个服务的PAM配置(以sshd为例)
cat /etc/pam.d/sshd
# CentOS 7/8 的sshd PAM配置通常引用system-auth和password-auth
# 查看实际的认证链
cat /etc/pam.d/system-auth
# Ubuntu的sshd PAM配置引用common-auth等
cat /etc/pam.d/common-auth
CentOS/RHEL的PAM配置特点:使用 authselect 工具管理(CentOS 8+),不建议直接编辑 system-auth 和 password-auth,因为authselect会覆盖手动修改。
# CentOS 8+ 查看当前authselect配置
sudo authselect current
# 创建自定义profile(基于sssd模板)
sudo authselect create-profile custom-hardening -b sssd
# 自定义profile的文件在:
ls /etc/authselect/custom/custom-hardening/
# CentOS 7 没有authselect,直接编辑 /etc/pam.d/system-auth 和 password-auth
2.2.3 pam_pwquality配置密码复杂度策略
pam_pwquality是pam_cracklib的替代品,用于强制密码复杂度。等保2.0三级要求密码长度不少于8位,包含大小写字母、数字、特殊字符中的三种。我们团队的标准比等保更严:最少12位,四种字符全部包含。
# 编辑密码质量配置文件
sudo vim /etc/security/pwquality.conf
# /etc/security/pwquality.conf
# 密码最小长度12位
minlen = 12
# 至少包含1个数字(负数表示"至少N个")
dcredit = -1
# 至少包含1个大写字母
ucredit = -1
# 至少包含1个小写字母
lcredit = -1
# 至少包含1个特殊字符
ocredit = -1
# 新密码与旧密码至少有8个字符不同
difok = 8
# 不允许超过3个连续相同字符(如aaa)
maxrepeat = 3
# 不允许超过3个连续递增/递减字符(如abc、321)
maxsequence = 3
# 不允许包含用户名
usercheck = 1
# 密码复杂度检查的字符类别数(至少包含4类中的3类)
minclass = 3
# 记住最近12个密码,不允许重复使用
# 这个参数在pam_unix中配置,不在pwquality中
# 拒绝包含的词(字典文件路径)
dictpath = /usr/share/cracklib/pw_dict
# 强制root用户也遵守密码策略
enforce_for_root
# 重试次数
retry = 3
在PAM中启用pam_pwquality:
# CentOS 7:编辑 /etc/pam.d/system-auth
# 找到password行,确保有pam_pwquality
# password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
# CentOS 8+:通过authselect启用
sudo authselect select sssd with-faillock with-mkhomedir --force
# Ubuntu:编辑 /etc/pam.d/common-password
# 添加或修改:
# password requisite pam_pwquality.so retry=3 enforce_for_root
验证密码策略生效:
# 用passwd命令测试(用普通用户)
passwd
# 尝试输入弱密码,比如 "123456" 或 "password"
# 预期输出:BAD PASSWORD: The password fails the dictionary check
# 尝试输入 "Abc12345"(只有8位)
# 预期输出:BAD PASSWORD: The password is shorter than 12 characters
# 查看pwquality模块的详细检查过程
sudo pam_tester --service login --user testuser --authenticate
配置密码历史记录(防止重复使用旧密码):
# 在 /etc/pam.d/system-auth(CentOS)或 /etc/pam.d/common-password(Ubuntu)中
# 找到 pam_unix.so 行,添加 remember=12 参数
# password sufficient pam_unix.so sha512 shadow try_first_pass use_authtok remember=12
# 密码历史存储在 /etc/security/opasswd 文件中
# 确保文件存在且权限正确
sudo touch /etc/security/opasswd
sudo chmod 600 /etc/security/opasswd
sudo chown root:root /etc/security/opasswd
2.2.4 pam_faillock配置登录失败锁定
pam_faillock替代了已废弃的pam_tally2(CentOS 8+移除了pam_tally2)。功能是统计认证失败次数,超过阈值后锁定账户一段时间。实测效果:配合SSH的MaxAuthTries,暴力破解在第5次尝试后账户被锁15分钟,攻击者每小时最多尝试20次,字典攻击完全失效。
# CentOS 8+/RHEL 8+ 使用authselect启用faillock
sudo authselect enable-feature with-faillock
# CentOS 7 手动编辑 /etc/pam.d/system-auth 和 /etc/pam.d/password-auth
# 在auth段添加(注意顺序):
CentOS 7 手动配置 /etc/pam.d/system-auth:
# auth段 - 在pam_unix.so之前添加preauth,之后添加authfail和authsucc
auth required pam_env.so
auth required pam_faillock.so preauth silent deny=5 unlock_time=900 fail_interval=900
auth sufficient pam_unix.so try_first_pass
auth [default=die] pam_faillock.so authfail deny=5 unlock_time=900 fail_interval=900
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so
# account段 - 添加faillock检查
account required pam_faillock.so
account required pam_unix.so
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 1000 quiet
account required pam_permit.so
CentOS 8+/RHEL 8+ 配置文件:
# faillock的独立配置文件(CentOS 8+支持)
sudo vim /etc/security/faillock.conf
# /etc/security/faillock.conf
# 失败5次后锁定
deny = 5
# 锁定时间900秒(15分钟),设为0表示永久锁定直到管理员手动解锁
unlock_time = 900
# 统计窗口900秒(15分钟内失败5次才锁定)
fail_interval = 900
# 不锁定root账户(生产环境建议设为yes,但要确保有其他恢复手段)
even_deny_root = false
# root账户锁定时间(如果even_deny_root=true)
root_unlock_time = 60
# 静默模式,不提示剩余尝试次数(防止攻击者探测策略)
silent
# 审计日志
audit
# 锁定记录存储目录
dir = /var/run/faillock
faillock管理命令:
# 查看某个用户的失败记录
sudo faillock --user opsadmin
# 输出示例:
# opsadmin:
# When Type Source Valid
# 2025-01-15 10:23:45 RHOST 192.168.1.50 V
# 2025-01-15 10:23:48 RHOST 192.168.1.50 V
# 2025-01-15 10:23:51 RHOST 192.168.1.50 V
# 手动解锁用户(清除失败记录)
sudo faillock --user opsadmin --reset
# 查看所有用户的失败记录
sudo faillock
2.2.5 pam_limits配置资源限制
pam_limits通过 /etc/security/limits.conf 控制用户和组的资源配额。生产环境必须配,不然一个用户fork炸弹就能把整台机器搞挂。我们线上出过一次事故:一个Java应用没限制线程数,内存泄漏后疯狂创建线程,把系统的PID号耗尽,连root都无法新建进程。
# 编辑资源限制配置
sudo vim /etc/security/limits.conf
# /etc/security/limits.conf
# 格式:<domain> <type> <item> <value>
# domain: 用户名、@组名、* 通配符
# type: soft(软限制,用户可调整到hard上限)、hard(硬限制,只有root能改)
# ===== 全局默认限制 =====
# 最大打开文件数(防止文件描述符泄漏)
* soft nofile 65535
* hard nofile 131072
# 最大进程数(防止fork炸弹)
* soft nproc 4096
* hard nproc 8192
# 最大内存锁定(防止swap被锁死)
* soft memlock 65536
* hard memlock 65536
# ===== 运维用户组 - 较高限制 =====
@ops-team soft nofile 131072
@ops-team hard nofile 262144
@ops-team soft nproc 8192
@ops-team hard nproc 16384
# ===== 应用服务账户 - 按需配置 =====
# Nginx工作进程需要大量文件描述符
nginx soft nofile 131072
nginx hard nofile 262144
# MySQL需要大量文件描述符和内存锁定
mysql soft nofile 65535
mysql hard nofile 131072
mysql soft memlock unlimited
mysql hard memlock unlimited
# Elasticsearch需要大量文件描述符和内存锁定
elasticsearch soft nofile 65535
elasticsearch hard nofile 131072
elasticsearch soft memlock unlimited
elasticsearch hard memlock unlimited
elasticsearch soft nproc 4096
elasticsearch hard nproc 4096
# ===== 普通开发用户 - 严格限制 =====
@developers soft nofile 4096
@developers hard nofile 8192
@developers soft nproc 1024
@developers hard nproc 2048
# 限制core dump文件大小(防止磁盘被撑满)
@developers soft core 0
@developers hard core 0
# 限制最大文件大小(2GB)
@developers hard fsize 2097152
确保PAM加载了pam_limits模块:
# 检查sshd的PAM配置中是否包含pam_limits
grep pam_limits /etc/pam.d/sshd
# 如果没有,添加到session段:
# session required pam_limits.so
# 检查system-auth中是否包含
grep pam_limits /etc/pam.d/system-auth
# 通常已经有了:session required pam_limits.so
验证限制生效:
# 切换到目标用户查看当前限制
su - opsadmin
ulimit -a
# 查看特定限制
ulimit -n # 最大打开文件数
ulimit -u # 最大进程数
ulimit -l # 最大内存锁定(KB)
# 查看某个运行中进程的实际限制
cat /proc/$(pgrep -f nginx | head -1)/limits
2.2.6 pam_access配置访问控制
pam_access通过 /etc/security/access.conf 控制谁能从哪里登录。比防火墙更细粒度——防火墙只能控制IP,pam_access可以控制"哪个用户从哪个IP登录"。
# 编辑访问控制配置
sudo vim /etc/security/access.conf
# /etc/security/access.conf
# 格式:permission : users/groups : origins
# permission: + 允许, - 拒绝
# origins: IP地址、网段、主机名、LOCAL(本地终端)、ALL
# 允许运维组从跳板机网段和本地终端登录
+ : @ops-team : 10.0.0.0/24 LOCAL
# 允许DBA组只从DBA跳板机登录
+ : @dba-team : 10.0.0.50
# 允许监控用户从监控服务器登录
+ : monitor : 10.0.0.60 10.0.0.61
# 允许root只从本地终端登录(物理控制台/VNC)
+ : root : LOCAL
# 拒绝其他所有访问(这行必须放最后)
- : ALL : ALL
这个配置的意思是:只有ops-team组能从10.0.0.0/24网段SSH登录,DBA只能从10.0.0.50登录,其他人全部拒绝。root只能在物理控制台操作,不能远程登录。
在PAM中启用pam_access:
# 编辑 /etc/pam.d/sshd 或 /etc/pam.d/system-auth
# 在account段添加:
# account required pam_access.so
# 或者只对sshd生效(推荐,不影响本地登录)
sudo vim /etc/pam.d/sshd
# 在account段添加:
# account required pam_access.so accessfile=/etc/security/access.conf
验证访问控制:
# 从允许的IP尝试登录
ssh -p 52222 opsadmin@目标服务器
# 预期:登录成功
# 从不允许的IP尝试登录
ssh -p 52222 opsadmin@目标服务器
# 预期:Permission denied
# 查看拒绝日志
sudo journalctl -u sshd | grep "access denied"
2.2.7 pam_time配置时间段访问控制
pam_time通过 /etc/security/time.conf 控制用户在什么时间段可以登录。比如限制开发人员只能在工作时间(周一到周五 9:00-18:00)登录生产服务器,非工作时间的操作必须走审批流程。
# 编辑时间控制配置
sudo vim /etc/security/time.conf
# /etc/security/time.conf
# 格式:services;ttys;users;times
# services: 服务名(sshd、login等),用|分隔多个
# ttys: 终端类型,*表示所有
# users: 用户名或@组名,!表示取反
# times: 时间段,格式为 日期时间范围
# 日期:Mo Tu We Th Fr Sa Su,Wk=工作日,Wd=周末,Al=所有
# 时间:HHMM-HHMM
# 开发人员只能在工作日9:00-18:00通过SSH登录
sshd;*;@developers;Wk0900-1800
# DBA只能在工作日8:00-22:00登录
sshd;*;@dba-team;Wk0800-2200
# 运维组不限制时间(7x24值班)
# 不配置就是不限制
# 禁止所有人在维护窗口(周日2:00-6:00)登录
# 用!取反实现
sshd;*;!root;Su0200-0600
在PAM中启用pam_time:
# 编辑 /etc/pam.d/sshd
# 在account段添加:
# account required pam_time.so
# 注意:pam_time只检查登录时刻,不会踢掉已经登录的用户
# 如果需要在时间到期后强制断开,需要配合脚本实现
2.2.8 sudo与PAM集成配置
sudo本身通过 /etc/sudoers 控制命令权限,但sudo的认证过程走PAM。/etc/pam.d/sudo 控制sudo时的认证行为——比如sudo时是否需要输密码、失败几次锁定、是否记录日志。
# 查看sudo的PAM配置
cat /etc/pam.d/sudo
生产环境sudoers分层设计:
# 编辑sudoers(必须用visudo,语法错误会导致sudo不可用)
sudo visudo
# 或者用独立文件(推荐,便于管理)
sudo visudo -f /etc/sudoers.d/ops-permissions
# /etc/sudoers.d/ops-permissions
# 运维组:可以执行所有命令,但需要输密码
%ops-team ALL=(ALL) ALL
# 高级运维:免密执行所有命令
%senior-ops ALL=(ALL) NOPASSWD: ALL
# DBA组:只能执行数据库相关命令
%dba-team ALL=(ALL) NOPASSWD: /usr/bin/mysql, /usr/bin/mysqldump, \
/usr/bin/mysqlbinlog, /usr/sbin/service mysqld *, \
/usr/bin/systemctl * mysqld, /usr/bin/systemctl * mariadb
# 开发人员:只能查看日志和重启应用
%developers ALL=(ALL) NOPASSWD: /usr/bin/tail -f /var/log/*, \
/usr/bin/less /var/log/*, \
/usr/bin/systemctl restart app-*, \
/usr/bin/systemctl status app-*
# 监控用户:只能执行监控相关命令
monitor ALL=(ALL) NOPASSWD: /usr/bin/top, /usr/bin/iostat, \
/usr/bin/vmstat, /usr/bin/free, /usr/bin/df, \
/usr/sbin/ss, /usr/bin/netstat
# 禁止所有人通过sudo执行的危险命令
Cmnd_Alias DANGEROUS = /usr/bin/rm -rf /, /usr/sbin/fdisk, \
/usr/sbin/mkfs*, /usr/bin/dd, /usr/sbin/shutdown, \
/usr/sbin/reboot, /usr/sbin/init, /usr/bin/kill -9 1
%ops-team ALL=(ALL) !DANGEROUS
sudo日志审计:
# 在 /etc/sudoers 中添加日志配置
# Defaults logfile="/var/log/sudo.log"
# Defaults log_input, log_output
# Defaults iolog_dir="/var/log/sudo-io/%{user}"
sudo visudo
# 添加以上三行(去掉注释符号)
# 查看sudo操作日志
sudo tail -f /var/log/sudo.log
# 回放sudo会话(查看用户具体执行了什么)
sudo sudoreplay -l
# 回放特定会话
sudo sudoreplay /var/log/sudo-io/opsadmin/00/00/01
2.2.9 pam_exec自定义脚本触发
pam_exec可以在认证的任意阶段执行自定义脚本。典型用途:用户登录时发送告警通知(钉钉/企业微信/邮件),用户登出时清理临时文件。
# 在 /etc/pam.d/sshd 的session段添加:
# session optional pam_exec.so /opt/scripts/login_notify.sh
登录通知脚本:
#!/bin/bash
# 文件名:/opt/scripts/login_notify.sh
# 功能:SSH登录时发送钉钉/企业微信告警
# PAM会传递以下环境变量:
# PAM_USER - 登录用户名
# PAM_RHOST - 来源IP
# PAM_SERVICE - 服务名(sshd)
# PAM_TYPE - 阶段类型(open_session/close_session)
# 只在登录时通知,登出不通知
if [ "$PAM_TYPE" != "open_session" ]; then
exit 0
fi
# 忽略系统用户
if [ "$(id -u "$PAM_USER" 2>/dev/null)" -lt 1000 ] 2>/dev/null; then
exit 0
fi
HOSTNAME=$(hostname)
LOGIN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN_HERE"
# 钉钉机器人通知
MESSAGE="SSH登录告警\n主机: ${HOSTNAME}\n用户: ${PAM_USER}\n来源IP: ${PAM_RHOST}\n时间: ${LOGIN_TIME}"
curl -s -H "Content-Type: application/json" \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MESSAGE}\"}}" \
"$WEBHOOK_URL" &>/dev/null &
# 后台执行curl,不阻塞登录过程
exit 0
# 设置脚本权限
sudo chmod 755 /opt/scripts/login_notify.sh
sudo chown root:root /opt/scripts/login_notify.sh
# 测试脚本(模拟PAM环境变量)
PAM_USER=opsadmin PAM_RHOST=192.168.1.50 PAM_SERVICE=sshd PAM_TYPE=open_session /opt/scripts/login_notify.sh
2.3 启动和验证
2.3.1 配置生效确认
PAM配置修改后立即生效,不需要重启任何服务。但sshd的PAM配置修改后,只对新建立的SSH连接生效,已有连接不受影响。
# 1. 验证PAM配置文件语法(没有专门的语法检查工具,只能通过测试验证)
# 先用一个测试用户验证,不要直接用生产账号
# 2. 创建测试用户
sudo useradd -m -s /bin/bash pamtest
sudo passwd pamtest
sudo usermod -aG sshusers pamtest
# 3. 新开终端测试登录(保持当前root会话不断开)
ssh -p 52222 pamtest@localhost
2.3.2 功能验证
# 1. 验证密码复杂度策略
sudo su - pamtest
passwd
# 尝试弱密码 "abc123" -> 预期被拒绝
# 尝试短密码 "Abc@1234" -> 预期被拒绝(不足12位)
# 尝试合规密码 "Str0ng@Pass2025" -> 预期成功
# 2. 验证登录失败锁定
# 连续用错误密码尝试5次
ssh -p 52222 pamtest@localhost # 输错密码5次
# 第6次尝试 -> 预期:账户被锁定
sudo faillock --user pamtest # 查看锁定状态
sudo faillock --user pamtest --reset # 解锁
# 3. 验证资源限制
su - pamtest
ulimit -n # 查看文件描述符限制
ulimit -u # 查看进程数限制
# 4. 验证访问控制(从不同IP测试)
# 从允许的IP登录 -> 成功
# 从不允许的IP登录 -> 被拒绝
# 5. 验证sudo权限分层
sudo su - pamtest
sudo systemctl status sshd # 根据sudoers配置决定是否允许
2.3.3 回滚方案
# 如果PAM配置改错导致无法登录:
# 1. 通过保留的root会话恢复
sudo cp -a /etc/pam.d.bak.$(date +%Y%m%d)/* /etc/pam.d/
# 2. 如果没有活跃会话,通过单用户模式恢复
# 重启服务器,在GRUB菜单按e编辑启动项
# 在linux行末尾添加 init=/bin/bash 或 single
# 按Ctrl+X启动
# 挂载根分区为读写:mount -o remount,rw /
# 恢复备份:cp -a /etc/pam.d.bak.*/* /etc/pam.d/
# 重启:exec /sbin/init
# 3. 云服务器通过VNC控制台操作
# 4. 物理机通过IPMI/iLO/iDRAC远程管理卡操作
三、示例代码和配置
3.1 完整配置示例
3.1.1 生产级 /etc/pam.d/system-auth 完整配置(CentOS 7)
这份配置整合了密码复杂度、登录锁定、资源限制,在我们团队管理的CentOS 7集群上跑了一年多,覆盖80+台服务器。CentOS 8+用authselect管理,不要直接抄这个文件。
# 文件路径:/etc/pam.d/system-auth
# 适用系统:CentOS 7 / RHEL 7
# 修改前务必备份:cp /etc/pam.d/system-auth /etc/pam.d/system-auth.bak
# ============================================
# auth 段 - 身份验证
# ============================================
# 设置环境变量
auth required pam_env.so
# 登录失败锁定 - 预检查(检查账户是否已被锁定)
auth required pam_faillock.so preauth silent audit deny=5 unlock_time=900 fail_interval=900
# UNIX密码验证(sufficient:密码正确则跳过后续auth模块)
auth sufficient pam_unix.so try_first_pass
# 登录失败锁定 - 记录失败(密码验证失败后记录)
auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900 fail_interval=900
# UID >= 1000 的用户才继续(过滤系统用户)
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
# 兜底拒绝(走到这里说明所有认证方式都失败了)
auth required pam_deny.so
# ============================================
# account 段 - 账户检查
# ============================================
# 检查faillock锁定状态
account required pam_faillock.so
# UNIX账户检查(密码过期、账户禁用等)
account required pam_unix.so
# 本地用户直接通过
account sufficient pam_localuser.so
# 系统用户(UID < 1000)直接通过
account sufficient pam_succeed_if.so uid < 1000 quiet
# 访问控制(基于access.conf)
account required pam_access.so
# 兜底允许
account required pam_permit.so
# ============================================
# password 段 - 密码修改
# ============================================
# 密码复杂度检查(参数在/etc/security/pwquality.conf中配置)
password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
# UNIX密码更新(sha512加密,记住最近12个密码)
password sufficient pam_unix.so sha512 shadow try_first_pass use_authtok remember=12
# 兜底拒绝
password required pam_deny.so
# ============================================
# session 段 - 会话管理
# ============================================
# 设置登录后的umask
session optional pam_keyinit.so revoke
session required pam_limits.so
# UNIX会话(记录utmp/wtmp)
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
# 登录通知脚本(可选)
session optional pam_exec.so /opt/scripts/login_notify.sh
3.1.2 PAM安全加固一键部署脚本
这个脚本把密码策略、登录锁定、资源限制、访问控制的配置打包成一键部署,支持CentOS 7和CentOS 8+两种模式。在新服务器交付时跑一次就行。
#!/bin/bash
# 文件名:pam_hardening.sh
# 功能:PAM安全加固一键部署
# 适用:CentOS 7/8/9, RHEL 7/8/9
# 用法:sudo bash pam_hardening.sh
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
# 检查root权限
if [[ $EUID -ne 0 ]]; then
log_error "必须以root权限运行"
exit 1
fi
# 检测系统版本
if [[ -f /etc/redhat-release ]]; then
OS_VERSION=$(rpm -E %{rhel})
log_info "检测到 RHEL/CentOS ${OS_VERSION}"
else
log_error "仅支持RHEL/CentOS系统"
exit 1
fi
# 备份
BACKUP_DIR="/etc/pam.d.bak.$(date +%Y%m%d_%H%M%S)"
cp -a /etc/pam.d "$BACKUP_DIR"
cp /etc/security/pwquality.conf /etc/security/pwquality.conf.bak.$(date +%Y%m%d)
cp /etc/security/limits.conf /etc/security/limits.conf.bak.$(date +%Y%m%d)
log_info "配置已备份到 $BACKUP_DIR"
# ===== 1. 配置密码复杂度 =====
log_info "配置密码复杂度策略..."
cat > /etc/security/pwquality.conf << 'PWEOF'
minlen = 12
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 8
maxrepeat = 3
maxsequence = 3
usercheck = 1
minclass = 3
enforce_for_root
retry = 3
PWEOF
# ===== 2. 配置登录失败锁定 =====
log_info "配置登录失败锁定..."
if [[ $OS_VERSION -ge 8 ]]; then
# CentOS 8+ 使用faillock.conf
cat > /etc/security/faillock.conf << 'FLEOF'
deny = 5
unlock_time = 900
fail_interval = 900
even_deny_root = false
silent
audit
dir = /var/run/faillock
FLEOF
# 通过authselect启用
authselect select sssd with-faillock --force 2>/dev/null || \
authselect select minimal with-faillock --force
else
# CentOS 7 手动配置(在2.2.4节已详细说明)
log_warn "CentOS 7需要手动编辑system-auth,请参考文档"
fi
# ===== 3. 配置资源限制 =====
log_info "配置资源限制..."
cat >> /etc/security/limits.conf << 'LIMEOF'
# === PAM Hardening Script Added ===
* soft nofile 65535
* hard nofile 131072
* soft nproc 4096
* hard nproc 8192
* soft memlock 65536
* hard memlock 65536
LIMEOF
# ===== 4. 创建密码历史文件 =====
touch /etc/security/opasswd
chmod 600 /etc/security/opasswd
chown root:root /etc/security/opasswd
# ===== 5. 配置密码过期策略 =====
log_info "配置密码过期策略..."
sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs
sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' /etc/login.defs
sed -i 's/^PASS_MIN_LEN.*/PASS_MIN_LEN 12/' /etc/login.defs
sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 14/' /etc/login.defs
log_info "===== PAM加固完成 ====="
log_info "备份目录: $BACKUP_DIR"
log_warn "请立即用新终端测试登录,确认配置正确"
log_warn "如果无法登录,用当前会话恢复: cp -a ${BACKUP_DIR}/* /etc/pam.d/"
3.2 实际应用案例
案例一:密码策略+登录锁定+资源限制组合配置
场景描述:某金融客户等保三级要求,密码12位以上含四种字符、90天强制更换、连续失败5次锁定15分钟、记住最近12次密码不允许重复。同时要限制普通用户资源使用,防止影响核心业务进程。
完整配置清单:
# 1. /etc/security/pwquality.conf - 密码复杂度
minlen = 12
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
difok = 8
maxrepeat = 3
maxsequence = 3
usercheck = 1
minclass = 3
enforce_for_root
retry = 3
# 2. /etc/security/faillock.conf - 登录锁定(CentOS 8+)
deny = 5
unlock_time = 900
fail_interval = 900
even_deny_root = false
silent
audit
# 3. /etc/login.defs - 密码过期策略
PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_MIN_LEN 12
PASS_WARN_AGE 14
# 4. /etc/security/limits.conf - 资源限制
* soft nofile 65535
* hard nofile 131072
* soft nproc 4096
* hard nproc 8192
@developers soft nofile 4096
@developers hard nofile 8192
@developers soft nproc 1024
@developers hard nproc 2048
@developers hard core 0
验证结果:
# 密码策略验证
$ passwd testuser
Changing password for user testuser.
New password: abc123
BAD PASSWORD: The password is shorter than 12 characters
New password: abcdefghijkl
BAD PASSWORD: The password fails the dictionary check - it is based on a dictionary word
New password: T3st@Secure2025
passwd: all authentication tokens updated successfully.
# 锁定验证(连续5次错误密码后)
$ ssh testuser@localhost
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
# 查看锁定状态
$ sudo faillock --user testuser
testuser:
When Type Source Valid
2025-01-15 14:23:01 RHOST 127.0.0.1 V
2025-01-15 14:23:05 RHOST 127.0.0.1 V
2025-01-15 14:23:08 RHOST 127.0.0.1 V
2025-01-15 14:23:11 RHOST 127.0.0.1 V
2025-01-15 14:23:14 RHOST 127.0.0.1 V
案例二:限制特定用户只能在工作时间登录
场景描述:外包开发人员只允许在工作日9:00-18:00通过SSH登录生产服务器查看日志,非工作时间禁止登录。运维组不受时间限制。
实现步骤:
# 创建外包开发组
sudo groupadd outsource-dev
# 将外包人员加入组
sudo usermod -aG outsource-dev dev-wang
sudo usermod -aG outsource-dev dev-li
# 编辑 /etc/security/time.conf
# 外包开发组只能在工作日9:00-18:00登录
sshd;*;@outsource-dev;Wk0900-1800
# 编辑 /etc/pam.d/sshd,在account段添加
# account required pam_time.so
# 工作时间内测试(假设当前是周三14:00)
ssh dev-wang@server
# 预期:登录成功
# 非工作时间测试(假设当前是周六10:00)
ssh dev-wang@server
# 预期:Authentication failure
# 日志中会记录:pam_time(sshd:account): access denied for user 'dev-wang'
# 运维用户不受限制
ssh opsadmin@server
# 预期:任何时间都能登录
案例三:基于pam_exec的登录审计与告警联动
场景描述:安全合规要求所有SSH登录必须有审计记录,非工作时间登录和非常用IP登录需要实时告警。通过pam_exec在登录时触发审计脚本,记录详细信息并根据规则发送告警。
审计脚本:
#!/bin/bash
# 文件名:/opt/scripts/login_audit.sh
# 功能:SSH登录审计+异常告警
# 在 /etc/pam.d/sshd 的session段调用:
# session optional pam_exec.so /opt/scripts/login_audit.sh
# 只处理登录事件
[ "$PAM_TYPE" != "open_session" ] && exit 0
# 审计日志文件
AUDIT_LOG="/var/log/ssh_audit.log"
# 告警webhook(钉钉/企业微信)
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
# 常用IP白名单文件
WHITELIST_FILE="/etc/security/ssh_ip_whitelist.conf"
HOSTNAME=$(hostname)
LOGIN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
HOUR=$(date '+%H')
DAY_OF_WEEK=$(date '+%u') # 1=周一, 7=周日
# 记录审计日志
echo"${LOGIN_TIME} | USER=${PAM_USER} | FROM=${PAM_RHOST} | SERVICE=${PAM_SERVICE} | HOST=${HOSTNAME}" >> "$AUDIT_LOG"
# 判断是否需要告警
NEED_ALERT=false
ALERT_REASON=""
# 规则1:非工作时间登录(22:00-08:00 或 周末)
if [ "$HOUR" -ge 22 ] || [ "$HOUR" -lt 8 ] || [ "$DAY_OF_WEEK" -ge 6 ]; then
NEED_ALERT=true
ALERT_REASON="非工作时间登录"
fi
# 规则2:非白名单IP登录
if [ -f "$WHITELIST_FILE" ]; then
if ! grep -q "^${PAM_RHOST}$""$WHITELIST_FILE" 2>/dev/null; then
NEED_ALERT=true
ALERT_REASON="${ALERT_REASON:+${ALERT_REASON}, }非白名单IP"
fi
fi
# 规则3:root用户登录(即使允许也要告警)
if [ "$PAM_USER" = "root" ]; then
NEED_ALERT=true
ALERT_REASON="${ALERT_REASON:+${ALERT_REASON}, }root用户登录"
fi
# 发送告警
if$NEED_ALERT; then
MESSAGE="SSH登录告警\n原因: ${ALERT_REASON}\n主机: ${HOSTNAME}\n用户: ${PAM_USER}\n来源: ${PAM_RHOST}\n时间: ${LOGIN_TIME}"
curl -s -H "Content-Type: application/json" \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MESSAGE}\"}}" \
"$WEBHOOK_URL" &>/dev/null &
fi
exit 0
# IP白名单文件
cat > /etc/security/ssh_ip_whitelist.conf << 'EOF'
10.0.0.1
10.0.0.2
10.0.0.50
172.16.1.100
EOF
# 设置权限
sudo chmod 755 /opt/scripts/login_audit.sh
sudo chown root:root /opt/scripts/login_audit.sh
sudo touch /var/log/ssh_audit.log
sudo chmod 640 /var/log/ssh_audit.log
审计日志输出示例:
2025-01-15 14:23:45 | USER=opsadmin | FROM=10.0.0.50 | SERVICE=sshd | HOST=web-prod-01
2025-01-15 22:15:30 | USER=deployer | FROM=192.168.1.88 | SERVICE=sshd | HOST=web-prod-01
2025-01-16 03:42:11 | USER=root | FROM=10.0.0.1 | SERVICE=sshd | HOST=db-prod-01
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 性能优化
PAM模块加载顺序影响认证速度:PAM按配置文件中的顺序依次加载模块。把最可能成功的认证方式放前面,用sufficient标志实现"快速通过"。比如本地密码认证放在LDAP认证前面,本地用户不需要等LDAP查询。实测在500人LDAP环境中,本地用户认证从平均1.2秒降到0.05秒。
# 优化前:先查LDAP再查本地
auth sufficient pam_ldap.so
auth sufficient pam_unix.so
# 优化后:先查本地再查LDAP
auth sufficient pam_unix.so try_first_pass
auth sufficient pam_ldap.so use_first_pass
pam_exec脚本必须异步执行耗时操作:pam_exec调用的脚本如果执行时间长(比如发HTTP请求),会阻塞登录过程。curl发钉钉通知要加 & 后台执行,脚本本身立即返回exit 0。实测不加后台执行时,钉钉接口偶尔超时会导致SSH登录卡住10秒以上。
# 错误写法:同步执行curl,会阻塞登录
curl -s -d "$DATA""$WEBHOOK_URL"
# 正确写法:后台执行,不阻塞
curl -s -d "$DATA""$WEBHOOK_URL" &>/dev/null &
exit 0
faillock记录目录用tmpfs:faillock默认把锁定记录写在 /var/run/faillock/,这个目录在多数系统上是tmpfs(内存文件系统),读写速度快且重启自动清空。如果改到磁盘目录,高并发认证时磁盘IO会成为瓶颈。
# 确认/var/run是tmpfs
df -T /var/run
# 输出应该是tmpfs类型
# faillock.conf中保持默认
dir = /var/run/faillock
4.1.2 安全加固
PAM配置修改前必须保留rescue终端:这条怎么强调都不过分。PAM配置错误会导致所有用户(包括root)无法登录。改之前开一个root的tmux/screen会话挂着,或者确认有VNC/IPMI等带外管理通道。我们团队有个不成文的规定:改PAM配置必须两人在场,一人操作一人保持会话。
# 改配置前的标准操作流程
# 1. 开一个tmux会话保持root登录
tmux new -s rescue
# 2. 备份当前配置
sudo cp -a /etc/pam.d /etc/pam.d.bak.$(date +%Y%m%d_%H%M%S)
# 3. 修改配置
# 4. 新开终端测试登录(不要断开rescue会话)
# 5. 确认正常后才关闭rescue会话
使用pam_faillock替代已废弃的pam_tally2:CentOS 8开始移除了pam_tally2模块,继续用会导致认证链断裂。pam_faillock功能更完善,支持独立配置文件(faillock.conf),参数调整不用改PAM配置文件。如果你还在CentOS 7上用pam_tally2,建议趁早迁移到pam_faillock,CentOS 7也支持。
# 检查是否还在用pam_tally2
grep pam_tally2 /etc/pam.d/*
# 如果有输出,说明还在用旧模块,需要替换
# 替换方法:把pam_tally2行替换为pam_faillock
# 旧:auth required pam_tally2.so deny=5 unlock_time=900
# 新:auth required pam_faillock.so preauth deny=5 unlock_time=900
分层权限设计:不要给所有运维人员相同的权限。我们团队的四层权限模型:
# /etc/sudoers.d/permission-levels
# L1 - 只读权限
%l1-users ALL=(ALL) NOPASSWD: /usr/bin/tail -f /var/log/*, \
/usr/bin/systemctl status *
# L2 - 应用运维
%l2-ops ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart app-*, \
/usr/bin/systemctl stop app-*, \
/usr/bin/systemctl start app-*, \
/usr/bin/tail -f /var/log/*, \
/usr/bin/less /var/log/*
# L3 - 高级运维
%l3-senior ALL=(ALL) ALL
%l3-senior ALL=(ALL) !DANGEROUS
# L4 - 超级管理员
%l4-admin ALL=(ALL) ALL
- L1 普通用户:只能查看日志、查看服务状态,不能重启服务、不能sudo
- L2 运维人员:可以重启应用服务、查看配置文件,sudo受限命令列表
- L3 高级运维/DBA:可以操作数据库、修改配置文件,sudo较宽但排除危险命令
- L4 超级管理员:完整sudo权限,但所有操作有审计日志
结合LDAP实现集中认证:超过20台服务器时,本地账户管理不现实。用LDAP(OpenLDAP或FreeIPA)集中管理用户,PAM通过pam_ldap或pam_sss对接LDAP。人员入职在LDAP加账号,所有服务器自动生效;离职在LDAP禁用,所有服务器立即无法登录。
4.1.3 高可用配置
- LDAP认证的高可用:LDAP服务器挂了会导致所有依赖LDAP认证的服务器无法登录。必须配置LDAP主从复制,PAM配置中指定多个LDAP服务器地址。同时保留本地管理员账号作为应急通道。
- PAM配置的版本管理:把
/etc/pam.d/ 和 /etc/security/ 下的配置文件纳入Ansible/Puppet管理,用Git做版本控制。配置变更走代码审查流程,避免手动修改导致的不一致。 - 备份策略:每次修改PAM配置前自动备份,保留最近30天的备份。建议用crontab每天备份一次
/etc/pam.d/ 目录。
4.2 注意事项
4.2.1 配置注意事项
PAM配置改错的后果比SSH配置改错严重得多。SSH改错最多是远程连不上,PAM改错是所有认证方式全部失效,本地终端也登不了。
- PAM配置文件中模块的顺序决定执行顺序,顺序错了逻辑就错了。pam_faillock的preauth必须在pam_unix之前,authfail必须在pam_unix之后
sufficient 标志的模块成功后会跳过后续同类型模块,放错位置会导致安全检查被跳过/etc/pam.d/system-auth 被多个服务引用(sshd、su、sudo、login),改这个文件影响面最大,改之前想清楚- CentOS 8+用authselect管理PAM配置,手动编辑system-auth会被authselect覆盖。要么用authselect的自定义profile,要么在
/etc/pam.d/sshd 等服务专用文件中修改 pam_deny.so 作为兜底模块放在auth段最后,确保所有认证方式都失败时返回拒绝,不要漏掉
4.2.2 常见错误
| | |
|---|
| | |
| passwd修改密码报"Authentication token manipulation error" | /etc/security/opasswd文件不存在或权限不对 | touch /etc/security/opasswd && chmod 600 /etc/security/opasswd |
| | 换一个不包含字典词的密码,或调整dictpath参数 |
| | 用 faillock --user USERNAME --reset 而不是 pam_tally2 --reset |
| PAM配置中缺少pam_limits.so,或limits.conf语法错误 | 检查 /etc/pam.d/sshd 中是否有 session required pam_limits.so |
| sudo报"account validation failure" | | 检查 /etc/pam.d/sudo 的account段,确认pam_unix.so存在 |
| access.conf中规则顺序错误,- : ALL : ALL 放在了允许规则前面 | 允许规则必须在拒绝规则前面,PAM按顺序匹配第一条命中的规则 |
4.2.3 兼容性问题
- 版本兼容:pam_faillock在CentOS 7上可用但没有独立配置文件(faillock.conf),参数只能写在PAM配置行内。CentOS 8+支持faillock.conf独立配置。pam_tally2在CentOS 8+已移除,升级系统后必须迁移到pam_faillock。
- 平台兼容:CentOS/RHEL使用
/etc/pam.d/system-auth 和 /etc/pam.d/password-auth,Ubuntu/Debian使用 /etc/pam.d/common-auth 和 /etc/pam.d/common-password。模块路径也不同:CentOS在 /usr/lib64/security/,Ubuntu在 /usr/lib/x86_64-linux-gnu/security/。 - 组件依赖:pam_pwquality依赖libpwquality和cracklib-dicts(密码字典)。最小化安装的系统可能缺少cracklib-dicts,导致字典检查不生效。用
rpm -q cracklib-dicts 或 dpkg -l libcrack2 确认已安装。 - authselect兼容:CentOS 8+引入authselect替代authconfig。直接编辑system-auth会被authselect覆盖。如果需要自定义PAM配置,用
authselect create-profile 创建自定义profile。
五、故障排查和监控
5.1 故障排查
5.1.1 日志查看
# 查看PAM相关的认证日志
# CentOS/RHEL
sudo tail -f /var/log/secure
# Ubuntu/Debian
sudo tail -f /var/log/auth.log
# 用journalctl过滤PAM相关日志
sudo journalctl | grep -i "pam_"
# 只看最近30分钟的PAM日志
sudo journalctl --since "30 minutes ago" | grep -i "pam_"
# 查看特定模块的日志
sudo journalctl | grep "pam_faillock"
sudo journalctl | grep "pam_pwquality"
sudo journalctl | grep "pam_access"
5.1.2 常见问题排查
问题一:PAM配置错误导致所有用户无法登录
这是PAM故障中最严重的情况。一旦发生,只能通过不经过PAM认证的方式恢复。
# 恢复方法1:通过已保留的root会话恢复
# 如果改配置前开了tmux/screen会话,直接在那个会话里操作
sudo cp -a /etc/pam.d.bak.20250115/* /etc/pam.d/
# 恢复方法2:单用户模式(物理机或VNC)
# 重启服务器,在GRUB菜单按e
# 找到linux行,在末尾添加 init=/bin/bash
# 按Ctrl+X启动
mount -o remount,rw /
cp -a /etc/pam.d.bak.*/* /etc/pam.d/
# 如果没有备份,恢复最小可用配置:
cat > /etc/pam.d/system-auth << 'EOF'
auth required pam_env.so
auth sufficient pam_unix.so try_first_pass
auth required pam_deny.so
account required pam_unix.so
password sufficient pam_unix.so sha512 shadow try_first_pass use_authtok
password required pam_deny.so
session required pam_limits.so
session required pam_unix.so
EOF
exec /sbin/init
# 恢复方法3:云服务器通过VNC控制台
# 登录云控制台 -> 实例管理 -> 远程连接 -> VNC
# 用root密码登录(VNC登录走/etc/pam.d/login,如果这个也改坏了就只能用方法2)
# 恢复方法4:挂载磁盘到另一台机器
# 云服务器可以卸载系统盘挂到另一台ECS上修复
问题二:密码策略过严导致无法修改密码
# 症状:passwd命令反复提示BAD PASSWORD,用户怎么设都不通过
# 排查:查看当前密码策略
cat /etc/security/pwquality.conf | grep -v "^#" | grep -v "^$"
# 常见原因1:minlen设太高(比如设了20)
# 解决:临时调低minlen,用户改完密码再调回来
sudo sed -i 's/^minlen.*/minlen = 12/' /etc/security/pwquality.conf
# 常见原因2:cracklib字典匹配过于严格
# 排查:用pwscore命令测试密码得分
echo"MyNewP@ss2025" | pwscore
# 输出分数,低于配置的阈值就会被拒绝
# 常见原因3:remember参数导致最近用过的密码不能重复
# 排查:
cat /etc/security/opasswd
# 如果需要清除密码历史(紧急情况)
sudo truncate -s 0 /etc/security/opasswd
# root用户绕过密码策略(应急用,不建议常规使用)
# root执行passwd时pwquality会警告但不会阻止
sudo passwd username
问题三:sudo认证失败排查
# 症状:sudo命令报 "sudo: PAM account management error: Authentication service cannot retrieve authentication info"
# 或者 "sudo: a]ccount validation failure, is your account locked?"
# 排查步骤:
# 1. 检查sudo的PAM配置
cat /etc/pam.d/sudo
# 2. 检查用户账户状态
sudo passwd -S username
# 输出含义:
# username PS 2025-01-15 1 90 14 -1 (Password set, SHA512 crypt.)
# PS=密码已设置, LK=已锁定, NP=无密码
# 3. 检查faillock是否锁定了用户
sudo faillock --user username
# 4. 检查账户是否过期
sudo chage -l username
# 关注 "Account expires" 和 "Password expires" 字段
# 5. 如果是LDAP用户,检查LDAP连接
sudo systemctl status sssd
sudo sssctl user-checks username
# 解决:
# 解锁faillock
sudo faillock --user username --reset
# 解锁passwd锁定
sudo passwd -u username
# 延长账户有效期
sudo chage -E -1 username
问题四:PAM模块加载失败
# 症状:日志中出现 "PAM unable to dlopen" 或 "Module is unknown"
# 原因:模块.so文件不存在或路径错误
# 排查:
# 1. 确认模块文件存在
ls -la /usr/lib64/security/pam_faillock.so
# 如果不存在,安装对应的包
sudo yum provides "*/pam_faillock.so"
# 输出会告诉你需要安装哪个包
# 2. 检查模块依赖
ldd /usr/lib64/security/pam_faillock.so
# 如果有 "not found" 的依赖库,需要安装
# 3. 检查SELinux是否阻止了模块加载
sudo ausearch -m avc -ts recent | grep pam
# 如果有denied记录,临时关闭SELinux测试
sudo setenforce 0
# 确认是SELinux问题后,添加策略而不是永久关闭
sudo audit2allow -a -M pam_custom
sudo semodule -i pam_custom.pp
5.1.3 调试模式
# PAM没有专门的调试命令,但可以通过以下方式排查
# 1. 用pamtester工具测试(需要安装)
sudo yum install -y pamtester # CentOS
sudo apt install -y pamtester # Ubuntu
# 测试sshd服务对某用户的认证
pamtester sshd opsadmin authenticate
# 会提示输入密码,然后显示认证结果
# 测试account检查
pamtester sshd opsadmin acct_mgmt
# 2. 临时开启PAM调试日志
# 在模块参数中添加 debug 关键字
# 例如:auth required pam_faillock.so preauth deny=5 debug
# 改完后查看 /var/log/secure 会有详细的模块执行日志
# 排查完务必去掉debug参数,否则日志量很大
# 3. 用strace跟踪PAM模块加载
sudo strace -f -e trace=open,openat -p $(pgrep -f sshd | head -1) 2>&1 | grep pam
# 可以看到sshd加载了哪些PAM模块文件
5.2 性能监控
5.2.1 关键指标监控
# 认证失败次数统计(最近1小时)
sudo journalctl --since "1 hour ago" | grep -c "authentication failure"
# 被faillock锁定的用户数
sudo faillock 2>/dev/null | grep -c "V$"
# 密码修改失败次数
sudo journalctl --since "1 hour ago" | grep -c "pam_pwquality"
# 当前登录用户数
who | wc -l
# sudo执行次数统计
sudo grep -c "COMMAND=" /var/log/sudo.log 2>/dev/null || echo 0
# 账户状态检查(过期账户数)
sudo awk -F: '{if($8!="" && $8<'$(date +%s)'/86400) print $1}' /etc/shadow | wc -l
5.2.2 监控指标说明
5.2.3 Prometheus监控规则
# pam_security_rules.yml
# 文件路径:/etc/prometheus/rules/pam_rules.yml
groups:
-name:pam_security
interval:60s
rules:
# 认证失败率告警
-alert:PAMAuthFailureHigh
expr:rate(pam_auth_failures_total[5m])>5
for:5m
labels:
severity:warning
annotations:
summary:"PAM认证失败率过高 ({{ $labels.instance }})"
description:"5分钟内认证失败率超过5次/秒,可能遭受暴力破解或PAM配置异常"
# 用户被锁定告警
-alert:PAMUserLocked
expr:pam_locked_users>3
for:2m
labels:
severity:warning
annotations:
summary:"多个用户被faillock锁定 ({{ $labels.instance }})"
description:"当前有 {{ $value }} 个用户被锁定"
# 密码即将过期告警
-alert:PAMPasswordExpiringSoon
expr:pam_password_expiring_users>0
for:1h
labels:
severity:info
annotations:
summary:"有用户密码即将过期 ({{ $labels.instance }})"
description:"{{ $value }} 个用户的密码将在14天内过期"
# sudo异常高频使用
-alert:SudoUsageHigh
expr:rate(sudo_commands_total[5m])>10
for:5m
labels:
severity:warning
annotations:
summary:"sudo使用频率异常 ({{ $labels.instance }})"
description:"sudo命令执行频率超过10次/秒,请检查是否有异常操作"
5.3 备份与恢复
5.3.1 备份策略
#!/bin/bash
# 文件名:backup_pam_config.sh
# 功能:备份PAM和安全相关配置
# 建议每天执行一次,保留最近30天
BACKUP_BASE="/data/backup/pam"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="$BACKUP_BASE/$DATE"
KEEP_DAYS=30
mkdir -p "$BACKUP_DIR"
# 备份PAM配置目录
sudo cp -a /etc/pam.d "$BACKUP_DIR/"
# 备份安全配置文件
sudo cp -a /etc/security/pwquality.conf "$BACKUP_DIR/"
sudo cp -a /etc/security/limits.conf "$BACKUP_DIR/"
sudo cp -a /etc/security/access.conf "$BACKUP_DIR/"
sudo cp -a /etc/security/time.conf "$BACKUP_DIR/" 2>/dev/null
sudo cp -a /etc/security/faillock.conf "$BACKUP_DIR/" 2>/dev/null
sudo cp -a /etc/security/opasswd "$BACKUP_DIR/"
# 备份账户相关文件
sudo cp -a /etc/login.defs "$BACKUP_DIR/"
sudo cp -a /etc/sudoers "$BACKUP_DIR/"
sudo cp -a /etc/sudoers.d "$BACKUP_DIR/" 2>/dev/null
# 设置备份权限
sudo chmod -R 600 "$BACKUP_DIR"
sudo chmod 700 "$BACKUP_DIR"
# 清理过期备份
find "$BACKUP_BASE" -maxdepth 1 -type d -mtime +$KEEP_DAYS -exec rm -rf {} \;
echo"PAM配置备份完成: $BACKUP_DIR"
5.3.2 恢复流程
确认恢复通道:
# 确保有不经过PAM的访问方式(VNC/IPMI/物理控制台)
# 或者有一个活跃的root会话
who am i
恢复PAM配置:
# 找到最近的备份
ls -lt /data/backup/pam/ | head -5
# 恢复配置
RESTORE_DIR="/data/backup/pam/20250115_020000"
sudo cp -a "$RESTORE_DIR/pam.d/"* /etc/pam.d/
sudo cp "$RESTORE_DIR/pwquality.conf" /etc/security/
sudo cp "$RESTORE_DIR/limits.conf" /etc/security/
sudo cp "$RESTORE_DIR/access.conf" /etc/security/
验证恢复结果:
# 新开终端测试登录
ssh opsadmin@localhost
# 测试sudo
sudo whoami
恢复sudoers(如果需要):
# sudoers文件必须用visudo验证语法
sudo cp "$RESTORE_DIR/sudoers" /tmp/sudoers.restore
sudo visudo -c -f /tmp/sudoers.restore
# 语法正确后再覆盖
sudo cp /tmp/sudoers.restore /etc/sudoers
sudo chmod 440 /etc/sudoers
六、总结
6.1 技术要点回顾
- PAM是Linux认证的底层框架:SSH登录、su切换、sudo提权、passwd改密码,所有认证行为都经过PAM。只改sudoers不碰PAM,等于只做了权限管控的表面功夫。
- 四种管理类型各司其职:auth验证身份、account检查账户状态、password控制密码修改、session管理会话环境。一次完整的登录流程会依次经过这四个阶段,每个阶段都可以插入安全控制逻辑。
- pam_pwquality + pam_faillock是密码安全的基础组合:pwquality从策略层面强制密码复杂度(12位+四种字符),faillock从行为层面防暴力破解(5次失败锁定15分钟)。两者配合覆盖了密码安全的两个维度。
- pam_limits防止资源滥用:不限制nofile和nproc的服务器,一个fork炸弹或文件描述符泄漏就能把整台机器搞挂。生产环境必须配置合理的资源上限。
- 分层权限设计是管理的核心:L1只读、L2应用运维、L3高级运维、L4超级管理员,每层权限边界清晰。配合pam_access控制登录来源、pam_time控制登录时间,实现立体化的权限管控。
- 改PAM配置前必须有恢复手段:这条规矩比任何技术细节都重要。备份配置、保留rescue会话、确认VNC/IPMI可用,三选一必须满足。
6.2 进阶学习方向
- FreeIPA整合了LDAP、Kerberos、DNS、CA,提供Web界面管理用户、组、主机、HBAC策略
- PAM通过SSSD对接FreeIPA,实现集中认证和授权
- 学习资源:FreeIPA官方文档 https://www.freeipa.org/page/Documentation
- 实践建议:用两台虚拟机搭建FreeIPA主从,配置客户端加入域,体验集中管理的便利
- Google Authenticator的PAM模块(pam_google_authenticator)可以给SSH登录加上TOTP二次验证
- 配置方式:在auth段添加
auth required pam_google_authenticator.so,配合 AuthenticationMethods publickey,keyboard-interactive 实现密钥+OTP双因素 - 学习资源:Google Authenticator PAM模块 GitHub仓库
- 实践建议:先在测试环境配置,注意保留应急登录通道,OTP配置错误会锁死所有用户
- PAM模块本质是实现了特定接口的共享库(.so文件),用C语言开发
- 可以开发自定义模块实现特殊需求:比如对接内部审批系统、实现地理位置限制、集成硬件令牌
- 学习资源:Linux-PAM Module Writers' Guide
- 实践建议:从最简单的pam_permit.c源码开始读,理解PAM模块的接口规范
6.3 参考资料
- Linux-PAM官方文档 - PAM架构、模块接口、配置语法的权威说明
- Red Hat PAM配置指南 - RHEL 8/9的PAM和authselect配置指南
- CIS Benchmark for Linux - CIS安全基线中密码策略和账户锁定章节
- pam_faillock手册页 - faillock模块的完整参数说明
- libpwquality文档 - 密码质量检查库的源码和文档
附录
A. 命令速查表
# ===== PAM配置管理 =====
cat /etc/pam.d/sshd # 查看SSH的PAM配置
cat /etc/pam.d/system-auth # 查看系统认证主配置(CentOS)
cat /etc/pam.d/common-auth # 查看系统认证主配置(Ubuntu)
ls /usr/lib64/security/pam_*.so # 列出已安装的PAM模块(CentOS)
sudo authselect current # 查看当前authselect配置(CentOS 8+)
sudo authselect list-features sssd # 列出可用的authselect特性
# ===== 密码策略管理 =====
cat /etc/security/pwquality.conf # 查看密码复杂度配置
echo"TestPass123" | pwscore # 测试密码强度评分
sudo chage -l username # 查看用户密码过期信息
sudo chage -M 90 -m 1 -W 14 username # 设置密码90天过期、最少1天、提前14天警告
sudo passwd -S username # 查看用户密码状态
# ===== faillock管理 =====
sudo faillock --user username # 查看用户失败记录
sudo faillock --user username --reset # 解锁用户(清除失败记录)
sudo faillock # 查看所有用户的失败记录
# ===== 资源限制管理 =====
ulimit -a # 查看当前用户所有限制
ulimit -n # 查看最大打开文件数
ulimit -u # 查看最大进程数
cat /proc/PID/limits # 查看指定进程的实际限制
# ===== 账户管理 =====
sudo passwd -l username # 锁定账户
sudo passwd -u username # 解锁账户
sudo usermod -aG groupname username # 将用户加入组
id username # 查看用户的UID和组信息
# ===== sudo管理 =====
sudo visudo # 安全编辑sudoers
sudo visudo -c # 检查sudoers语法
sudo -l -U username # 查看用户的sudo权限
sudo sudoreplay -l # 列出sudo会话记录
# ===== 调试排查 =====
pamtester sshd username authenticate # 测试PAM认证
sudo journalctl | grep "pam_"# 查看PAM相关日志
ldd /usr/lib64/security/pam_faillock.so # 检查模块依赖
B. 配置参数详解
pwquality.conf 关键参数:
faillock.conf 关键参数:
limits.conf 常用资源类型:
C. 术语表
| | |
|---|
| Pluggable Authentication Modules | 可插拔认证模块,Linux统一认证框架,控制登录、su、sudo等所有认证行为 |
| | PAM四种管理类型之一,负责验证用户身份(密码、密钥、OTP等) |
| | PAM四种管理类型之一,负责检查账户状态(是否过期、是否锁定、是否允许访问) |
| | PAM四种管理类型之一,负责密码修改时的策略检查和更新操作 |
| | PAM四种管理类型之一,负责登录后的会话环境设置(资源限制、日志记录等) |
| | PAM控制标志,模块失败会记录但继续检查后续模块,最终返回失败 |
| | PAM控制标志,模块失败立即返回,不再检查后续模块 |
| | PAM控制标志,模块成功且前面没有required失败则立即返回成功 |
| | 密码质量检查模块,强制密码复杂度策略,替代旧的pam_cracklib |
| | 登录失败锁定模块,统计失败次数并锁定账户,替代已废弃的pam_tally2 |
| | 资源限制模块,通过limits.conf控制用户的文件描述符、进程数等资源配额 |
| | 访问控制模块,通过access.conf控制用户从哪些来源可以登录 |
| | CentOS 8+的认证配置管理工具,替代旧的authconfig,统一管理PAM配置 |
| System Security Services Daemon | 系统安全服务守护进程,用于对接LDAP/FreeIPA等集中认证服务 |
| Host-Based Access Control | 基于主机的访问控制,FreeIPA中控制用户能登录哪些主机的策略 |