实战指南:基于对端握手消息按需获取 TLS 证书)
Envoy 证书选择器Certificate Selectors实战指南基于对端握手消息按需获取 TLS 证书【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy本指南系统讲解 Envoy 的证书选择器Certificate Selectors扩展机制——它允许根据 TLS 对端握手消息如 SNI、传输层 socket 选项、下游 filter state动态挑选证书并以on_demand_secret选择器为核心深入解析如何在不阻塞监听器/集群初始化的前提下通过 SDS 按需拉取证书并暂停/恢复握手。读完本文你将掌握证书选择器的配置结构、三种证书映射器mapper的用法、下游与上游场景的完整 YAML 配置以及其底层源码实现原理与可观测性指标。什么是证书选择器在 证书选择器 API 文档 中Envoy 将证书选择器定义为一类扩展These extensions allow selecting a TLS certificate based on the peer handshake message即这些扩展允许根据对端握手消息来选择一个 TLS 证书。传统 TLS 配置中一个监听器或一个上游集群在CommonTlsContext里静态或通过 SDS绑定一个固定的服务端/客户端证书。而在多租户、证书种类繁多的场景下你希望在握手尚未完成时就根据对端 ClientHello 里携带的信息典型的是 SNI来决定这次握手该出示哪张证书。证书选择器正是为此而生它在 TLS 握手过程中介入读取对端握手消息通过证书映射器certificate mapper把握手消息推导为证书标识secret name结合 SDSSecret Discovery Service按需获取对应的证书然后继续完成握手。其核心配置类型是envoy.extensions.transport_sockets.tls.cert_selectors.on_demand_secret.v3.Config源码定义见 api/envoy/extensions/transport_sockets/tls/cert_selectors/on_demand_secret/v3/config.proto扩展名为envoy.tls.certificate_selectors.on_demand_secret。为什么需要按需证书与普通 SDS 的对比要理解 on-demand 选择器的价值先看普通 SDS 的行为。在 Secret discovery service (SDS) 文档中明确说明如果一个监听器的服务端证书需要通过 SDS 远程获取在证书获取完成之前该监听器不会被标记为 active其端口不会打开。如果 Envoy 因连接失败或响应数据错误而无法获取证书监听器会被标记为 active、端口会打开但对该端口的连接会被 reset。上游集群同理集群客户端证书未就绪前集群不会被标记为 active若获取失败集群虽可处理请求但路由到该集群的请求会被拒绝。也就是说普通 SDS 的证书获取会阻塞父资源listener / cluster的初始化。这在证书只有一张或少数几张、且必须在启动时就绪的场景下是合理的但在多租户场景中一个监听器可能要面对成千上万张证书每个租户一张全部预取既不现实也无必要。on-demand 选择器docs/root/configuration/security/secret.rst#L279-L300 的 On-demand certificates 一节改变了这一模型默认情况下SDS 证书获取会阻塞引用它的监听器和集群的初始化。在某些情况下更适合在没有SDS secret 的情况下接受连接并使用对端 hello 消息中的字段如 SNI按需推导 secret 名称来请求证书。Envoy 提供了一个 on-demand 证书选择器如果证书不存在它暂停 TLS 握手并发起 SDS 请求收到响应后继续握手。其工作流程为依据 config.proto 的注释父集群或监听器无需 warming即可接受连接握手过程中从对端 hello 消息推导出 secret 名称下游用 ClientHello上游用 ServerHello 及传输 socket 选项发起 SDS 资源请求暂停握手SDS 响应到达后用返回的证书恢复握手若 SDS 服务器指示资源被删除则握手失败并停止对该资源的 SDS 订阅。配置结构Config 的三大字段Config消息config.proto包含三个字段字段类型必填说明config_sourceconfig.core.v3.ConfigSource是定义 secret 的配置来源即 SDS 服务器的连接方式gRPC / Delta gRPC / 文件等certificate_mapperconfig.core.v3.TypedExtensionConfig是扩展点指定一个函数用来计算 secret 名称。下游选择器在收到客户端CLIENT HELLO后调用上游选择器使用传输 socket 选项和SERVER HELLO。对应扩展类别envoy.tls.certificate_mappers与envoy.tls.upstream_certificate_mappersprefetch_secret_namesrepeated string否配置加载时尚未收到任何请求前即开始拉取的 secret 资源名列表。父资源立即完成初始化不等待拉取完成其中config_source在 proto 中带有(validate.rules).message {required: true}校验certificate_mapper同样必填。注意与常规证书一样按需获取的证书仍然通过外层 common TLS context 进行配置例如对加载的证书设置 FIPS 合规策略config.proto。从源码实现看source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.cc#L69-L80SecretManager构造函数会遍历prefetch_secret_names对每个名字调用addCertificateConfig(name, nullptr, factory_context.initManager())提前建立订阅并且通过factory_context.scope().createScope(on_demand_secret.)为统计指标建立独立的 stats scope。三种证书映射器Certificate Mappercertificate_mapper是决定用握手消息推导出哪个 secret 名称的关键扩展点。仓库中提供了三种官方实现目录见 api/envoy/extensions/transport_sockets/tls/cert_mappers1. SNI 映射器sni定义于 api/envoy/extensions/transport_sockets/tls/cert_mappers/sni/v3/config.proto扩展名envoy.tls.certificate_mappers.sni作用使用 TLS ClientHello 中的 SNI 值作为下游选择器的 secret 资源名称字段default_value必填最小长度 1——当 SNI 为空或缺失时用作 secret 名称的默认值。其实现source/extensions/transport_sockets/tls/cert_mappers/sni/config.cc#L13-L24调用SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)取出 SNI若为空则回退到default_value_absl::string_view sni absl::NullSafeStringView( SSL_get_servername(ssl_client_hello.ssl, TLSEXT_NAMETYPE_host_name)); return sni.empty() ? default_value_ : std::string(sni);2. 静态名称映射器static_name定义于 api/envoy/extensions/transport_sockets/tls/cert_mappers/static_name/v3/config.proto扩展名envoy.tls.certificate_mappers.static_name作用将所有连接映射到一个固定的 secret 名称字段name必填最小长度 1——所有连接统一使用的 secret 名称。它的价值在于行为上类似常规 SDS TLS 证书但监听器不需要等到 SDS 响应到达才开始监听这正是 secret.rst 中第二个下游示例的用途。3. 过滤器状态覆盖映射器filter_state_override定义于 api/envoy/extensions/transport_sockets/tls/cert_mappers/filter_state_override/v3/config.proto扩展名envoy.tls.upstream_certificate_mappers.filter_state_override作用使用 filter state 中键为envoy.tls.certificate_mappers.on_demand_secret的值作为 secret 资源名称该 filter state 预期从下游连接共享过来字段default_value必填最小长度 1——当 filter state 缺失时使用的默认 secret 名称。该键被记录在 Well Known Filter State Objects 中它允许通过下游过滤器按连接粒度覆盖要使用的证书。测试代码中也验证了这一点test/extensions/transport_sockets/tls/cert_selectors/on_demand/config_test.cc#L115通过setData(envoy.tls.certificate_mappers.on_demand_secret, ...)注入该对象。下游Downstream配置示例以下是官方文档给出的下游TLS 上下文配置使用 SNI 字段作为 SDS 请求中的 secret 名称见 secret.rst#L322-L348common_tls_context: custom_tls_certificate_selector: name: on-demand typed_config: type: type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_selectors.on_demand_secret.v3.Config config_source: api_config_source: api_type: DELTA_GRPC grpc_services: - envoy_grpc: cluster_name: some_xds_cluster certificate_mapper: name: sni typed_config: type: type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_mappers.sni.v3.SNI default_value: default_host # Starts fetching the secret prior to any requests. prefetch_secret_names: - default_host disable_stateless_session_resumption: true disable_stateful_session_resumption: true要点解读custom_tls_certificate_selector挂在common_tls_context下name: on-demand指向注册名envoy.tls.certificate_selectors.on_demand_secretconfig_source使用DELTA_GRPC Envoy gRPC 连接到名为some_xds_cluster的 xDS/SDS 集群见下文为什么推荐 DELTA_GRPCcertificate_mapper使用sni映射器SNI 缺失时回退到default_hostprefetch_secret_names预取default_host让最常见的默认证书在首个请求到来前就已就绪因为按需证书不支持会话恢复见限制与注意示例显式设置了disable_stateless_session_resumption和disable_stateful_session_resumption为true。第二个下游示例与常规 SDS TLS 证书行为类似但监听器不会被 SDS 响应阻塞——连接在 TLS 握手期间被接受并暂停收到证书后恢复secret.rst#L355-L377。它用static_name映射器把证书固定为secret_0common_tls_context: custom_tls_certificate_selector: name: on-demand typed_config: type: type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_selectors.on_demand_secret.v3.Config config_source: api_config_source: api_type: DELTA_GRPC grpc_services: - envoy_grpc: cluster_name: some_xds_cluster certificate_mapper: name: sni typed_config: type: type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_mappers.static_name.v3.StaticName name: secret_0 prefetch_secret_names: - secret_0 disable_stateless_session_resumption: true disable_stateful_session_resumption: true上游Upstream配置示例借助 filter state 传递证书名上游场景中on-demand 选择器需要在建立到后端的 TLS 连接时选择客户端证书。官方示例使用filter_state_override映射器从下游监听器动态传入的 filter state 值决定 secret 名称secret.rst#L382-L400common_tls_context: custom_tls_certificate_selector: name: on-demand typed_config: type: type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_selectors.on_demand_secret.v3.Config config_source: api_config_source: api_type: DELTA_GRPC grpc_services: - envoy_grpc: cluster_name: some_xds_cluster certificate_mapper: name: filter_state_override typed_config: type: type.googleapis.com/envoy.extensions.transport_sockets.tls.cert_mappers.filter_state_override.v3.Config default_value: default_secret要使上述上游配置生效必须在下游 filter chain 中写入该值例如使用set_filter_state网络过滤器secret.rst#L405-L417name: envoy.filters.network.set_filter_state typed_config: type: type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config on_new_connection: - object_key: envoy.tls.certificate_mappers.on_demand_secret factory_key: envoy.hashable_string format_string: text_format_source: inline_string: my_secret_name shared_with_upstream: ONCE注意shared_with_upstream: ONCE该 filter state 会被共享到上游连接且只消费一次上游选择器据此推导出 secret 名my_secret_name。动态更新与删除语义按需证书并非一次性快照它会跟随父 TLS 上下文联动更新。文档明确secret.rst#L291-L295通过 on-demand SDS 获取的证书与上下文中定义的常规 TLS 证书配置方式相同父级所有设置都会应用。如果父 TLS 上下文发生动态更新例如 validation context 的 SDS 更新on-demand 证书上下文也会收到并随之更新。因此握手恢复时使用的是最新版本的 CA secret。在删除语义上文档建议secret.rst#L297-L300On-demand SDS 应使用 DELTA_GRPC 来管理数据平面 secret 的删除。通过 xDS 响应发送的资源删除resource removal会取消数据平面对该特定 secret 名称的订阅。而使用常规 GRPC xDS 协议时每个映射 secret 的订阅会一直保持到父资源监听器或集群被移除。这解释了为何官方示例统一采用api_type: DELTA_GRPC。源码级原理SecretManager 与异步握手on-demand 选择器的实现位于 source/extensions/transport_sockets/tls/cert_selectors/on_demand/头文件 config.h。核心组件如下SecretManagerconfig.h#L163-L233维护对 SDS secret 的动态订阅把 xDS 形态的 secret 转换成 BoringSSL TLS 上下文同时套用父 TLS 配置。对外提供addCertificateConfig主线程上开启订阅并注册更新句柄、updateCertificate更新证书并通知挂起连接、updateAll父上下文配置变更时重建所有上下文、removeCertificateConfig删除缓存状态及活跃订阅、fetchCertificate按需开始拉取等接口。AsyncContext/ServerAsyncContext/ClientAsyncContextconfig.h#L65-L128每个证书对应一个低层 TLS 上下文服务端上下文额外暴露ocspStaplePolicy()供 OCSP stapling 决策。Handleconfig.h#L130-L157Ssl::SelectionHandle的实现。同步场景下持有选中的证书上下文以延长其生命周期异步场景下保存握手恢复回调SDS 返回后调用notify()——在 config.cc#L46-L62 中可以看到它把结果通过dispatcher.post()投递回连接所在的 worker 线程再调用onCertificateSelectionResult恢复握手。AsyncSelector/UpstreamAsyncSelectorconfig.h#L249-L281每个 worker 上每个 TLS socket 对应一个异步选择器。下游版本在selectTlsContext(const SSL_CLIENT_HELLO, ...)中基于 ClientHello 选证书上游版本则在selectTlsContext(const SSL, const Network::TransportSocketOptionsConstSharedPtr, ...)中基于 ServerHello 与传输 socket 选项选证书。注册与工厂OnDemandTlsCertificateSelectorConfigFactory与UpstreamOnDemandTlsCertificateSelectorConfigFactory均以envoy.tls.certificate_selectors.on_demand_secret为扩展名注册config.h#L330-L364分别对应下游与上游选择器工厂且两者都实现了onConfigUpdate()以便父上下文更新时联动刷新。值得注意的一点config.h#L260-L263异步选择器的findTlsContext(...)对 QUIC 直接PANIC(Not supported with QUIC)——即该选择器目前不支持 QUIC仅用于 TCP/TLS 握手路径。统计指标StatisticsSecretManager在构造时以on_demand_secret.为前缀创建统计 scopeconfig.cc#L72指标定义见 config.h#L21-L24 的ALL_CERT_SELECTION_STATS。除标准 SDS 的 订阅统计sds.SECRET_NAME.*命名空间外on-demand 扩展额外产出以下指标secret.rst#L302-L313名称类型描述cert_requestedCounter新建的 SDS 订阅总数cert_updatedCounter证书更新总数cert_activeGauge活跃的证书订阅与证书数量命名空间下游监听器listener.stat_prefix.on_demand_secret.*上游集群cluster.stat_prefix.on_demand_secret.*。集成测试 test/extensions/transport_sockets/tls/cert_selectors/on_demand/integration_test.cc#L274-L275 也验证了上游场景下cluster.cluster_0.on_demand_secret.STAT的命名方式。利用这些指标你可以监控按需证书的订阅增长、更新频率与当前活跃证书数判断预取策略是否合理。限制与注意事项会话恢复不支持文档明确 Session resumption is currently not supported for on-demand certificatessecret.rst#L315-L317因此官方示例都显式禁用 stateless 与 stateful 会话恢复。QUIC 不支持从源码看异步选择器在 QUIC 路径上直接 panicconfig.h#L260-L263该功能面向 TCP TLS 握手。优先使用 DELTA_GRPC为保证 SDS 侧删除 secret 后数据平面能及时取消订阅并停止缓存应使用 Delta xDS 协议。父上下文联动按需证书受父 TLS 上下文含 validation context SDS 更新的配置约束父级动态更新会重建全部按需证书上下文对应SecretManager::updateAll()。预取权衡prefetch_secret_names只是提前发起拉取父资源初始化不会等待其完成它适合预热高频证书减少首个请求的握手等待时间但不应期望它提供同步就绪保证。总结Envoy 的证书选择器把选证书从静态配置中解放出来on_demand_secret选择器 证书映射器sni/static_name/filter_state_override组合可在握手阶段依据 ClientHello/ServerHello/filter state 推导证书名并通过 SDS 按需拉取、暂停再恢复握手从而在不阻塞监听器与集群初始化的前提下支持多租户海量证书场景。结合本文给出的下游、上游完整 YAML 示例、DELTA_GRPC 使用建议、源码级执行流程与on_demand_secret.*统计指标你可以在自己的 Envoy 部署中直接落地按需证书方案。进一步参考证书选择器 API 目录api/envoy/extensions/transport_sockets/tls/cert_selectors/on_demand_secret/v3/config.protoSDS 完整配置与密钥轮换docs/root/configuration/security/secret.rst三种映射器 protoapi/envoy/extensions/transport_sockets/tls/cert_mappers选择器源码实现source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.cc集成测试test/extensions/transport_sockets/tls/cert_selectors/on_demand/integration_test.cc【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考