TLS

Envoy 支持在监听器中终止 TLS,以及在与上游集群建立连接时发起 TLS。支持足以让 Envoy 为现代 Web 服务执行标准边缘代理职责,以及与具有高级 TLS 要求(TLS1.2、SNI 等)的外部服务建立连接。 Envoy 支持以下 TLS 功能

  • 可配置的密码:每个 TLS 监听器和客户端都可以指定其支持的密码。

  • 客户端证书:除服务器证书验证之外,上游/客户端连接还可以提供客户端证书。

  • 证书验证和固定:证书验证选项包括基本链验证、主体名称验证和哈希固定。

  • 证书吊销:如果提供证书吊销列表 (CRL),Envoy 可以针对 CRL 检查对等证书。

  • ALPN:TLS 监听器支持 ALPN。HTTP 连接管理器使用此信息(以及协议推断)来确定客户端是使用 HTTP/1.1 还是 HTTP/2。

  • SNI:SNI 支持服务器(监听器)连接和客户端(上游)连接。

  • 会话恢复:服务器连接支持通过 TLS 会话票证恢复以前的会话(见 RFC 5077)。在热重启和并行 Envoy 实例之间可以执行恢复(通常在前端代理配置中很有用)。

  • BoringSSL 私钥方法:可以异步地从扩展中执行 TLS 私钥操作(签名和解密)。这允许扩展 Envoy 以支持各种密钥管理方案(如 TPM)和 TLS 加速。此机制使用BoringSSL 私钥方法接口

  • OCSP 钉扎:可以将在线证书状态协议响应钉扎到证书。

底层实现

目前,Envoy 被编写为使用BoringSSL 作为 TLS 提供程序。

FIPS 140-2

BoringSSL 可以按照FIPS 兼容模式构建,遵循来自BoringCrypto 模块安全策略的构建说明,使用--define boringssl=fips Bazel 选项。目前,此选项仅在 Linux-x86_64 上可用。

可以通过检查--version 输出中是否包含BoringSSL-FIPS 来验证 FIPS 构建的正确性。

需要注意的是,虽然使用 FIPS 兼容模块是 FIPS 合规性的必要条件,但它本身还不够,并且根据具体情况,可能还需要其他步骤。其他要求可能包括仅使用批准的算法和/或仅使用通过在 FIPS 批准模式下运行的模块生成的私钥。有关更多信息,请参阅BoringCrypto 模块安全策略和/或经认可的 CMVP 实验室

请注意,FIPS 兼容构建基于比非 FIPS 构建更旧的 BoringSSL 版本,它不支持最新的 QUIC API。

启用证书验证

除非验证上下文指定一个或多个受信任的颁发机构证书,否则不会启用上游和下游连接的证书验证。

示例配置

static_resources:
  listeners:
  - name: listener_0
    address: {socket_address: {address: 127.0.0.1, port_value: 10000}}
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          route_config:
            virtual_hosts:
            - name: default
              domains: ["*"]
              routes:
              - match: {prefix: "/"}
                route:
                  cluster: some_service
      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
          common_tls_context:
            tls_certificates:
            - certificate_chain: {filename: "certs/servercert.pem"}
              private_key: {filename: "certs/serverkey.pem"}
            validation_context:
              trusted_ca:
                filename: certs/cacert.pem
  clusters:
  - name: some_service
    type: STATIC
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: some_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 1234
    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"}
            ocsp_staple: {"filename": "certs/server_ocsp_resp.der"}
          validation_context:
            match_typed_subject_alt_names:
            - san_type: DNS
              matcher:
                exact: "foo"
            trusted_ca:
              filename: /etc/ssl/certs/ca-certificates.crt

/etc/ssl/certs/ca-certificates.crt 是 Debian 系统上系统 CA 捆绑包的默认路径。trusted_ca 以及 match_typed_subject_alt_names 使 Envoy 以与例如标准 Debian 安装中的 cURL 相同的方式验证 127.0.0.1:1234 的服务器身份为“foo”。Linux 和 BSD 上系统 CA 捆绑包的常用路径为

  • /etc/ssl/certs/ca-certificates.crt (Debian/Ubuntu/Gentoo 等)

  • /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem (CentOS/RHEL 7)

  • /etc/pki/tls/certs/ca-bundle.crt (Fedora/RHEL 6)

  • /etc/ssl/ca-bundle.pem (OpenSUSE)

  • /usr/local/etc/ssl/cert.pem (FreeBSD)

  • /etc/ssl/cert.pem (OpenBSD)

有关其他 TLS 选项,请参阅UpstreamTlsContextsDownstreamTlsContexts 的参考。

注意

如果只指定了trusted_ca,Envoy 会验证所提供证书的证书链,但不会验证其主体名称、哈希等。通常需要其他验证上下文配置,具体取决于部署情况。

自定义证书验证器

上面解释的配置由“默认”证书验证器使用。Envoy 还支持 envoy.tls.cert_validator 扩展类别中的自定义验证器,这些验证器可以在 CertificateValidationContext 上配置。

例如,可以将 Envoy 配置为按照SPIFFE 规范使用单个监听器或集群中的多个信任捆绑包来验证对等证书。有关更多详细信息,请参阅custom_validator_config 字段的文档

证书选择

DownstreamTlsContexts 支持多个 TLS 证书。这些可以是针对多个服务器名称模式的 RSA 和 ECDSA 证书的混合。

证书配置/加载规则

  • DNS SAN 或主体通用名称被提取为服务器名称模式,以在握手期间匹配 SNI。如果证书中存在 DNS SAN,则不会使用主体通用名称。

  • 类似于“test.example.com”的 FQDN 和类似于“*.example.com”的通配符同时有效,将被加载为两种不同的服务器名称模式。

  • 如果为相同名称或名称模式指定了特定类型(RSA 或 ECDSA)的多个证书,则加载的第一个证书将用于该名称。

  • 拒绝非 P-256、P-384 或 P-521 服务器 ECDSA 证书。

  • 在给定的DownstreamTlsContext 中,不能混合静态证书和 SDS 证书。

证书选择规则

  • 如果客户端支持 SNI,例如 SNI 为“test.example.com”,则它会查找与 SNI 完全匹配的证书。如果证书符合 OCSP 策略并与密钥类型匹配,则选择该证书进行握手。如果证书符合 OCSP 策略,但密钥类型为 RSA,而客户端支持 ECDSA,则将其标记为候选证书并继续搜索,直到选择与之完美匹配的证书或证书耗尽。如果没有完美匹配,则选择候选证书进行握手。

  • 如果客户端支持 SNI,但没有从与 SNI 完全匹配的证书中选择证书,则它会匹配通配符服务器名称。例如,如果 SNI 为“test.example.com”,则使用“test.example.com”的证书优先于“*.example.com”。并且通配符匹配只适用于 1 级深度,因此“*.com”将不会匹配“test.example.com”。之后,它会在每个证书上执行 OCSP 和密钥类型检查,这与完全匹配 SNI 后的情况相同。

  • 如果在匹配通配符名称的证书中没有选择证书,则如果存在候选证书,则选择该证书进行握手。如果没有候选证书,请检查full_scan_certs_on_sni_mismatch,如果已启用,则进行全面扫描所有证书,否则选择第一个证书进行握手。

  • 如果客户端根本没有提供 SNI,则无论full_scan_certs_on_sni_mismatch 为 false 还是 true,都会进行全面扫描。

  • 完整扫描对每个证书执行 OCSP 和密钥类型检查,这与上面描述的精确 SNI 匹配相同。如果未选择任何证书,它将回退到整个列表中的第一个证书。

  • 目前仅支持两种密钥类型:RSA 或 ECDSA。如果客户端支持 P-256、P-384 或 P-521 ECDSA,则优先选择 P-256、P-384 或 P-521 ECDSA 证书,而不是 RSA 证书。它回退到的证书可能会导致握手失败。例如,客户端仅支持 RSA 证书,而证书仅支持 ECDSA。

  • 最终选择的证书必须符合 OCSP 策略。如果未找到此类证书,则拒绝连接。

注意

通过支持基于 SNI 的证书选择,它允许为多个主机名配置大量证书。full_scan_certs_on_sni_mismatch 用于确定当客户端提供 SNI 时,我们是否在 SNI 不匹配的情况下继续进行完整扫描。在此上下文中,SNI 不匹配包含两种情况,一种是没有任何证书与 SNI 匹配,另一种是存在与 SNI 匹配的证书,但这些证书上的 OCSP 策略失败。默认情况下,full_scan_certs_on_sni_mismatch 为 false,因此默认情况下禁用完整扫描。如果启用完整扫描,它将在 SNI 不匹配的情况下从整个证书列表中查找证书,由于 O(n) 复杂性,这可能成为潜在的 DoS 攻击问题。

目前,UpstreamTlsContexts 仅支持单个 TLS 证书。

秘密发现服务 (SDS)

TLS 证书可以在静态资源中指定,也可以从远程获取。通过从 文件系统获取 SDS 配置 或通过从 SDS 服务器推送更新来支持静态资源的证书轮换。有关详细信息,请参阅 SDS

OCSP 钉扎

DownstreamTlsContexts 支持在握手期间将在线证书状态协议 (OCSP) 响应钉扎到 TLS 证书。该 ocsp_staple 字段允许操作员在上下文中为每个证书提供预先计算的 OCSP 响应。单个响应可能与多个证书无关。如果提供,OCSP 响应必须有效并确认证书未被吊销。已过期的 OCSP 响应会被接受,但可能会导致下游连接错误,具体取决于 OCSP 钉扎策略。

DownstreamTlsContexts 支持一个 ocsp_staple_policy 字段来控制当与证书关联的 OCSP 响应丢失或过期时,Envoy 应该停止使用证书还是继续使用证书,而无需钉扎。标记为 must-staple 的证书需要有效的 OCSP 响应,无论 OCSP 钉扎策略如何。在实践中,must-staple 证书导致 cEnvoy 的行为就像 OCSP 钉扎策略是 MUST_STAPLE。在 OCSP 响应过期后,Envoy 不会为新的连接使用 must-staple 证书。

OCSP 响应永远不会钉扎到不通过 status_request 扩展指示支持 OCSP 钉扎的 TLS 请求。

以下运行时标志用于调整 OCSP 响应的要求并覆盖 OCSP 策略。这些标志默认设置为 true

  • envoy.reloadable_features.require_ocsp_response_for_must_staple_certs: 禁用此选项允许操作员在配置中省略 must-staple 证书的 OCSP 响应。

  • envoy.reloadable_features.check_ocsp_policy: 禁用此选项将禁用 OCSP 策略检查。如果客户端支持,则会在可用时钉扎 OCSP 响应,即使响应已过期。如果不存在响应,则跳过钉扎。

OCSP 响应将被忽略 UpstreamTlsContexts

身份验证过滤器

Envoy 提供了一个网络过滤器,该过滤器通过从 REST VPN 服务获取的主体执行 TLS 客户端身份验证。此过滤器将呈现的客户端证书哈希与主体列表进行匹配,以确定是否允许连接。还可以配置可选的 IP 允许列表。此功能可用于为 Web 基础设施构建边缘代理 VPN 支持。

客户端 TLS 身份验证过滤器 配置参考

自定义握手程序扩展

CommonTlsContext 有一个 custom_handshaker 扩展,可用于完全覆盖 SSL 握手行为。这对于实现任何难以用回调表达的 TLS 行为非常有用。无需编写自定义握手程序即可使用私钥方法,请参阅上面描述的 私钥方法接口

为了避免重新实现所有 Ssl::ConnectionInfo 接口,自定义实现可以选择扩展 Envoy::Extensions::TransportSockets::Tls::SslHandshakerImpl

自定义握手程序需要通过 HandshakerCapabilities 明确声明它们负责哪些 TLS 功能。默认的 Envoy 握手程序将管理其余部分。

一个有用的示例握手程序,名为 SslHandshakerImplForTest,位于 此测试 中,并演示了特殊情况下的 SSL_ERROR 处理和回调。

故障排除

当 Envoy 在建立与上游集群的连接时发起 TLS 时,任何错误都将记录到 UPSTREAM_TRANSPORT_FAILURE_REASON 字段或 AccessLogCommon.upstream_transport_failure_reason 字段。

当 Envoy 监听器获取连接并与下游执行 TLS 时,任何错误都将记录到 DOWNSTREAM_TRANSPORT_FAILURE_REASON 字段或 AccessLogCommon.downstream_transport_failure_reason 字段。

常见错误包括

  • Secret is not supplied by SDS: Envoy 仍在等待 SDS 传递密钥/证书或根 CA。

  • SSLV3_ALERT_CERTIFICATE_EXPIRED: 对等证书已过期,配置中不允许。

  • SSLV3_ALERT_CERTIFICATE_UNKNOWN: 对等证书不在配置中指定的 SPKI 中。

  • SSLV3_ALERT_HANDSHAKE_FAILURE: 握手失败,通常是由于上游需要客户端证书,但未提供。

  • TLSV1_ALERT_PROTOCOL_VERSION: TLS 协议版本不匹配。

  • TLSV1_ALERT_UNKNOWN_CA: 对等证书 CA 不在受信任的 CA 中。

BoringSSL 可能引发的更详细的错误列表可以在 此处 找到。