Skip to content

TLS server_name override can be wrongly skipped when a direct destination shares an authority with an HTTPS proxy #25914

Description

@gwenaskell

Context

Follow-up to the tls.server_name OpenSSL fixes in #25881 and #25899.

To keep tls.server_name from being applied to the TLS connection to an HTTPS forward proxy (which must verify the proxy's own certificate against the proxy host), build_https_connector skips the server_name override when the per-connection callback's target authority matches a configured https:// proxy authority (ProxyAuthorities::matches, src/http.rs).

Problem

The connector callback only sees the target host:port of the connection it is dialing. A connection to a given authority can be either:

  • a legitimate TLS connection to the proxy (override should be skipped), or
  • a direct connection to a destination that happens to share the exact same host:port as the proxy — e.g. a no_proxy bypass, or an https:// destination not routed through a proxy that only intercepts a different scheme (override should be applied).

Both produce identical callback inputs but require opposite decisions, so when a direct destination's authority collides exactly with a configured https:// proxy authority, server_name is skipped and certificate verification falls back to the URL host — the very hostname mismatch this option is meant to avoid.

This is a pathological configuration (a data endpoint sharing the exact host:port with the forward proxy), but the behavior is silent.

Why it isn't easily fixed

  • The ambiguity is fundamental at the connector-callback layer: hyper-proxy reuses one inner connector for both proxy and direct (no_proxy) connections, and the destination URI is not known until request time.
  • Bringing no_proxy into the check does not disambiguate: the same authority can serve as both a proxy target and a no_proxy destination in the same config.
  • Early failure at init is not feasible centrally: build_proxy_connector / HttpClient::new do not know destination URIs at construction (they arrive per-request). The destination authority is only known inside each individual sink/source, so any early-fail check would have to be added per-component and would need to re-implement no_proxy / scheme-based routing to decide whether a given endpoint would be dialed directly — scattered, incomplete (dynamic/multiple endpoints), and not worth it for a pathological config.

Possible directions

  • Narrow the tracked authorities to proxy.https only (an https:// URL in the proxy.http field only tunnels plaintext http destinations, for which server_name is meaningless), removing one concrete slice of the problem.
  • Consider architectural options that make proxy vs. direct connections distinguishable at the TLS layer (e.g. separate connectors, or threading per-request intent into the callback).
  • Optionally, per-sink init-time validation/warning when a sink's endpoint authority collides with a configured proxy authority while tls.server_name is set.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: tech debtA code change that does not add user value.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions