5353import com .google .cloud .spanner .admin .database .v1 .stub .DatabaseAdminStubSettings ;
5454import com .google .cloud .spanner .admin .instance .v1 .InstanceAdminSettings ;
5555import com .google .cloud .spanner .admin .instance .v1 .stub .InstanceAdminStubSettings ;
56+ import com .google .cloud .spanner .omni .DynamicKeyManager ;
57+ import com .google .cloud .spanner .omni .DynamicTrustManager ;
5658import com .google .cloud .spanner .omni .SpannerOmniCredentials ;
5759import com .google .cloud .spanner .spi .SpannerRpcFactory ;
5860import com .google .cloud .spanner .spi .v1 .ChannelEndpointCacheFactory ;
8587import io .grpc .netty .shaded .io .grpc .netty .GrpcSslContexts ;
8688import io .grpc .netty .shaded .io .grpc .netty .NettyChannelBuilder ;
8789import io .grpc .netty .shaded .io .netty .handler .ssl .SslContext ;
90+ import io .grpc .netty .shaded .io .netty .handler .ssl .SslContextBuilder ;
8891import io .opencensus .trace .Tracing ;
8992import io .opentelemetry .api .GlobalOpenTelemetry ;
9093import io .opentelemetry .api .OpenTelemetry ;
@@ -941,14 +944,14 @@ protected SpannerOptions(Builder builder) {
941944 transportChannelExecutorThreadNameFormat = builder .transportChannelExecutorThreadNameFormat ;
942945 channelProvider = builder .channelProvider ;
943946 channelEndpointCacheFactory = builder .channelEndpointCacheFactory ;
944- if (builder .mTLSContext != null ) {
947+ if (builder .omniSslContext != null ) {
945948 channelConfigurator =
946949 channelBuilder -> {
947950 if (builder .channelConfigurator != null ) {
948951 channelBuilder = builder .channelConfigurator .apply (channelBuilder );
949952 }
950953 if (channelBuilder instanceof NettyChannelBuilder ) {
951- ((NettyChannelBuilder ) channelBuilder ).sslContext (builder .mTLSContext );
954+ ((NettyChannelBuilder ) channelBuilder ).sslContext (builder .omniSslContext );
952955 }
953956 return channelBuilder ;
954957 };
@@ -1292,6 +1295,13 @@ public GoogleCredentials getDefaultSpannerOmniCredentials() {
12921295 public static class Builder
12931296 extends ServiceOptions .Builder <Spanner , SpannerOptions , SpannerOptions .Builder > {
12941297 private static Builder prepareBuilder (Builder builder ) {
1298+ if (builder .sslContextBuilder != null ) {
1299+ try {
1300+ builder .omniSslContext = builder .sslContextBuilder .build ();
1301+ } catch (Exception e ) {
1302+ throw SpannerExceptionFactory .asSpannerException (e );
1303+ }
1304+ }
12951305 if (builder .instanceType == InstanceType .OMNI ) {
12961306 builder .enableBuiltInMetrics = false ;
12971307 builder .setProjectId (SPANNER_OMNI_PROJECT_ID );
@@ -1314,7 +1324,7 @@ private static Builder prepareBuilder(Builder builder) {
13141324 }
13151325 if (builder .credentials instanceof SpannerOmniCredentials ) {
13161326 ((SpannerOmniCredentials ) builder .credentials )
1317- .initChannel (builder .usePlainText , builder .mTLSContext );
1327+ .initChannel (builder .usePlainText , builder .omniSslContext );
13181328 }
13191329 } else {
13201330 if (builder .username != null || builder .secretBytes != null ) {
@@ -1399,7 +1409,8 @@ private static Builder prepareBuilder(Builder builder) {
13991409 private MetricsProvider metricsProvider = DefaultMetricsProvider .INSTANCE ;
14001410 private boolean enableLocationApi = SpannerOptions .environment .isEnableLocationApi ();
14011411 private String monitoringHost = SpannerOptions .environment .getMonitoringHost ();
1402- private SslContext mTLSContext = null ;
1412+ private SslContextBuilder sslContextBuilder = null ;
1413+ private SslContext omniSslContext = null ;
14031414 private boolean usePlainText = false ;
14041415 private TransactionOptions defaultTransactionOptions = TransactionOptions .getDefaultInstance ();
14051416 private RequestOptions .ClientContext clientContext ;
@@ -2240,21 +2251,35 @@ public Builder setEmulatorHost(String emulatorHost) {
22402251
22412252 /**
22422253 * Configures mTLS authentication using the provided client certificate and key files. mTLS via
2243- * useClientCert is only supported for Spanner Omni instances.
2254+ * useClientCert is only supported for Spanner Omni instances. Certificates and keys are loaded
2255+ * dynamically and reloaded automatically when rotated on disk.
22442256 *
22452257 * @param clientCertificate Path to the client certificate file.
22462258 * @param clientCertificateKey Path to the client private key file.
2247- * @throws SpannerException If an error occurs while configuring the mTLS context
22482259 */
22492260 public Builder useClientCert (String clientCertificate , String clientCertificateKey ) {
2250- try {
2251- this .mTLSContext =
2252- GrpcSslContexts .forClient ()
2253- .keyManager (new File (clientCertificate ), new File (clientCertificateKey ))
2254- .build ();
2255- } catch (Exception e ) {
2256- throw SpannerExceptionFactory .asSpannerException (e );
2261+ Preconditions .checkNotNull (clientCertificate , "clientCertificate cannot be null" );
2262+ Preconditions .checkNotNull (clientCertificateKey , "clientCertificateKey cannot be null" );
2263+ if (this .sslContextBuilder == null ) {
2264+ this .sslContextBuilder = GrpcSslContexts .forClient ();
2265+ }
2266+ this .sslContextBuilder .keyManager (
2267+ new DynamicKeyManager (new File (clientCertificate ), new File (clientCertificateKey )));
2268+ return this ;
2269+ }
2270+
2271+ /**
2272+ * Configures the server root CA certificate for SSL/TLS authentication. The CA certificate is
2273+ * loaded dynamically and reloaded automatically when rotated on disk.
2274+ *
2275+ * @param caCertificate Path to the server root CA certificate file.
2276+ */
2277+ public Builder setCaCertificate (String caCertificate ) {
2278+ Preconditions .checkNotNull (caCertificate , "caCertificate cannot be null" );
2279+ if (this .sslContextBuilder == null ) {
2280+ this .sslContextBuilder = GrpcSslContexts .forClient ();
22572281 }
2282+ this .sslContextBuilder .trustManager (new DynamicTrustManager (new File (caCertificate )));
22582283 return this ;
22592284 }
22602285
0 commit comments