Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
import com.google.cloud.spanner.admin.database.v1.stub.DatabaseAdminStubSettings;
import com.google.cloud.spanner.admin.instance.v1.InstanceAdminSettings;
import com.google.cloud.spanner.admin.instance.v1.stub.InstanceAdminStubSettings;
import com.google.cloud.spanner.omni.DynamicKeyManager;
import com.google.cloud.spanner.omni.DynamicTrustManager;
import com.google.cloud.spanner.omni.SpannerOmniCredentials;
import com.google.cloud.spanner.spi.SpannerRpcFactory;
import com.google.cloud.spanner.spi.v1.ChannelEndpointCacheFactory;
Expand Down Expand Up @@ -85,6 +87,7 @@
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.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
import io.opencensus.trace.Tracing;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
Expand Down Expand Up @@ -941,14 +944,14 @@ protected SpannerOptions(Builder builder) {
transportChannelExecutorThreadNameFormat = builder.transportChannelExecutorThreadNameFormat;
channelProvider = builder.channelProvider;
channelEndpointCacheFactory = builder.channelEndpointCacheFactory;
if (builder.mTLSContext != null) {
if (builder.omniSslContext != null) {
channelConfigurator =
channelBuilder -> {
if (builder.channelConfigurator != null) {
channelBuilder = builder.channelConfigurator.apply(channelBuilder);
}
if (channelBuilder instanceof NettyChannelBuilder) {
((NettyChannelBuilder) channelBuilder).sslContext(builder.mTLSContext);
((NettyChannelBuilder) channelBuilder).sslContext(builder.omniSslContext);
}
return channelBuilder;
};
Expand Down Expand Up @@ -1292,6 +1295,13 @@ public GoogleCredentials getDefaultSpannerOmniCredentials() {
public static class Builder
extends ServiceOptions.Builder<Spanner, SpannerOptions, SpannerOptions.Builder> {
private static Builder prepareBuilder(Builder builder) {
if (builder.sslContextBuilder != null) {
try {
builder.omniSslContext = builder.sslContextBuilder.build();
} catch (Exception e) {
throw SpannerExceptionFactory.asSpannerException(e);
}
}
if (builder.instanceType == InstanceType.OMNI) {
builder.enableBuiltInMetrics = false;
builder.setProjectId(SPANNER_OMNI_PROJECT_ID);
Expand All @@ -1314,7 +1324,7 @@ private static Builder prepareBuilder(Builder builder) {
}
if (builder.credentials instanceof SpannerOmniCredentials) {
((SpannerOmniCredentials) builder.credentials)
.initChannel(builder.usePlainText, builder.mTLSContext);
.initChannel(builder.usePlainText, builder.omniSslContext);
}
} else {
if (builder.username != null || builder.secretBytes != null) {
Expand Down Expand Up @@ -1399,7 +1409,8 @@ private static Builder prepareBuilder(Builder builder) {
private MetricsProvider metricsProvider = DefaultMetricsProvider.INSTANCE;
private boolean enableLocationApi = SpannerOptions.environment.isEnableLocationApi();
private String monitoringHost = SpannerOptions.environment.getMonitoringHost();
private SslContext mTLSContext = null;
private SslContextBuilder sslContextBuilder = null;
private SslContext omniSslContext = null;
private boolean usePlainText = false;
private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance();
private RequestOptions.ClientContext clientContext;
Expand Down Expand Up @@ -2240,21 +2251,35 @@ public Builder setEmulatorHost(String emulatorHost) {

/**
* Configures mTLS authentication using the provided client certificate and key files. mTLS via
* useClientCert is only supported for Spanner Omni instances.
* useClientCert is only supported for Spanner Omni instances. Certificates and keys are loaded
* dynamically and reloaded automatically when rotated on disk.
*
* @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
*/
public Builder useClientCert(String clientCertificate, String clientCertificateKey) {
try {
this.mTLSContext =
GrpcSslContexts.forClient()
.keyManager(new File(clientCertificate), new File(clientCertificateKey))
.build();
} catch (Exception e) {
throw SpannerExceptionFactory.asSpannerException(e);
Preconditions.checkNotNull(clientCertificate, "clientCertificate cannot be null");
Preconditions.checkNotNull(clientCertificateKey, "clientCertificateKey cannot be null");
if (this.sslContextBuilder == null) {
this.sslContextBuilder = GrpcSslContexts.forClient();
}
this.sslContextBuilder.keyManager(
new DynamicKeyManager(new File(clientCertificate), new File(clientCertificateKey)));
return this;
}

/**
* Configures the server root CA certificate for SSL/TLS authentication. The CA certificate is
* loaded dynamically and reloaded automatically when rotated on disk.
*
* @param caCertificate Path to the server root CA certificate file.
*/
public Builder setCaCertificate(String caCertificate) {
Preconditions.checkNotNull(caCertificate, "caCertificate cannot be null");
if (this.sslContextBuilder == null) {
this.sslContextBuilder = GrpcSslContexts.forClient();
}
this.sslContextBuilder.trustManager(new DynamicTrustManager(new File(caCertificate)));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.cloud.spanner.connection.ConnectionProperties.AUTOCOMMIT;
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.CA_CERTIFICATE;
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;
Expand Down Expand Up @@ -168,6 +169,7 @@ public class ConnectionOptions {
static final String DEFAULT_CREDENTIALS = null;
static final String DEFAULT_CLIENT_CERTIFICATE = null;
static final String DEFAULT_CLIENT_KEY = null;
static final String DEFAULT_CA_CERTIFICATE = null;
static final String DEFAULT_OAUTH_TOKEN = null;
static final Integer DEFAULT_MIN_SESSIONS = null;
static final Integer DEFAULT_MAX_SESSIONS = null;
Expand Down Expand Up @@ -242,6 +244,9 @@ public class ConnectionOptions {
/** Client key path to establish mTLS */
static final String CLIENT_KEY_PROPERTY_NAME = "clientKey";

/** Server root CA certificate path for SSL/TLS */
static final String CA_CERTIFICATE_PROPERTY_NAME = "caCertificate";

/** Name of the 'autocommit' connection property. */
public static final String AUTOCOMMIT_PROPERTY_NAME = "autocommit";

Expand Down Expand Up @@ -676,6 +681,21 @@ public Builder setType(SpannerOptions.InstanceType instanceType) {
return this;
}

public Builder setClientCertificate(String clientCertificate) {
setConnectionPropertyValue(CLIENT_CERTIFICATE, clientCertificate);
return this;
}

public Builder setClientCertificateKey(String clientCertificateKey) {
setConnectionPropertyValue(CLIENT_KEY, clientCertificateKey);
return this;
}

public Builder setCaCertificate(String caCertificate) {
setConnectionPropertyValue(CA_CERTIFICATE, caCertificate);
return this;
}

/**
* @return the {@link ConnectionOptions}
*/
Expand Down Expand Up @@ -1300,6 +1320,10 @@ String getClientCertificateKey() {
return getInitialConnectionPropertyValue(CLIENT_KEY);
}

String getCaCertificate() {
return getInitialConnectionPropertyValue(CA_CERTIFICATE);
}

/**
* The (custom) user agent string to use for this connection. If <code>null</code>, then the
* default JDBC user agent string will be used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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.BATCH_DML_UPDATE_COUNT_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.CA_CERTIFICATE_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;
Expand All @@ -42,6 +43,7 @@
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_BATCH_DML_UPDATE_COUNT;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_CA_CERTIFICATE;
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;
Expand Down Expand Up @@ -329,6 +331,13 @@ public class ConnectionProperties {
DEFAULT_CLIENT_KEY,
StringValueConverter.INSTANCE,
Context.STARTUP);
static final ConnectionProperty<String> CA_CERTIFICATE =
create(
CA_CERTIFICATE_PROPERTY_NAME,
"Specifies the file path to the server root CA certificate for SSL/TLS validation.",
DEFAULT_CA_CERTIFICATE,
StringValueConverter.INSTANCE,
Context.STARTUP);
static final ConnectionProperty<String> CREDENTIALS_URL =
create(
CREDENTIALS_PROPERTY_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ static class SpannerPoolKey {
private final boolean enableEndToEndTracing;
private final String clientCertificate;
private final String clientCertificateKey;
private final String caCertificate;
private final SpannerOptions.InstanceType instanceType;
private final Boolean enableDirectAccess;
private final String universeDomain;
Expand Down Expand Up @@ -221,6 +222,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException {
this.enableEndToEndTracing = options.isEndToEndTracingEnabled();
this.clientCertificate = options.getClientCertificate();
this.clientCertificateKey = options.getClientCertificateKey();
this.caCertificate = options.getCaCertificate();
this.instanceType = options.getInstanceType();
this.enableDirectAccess = options.isEnableDirectAccess();
this.universeDomain = options.getUniverseDomain();
Expand Down Expand Up @@ -261,6 +263,7 @@ public boolean equals(Object o) {
&& Objects.equals(this.enableEndToEndTracing, other.enableEndToEndTracing)
&& Objects.equals(this.clientCertificate, other.clientCertificate)
&& Objects.equals(this.clientCertificateKey, other.clientCertificateKey)
&& Objects.equals(this.caCertificate, other.caCertificate)
&& Objects.equals(this.instanceType, other.instanceType)
&& Objects.equals(this.enableDirectAccess, other.enableDirectAccess)
&& Objects.equals(this.universeDomain, other.universeDomain)
Expand Down Expand Up @@ -296,6 +299,7 @@ public int hashCode() {
this.enableEndToEndTracing,
this.clientCertificate,
this.clientCertificateKey,
this.caCertificate,
this.instanceType,
this.enableDirectAccess,
this.universeDomain,
Expand Down Expand Up @@ -540,6 +544,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) {
if (key.clientCertificate != null && key.clientCertificateKey != null) {
builder.useClientCert(key.clientCertificate, key.clientCertificateKey);
}
if (key.caCertificate != null) {
builder.setCaCertificate(key.caCertificate);
}
if (key.instanceType != null) {
builder.setType(key.instanceType);
}
Expand Down
Loading
Loading