Skip to content

GH-1319: [JDBC] Add hostnameOverride connection property for TLS certificate verification and SNI - #1363

Open
fredjoonpark wants to merge 3 commits into
apache:mainfrom
fredjoonpark:GH-1319-hostname-override
Open

fredjoonpark wants to merge 3 commits into
apache:mainfrom
fredjoonpark:GH-1319-hostname-override

Conversation

@fredjoonpark

@fredjoonpark fredjoonpark commented Oct 15, 2026 •

Copy link
Copy Markdown

What's Changed

Adds an optional hostnameOverride connection property to the Flight SQL JDBC driver. When TLS is enabled, the driver verifies the server certificate against this hostname and sends it for SNI and as the gRPC :authority, instead of using the host in the JDBC URL. This lets the driver connect with full certificate verification through SSH tunnels, port-forwarding, private endpoints, or IP addresses that don't match the server certificate. Today the only option in those cases is disableCertificateVerification=true.

  • New property hostnameOverride (string, default unset) in ArrowFlightConnectionProperty, passed from ArrowFlightConnection to ArrowFlightSqlClientHandler.Builder.
  • Builder.build() calls NettyClientBuilder.overrideHostname(...) when encryption is on. The property is ignored when useEncryption=false, and a blank value is treated as unset.
  • Extra endpoint locations in getStreams(): the override is applied only when the endpoint host and port match the original connection (host compared case-insensitively, IPv6 brackets ignored). Endpoints at any other location, or any endpoint when the original connection isn't encrypted, are verified against their own hostname, as they are today. This logic is in Builder.getHostnameOverrideForEndpoint(URI) so it can be unit-tested.
  • Documented in docs/source/flight_sql_jdbc_driver.rst next to tlsRootCerts.

Example:

jdbc:arrow-flight-sql://192.0.2.10:443?useEncryption=true&tlsRootCerts=/path/ca.pem&hostnameOverride=flight.example.com

Tests

  • New ConnectionTlsHostnameOverrideTest. It uses the existing test certificate, which is issued for localhost and has no IP SAN. Its servers bind to 127.0.0.1 and the tests connect there, so they don't depend on how localhost resolves. Failure cases assert that the cause is a CertificateException, not just any SQLException:
    • without the property, the connection fails (No subject alternative names present);
    • with hostnameOverride=localhost, set in the URL or in Properties, it succeeds with full verification;
    • a non-matching override fails, which shows verification uses the override;
    • a blank hostnameOverride= behaves as unset;
    • the property is ignored for unencrypted connections;
    • a partitioned query whose endpoint is at the connection's host and port succeeds, which shows the override reaches endpoint clients;
    • a partitioned query whose endpoint is on the connection's IP but a different port fails, which shows the override is not applied there.
  • ArrowFlightSqlClientHandlerBuilderTest: the default is unset and the builder copy keeps the value. The endpoint rule is covered for same host and port, different port, no port, different case, IPv6 with and without brackets, different host, unencrypted connection, blank value, and no override set.
  • ArrowFlightConnectionConfigImplTest: the property is parsed.

This change is additive and has no breaking changes.

Closes #1319.

@fredjoonpark
fredjoonpark requested a review from jbonofre October 21, 2026 22:19
@fredjoonpark

Copy link
Copy Markdown
Author

@lidavidm @laurentgo @wgtmac @jbonofre any chance we can get this reviewed?

@jbonofre jbonofre added the bug-fix PRs that fix a big. label Oct 30, 2026
@apache apache deleted a comment from github-actions Bot Oct 30, 2026
@github-actions github-actions Bot added this to the 20.0.0 milestone Oct 30, 2026
@jbonofre

Copy link
Copy Markdown
Member

@fredjoonpark I'm resuming the review on this one.

if (!useEncryption
|| hostnameOverride == null
|| endpointHost == null
|| !endpointHost.equalsIgnoreCase(host)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignores the port (the builder test asserts it), so the override applies to every TLS endpoint on the connection host. If the server returns an endpoint on another port of that host that serves a different certificate (e.g. grpc+tls://10.0.0.5:9000 with a cert for 10.0.0.5 or its own name). It's now verified against hostnameOverride and fails, and there's no way to opt out.

Could we match on host and port, so the override only applies to the location it was configured for?

Suggested change
|| !endpointHost.equalsIgnoreCase(host)) {
|| endpointUri.getPort() != port
|| !endpointHost.equalsIgnoreCase(host)) {

If covering every port on the host is need, I'm fine keeping it, but I would love to document it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK we can go with your suggestion. I'd originally left the port out for servers that hand out endpoints on other ports of the same host, but we can add this as an opt-in flag later if anyone asks for it.

The builder test now expects no override on a different port, and the endpoint integration test is split- an endpoint at the connection's host and port uses the override, and one on a different port fails verification.

*/
@VisibleForTesting
@Nullable String getHostnameOverrideForEndpoint(final URI endpointUri) {
final String endpointHost = endpointUri.getHost();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URI#getHost keeps the brackets on IPv6 literals, but host doesn't always have them. JDBC URLs are fine (also parsed with URI#getHost), but when the host comes from Properties or the DataSource as ::1, the root connection works (Location.forGrpcTls adds the brackets) while "[::1]".equalsIgnoreCase("::1") is false.
Endpoints on the same host then silently skip the override and fail verification.

Could we strip the brackets on both sides before comparing (and add a test with withHost("::1")? Something like:

private static String stripBrackets(final String host) {
  return host != null && host.startsWith("[") && host.endsWith("]")
      ? host.substring(1, host.length() - 1)
      : host;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes good catch. Both sides now go through stripBrackets before the comparison, and I added a test for it.

Comment thread docs/source/flight_sql_jdbc_driver.rst Outdated
Comment on lines +127 to +129
that does not match the server certificate. Endpoints returned by
the server are only verified against this hostname if they are on
the same host as the connection. Ignored when ``useEncryption`` is

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whichever way the port question goes (see my comment on getHostnameOverrideForEndpoint), could we make this explicit? Either "on the same host and port as the connection" or "on the same host as the connection, on any port".

@jbonofre

Copy link
Copy Markdown
Member

I will rebase, as the CI has been fixed on main.

Fred Park added 2 commits October 30, 2026 06:14
Normalize hostnameOverride in the builder so an empty or blank value
(e.g. from a templated URL) falls back to the connection host instead of
failing with an invalid authority. Also skip the override for endpoints
when encryption is disabled, and document that it is sent as the gRPC
:authority.
@jbonofre
jbonofre force-pushed the GH-1319-hostname-override branch from c707cd6 to 7ee704d Compare October 30, 2026 06:14
Only apply hostnameOverride to endpoint locations on the same host and
port as the connection, so endpoints on other ports are verified against
their own hostname. Ignore IPv6 brackets when comparing hosts, since a
host set through properties may not have them.
@fredjoonpark
fredjoonpark requested a review from jbonofre October 30, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PRs that fix a big.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[JDBC] Add a hostnameOverride connection property to verify TLS against a hostname different from the connection host

2 participants