Describe the enhancement requested
The Flight SQL JDBC driver has no way to set the hostname used for TLS certificate verification (and SNI) separately from the host it connects to. The driver always verifies the server certificate against the host in the JDBC URL.
This breaks TLS whenever the client connects through an address or hostname that doesn't match the server's certificate, for example:
- Tunnels and port-forwarding: reaching a private server through SSH (
ssh -L), AWS Systems Manager Session Manager port forwarding, or kubectl port-forward. The client connects to localhost, but the certificate is issued for the server's real hostname.
- Private endpoints and aliases: connecting through a hostname other than the one on the certificate, for example an AWS PrivateLink interface endpoint DNS name (
vpce-...amazonaws.com) when private DNS isn't enabled, or an internal alias that points at a load balancer.
- Connecting by IP address: service discovery or client-side load balancing that returns IP addresses (for example Kubernetes endpoints or AWS Cloud Map), when the certificate only contains DNS names in its SAN.
- Private network gateways that require the client to dial a specific address (IPv4 or IPv6) instead of the server's own hostname.
The available workarounds all have significant drawbacks:
disableCertificateVerification=true removes server authentication entirely.
- Making the certificate's hostname resolve to the target address (
/etc/hosts, custom DNS, or a JDK InetAddressResolverProvider) applies to the whole OS or JVM. It requires control over that environment, and it can't send the same hostname to different addresses for different connections in one process.
- Adding the connection address (
localhost, an IP address or an endpoint hostname) to the server certificate's SAN requires control over the server certificate. Managed services usually don't allow that, and the address may change.
- Using
FlightClient directly with overrideHostname works, but doesn't help JDBC-based tools.
Example
jdbc:arrow-flight-sql://192.0.2.10:443?useEncryption=true&tlsRootCerts=/path/ca.pem
fails with:
No subject alternative names matching IP address 192.0.2.10 found
even though the server presents a valid certificate for flight.example.com.
Observed with flight-sql-jdbc-driver 19.0.0. The code path is unchanged on main.
Flight client already supports this
FlightClient.Builder.overrideHostname(String) and NettyClientBuilder.overrideHostname(String) already exist and map to gRPC's overrideAuthority. This was confirmed as the supported way to set SNI for the Java Flight client in apache/arrow#26155 (ARROW-10144). The JDBC driver builds its client with NettyClientBuilder in ArrowFlightSqlClientHandler.Builder.build(), but never calls overrideHostname, and there is no connection property to set it.
Proposal
- Add an optional connection property,
hostnameOverride (string, default unset), to ArrowFlightConnectionProperty.
- When set and
useEncryption=true, call clientBuilder.overrideHostname(value) in ArrowFlightSqlClientHandler.Builder.build().
- Handle the clients the driver creates for additional endpoint locations in
getStreams(). These are built from a copy of the original builder (new Builder(original).withHost(endpointUri.getHost())...), so a plain copied field would wrongly verify a different endpoint host against the override. Apply the override only when the endpoint host matches the original connection host, and otherwise clear it so those locations keep the current behavior.
- Ignore the property when encryption is disabled, and document it next to
disableCertificateVerification and tlsRootCerts.
Prior art in other JDBC drivers
- Trino JDBC:
hostnameInCertificate
- Microsoft SQL Server JDBC:
hostNameInCertificate
Tests
Connect over TLS to an IP literal (for example 127.0.0.1 or [::1]) using a test certificate whose SAN only contains a DNS name:
- Without the property: verification fails.
- With
hostnameOverride=<dns-name>: the connection succeeds with full verification.
I'm happy to submit a PR for this.
Describe the enhancement requested
The Flight SQL JDBC driver has no way to set the hostname used for TLS certificate verification (and SNI) separately from the host it connects to. The driver always verifies the server certificate against the
hostin the JDBC URL.This breaks TLS whenever the client connects through an address or hostname that doesn't match the server's certificate, for example:
ssh -L), AWS Systems Manager Session Manager port forwarding, orkubectl port-forward. The client connects tolocalhost, but the certificate is issued for the server's real hostname.vpce-...amazonaws.com) when private DNS isn't enabled, or an internal alias that points at a load balancer.The available workarounds all have significant drawbacks:
disableCertificateVerification=trueremoves server authentication entirely./etc/hosts, custom DNS, or a JDKInetAddressResolverProvider) applies to the whole OS or JVM. It requires control over that environment, and it can't send the same hostname to different addresses for different connections in one process.localhost, an IP address or an endpoint hostname) to the server certificate's SAN requires control over the server certificate. Managed services usually don't allow that, and the address may change.FlightClientdirectly withoverrideHostnameworks, but doesn't help JDBC-based tools.Example
fails with:
even though the server presents a valid certificate for
flight.example.com.Observed with
flight-sql-jdbc-driver19.0.0. The code path is unchanged onmain.Flight client already supports this
FlightClient.Builder.overrideHostname(String)andNettyClientBuilder.overrideHostname(String)already exist and map to gRPC'soverrideAuthority. This was confirmed as the supported way to set SNI for the Java Flight client in apache/arrow#26155 (ARROW-10144). The JDBC driver builds its client withNettyClientBuilderinArrowFlightSqlClientHandler.Builder.build(), but never callsoverrideHostname, and there is no connection property to set it.Proposal
hostnameOverride(string, default unset), toArrowFlightConnectionProperty.useEncryption=true, callclientBuilder.overrideHostname(value)inArrowFlightSqlClientHandler.Builder.build().getStreams(). These are built from a copy of the original builder (new Builder(original).withHost(endpointUri.getHost())...), so a plain copied field would wrongly verify a different endpoint host against the override. Apply the override only when the endpoint host matches the original connection host, and otherwise clear it so those locations keep the current behavior.disableCertificateVerificationandtlsRootCerts.Prior art in other JDBC drivers
hostnameInCertificatehostNameInCertificateTests
Connect over TLS to an IP literal (for example
127.0.0.1or[::1]) using a test certificate whose SAN only contains a DNS name:hostnameOverride=<dns-name>: the connection succeeds with full verification.I'm happy to submit a PR for this.