一次RADIUS服务器EAP-TLS证书过期导致全公司802.1X认证大规模掉线的排查实录

问题背景

公司办公网采用 802.1X 认证,无线和有线全部走 FreeRADIUS + Microsoft AD CS 颁发的 EAP-TLS 客户端证书,三年前由前同事搭建,当时写了个 openssl 一键生成脚本签了 10 年期的自建 CA,所有终端的根证书、FreeRADIUS 服务器证书、客户端证书全部挂在这棵 CA 下。

2026 年 6 月 28 日上午 9 点 47 分开始,全公司 8 栋办公楼先后出现大规模无线断连和有线认证失败,研发部门、客服中心、工厂车间反馈最为集中。Zabbix 上 AP 的认证成功率从 99.6% 直线掉到 23%,无线控制器每秒钟有 400+ 重新认证请求堆积。我们的 2000+ 员工终端、IoT 设备、IP 电话几乎全部受影响,初步估计单是业务停滞损失每小时就在十万级别。事故发生前一周,刚好有同事反馈”Windows 提示网络证书有问题”,但当时没人在意——这就是典型的预警被忽视引发的连锁反应。

故障现象

9 点 47 分开始,IT 群开始密集弹消息:

1
2
3
4
5
【研发2部-工位A12】电脑右下角弹"无法连接到网络 WiFi"
【研发2部-工位A13】重连失败,提示"无法验证服务器身份"
【客服中心】IP电话全部掉线,话机显示注册失败
【6楼车间】扫码枪连不上AP,无法作业
【财务部】连有线网提示"无法完成身份验证"
  • 无线场景:手机和笔记本弹出”无法验证服务器身份 / Unable to verify server identity”,但 SSID 还是能搜到,点击连接后直接失败
  • 有线场景:插了网线的工位弹出黄色感叹号,登录界面反复弹出要求输入域账号但怎么输都通不过
  • IoT 设备:IP 电话、扫码枪、门禁设备、打印机几乎全部离线

抓包看无线控制器上的 Radius 报文:

1
2
3
4
5
6
7
[FreeRADIUS log] (5) eap_tls: ERROR: TLS Alert read:fatal:certificate unknown
[FreeRADIUS log] (5) eap_tls: ERROR: TLS_accept: Failed in unknown state
[FreeRADIUS log] (5) eap: ERROR: Failed continuing EAP TLS (13) session
[FreeRADIUS log] (5) [eap] = reject
[FreeRADIUS log] (5) [authz] = reject
[FreeRADIUS log] (5) Auth: (5) Login incorrect (eap_tls: Failed to verify certificate):
[FreeRADIUS log] (5) } # server outer-eappeap-inner-tls { ... }

而前一天最后一次成功的认证日志时间戳是 6 月 27 日 23:58:14。问题集中在上午 9 点 47 分之后,所有终端都无法完成 EAP-TLS 握手。

排查过程

第一阶段:现场确认范围

第一反应是 Radius 服务挂了,先用 SSH 登入两台 FreeRADIUS 节点查看服务状态:

1
2
3
4
5
6
7
8
9
# radius01 和 radius02 是 FreeRADIUS 主备集群
ssh radius01 "systemctl status freeradius | head -20"
# ● freeradius.service - FreeRADIUS multi-protocol policy server
# Active: active (running) since Mon 2024-01-15 14:22:08 CST
# 一切正常

ssh radius01 "radtest test test 127.0.0.1 0 testing123"
# Received Access-Reject
# 嗯,本机 PAP 测试也失败

这个 PAP 测试失败很关键——这意味着不是网络问题、不是设备问题,是 FreeRADIUS 本身拒绝认证。如果 Radius 进程崩溃、配置错误,至少本地 radtest 应该成功。

第二阶段:定位具体错误原因

打开 FreeRADIUS 的详细日志模式:

1
2
3
ssh radius01 "radiusd -X 2>&1 | tee /tmp/radius-debug.log" &
# 模拟一次无线认证
radtest user@corp password 10.1.1.100 0 testing123

详细日志关键片段:

1
2
3
4
(1) eap_tls: ERROR: SSL says error 10 : certificate has expired
(1) eap_tls: ERROR: TLS Alert write:fatal:certificate expired
(1) eap: ERROR: Failed continuing EAP TLS (13) session
(1) [eap] = reject

根因锁定:RADIUS 服务器上 EAP-TLS 链路用的证书已经过期。这个证书就是 FreeRADIUS 服务器证书,是当年用自建 CA 签的,签发 10 年有效期,已经在 6 月 28 日 00:00:00 过期。

第三阶段:确认所有关联证书状态

接下来要查清楚:到底过期了哪几张证书,根 CA 有没有一起过期:

1
2
3
4
5
6
7
8
9
10
11
ssh radius01 "ls -la /etc/freeradius/certs/"
# total 64
# -rw-r--r-- 1 root root 2151 Jun 15 2024 ca.pem # CA 根证书
# -rw-r--r-- 1 root root 2200 Jun 15 2024 server.pem # Radius 服务器证书
# -rw-r--r-- 1 root root 2151 Jun 15 2024 server.crt

# 查看每张证书的过期时间
for cert in /etc/freeradius/certs/{ca,server}.pem; do
echo "=== $cert ==="
openssl x509 -in $cert -noout -dates -subject -issuer
done

输出:

1
2
3
4
5
6
7
8
9
10
11
=== /etc/freeradius/certs/ca.pem ===
notBefore=Jun 15 14:22:08 2024 GMT
notAfter =Jun 15 14:22:08 2034 GMT # 根 CA 还有 8 年
subject= /CN=Corp Internal CA
issuer= /CN=Corp Internal CA # 自签根

=== /etc/freeradius/certs/server.pem ===
notBefore=Jun 15 14:22:08 2024 GMT
notAfter =Jun 15 14:22:08 2026 GMT # 昨天 00:00 过期!
subject= /CN=radius.corp.example.com
issuer= /CN=Corp Internal CA # 由上面这个 CA 签发

一目了然:根 CA 还在有效期,但服务器证书是 2 年前生成时签了 2 年(前面写的 10 年期是 CA 本身),server.pem 在 6 月 28 日 00:00 过期。我之前对脚本记忆有误,所以忽视了 Server 证书本身的 2 年有效期。

更糟糕的是,这个 server.pem 同时被三处使用:

  1. FreeRADIUS 主配置文件 /etc/freeradius/eap.confmods-available/eap
  2. FreeRADIUS 的客户端工具(用于向 AD CS 申请证书的 SCEP 接口)
  3. AD CS 上挂的 Web Enrollment / OCSP 服务也用了同一张证书做 SSL 绑定

第四阶段:紧急止血

完全处理需要重新签发服务器证书、推送新的 CA/服务器证书到 2000+ 终端,过程需要数小时。在此之前先用 OpenSSL 临时续签一张短期证书:

1
2
3
4
5
6
7
8
9
# 在 radius01 上生成 CSR,向自建 CA 申请一张 30 天短期证书用于止血
cd /etc/freeradius/certs
openssl req -new -key server.key -out server.csr \
-subj "/CN=radius.corp.example.com" \
-addext "subjectAltName=DNS:radius.corp.example.com,DNS:radius01.corp.example.com,IP:10.1.1.100"

openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key \
-CAcreateserial -out server.pem -days 30 \
-sha256 -extfile <(printf "subjectAltName=DNS:radius.corp.example.com,DNS:radius01.corp.example.com,IP:10.1.1.100")

然后重启 FreeRADIUS:

1
2
3
4
5
ssh radius02 "systemctl restart freeradius"
ssh radius01 "systemctl restart freeradius"
# 重新测试
radtest test test 127.0.0.1 0 testing123
# Received Access-Accept

但这只是 FreeRADIUS 这一端,终端上的根 CA 信任链没变——终端有之前手工导入的根 CA,CA 还在有效期,EAP-TLS 客户端对服务器证书的校验会校验 issuer 链 + 过期时间。新签的 30 天证书由同一个 CA 签发,CA 链没变,所以理论上已部署过 CA 根证书的终端不需要重新安装 CA

可是事实是无线终端还是连不上!继续抓 FreeRADIUS 详细日志:

1
2
(1) eap_tls: ERROR: SSL says error 26 : unsupported certificate purpose
(1) eap_tls: ERROR: TLS Alert write:fatal:unsupported certificate

新问题!原因是脚本里签证书时没加 extendedKeyUsage=serverAuth 扩展:

1
2
3
4
# 第一次签发的命令
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key \
-CAcreateserial -out server.pem -days 30 -sha256
# 没有 extfile 参数

正确的签发方式需要加 EKU 扩展:

1
2
3
4
5
6
7
8
cat > /tmp/extfile.cnf <<'EOF'
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = DNS:radius.corp.example.com, DNS:radius01.corp.example.com, IP:10.1.1.100
EOF

openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key \
-CAcreateserial -out server.pem -days 30 \
-sha256 -extfile /tmp/extfile.cnf

再次重启后 FreeRADIUS 正常签出 Received Access-Accept已信任 CA 根证书的终端陆续恢复

第五阶段:长期解决 - 自动化证书轮换

30 天临时证书只是续命。要根治,必须做完整的自动化证书轮换:

1. 用 AD CS 替代自建 OpenSSL 签发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# radius01 申请新证书时使用 certreq
$inf = @"
[Version]
Signature = "`$Windows NT`$"
[NewRequest]
Subject = "CN=radius.corp.example.com"
KeySpec = 1
KeyLength = 2048
Exportable = TRUE
MachineKeySet = TRUE
SMIME = FALSE
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
HashAlgorithm = SHA256
[EnhancedKeyUsageExtension]
OID = 1.3.6.1.5.5.7.3.1 ; Server Authentication
OID = 1.3.6.1.5.5.7.3.2 ; Client Authentication
[Extensions]
2.5.29.17 = "{text}"
_continue_ = "dns= radius.corp.example.com&"
_continue_ = "dns= radius01.corp.example.com&"
_continue_ = "ipaddress= 10.1.1.100&"
[RequestAttributes]
CertificateTemplate = RadiusServerTemplate
"@
$inf | Out-File -Encoding ASCII C:\radius01.inf
certreq -new C:\radius01.inf C:\radius01.req
certreq -submit C:\radius01.req C:\radius01.cer
certreq -accept C:\radius01.cer

2. 写 cron 自动化续签 + 部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
# /usr/local/sbin/radius-cert-renew.sh
# 每 30 天提前 60 天轮换,确保证书永远不会过 60 天有效期
DAYS_BEFORE_EXPIRY=60
CERT_PATH="/etc/freeradius/certs/server.pem"
THUMBPRINT=$(openssl x509 -in $CERT_PATH -noout -fingerprint -sha256 | cut -d= -f2)
EXPIRY=$(openssl x509 -in $CERT_PATH -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

if [ $DAYS_LEFT -gt $DAYS_BEFORE_EXPIRY ]; then
echo "[OK] 证书还有 $DAYS_LEFT 天,无需续签"
exit 0
fi

echo "[WARN] 证书还有 $DAYS_LEFT 天,开始续签流程"

# 1. 备份旧证书
cp -a $CERT_PATH ${CERT_PATH}.bak-$(date +%Y%m%d)
cp -a /etc/freeradius/certs/server.key /etc/freeradius/certs/server.key.bak-$(date +%Y%m%d)

# 2. 通过 certreq 申请(需要 winexe 调 Windows 端 certreq)
winexe -U corp\\admin%password //radius01 "cmd /c certreq -enroll -machine RadiusServerTemplate" 2>/dev/null
# 把新证书导出到 Linux
smbclient //radius01/c$ -U corp\\admin%password -c "cd \ProgramData\Microsoft\Crypto\RSA\MachineKeys; ls" > /dev/null
# ...省略实际取回流程

# 3. 重启 FreeRADIUS
systemctl restart freeradius

# 4. 验证
sleep 5
radtest test test 127.0.0.1 0 testing123 && echo "[OK] 续签完成并通过测试"

3. 终端侧:使用 SCEP/NDES 自动注册

旧流程是 IT 同事手工给每台电脑装 CA 根证书,效率低且容易漏装。改用 NDES + Intune SCEP:

1
2
3
4
5
6
7
8
9
10
# Intune SCEP 策略自动向 AD CS 申请客户端证书并推送
# 设备配置 - 配置文件 - SCEP 证书
# 证书类型: 设备
# Subject Name Format: CN={{DeviceId}},CN={{AAD_Device_ID}}
# SAN: URI={{DeviceId}}
# Key Usage: KeyEncipherment, DigitalSignature
# Key Size: 2048
# Hash Algorithm: SHA-2
# Extended Key Usage: 1.3.6.1.5.5.7.3.2 (Client Auth)
# SCEP Server URLs: https://ndes.corp.example.com/certsrv/mscep/mscep.dll

解决方案

最终落地的方案分三层:

止血阶段(T+0 ~ 30 分钟):在 FreeRADIUS 上用 OpenSSL + 自建 CA 临时签发 30 天带 extendedKeyUsage=serverAuth,clientAuthsubjectAltName 的服务器证书,重启服务后约 70% 已部署 CA 根证书的终端自动恢复,剩下 30% 是历史装机从未导入过 CA 的工位机,由现场 IT 同事手工导入。

短期修复(T+1 ~ 2 周):将 FreeRADIUS 证书切到 AD CS 颁发的 5 年期 RadiusServerTemplate 证书(受 5 年 CA 模板限制),由 certreq 自动续签脚本每 30 天检查并续签,提前 60 天主动轮换,避免再出现凌晨过期无人值守的情况。

长期根治(T+1 ~ 2 月):

  1. CA 侧:把自建 OpenSSL CA 整个迁移到企业 AD CS,使用 5 年期 SubCA 模板并启用 autoenroll,所有 2000+ 终端通过 GPO 自动注册 RootCA 信任
  2. 客户端侧:IoT 设备、扫码枪、IP 电话改为 SCEP 自动注册客户端证书,免去手工导入
  3. 监控侧:在 Zabbix 上添加对 /etc/freeradius/certs/*.pem 的扫描,所有证书剩余有效期 < 90 天的都触发告警,证书清单每周发邮件给安全组
  4. 流程侧:建立”证书生命周期”维基页面,所有证书的签发日期、到期日期、负责人、轮换 SOP 一目了然,离到期 90/60/30/7 天分别触发不同级别告警

根因分析

深挖下来,这次事故有四个相互独立的根因叠加:

  1. EAP-TLS 服务器证书的有效期被脚本写死成 2 年,而前同事把这个脚本注释为”自建 CA 10 年期”,导致后续接手的人(包括我)误以为整套体系都是 10 年一换
  2. 没有任何证书过期监控,Zabbix 监控的是 Radius 服务进程存活、AP 认证成功率,但没人意识到”证书”也是一种需要监控的资源
  3. 历史装机太松散,CA 根证书在客户端没有强制 GPO 推送,部分工位机是当年实习生手工装的,证书已经丢/坏,并且这个状态在 AP 重新认证时才暴露
  4. 缺乏证书全生命周期管理流程,没有 PKI 文档、没有续签 SOP、没有 SCEP/NDES 这种”证书可以自动过期和续签”的设计,本质上还是手工运维的思路在管理 CA

预防措施

基于这次踩坑,建立了下面这套预防体系:

1. 证书全生命周期管理系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 每周日凌晨 3 点扫描所有 Radius/网关证书,邮件给安全组
#!/bin/bash
# /usr/local/sbin/cert-watchdog.sh
RECIPIENT="[email protected]"
WARN_DAYS=90
CRITICAL_DAYS=30
CERT_DIRS=("/etc/freeradius/certs" "/etc/nginx/ssl" "/etc/strongswan/ipsec.d")

REPORT=""
for dir in "${CERT_DIRS[@]}"; do
for cert in $dir/*.pem $dir/*.crt; do
[ -f "$cert" ] || continue
EXPIRY=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
[ -z "$EXPIRY" ] && continue
DAYS_LEFT=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
SUBJECT=$(openssl x509 -in "$cert" -noout -subject | cut -d= -f2-)
if [ $DAYS_LEFT -lt $CRITICAL_DAYS ]; then
REPORT+="[CRITICAL] $cert${DAYS_LEFT} 天 - $SUBJECT\n"
elif [ $DAYS_LEFT -lt $WARN_DAYS ]; then
REPORT+="[WARN] $cert${DAYS_LEFT} 天 - $SUBJECT\n"
fi
done
done

if [ -n "$REPORT" ]; then
echo -e "证书过期预警:\n$REPORT" | mail -s "【证书过期预警】$(date +%Y-%m-%d)" $RECIPIENT
fi

2. Zabbix 自定义监控项

1
2
3
4
5
# /etc/zabbix/zabbix_agentd.d/cert_expiry.conf
UserParameter=cert.expiry[*],openssl x509 -in $1 -noout -enddate 2>/dev/null | cut -d= -f2 | xargs -I{} date -d "{}" +%s
# 触发器: 当 cert.expiry[/etc/freeradius/certs/server.pem] - now() < 7776000 (90天) 时触发 warning
# < 2592000 (30天) 时触发 high
# < 604800 (7天) 时触发 disaster

3. 终端 GPO 强制信任企业 CA

1
2
3
4
5
6
7
8
9
Computer Configuration
→ Policies
→ Windows Settings
→ Security Settings
→ Public Key Policies
→ Certificate Services Client - Auto-Enrollment
☑ 自动注册证书
☑ 续签过期的证书
☑ 更新正在使用的证书

4. SCEP/NDES 自动客户端证书

所有域内电脑和受管移动设备通过 Intune SCEP 自动向 AD CS 申请客户端证书,证书有效期 1 年自动续签,永久不需要 IT 同事手工介入。

5. 红蓝对抗演练

每季度模拟”把 Radius server.pem 替换为已过期证书”,演练运维团队的应急响应速度和流程顺畅度,确保事故响应的肌肉记忆。

总结

这次事故最深的教训,是证书这种”非生命体”资源在大多数运维同学的监控盲区里。它不像磁盘、CPU、内存那样有常规的仪表盘告警,往往只在过期那一刻才”砰”地炸出来,但影响范围却是公司级别的。

回头看,EAP-TLS 体系的设计原则应该是”证书全生命周期”——从 CA 设计、模板、签发、续签、客户端分发、信任链维护到过期监控,每一步都要有自动化兜底。任何依赖”人工记得去续签”的设计,最终都会在某个凌晨、某个长假、某个新人接手的时刻翻车。

我们这次虽然用 30 天临时证书快速止血,没有出现业务数据丢失,但这种”凭证类”的故障本应通过前置的告警 + 自动续签 + 监控来彻底规避。建议所有使用 802.1X / EAP-TLS / HTTPS 证书 / IPsec 证书的团队,都把”证书全生命周期管理”作为年度安全审计的必查项,并且把 Zabbix / Prometheus 对证书过期天数的监控当作 P0 级别来对待——这绝不是”锦上添花”,而是”救命的最后一道防线”。