Envoy 证书管理实践:静态证书、SDS 动态更新与文件系统密钥轮转
2026/9/14 22:11:39 网站建设 项目流程

Envoy 证书管理实践:静态证书、SDS 动态更新与文件系统密钥轮转

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

Envoy 通过两类机制管理 TLS 证书:基于CommonTlsContext的静态引用证书,以及通过 Secret Discovery Service(SDS)下发的动态证书。本文基于 证书管理文档 与 SDS 配置文档,结合仓库源码,完整讲解证书的配置方式、三种典型配置示例、文件系统密钥轮转(含watched_directory原子替换方案)、SDS 统计指标,以及面向多租户场景的按需证书(on-demand certificates)能力。读完本文,你可以直接在 Envoy 中落地静态/动态证书方案,并理解证书轮转在数据面内的实现细节。

两种证书管理机制总览

Envoy 的证书管理在架构上分为两类,二者的核心差异在于是否会自动重新加载证书

  1. 静态证书:通过CommonTlsContext直接引用(例如tls_certificates字段内联证书或指定文件路径)。这类证书不会自动重新加载,更换证书需要重启代理,或者重新下发引用它们的 cluster/listener 配置。
  2. SDS 证书:通过 Secret Discovery Service 引用。证书既可以引用本地文件(当所在父目录发生 move 事件时自动重载),也可以通过外部 SDS 服务器主动推送新证书。

静态证书与热重启(hot restart)

对于静态证书,官方推荐借助热重启在不丢流量的前提下换上新证书。Envoy 的热重启由独立的重启器脚本实现,仓库中对应 hot-restarter.py,新进程与旧进程共享监听端口,完成平滑切换。适用前提是:你希望避免 SDS 基础设施(gRPC 服务器、mTLS 认证等)的复杂度,且证书轮换频率不高——此时“配置下发 + 热重启”是最简单的运维路径。

SDS 带来的运维收益

SDS 文档 明确给出了引入动机:没有 SDS 时,在 k8s 部署中证书必须创建为 Secret 并挂载进代理容器;证书过期后需要更新 Secret 并重新部署容器。而使用 SDS 后,中心 SDS 服务器把证书推送到所有 Envoy 实例,证书过期时服务器推送新证书即可,Envoy 立即启用新证书,无需重新部署

SDS 配置要素与失败语义

SdsSecretConfig 的两种引用方式

SdsSecretConfig用于指定 secret,其name为必填字段:

  • sds_config字段为空,则name指向 bootstrapstatic_resources中声明的secrets(即 SDS 配置文档 中的Example one);
  • 否则sds_config作为一个ConfigSource指向远程 SDS 服务器。使用远程 SDS 服务时,api_config_source必须指定grpc_service,因为 SDS 只支持 gRPC。

SdsSecretConfigCommonTlsContext中的两个字段使用:

  • tls_certificate_sds_secret_configs:通过 SDS 获取TlsCertificate(服务端/客户端证书);
  • validation_context_sds_secret_config:通过 SDS 获取CertificateValidationContext(CA 校验上下文)。

证书未就绪时的行为(重要失败语义)

SDS 文档对“取证书失败”时的行为有明确约定,这是生产排障时最需要了解的语义:

  • Listener:若其服务器证书需要远程 SDS 获取,在证书取到之前不会被标记为 active,端口不会打开。如果因连接失败或响应数据非法而获取失败,listener 会被标记为 active、端口会打开,但连接到该端口的连接会被 reset
  • Upstream cluster:类似地,需要远程 SDS 客户端证书的 cluster 在证书取到前不会被标记为 active 也不会被使用;获取失败后 cluster 会被标记为 active,但路由到该 cluster 的请求会被拒绝。
  • 静态 cluster 使用 SDS 时,若需要额外定义 SDS cluster(使用 Google gRPC 则不需要),该 SDS cluster 必须在引用它的静态 cluster之前定义。

SDS 连接的安全性要求

Envoy 与 SDS 服务器之间的连接必须是安全的,两种典型方案:

  • 在同一主机上运行 SDS 服务器,通过Unix Domain Socket连接;
  • 否则连接必须使用带认证客户端证书的TLS(mTLS)

当前用于认证的凭证类型包括:mTLS(此时 SDS 连接所用的客户端证书必须静态配置,构成典型的“先静态引导、后动态轮转”模式)与AWS IAM SigV4。SDS 服务器需要实现 gRPC 服务SecretDiscoveryService(服务定义见 sds.proto),其交互协议与其他 xDS 服务一致。

示例一:static_resources 中声明 secrets

下面的示例展示在 bootstrapstatic_resources中声明 secret,并在 cluster 与 listener 中按名字引用:

static_resources: secrets: - name: server_cert tls_certificate: certificate_chain: filename: certs/servercert.pem private_key: filename: certs/serverkey.pem - name: client_cert tls_certificate: certificate_chain: filename: certs/clientcert.pem private_key: filename: certs/clientkey.pem - name: validation_context validation_context: trusted_ca: filename: certs/cacert.pem verify_certificate_hash: E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD clusters: - connect_timeout: 0.25s load_assignment: cluster_name: local_service_tls ... transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificate_sds_secret_configs: - name: client_cert listeners: .... filter_chains: transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificate_sds_secret_configs: - name: server_cert validation_context_sds_secret_config: name: validation_context

要点解析:

  • secrets静态资源中声明了 3 个 secret:client_certserver_certvalidation_context。此处证书不是远程获取的,而是引用本地文件;verify_certificate_hash展示了如何固定校验特定 CA 证书的哈希值。
  • cluster 配置在tls_certificate_sds_secret_configs中按名字引用client_cert作为 mTLS 客户端证书。
  • listener 的DownstreamTlsContexttls_certificate_sds_secret_configs中引用server_cert,并在validation_context_sds_secret_config中引用validation_context(例如用于对客户端证书做 CA 校验)。
  • 注意:虽然名为 “sds_secret_configs”,但引用 bootstrap 内静态 secret 时并不涉及网络传输,本质是按名字间接引用,方便 listener/cluster 复用同一份证书定义。

示例二:从远程 SDS 服务器获取证书

完整可运行的示例配置见 sds-source-example.yaml,其核心结构如下:

static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 8000 filter_chains: - transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificate_sds_secret_configs: - name: server_cert sds_config: api_config_source: api_type: GRPC grpc_services: - envoy_grpc: cluster_name: sds_server_mtls validation_context_sds_secret_config: name: validation_context sds_config: api_config_source: api_type: GRPC grpc_services: - envoy_grpc: cluster_name: sds_server_uds clusters: - name: sds_server_mtls typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions explicit_http_config: http2_protocol_options: {} load_assignment: cluster_name: sds_server_mtls endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 8234 transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: filename: certs/servercert.pem private_key: filename: certs/serverkey.pem - name: sds_server_uds typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions explicit_http_config: http2_protocol_options: {} load_assignment: cluster_name: sds_server_uds endpoints: - lb_endpoints: - endpoint: address: pipe: path: /tmp/uds_path - name: example_cluster load_assignment: cluster_name: local_service_tls endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 8443 transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificate_sds_secret_configs: - name: client_cert sds_config: api_config_source: api_type: GRPC grpc_services: - google_grpc: target_uri: unix:/tmp/uds_path stat_prefix: sds_uds_server

这个示例展示了三种访问 SDS 服务器的方式,SDS 服务器提供client_certserver_certvalidation_context三个 secret:

用法传输通道认证方式说明
clusterexample_clusterclient_certgoogle_grpc+ UDSunix:/tmp/uds_pathUDS(本机)直接指定target_uri,不需要 Envoy cluster
listener 的server_certenvoy_grpc+ clustersds_server_mtls127.0.0.1:8234mTLSsds_server_mtls静态配置的客户端证书(certs/servercert.pem/serverkey.pem)与 SDS 服务器做 mTLS,体现“引导凭证必须静态”的原则
listener 的validation_contextenvoy_grpc+ clustersds_server_uds/tmp/uds_pathUDS通过本地 Unix Domain Socket 连接

两个细节值得注意:envoy_grpc通道要求 cluster 显式启用 HTTP/2(示例中通过typed_extension_protocol_optionsHttpProtocolOptions设置http2_protocol_options: {});而google_grpc通道绕过 Envoy cluster,由底层 gRPC 客户端直连目标 URI,因此 SDS 文档才说“使用 Google gRPC 时不需要定义 SDS cluster”。

密钥轮转(Key Rotation)与文件系统监视

原理:inotify 监视父目录的 move 事件

当 gRPC SDS 不可行或不期望时(例如轮转 SDS 自身引导凭证的场景),SDS 支持对引用了文件系统路径的 secret 做文件系统轮转。目前支持两种 secret 类型:TlsCertificateCertificateValidationContext

默认行为是:包含 secret 的目录会被监视文件系统 move 事件。例如/foo/bar/baz/cert.pem会监视其父目录/foo/bar/baz。通过TlsCertificate.watched_directoryCertificateValidationContext.watched_directory字段可以显式控制被监视的目录,允许把监视点上移到路径的祖先目录(如/foo/bar),这对实现通用的密钥轮转方案非常有用。

从源码结构可以印证这一机制:SdsApi在装载 secret 时,若tls_certificate带有watched_directory,会创建Config::WatchedDirectory并注册回调(见 sds_api.cc 中secret.tls_certificate().has_watched_directory()分支,validation_context侧逻辑类似,见 sds_api.cc)。

一个重要的实现细节在 sds_api.cc 的onWatchUpdate()中:文件系统事件触发后会重新读取全部文件内容并计算哈希,只有哈希变化才触发更新;而且为了防止“读取过程中恰好发生轮转”导致读到新旧混合状态,代码会用最多 5 次有界重试反复加载文件直到两次哈希一致,若仍不一致会打印Unable to atomically refresh secrets due to > 5 non-atomic rotations observed的告警;若解析失败则记录告警并递增key_rotation_failed计数器。这解释了为什么官方推荐用原子 move 而非直接改写文件来替换证书。

符号链接轮转方案(watched_directory 实战)

一个改进原子性的常见轮转方案是:维护活跃符号链接/certs/current,用原子 move 操作替换该符号链接。此时监视点需要建在证书的祖父目录上,Envoy 通过watched_directory支持这种方案:

resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: tls_sds tls_certificate: certificate_chain: filename: /certs/current/sds_cert.pem private_key: filename: /certs/current/sds_key.pem watched_directory: path: /certs
resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: validation_context_sds validation_context: trusted_ca: filename: /certs/current/cacert.pem watched_directory: path: /certs

对应的证书轮转命令只需两步:

ln -s <path to new secrets> /certs/new && mv -Tf /certs/new /certs/current

mv -Tf对符号链接本身做原子替换,/certs目录上收到的 move 事件即触发 Envoy 重载,全程无需重启、无需重新下发配置。

xDS gRPC 连接的证书轮转(解决自举问题)

Envoy 与 xDS 服务器之间的 gRPC 连接自身的证书管理存在自举问题:SDS 服务器无法管理“连接它自己所需的证书”。SDS 文档 给出的方案是从文件系统引导 xDS 连接凭证——证书与密钥文件通过 inotify 监视并自动重载,无需重启;相比之下,Example two中用远程 SDS 下发的 xDS 凭证在更新后需要重启才能重载。

cluster 侧配置(path_config_source指向本地 SDS 配置文件):

clusters: - name: control_plane type: LOGICAL_DNS connect_timeout: 1s load_assignment: cluster_name: control_plane endpoints: - lb_endpoints: - endpoint: address: socket_address: address: controlplane port_value: 8443 typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions explicit_http_config: http2_protocol_options: {} transport_socket: name: "envoy.transport_sockets.tls" typed_config: "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext" common_tls_context: tls_certificate_sds_secret_configs: name: tls_sds sds_config: path_config_source: path: /etc/envoy/tls_certificate_sds_secret.yaml validation_context_sds_secret_config: name: validation_context_sds sds_config: path_config_source: path: /etc/envoy/validation_context_sds_secret.yaml

/etc/envoy/tls_certificate_sds_secret.yaml(客户端证书链与私钥路径):

resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: tls_sds tls_certificate: certificate_chain: filename: /certs/sds_cert.pem private_key: filename: /certs/sds_key.pem

/etc/envoy/validation_context_sds_secret.yaml(用于校验 xDS 服务器证书的 CA 包路径):

resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: validation_context_sds validation_context: trusted_ca: filename: /certs/cacert.pem

在上例中监视点建立在/certs,该目录内的文件移动会触发更新;若改用前文/certs/current符号链接方案,则配合watched_directory: /certs实现更高原子性的轮转。

可观测性:SDS 相关统计指标

SSL socket factory 计数器

SSL socket factory 输出以下 SDS 相关统计,均为 counter 类型:

Downstream listener(命名空间listener.<LISTENER_IP>.server_ssl_socket_factory.*):

名称说明
ssl_context_update_by_sdsSSL 上下文被更新的总次数
downstream_context_secrets_not_ready因 SSL 证书为空而被 reset 的下游连接数

Upstream cluster(命名空间cluster.<CLUSTER_NAME>.client_ssl_socket_factory.*):

名称说明
ssl_context_update_by_sdsSSL 上下文被更新的总次数
upstream_context_secrets_not_ready因 SSL 证书为空而被 reset 的上游连接数

在源码中,这些计数器定义于 ssl_socket.h 的统计宏中;当 SDS 更新触达 socket 时分别在 server_ssl_socket.cc 与 client_ssl_socket.cc 处递增,与上文失败语义(连接被 reset)一一对应。

SDS 订阅统计

每个 SDS 订阅在sds.<SECRET_NAME>.*命名空间下维护标准 xDS 订阅统计树(根在sds.,通过XDS_RESOURCE_NAME标签区分,见 sds_api.cc 中stats.createScopeWithTaggedName("sds", ...)的实现),此外还追踪:

名称说明
key_rotation_failed文件系统密钥轮转在 SDS 更新之外失败的总次数

排障经验:若 SDS 推送正常但证书行为异常,先看sds.<SECRET_NAME>.*下的版本与更新计数;若文件系统轮转失败,key_rotation_failed会增长,并伴随日志Failed to reload certificates: <原因>(对应 sds_api.cc 中捕获EnvoyException的分支)。

按需证书(On-demand Certificates)

默认情况下,SDS 证书获取会阻塞引用它的 listener 与 cluster 的初始化。在某些场景(尤其是多租户部署中,单个 listener 或上游 cluster 需要向对端呈现多种证书)更合理的做法是:先接受连接,再根据对端 hello 消息中的字段(如 SNI)按需请求证书。Envoy 提供envoy.tls.certificate_selectors.on_demand_secret扩展(实现位于 cert_selectors 目录):暂停 TLS 握手,对缺失的证书发起 SDS 请求,收到响应后继续握手。

关键行为:

  • 按需获取的证书配置方式与普通 TLS 证书相同,例如父上下文的所有设置都会应用;若父 TLS 上下文发生动态更新(如 validation context 的 SDS 更新),按需证书上下文也会同步更新,因此握手恢复时使用的是最新版本的 CA secret。
  • 建议配合DELTA_GRPC使用:xDS 响应中的资源删除会取消数据面对该 secret 名的订阅;而普通 GRPC xDS 协议下,每个已映射 secret 的订阅会一直活跃,直到父资源(listener 或 cluster)被删除。
  • 限制:按需证书目前不支持 session resumption。

相关统计:downstream 在listener.<stat_prefix>.on_demand_secret.*命名空间,upstream 在cluster.<stat_prefix>.on_demand_secret.*

名称类型说明
cert_requestedCounter新建 SDS 订阅总数
cert_updatedCounter证书更新总数
cert_activeGauge活跃的证书订阅与证书数

配置示例

使用 SNI 字段作为 SDS 请求中的 secret 名的downstreamTLS 上下文(snimapper 带default_value,并预取默认证书):

common_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

与常规 SDS 证书配置等效、但不阻塞listener 启动的 downstream 配置:连接会被接受并在 TLS 握手期间暂停,证书到达后再恢复。此例使用static_namemapper 固定 secret 名:

common_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: static_name 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侧可以使用从下游 listener 传递的动态 filter state 值作为 secret 名(filter_state_overridemapper):

common_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"

上述 upstream 的 filter state override 要生效,需要在下游 filter chain 中写入对应值,例如:

name: 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

注意两个示例中的disable_stateless_session_resumption/disable_stateful_session_resumption:因为按需证书不支持 session resumption,配置时应显式关闭会话恢复。

小结:如何选择证书管理方案

场景推荐方案
证书轮换低频、可接受短暂重启静态CommonTlsContext+ 热重启
需要不重新部署更新证书远程 gRPC SDS(mTLS 或 UDS 保护通道)
轮转 SDS 引导凭证本身、无法用 gRPC SDS文件系统 SDS +watched_directory符号链接原子轮转
多租户、单 listener/cluster 呈现大量证书on-demand certificate selector + DELTA_GRPC

所有示例中的字段语义可进一步对照 SDS 配置文档、完整示例 sds-source-example.yaml,以及 SDS 客户端实现 source/common/secret/sds_api.cc 与其订阅管理层 secret_manager_impl.cc;TLS 上下文的构建逻辑位于 source/extensions/transport_sockets/tls/ 下的upstream_configdownstream_config实现中。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询