Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/source/flight_sql_jdbc_driver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ case-sensitive. The supported parameters are:
- Path to PEM-encoded root certificates for TLS - use this as
an alternative to ``trustStore``

* - hostnameOverride
- null
- When TLS is enabled, the hostname to verify the server certificate
against and to send for SNI and as the gRPC ``:authority`` header,
instead of **HOSTNAME**. Use this when
connecting through a tunnel, a private endpoint, or an IP address
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 and port as the connection. Ignored when
``useEncryption`` is false.

* - clientCertificate
- null
- Path to PEM-encoded client mTLS certificate when the Flight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private static ArrowFlightSqlClientHandler createNewClientHandler(
.withBufferAllocator(allocator)
.withEncryption(config.useEncryption())
.withDisableCertificateVerification(config.getDisableCertificateVerification())
.withHostnameOverride(config.getHostnameOverride())
.withToken(config.getToken())
.withCallOptions(config.toCallOption())
.withRetainCookies(config.retainCookies())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ public List<CloseableEndpointStreamPair> getStreams(final FlightInfo flightInfo)
.withHost(endpointUri.getHost())
.withPort(endpointUri.getPort())
.withEncryption(endpointUri.getScheme().equals(LocationSchemes.GRPC_TLS))
.withHostnameOverride(builder.getHostnameOverrideForEndpoint(endpointUri))
.withClientCache(flightClientCache)
.withConnectTimeout(builder.connectTimeout);

Expand Down Expand Up @@ -677,6 +678,8 @@ public static final class Builder {

@VisibleForTesting boolean disableCertificateVerification;

@VisibleForTesting @Nullable String hostnameOverride;

@VisibleForTesting boolean useSystemTrustStore = true;

@VisibleForTesting String tlsRootCertificatesPath;
Expand Down Expand Up @@ -732,6 +735,7 @@ public Builder() {}
this.token = original.token;
this.useEncryption = original.useEncryption;
this.disableCertificateVerification = original.disableCertificateVerification;
this.hostnameOverride = original.hostnameOverride;
this.useSystemTrustStore = original.useSystemTrustStore;
this.tlsRootCertificatesPath = original.tlsRootCertificatesPath;
this.clientCertificatePath = original.clientCertificatePath;
Expand Down Expand Up @@ -840,6 +844,54 @@ public Builder withDisableCertificateVerification(
return this;
}

/**
* Sets the hostname to use for TLS certificate verification and SNI instead of the host this
* handler connects to. It is also sent as the gRPC authority ({@code :authority} header).
* Ignored when encryption is disabled.
*
* @param hostnameOverride the hostname to verify the server certificate against, or null or
* blank to use the host.
* @return this instance.
*/
public Builder withHostnameOverride(@Nullable final String hostnameOverride) {
final String trimmed = hostnameOverride == null ? null : hostnameOverride.trim();
this.hostnameOverride = trimmed == null || trimmed.isEmpty() ? null : trimmed;
return this;
}

/**
* Gets the hostname override to use when connecting to an endpoint location. The override is
* only valid for the host and port it was configured for, so endpoints at any other location
* are verified against their own hostname. Like the override itself, it is ignored when the
* original connection does not use encryption.
*
* @param endpointUri the endpoint location.
* @return the hostname override if the endpoint is on the original host and port, otherwise
* null.
*/
@VisibleForTesting
@Nullable String getHostnameOverrideForEndpoint(final URI endpointUri) {
final String endpointHost = stripBrackets(endpointUri.getHost());
if (!useEncryption
|| hostnameOverride == null
|| endpointHost == null
|| endpointUri.getPort() != port
|| !endpointHost.equalsIgnoreCase(stripBrackets(host))) {
return null;
}
return hostnameOverride;
}

/**
* Strips the brackets from an IPv6 literal. {@link URI#getHost()} keeps them, but a host set
* through properties may not have them.
*/
private static @Nullable String stripBrackets(@Nullable final String host) {
return host != null && host.startsWith("[") && host.endsWith("]")
? host.substring(1, host.length() - 1)
: host;
}

/**
* Sets whether to use the certificates from the operating system.
*
Expand Down Expand Up @@ -1070,6 +1122,10 @@ public ArrowFlightSqlClientHandler build() throws SQLException {
clientBuilder.location(location);

if (useEncryption) {
if (hostnameOverride != null) {
clientBuilder.overrideHostname(hostnameOverride);
}

if (disableCertificateVerification) {
clientBuilder.verifyServer(false);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ public String getTlsRootCertificatesPath() {
return ArrowFlightConnectionProperty.TLS_ROOT_CERTS.getString(properties);
}

/**
* Gets the hostname to use for TLS certificate verification and SNI instead of the host.
*
* @return the hostname override, or null if not set.
*/
public String getHostnameOverride() {
return ArrowFlightConnectionProperty.HOSTNAME_OVERRIDE.getString(properties);
}

public String getClientCertificatePath() {
return ArrowFlightConnectionProperty.CLIENT_CERTIFICATE.getString(properties);
}
Expand Down Expand Up @@ -258,6 +267,7 @@ public enum ArrowFlightConnectionProperty implements ConnectionProperty {
TRUST_STORE_PASSWORD("trustStorePassword", null, Type.STRING, false),
USE_SYSTEM_TRUST_STORE("useSystemTrustStore", true, Type.BOOLEAN, false),
TLS_ROOT_CERTS("tlsRootCerts", null, Type.STRING, false),
HOSTNAME_OVERRIDE("hostnameOverride", null, Type.STRING, false),
CLIENT_CERTIFICATE("clientCertificate", null, Type.STRING, false),
CLIENT_KEY("clientKey", null, Type.STRING, false),
THREAD_POOL_SIZE("threadPoolSize", 1, Type.NUMBER, false),
Expand Down
Loading
Loading