news 2026/9/12 14:29:04

Envoy TLS 异步证书选择连接拆除误报断言修复解析:on_demand_secret 扩展的握手生命周期治理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Envoy TLS 异步证书选择连接拆除误报断言修复解析:on_demand_secret 扩展的握手生命周期治理

Envoy TLS 异步证书选择连接拆除误报断言修复解析:on_demand_secret 扩展的握手生命周期治理

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

本篇技术指南围绕 Envoy 主分支changelogs/current/bug_fixes中的一条 TLS bug 修复展开:当异步证书选择(asynchronous certificate selection)仍在进行时,如果连接被拆除(connection teardown),会产生误报的IS_ENVOY_BUG断言。文章将剖析该问题的触发场景、修复后的防御机制,并结合envoy.tls.certificate_selectors.on_demand_secret扩展的配置 proto、核心实现与集成测试,完整还原「握手挂起 → SDS 拉取证书 → 连接被重置 → 回调安全忽略」的全链路处理逻辑。读完本文,你将掌握 on-demand 证书选择的配置方法、异步握手回调的生命周期约束,以及如何从源码层面验证此类连接竞态问题的修复。

一、变更条目速览

本次修复记录位于 changelogs/current/bug_fixes/tls__allow-connection-teardown-during-async-cert-selection.rst,原文为:

Fixed a bug in TLS where a false positiveIS_ENVOY_BUGassertion was triggered when a connection was torn down while asynchronous certificate selection was still in progress.

用一句话概括:TLS 握手中,当异步证书选择尚未完成时,若连接被拆除,Envoy 不再误报IS_ENVOY_BUG断言IS_ENVOY_BUG是 Envoy 断言体系中的一种,用于标记「本不该发生、但发生了也需兜底」的异常路径(区别于直接崩溃的ASSERT),其语义偏重「这里出现了一个 bug」,因此对于「连接在异步选择期间被拆除」这种正常的资源竞争场景,触发它是误报。

该修复的核心价值在于:在 on-demand 证书选择(证书并非在配置加载时预置,而是在 TLS 握手过程中按需从 SDS 拉取)这种异步模型下,连接拆除与证书回调完成之间天然存在竞态,修复后这条路径被显式标记为「预期行为」,而非「程序缺陷」。

二、背景:什么是 on-demand 异步证书选择

2.1 扩展定位

修复所涉及的扩展是envoy.tls.certificate_selectors.on_demand_secret,其 API 定义位于 api/envoy/extensions/transport_sockets/tls/cert_selectors/on_demand_secret/v3/config.proto,proto 文档对其工作方式描述如下:

Fetches the secret on-demand while allowing the parent cluster or listener to accept connections without warming. During the handshake, a secret name is derived from the peer hello message, an SDS resource request starts, and the handshake is paused. Once an SDS response is received with a resource, the handshake is resumed with the provided certificate. If the SDS server indicates the resource removal, the handshake is failed, and the SDS subscription to the resource is stopped.

即:父级监听器(listener)或集群在证书尚未就绪时即可接受连接而无需 warm up;握手期间从对端的 hello 消息推导出 secret 名称并发起 SDS 资源请求,同时挂起(pause)握手;收到 SDS 响应后,用返回的证书恢复握手;若 SDS 服务器指示资源被移除,则握手失败并停止对该资源的订阅。

实现代码位于 source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.cc 与 source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.h。

2.2 与常规 SDS 的区别

  • 常规 SDS:证书在 listener/cluster 初始化(warm up)时同步或异步加载,未加载完成前父资源无法进入就绪状态;
  • on-demand 证书:父资源不等证书即可就绪,证书延迟到「第一条连接的手握握手中」按需获取,从而支持超大证书池(如按 SNI 区分的海量域名证书)而不拖慢启动。

两者共用同一套外层 common TLS context 配置(例如对加载的证书施加 FIPS 合规策略),这一点在 config.proto 中亦有说明。

三、Config 配置项详解

on_demand_secretConfig消息共三个字段,均在 config.proto 中定义:

字段类型必填说明
config_sourceconfig.core.v3.ConfigSource是(validate 规则required: true定义 secret 的配置来源,即 SDS 的 xDS 配置源
certificate_mapperconfig.core.v3.TypedExtensionConfig是(required: true扩展点,指定「如何从握手消息计算 secret 名称」的函数;下游场景在收到客户端CLIENT_HELLO后调用,上游场景则基于 transport socket options 与SERVER_HELLO调用,对应两类扩展分类:envoy.tls.certificate_mappers(下游)与envoy.tls.upstream_certificate_mappers(上游)
prefetch_secret_namesrepeated string配置加载时(收到任何请求之前)即开始拉取的 secret 资源名列表;父资源初始化时不必等待这些拉取完成

3.1 完整配置示例

综合 integration_test.cc 中的用例,一个最小可用的下游配置如下:

common_tls_context: custom_tls_certificate_selector: name: envoy.tls.certificate_selectors.on_demand_secret typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_selectors.on_demand_secret.v3.Config config_source: resource_api_version: V3 api_config_source: api_type: DELTA_GRPC transport_api_version: V3 grpc_services: - envoy_grpc: cluster_name: sds_cluster timeout: 300s certificate_mapper: name: static-name typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_mappers.static_name.v3.StaticName name: server prefetch_secret_names: - server

要点说明:

  • 证书映射器(certificate_mapper):测试中常使用static-name(返回固定名称)或sni(按 SNI 推导名称,如default_value: "*"),对应的 mapper 实现位于 source/extensions/transport_sockets/tls/cert_mappers(含static_namesnifilter_state_override三种);
  • SDS 配置源:测试通过 DELTA_GRPC 指向名为sds_cluster的 gRPC 集群(见 integration_test.cc 的setConfigSource);
  • 会话恢复限制:配置 on-demand 证书时必须同时关闭无状态与有状态会话恢复(disable_stateless_session_resumptiondisable_stateful_session_resumption均置为 true),否则工厂创建阶段直接返回InvalidArgumentError——因为会话 ID 由父 TLS context 中的 server name 与证书生成,若允许用该 ID 恢复「父 context 中不存在的」按需证书是不安全的(见 config.cc);
  • QUIC 限制:下游 on-demand 选择器明确不支持 QUIC 监听器,配置时若for_quic为 true 同样返回InvalidArgumentError

四、异步选择的核心机制:SecretManager 与 Handle

4.1 整体架构

从 config.h 的类定义可以梳理出如下协作关系:

  • SecretManager:维护对 SDS secret 的动态订阅,将 xDS 形式的 secret 转换为 BoringSSL TLS context,并应用父级 TLS 配置。内部有两份状态:主线程可访问的cache_(记录订阅与待通知回调),以及每线程的 lock-free 缓存ThreadLocalCerts(记录名称 → 就绪 TLS context 的映射);
  • AsyncSelector/UpstreamAsyncSelector:每个 worker 上每个 TLS socket 各持有一个的选择器实例。下游在收到SSL_CLIENT_HELLO时触发selectTlsContext,上游则在SERVER_HELLO基础上触发;
  • Handle:代表一次「等待证书」的异步请求,持有证书选择回调(CertificateSelectionCallbackPtr)与客户端 OCSP 能力标记;一旦证书就绪,由notify()把结果投递给挂起的连接;
  • AsyncContext/ServerAsyncContext/ClientAsyncContext:承载所选证书对应的底层 TLS context 及其 OCSP 策略。

4.2 选择流程(两阶段)

在 config.cc 的BaseAsyncSelector::doSelectTlsContext中可以看到典型的两阶段选择逻辑:

  1. 命中缓存(同步路径)secret_manager_->getContext(name)返回已就绪的 TLS context,直接构造同步Handle并返回SelectionStatus::Success,无需等待;
  2. 未命中(异步路径):返回SelectionStatus::Pending,并调用fetchCertificate(name, cb, client_ocsp_capable)发起异步拉取,握手就此挂起。

4.3 回调投递与线程模型

Handle::notify(config.cc)是异步结果的落点:

void Handle::notify(AsyncContextConstSharedPtr cert_ctx) { ASSERT(cb_); bool staple = false; if (cert_ctx) { active_context_ = cert_ctx; staple = (ocspStapleAction(...) == Ssl::OcspStapleAction::Staple); } Event::Dispatcher& dispatcher = cb_->dispatcher(); dispatcher.post([cb = std::move(cb_), cert_ctx, staple] { cb->onCertificateSelectionResult( makeOptRefFromPtr(cert_ctx ? &cert_ctx->tlsContext() : nullptr), staple); }); cb_ = nullptr; }

关键点:

  • SecretManager的所有状态变更(addCertificateConfigupdateCertificateupdateAlldoRemoveCertificateConfig)都强制要求在主线程(ASSERT_IS_MAIN_OR_TEST_THREAD());
  • 结果通过dispatcher.post异步投递到连接所在 worker 线程,代码注释也提示未来可在 dispatcher 外层循环中对事件做批处理优化;
  • 证书被移除(SDS 指示资源删除)时以nullptr通知,此时连接侧收到空上下文,握手将失败、订阅随之停止,这也正是 proto 文档所描述的「resource removal → handshake failed」路径。

4.4 指标统计

on_demand_secret扩展在主线程 scopeon_demand_secret.下维护三组指标(见 config.h):

  • cert_requested(Counter):发起的证书拉取请求数;
  • cert_updated(Counter):证书更新并刷新线程本地缓存次数;
  • cert_active(Gauge,Accumulate):当前活跃的证书订阅数。

集成测试即通过这些指标断言行为,例如 integration_test.cc 中断言首条连接触发cert_requested为 1,随后第二条连接复用缓存(仍为 1),而sds.server.update_successcert_updated各为 1。

五、bug 根因:连接拆除与挂起握手的竞态

5.1 触发场景还原

结合源码注释与修复内容,误报IS_ENVOY_BUG的场景可以还原为:

  1. 客户端连接到达,TLS 握手开始;
  2. 选择器判定证书未缓存,返回PendingHandle被注册到SecretManager握手挂起
  3. 在 SDS 响应尚未返回的窗口期内,客户端主动断开连接,或服务端因其他原因拆除该连接(如 filter chain 被移除、空闲超时、连接被 reset);
  4. SDS 响应随后到达,updateCertificate遍历entry.callbacks_并调用handle->notify(cert_context),最终触发连接侧的回调onCertificateSelectionResult
  5. 此时连接上下文已被销毁,回调落在一个已拆除的连接上,于是触发误报的IS_ENVOY_BUG断言。

5.2 源码中的佐证

修复相关的防御性设计在代码中留下了清晰痕迹:

(a)弱引用守卫 fetch 请求。SecretManager::fetchCertificate(config.cc)在把请求投递到主线程时同时持有weak_this(SecretManager 本身)与weak_handle(本次请求的 Handle):

// The manager might need to be destroyed after posting from a worker because // the filter chain is being removed. Therefore, use a weak_ptr and ignore // the request to fetch a secret. Handle can also be destroyed because the // underlying connection is reset, and handshake is cancelled. factory_context_.mainThreadDispatcher().post( [weak_this = std::weak_ptr<SecretManager>(shared_from_this()), name = std::string(secret_name), weak_handle = std::weak_ptr<Handle>(handle)]() mutable { auto that = weak_this.lock(); auto handle = weak_handle.lock(); if (that && handle) { that->addCertificateConfig(name, handle, {}); } });

注释明确写到:filter chain 被移除时 manager 可能先于 worker 上的 socket 销毁,因此用弱引用并「忽略」过期的 fetch 请求;Handle 也可能因为底层连接被 reset、握手被取消而销毁。这正是本 bug 修复对应的语义:把「连接拆除导致 Handle 失效」视为预期路径,而不是断言失败。

(b)回调端的空值防御。连接侧的回调实现在 source/common/tls/ssl_handshaker.cc:

void CertificateSelectionCallbackImpl::onSslHandshakeCancelled() { extended_socket_info_.reset(); } void CertificateSelectionCallbackImpl::onCertificateSelectionResult( OptRef<const Ssl::TlsContext> selected_ctx, bool staple) { if (!extended_socket_info_.has_value()) { return; } extended_socket_info_->onCertificateSelectionCompleted(selected_ctx, staple, true); }

当连接被拆除时,SslExtendedSocketInfoImpl析构(ssl_handshaker.cc)会调用onSslHandshakeCancelled()使extended_socket_info_置空;此后即使异步证书结果姗姗来迟,onCertificateSelectionResult也会在has_value()检查处直接返回,静默忽略过期结果,而不再触发IS_ENVOY_BUG断言。同一模式也适用于证书校验路径的ValidateResultCallbackImpl(握手取消时onSslHandshakeCancelled同样重置状态)。

从源码结构可以推断:该修复的核心正是「把异步回调与连接生命周期的解耦从『假定回调必达且连接必存』修正为『回调可能迟到、连接可能先亡,二者皆需防御』」。

六、修复的验证:集成测试覆盖

6.1 测试布局

该功能的测试分两层:

  • 单元级:config_test.cc 覆盖工厂创建行为,包括BasicLoadTest(默认配置可正常创建)、BasicLoadTestQuic(QUIC 场景)、BasicLoadTestStatelessResumption/BasicLoadTestStatefulResumption(开启会话恢复时创建失败)、QuicCall(QUIC 调用不被支持)等;
  • 集成级:integration_test.cc 通过真实 SDS(DELTA_GRPC)与 TLS 握手验证端到端行为。

6.2 与连接拆除直接相关的用例模式

集成测试大量使用conn.reset()/conn->close()(如 integration_test.cc 等数十处),其中尤具代表性的流程为:

  1. 建立连接并等待证书请求(waitCertsRequested(1));
  2. 建立 xDS 连接并下发 SDS 响应(waitSendSdsResponse("server"));
  3. 连接完成握手、收发数据(sendAndReceiveTlsData("hello", "world"));
  4. conn.reset()拆除连接;
  5. 通过指标断言验证:cert_requested为 1、cert_active为 1、sds.server.update_success为 1、sds.server.update_rejected为 0,且后续第二条连接直接复用缓存、不再触发 SDS 拉取。

这些用例与本次修复高度相关:它们反复在「证书就绪/未就绪」的不同时间点拆除连接,正是为了覆盖「连接先亡、回调后到」的竞态窗口,确保拆除路径不会触发IS_ENVOY_BUG断言。

6.3 测试中的关键配置断言

测试还对 on-demand 配置施加了严格的运行约束:

  • 下游 TLS context 必须设置disable_stateless_session_resumption(true)disable_stateful_session_resumption(true)(integration_test.cc),与工厂层的校验逻辑相互印证;
  • SDS 使用 DELTA_GRPC 与resource_api_version: V3,gRPC 服务超时 300s。

七、运维与排障建议

基于以上源码分析,给使用或排查 on-demand 证书选择的读者几点建议:

  1. 预期「握手可能长时间挂起」:证书拉取期间握手暂停,若 SDS 集群不可用,连接会一直等待。生产环境应配合握手/连接超时,并监控cert_requestedcert_active指标判断是否出现证书拉取积压;
  2. 不要误把连接重置当故障:连接在证书拉取完成前被客户端断开属于正常竞态,修复后 Envoy 不会再输出IS_ENVOY_BUG;若仍看到该类断言,说明运行版本早于本次修复,应升级到包含该变更的版本;
  3. 会话恢复与 QUIC 限制是硬约束:配置校验失败(InvalidArgumentError)时,优先检查是否未关闭两种 session resumption,或是否试图用于 QUIC 监听器;
  4. 善用 prefetch:对热点证书使用prefetch_secret_names提前拉取,可将首条连接的「拉取等待」转化为「缓存命中」,显著降低首个请求的握手延迟(参见BasicSuccessWithPrefetch用例的指标表现)。

八、小结

本次修复针对的是一个容易在真实流量下高频触发的竞态误报:on-demand 异步证书选择天然存在「证书回调到达」与「连接拆除」的时间窗口竞争。修复通过双弱引用守卫(SecretManagerHandle)、握手取消状态重置、以及回调端has_value()防御三层机制,把这条路径从「程序 bug 断言」降级为「预期内的正常生命周期事件」。

该修复对应的功能主线——envoy.tls.certificate_selectors.on_demand_secret扩展——让 Envoy 得以在证书海量、按需分发(例如按 SNI 区分的多租户证书场景)时保持监听器快速就绪,其完整链路(proto 配置 → SecretManager → Handle 回调 → BoringSSL context)均可通过本文引用的源码文件与集成测试深入研读。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 14:28:09

AI模型管理与部署实战:从实验室到生产环境的全链路指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 14:28:06

MCU语音唤醒实战:Q15定点数、手写汇编与内存布局深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 14:26:45

多变量时间序列预测新框架:GJO优化TCN-BiGRU-Attention实践

这篇代码是我去年在风电功率预测项目里沉淀下来的东西。当时面临的核心问题很简单&#xff1a;多变量时间序列里&#xff0c;时序依赖长、变量耦合复杂、模型超参数又多&#xff0c;随便拍一套参数进去&#xff0c;模型表现就飘忽不定。后来把金豹优化算法&#xff08;GJO&…

作者头像 李华
网站建设 2026/9/12 14:23:18

AI生成内容质量评估的六大维度与实践方法

1. 为什么AI生成内容需要系统评估&#xff1f;去年有个做自媒体的朋友兴冲冲地告诉我&#xff0c;他用AI工具一天能产出50篇营销文案。结果投放后发现&#xff0c;有三分之一的内容存在事实错误&#xff0c;还有几篇甚至出现了品牌名称混淆的尴尬情况。这个真实案例让我意识到&…

作者头像 李华