服务网格里的 mTLS 证书怎么轮换才不断流?证书过期之后会发生什么?换 CA 的时候先动哪一边?这篇用 Envoy 1.39.3 的文件型 SDS、自己签发的小 CA 和 Python 客户端,把握手失败的各种错误信息和轮换的窗口逐项量出来。
客户端 | 结果 | 详情 | 耗时(ms) -------------------+----+------------------------------------------------------------------------------------------------------------------------------------------+------- (a)CA1 签发的有效客户端证书 | 成功 | HTTP 200,服务端证书序列号尾号 …469362 | 5 (b)不带客户端证书 | 失败 | SSLError: [SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2713) | 1 (c)CA2 签发的有效客户端证书 | 成功 | HTTP 200,服务端证书序列号尾号 …469362 | 2 (d)未知 CA3 签发的客户端证书 | 失败 | SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2713) | 1 (e)已经过期的客户端证书 | 失败 | SSLError: [SSL: SSLV3_ALERT_CERTIFICATE_EXPIRED] ssl/tls alert certificate expired (_ssl.c:2713) | 2 (f)客户端不信任服务端的 CA | 失败 | SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:108 | 1 上面的错误信息来自客户端 Python ssl 抛出的异常(握手和第一次读写都在 hs() 里统一捕获);服务端拒绝客户端证书时,客户端看到的是 TLS alert,不是 HTTP 响应
不带证书、未知 CA、过期证书、客户端不信任服务端的 CA,各有各的 TLS alert,而且都是立即失败,不是超时。
共 199 次握手,失败 0 次;服务端证书序列号变化:旧 …782540 → 新 …507228;替换 SDS 文件的时刻 2.00 s,客户端第一次看到新证书的时刻 2.03 s(相差 30 ms);新证书出现之后又看到旧证书的次数 0 原子替换 SDS 文件之后,新的握手很快(见上一行)用上新证书,期间没有握手失败
用 SDS 文件原子替换服务端证书,新握手很快用上新证书,本次测量没有握手失败。
旧证书(有效期 5 s)的握手在 0.0~4.5 s 成功(22 次);失败 16 次,时间范围 4.7~7.8 s;错误信息:SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1082);第 8.0 s 轮换,第一次用新证书成功在 8.0 s Envoy 在证书过期之后仍然把它提供给客户端(它没有因为过期而撤下证书),是客户端验证失败拒绝的;所以过期的窗口从证书到期一直延续到轮换完成,期间所有新握手都失败——证书要在到期之前很久轮换(例如有效期的一半),而不是到期时
Envoy 不会自己撤下过期证书,客户端拒绝握手,所有新连接失败,直到轮换完成。要在有效期过半时就轮换。
组合 | 结果 | 详情
-----------------------------------+----+-----------------------------------------------------------------------------------------------------------------------------------------
(a)信任 CA1,服务端证书 CA1 | 成功 | HTTP 200
(b)信任 CA1,服务端证书 CA2(顺序错:服务端先换) | 失败 | SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:108
(c)信任 CA1+CA2,服务端证书 CA2(正确:先分发信任包) | 成功 | HTTP 200
(d)信任 CA2,服务端证书 CA1(客户端先去掉了 CA1) | 失败 | SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:108
(e)信任 CA2,服务端证书 CA2(最终状态) | 成功 | HTTP 200
信任包要先于证书发出去:服务端证书先换成新 CA 而客户端还不信任它,握手立刻失败;客户端过早去掉旧 CA 同理。所以 CA 轮换分三步:分发包含新旧两个 CA 的信任包 → 换签发者 → 去掉旧 CA,每一步要等上一步在所有实例上完成
ERRORS {}先分发新旧两个 CA 的信任包,再换签发者,最后去掉旧 CA;每一步等上一步在所有实例上完成。