From f79a314745b7f69ea043b14e64e1a0998bac6f91 Mon Sep 17 00:00:00 2001 From: sagnghos Date: Sun, 29 Dec 2024 13:53:14 +0000 Subject: [PATCH 1/6] feat(spanner): mTLS setup for spanner external host clients --- .../google/cloud/spanner/SpannerOptions.java | 50 +++++++++++++++++++ .../spanner/connection/ConnectionOptions.java | 29 +++++++++-- .../connection/ConnectionProperties.java | 18 +++++++ .../cloud/spanner/connection/SpannerPool.java | 4 ++ 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index bc81b42903a..ce2bfe03e03 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -27,9 +27,11 @@ import com.google.api.gax.core.GaxProperties; import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.grpc.GrpcInterceptorProvider; +import com.google.api.gax.grpc.GrpcTransportChannel; import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; +import com.google.api.gax.rpc.FixedTransportChannelProvider; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; @@ -69,13 +71,19 @@ import io.grpc.CompressorRegistry; import io.grpc.Context; import io.grpc.ExperimentalApi; +import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.MethodDescriptor; +import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; +import java.io.File; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.time.Duration; import java.util.ArrayList; @@ -90,6 +98,8 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -942,6 +952,7 @@ public static class Builder private CloseableExecutorProvider asyncExecutorProvider; private String compressorName; private String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST"); + private ManagedChannel managedChannel; private boolean leaderAwareRoutingEnabled = true; private boolean attemptDirectPath = true; private DirectedReadOptions directedReadOptions; @@ -1485,6 +1496,28 @@ public Builder setEmulatorHost(String emulatorHost) { return this; } + public Builder useClientCert(String host, String clientCertificate, String clientKey) { + try { + URI uri = new URI(host); + managedChannel = + NettyChannelBuilder.forAddress(uri.getHost(), uri.getPort()) + .sslContext( + GrpcSslContexts.forClient() + .keyManager(new File(clientCertificate), new File(clientKey)) + .build()) + .build(); + + setChannelProvider( + FixedTransportChannelProvider.create(GrpcTransportChannel.create(managedChannel))); + } catch (URISyntaxException e) { + throw new IllegalArgumentException( + "Invalid host format. Expected format: 'protocol://host[:port]'.", e); + } catch (Exception e) { + throw new RuntimeException("Unexpected error during mTLS setup.", e); + } + return this; + } + /** * Sets OpenTelemetry object to be used for Spanner Metrics and Traces. GlobalOpenTelemetry will * be used as fallback if this options is not set. @@ -1593,6 +1626,23 @@ public SpannerOptions build() { this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); // As we are using plain text, we should never send any credentials. this.setCredentials(NoCredentials.getInstance()); + } else if (managedChannel != null) { + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + final Logger logger = Logger.getLogger(SpannerOptions.class.getName()); + try { + managedChannel.shutdown(); + logger.log( + Level.INFO, "[SpannerOptions] ManagedChannel shut down successfully."); + } catch (Exception e) { + logger.log( + Level.WARNING, + "[SpannerOptions] Failed to shut down ManagedChannel.", + e); + } + })); } if (this.numChannels == null) { this.numChannels = diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java index 6e991816ab6..eea3e9f34dc 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java @@ -20,6 +20,8 @@ import static com.google.cloud.spanner.connection.ConnectionProperties.AUTO_CONFIG_EMULATOR; import static com.google.cloud.spanner.connection.ConnectionProperties.AUTO_PARTITION_MODE; import static com.google.cloud.spanner.connection.ConnectionProperties.CHANNEL_PROVIDER; +import static com.google.cloud.spanner.connection.ConnectionProperties.CLIENT_CERTIFICATE; +import static com.google.cloud.spanner.connection.ConnectionProperties.CLIENT_KEY; import static com.google.cloud.spanner.connection.ConnectionProperties.CREDENTIALS_PROVIDER; import static com.google.cloud.spanner.connection.ConnectionProperties.CREDENTIALS_URL; import static com.google.cloud.spanner.connection.ConnectionProperties.DATABASE_ROLE; @@ -225,6 +227,8 @@ public String[] getValidValues() { static final boolean DEFAULT_USE_VIRTUAL_THREADS = false; static final boolean DEFAULT_USE_VIRTUAL_GRPC_TRANSPORT_THREADS = false; static final String DEFAULT_CREDENTIALS = null; + static final String DEFAULT_CLIENT_CERTIFICATE = null; + static final String DEFAULT_CLIENT_KEY = null; static final String DEFAULT_OAUTH_TOKEN = null; static final Integer DEFAULT_MIN_SESSIONS = null; static final Integer DEFAULT_MAX_SESSIONS = null; @@ -263,6 +267,10 @@ public String[] getValidValues() { private static final String DEFAULT_EMULATOR_HOST = "http://localhost:9010"; /** Use plain text is only for local testing purposes. */ static final String USE_PLAIN_TEXT_PROPERTY_NAME = "usePlainText"; + /** Client certificate path to establish mTLS */ + static final String CLIENT_CERTIFICATE_PROPERTY_NAME = "clientCertificate"; + /** Client key path to establish mTLS */ + static final String CLIENT_KEY_PROPERTY_NAME = "clientKey"; /** Name of the 'autocommit' connection property. */ public static final String AUTOCOMMIT_PROPERTY_NAME = "autocommit"; /** Name of the 'readonly' connection property. */ @@ -434,6 +442,12 @@ static boolean isEnableTransactionalConnectionStateForPostgreSQL() { USE_PLAIN_TEXT_PROPERTY_NAME, "Use a plain text communication channel (i.e. non-TLS) for communicating with the server (true/false). Set this value to true for communication with the Cloud Spanner emulator.", DEFAULT_USE_PLAIN_TEXT), + ConnectionProperty.createStringProperty( + CLIENT_CERTIFICATE_PROPERTY_NAME, + "Specifies the file path to the client certificate required for establishing an mTLS connection."), + ConnectionProperty.createStringProperty( + CLIENT_KEY_PROPERTY_NAME, + "Specifies the file path to the client private key required for establishing an mTLS connection."), ConnectionProperty.createStringProperty( USER_AGENT_PROPERTY_NAME, "The custom user-agent property name to use when communicating with Cloud Spanner. This property is intended for internal library usage, and should not be set by applications."), @@ -828,6 +842,7 @@ public static Builder newBuilder() { private final Credentials fixedCredentials; private final String host; + private boolean isExternalHost; private final String projectId; private final String instanceId; private final String databaseName; @@ -841,10 +856,10 @@ public static Builder newBuilder() { private ConnectionOptions(Builder builder) { Matcher matcher; - boolean isExternalHost = false; + this.isExternalHost = false; if (builder.isValidExternalHostUri(builder.uri)) { matcher = Builder.EXTERNAL_HOST_PATTERN.matcher(builder.uri); - isExternalHost = true; + this.isExternalHost = true; } else { matcher = Builder.SPANNER_URI_PATTERN.matcher(builder.uri); } @@ -967,7 +982,7 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null String projectId = "default"; String instanceId = matcher.group(Builder.INSTANCE_GROUP); - if (!isExternalHost) { + if (!this.isExternalHost) { projectId = matcher.group(Builder.PROJECT_GROUP); } else if (instanceId == null) { instanceId = "default"; @@ -1291,6 +1306,14 @@ boolean isUsePlainText() { || getInitialConnectionPropertyValue(USE_PLAIN_TEXT); } + String getClientCertificate() { + return getInitialConnectionPropertyValue(CLIENT_CERTIFICATE); + } + + String getClientCertificateKey() { + return getInitialConnectionPropertyValue(CLIENT_KEY); + } + /** * The (custom) user agent string to use for this connection. If null, then the * default JDBC user agent string will be used. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java index fd40efa8f4a..6f2628e5a04 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java @@ -22,6 +22,8 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.AUTO_PARTITION_MODE_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.CHANNEL_PROVIDER_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.CLIENT_CERTIFICATE_PROPERTY_NAME; +import static com.google.cloud.spanner.connection.ConnectionOptions.CLIENT_KEY_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.CREDENTIALS_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.CREDENTIALS_PROVIDER_PROPERTY_NAME; import static com.google.cloud.spanner.connection.ConnectionOptions.DATABASE_ROLE_PROPERTY_NAME; @@ -33,6 +35,8 @@ import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_AUTO_PARTITION_MODE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_CHANNEL_PROVIDER; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_CLIENT_CERTIFICATE; +import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_CLIENT_KEY; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_CREDENTIALS; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DATABASE_ROLE; import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_DATA_BOOST_ENABLED; @@ -192,6 +196,20 @@ public class ConnectionProperties { BooleanConverter.INSTANCE, Context.STARTUP); + static final ConnectionProperty CLIENT_CERTIFICATE = + create( + CLIENT_CERTIFICATE_PROPERTY_NAME, + "Specifies the file path to the client certificate required for establishing an mTLS connection.", + DEFAULT_CLIENT_CERTIFICATE, + StringValueConverter.INSTANCE, + Context.STARTUP); + static final ConnectionProperty CLIENT_KEY = + create( + CLIENT_KEY_PROPERTY_NAME, + "Specifies the file path to the client private key required for establishing an mTLS connection.", + DEFAULT_CLIENT_KEY, + StringValueConverter.INSTANCE, + Context.STARTUP); static final ConnectionProperty CREDENTIALS_URL = create( CREDENTIALS_PROPERTY_NAME, diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java index 81246e41938..28be962a2fc 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java @@ -393,6 +393,10 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { // Set a custom channel configurator to allow http instead of https. builder.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); } + if (options.getClientCertificate() != null && options.getClientCertificateKey() != null) { + builder.useClientCert( + options.getHost(), options.getClientCertificate(), options.getClientCertificateKey()); + } if (options.getConfigurator() != null) { options.getConfigurator().configure(builder); } From 716b782063320caa073c64d9ab9f3503a38d41ac Mon Sep 17 00:00:00 2001 From: sagnghos Date: Mon, 30 Dec 2024 05:33:30 +0000 Subject: [PATCH 2/6] feat(spanner): mTLS setup for spanner clients --- .../src/main/java/com/google/cloud/spanner/SpannerOptions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index ce2bfe03e03..47c4692b25e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -1627,6 +1627,7 @@ public SpannerOptions build() { // As we are using plain text, we should never send any credentials. this.setCredentials(NoCredentials.getInstance()); } else if (managedChannel != null) { + // Add shutdown hook for the ManagedChannel if created to prevent resource leak Runtime.getRuntime() .addShutdownHook( new Thread( From ddbb90afac10b3084c6975ee36da4fb0354cb87a Mon Sep 17 00:00:00 2001 From: sagnghos Date: Mon, 30 Dec 2024 05:41:10 +0000 Subject: [PATCH 3/6] feat(spanner): removing isExternalHost as a data member of the builder class --- .../google/cloud/spanner/connection/ConnectionOptions.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java index eea3e9f34dc..d205699bb9e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java @@ -842,7 +842,6 @@ public static Builder newBuilder() { private final Credentials fixedCredentials; private final String host; - private boolean isExternalHost; private final String projectId; private final String instanceId; private final String databaseName; @@ -856,10 +855,10 @@ public static Builder newBuilder() { private ConnectionOptions(Builder builder) { Matcher matcher; - this.isExternalHost = false; + boolean isExternalHost = false; if (builder.isValidExternalHostUri(builder.uri)) { matcher = Builder.EXTERNAL_HOST_PATTERN.matcher(builder.uri); - this.isExternalHost = true; + isExternalHost = true; } else { matcher = Builder.SPANNER_URI_PATTERN.matcher(builder.uri); } @@ -982,7 +981,7 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null String projectId = "default"; String instanceId = matcher.group(Builder.INSTANCE_GROUP); - if (!this.isExternalHost) { + if (!isExternalHost) { projectId = matcher.group(Builder.PROJECT_GROUP); } else if (instanceId == null) { instanceId = "default"; From 03ce64f59acb88910189aab6284d943c8639bba0 Mon Sep 17 00:00:00 2001 From: sagnghos Date: Thu, 2 Jan 2025 12:13:27 +0000 Subject: [PATCH 4/6] feat(spanner): added spanner options method usePlainText for abstraction --- .../main/java/com/google/cloud/spanner/SpannerOptions.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 47c4692b25e..1bbbbb0939b 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -1518,6 +1518,12 @@ public Builder useClientCert(String host, String clientCertificate, String clien return this; } + public Builder usePlainText() { + this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); + this.setCredentials(NoCredentials.getInstance()); + return this; + } + /** * Sets OpenTelemetry object to be used for Spanner Metrics and Traces. GlobalOpenTelemetry will * be used as fallback if this options is not set. From 7259b976436691467ab8aa2b7290851d160d67aa Mon Sep 17 00:00:00 2001 From: sagnghos Date: Tue, 7 Jan 2025 13:42:44 +0000 Subject: [PATCH 5/6] feat(spanner): replaced setting channel provider with setting channel configurator --- .../google/cloud/spanner/SpannerOptions.java | 72 +++++++------------ .../cloud/spanner/connection/SpannerPool.java | 9 ++- 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 1bbbbb0939b..1d0c702cdfc 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -27,11 +27,9 @@ import com.google.api.gax.core.GaxProperties; import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.grpc.GrpcInterceptorProvider; -import com.google.api.gax.grpc.GrpcTransportChannel; import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.ApiCallContext; -import com.google.api.gax.rpc.FixedTransportChannelProvider; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; @@ -71,19 +69,17 @@ import io.grpc.CompressorRegistry; import io.grpc.Context; import io.grpc.ExperimentalApi; -import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.MethodDescriptor; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.time.Duration; import java.util.ArrayList; @@ -98,8 +94,6 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -952,7 +946,6 @@ public static class Builder private CloseableExecutorProvider asyncExecutorProvider; private String compressorName; private String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST"); - private ManagedChannel managedChannel; private boolean leaderAwareRoutingEnabled = true; private boolean attemptDirectPath = true; private DirectedReadOptions directedReadOptions; @@ -963,6 +956,7 @@ public static class Builder private boolean enableEndToEndTracing = SpannerOptions.environment.isEnableEndToEndTracing(); private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics(); private String monitoringHost = SpannerOptions.environment.getMonitoringHost(); + private SslContext mTLSContext = null; private static String createCustomClientLibToken(String token) { return token + " " + ServiceOptions.getGoogApiClientLibName(); @@ -1496,34 +1490,27 @@ public Builder setEmulatorHost(String emulatorHost) { return this; } - public Builder useClientCert(String host, String clientCertificate, String clientKey) { + /** + * Configures mTLS authentication using the provided client certificate and key files. mTLS is + * only supported for external spanner hosts. + * + * @param clientCertificate Path to the client certificate file. + * @param clientCertificateKey Path to the client private key file. + * @throws SpannerException If an error occurs while configuring the mTLS context + */ + @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3574") + public Builder useClientCert(String clientCertificate, String clientCertificateKey) { try { - URI uri = new URI(host); - managedChannel = - NettyChannelBuilder.forAddress(uri.getHost(), uri.getPort()) - .sslContext( - GrpcSslContexts.forClient() - .keyManager(new File(clientCertificate), new File(clientKey)) - .build()) + this.mTLSContext = + GrpcSslContexts.forClient() + .keyManager(new File(clientCertificate), new File(clientCertificateKey)) .build(); - - setChannelProvider( - FixedTransportChannelProvider.create(GrpcTransportChannel.create(managedChannel))); - } catch (URISyntaxException e) { - throw new IllegalArgumentException( - "Invalid host format. Expected format: 'protocol://host[:port]'.", e); } catch (Exception e) { - throw new RuntimeException("Unexpected error during mTLS setup.", e); + throw SpannerExceptionFactory.asSpannerException(e); } return this; } - public Builder usePlainText() { - this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); - this.setCredentials(NoCredentials.getInstance()); - return this; - } - /** * Sets OpenTelemetry object to be used for Spanner Metrics and Traces. GlobalOpenTelemetry will * be used as fallback if this options is not set. @@ -1632,24 +1619,15 @@ public SpannerOptions build() { this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); // As we are using plain text, we should never send any credentials. this.setCredentials(NoCredentials.getInstance()); - } else if (managedChannel != null) { - // Add shutdown hook for the ManagedChannel if created to prevent resource leak - Runtime.getRuntime() - .addShutdownHook( - new Thread( - () -> { - final Logger logger = Logger.getLogger(SpannerOptions.class.getName()); - try { - managedChannel.shutdown(); - logger.log( - Level.INFO, "[SpannerOptions] ManagedChannel shut down successfully."); - } catch (Exception e) { - logger.log( - Level.WARNING, - "[SpannerOptions] Failed to shut down ManagedChannel.", - e); - } - })); + } + if (mTLSContext != null) { + this.setChannelConfigurator( + builder -> { + if (builder instanceof NettyChannelBuilder) { + ((NettyChannelBuilder) builder).sslContext(mTLSContext); + } + return builder; + }); } if (this.numChannels == null) { this.numChannels = diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java index 28be962a2fc..2db9f8aa96d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java @@ -161,6 +161,8 @@ static class SpannerPoolKey { private final Boolean enableExtendedTracing; private final Boolean enableApiTracing; private final boolean enableEndToEndTracing; + private final String clientCertificate; + private final String clientCertificateKey; @VisibleForTesting static SpannerPoolKey of(ConnectionOptions options) { @@ -192,6 +194,8 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException { this.enableExtendedTracing = options.isEnableExtendedTracing(); this.enableApiTracing = options.isEnableApiTracing(); this.enableEndToEndTracing = options.isEndToEndTracingEnabled(); + this.clientCertificate = options.getClientCertificate(); + this.clientCertificateKey = options.getClientCertificateKey(); } @Override @@ -393,9 +397,8 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { // Set a custom channel configurator to allow http instead of https. builder.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); } - if (options.getClientCertificate() != null && options.getClientCertificateKey() != null) { - builder.useClientCert( - options.getHost(), options.getClientCertificate(), options.getClientCertificateKey()); + if (key.clientCertificate != null && key.clientCertificateKey != null) { + builder.useClientCert(key.clientCertificate, key.clientCertificateKey); } if (options.getConfigurator() != null) { options.getConfigurator().configure(builder); From a6a60b2d5965b5768959399409be35be9a1c5e3c Mon Sep 17 00:00:00 2001 From: sagnghos Date: Tue, 7 Jan 2025 19:24:13 +0000 Subject: [PATCH 6/6] feat(spanner): added cert and key to hashCode and equals of SpannerPoolKey --- .../com/google/cloud/spanner/connection/SpannerPool.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java index 2db9f8aa96d..9558947156c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java @@ -218,7 +218,9 @@ public boolean equals(Object o) { && Objects.equals(this.openTelemetry, other.openTelemetry) && Objects.equals(this.enableExtendedTracing, other.enableExtendedTracing) && Objects.equals(this.enableApiTracing, other.enableApiTracing) - && Objects.equals(this.enableEndToEndTracing, other.enableEndToEndTracing); + && Objects.equals(this.enableEndToEndTracing, other.enableEndToEndTracing) + && Objects.equals(this.clientCertificate, other.clientCertificate) + && Objects.equals(this.clientCertificateKey, other.clientCertificateKey); } @Override @@ -237,7 +239,9 @@ public int hashCode() { this.openTelemetry, this.enableExtendedTracing, this.enableApiTracing, - this.enableEndToEndTracing); + this.enableEndToEndTracing, + this.clientCertificate, + this.clientCertificateKey); } }