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
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ void start() {
}

try {
if (Strings.isNullOrEmpty(options.getProxyTLSCertPath())
!= Strings.isNullOrEmpty(options.getProxyTLSKeyPath())) {
throw new IllegalArgumentException(
"Both proxyTLSCertPath and proxyTLSKeyPath must be specified for TLS support");
}
Credentials credentials = options.getCredentials();
if (options.usePlainText() || !Strings.isNullOrEmpty(options.getExperimentalHostEndpoint())) {
credentials = null;
Expand Down Expand Up @@ -180,10 +185,21 @@ void start() {
new AdapterClientWrapper(adapterClient, attachmentsCache, sessionManager);

// Start listening on the specified host and port.
serverSocket =
new ServerSocket(
options.getTcpPort(), DEFAULT_CONNECTION_BACKLOG, options.getInetAddress());
LOG.info("Local TCP server started on {}:{}", options.getInetAddress(), options.getTcpPort());
if (options.useProxyTLS()) {
try {
serverSocket = createSSLServerSocket();
LOG.info(
"Local TLS server started on {}:{}", options.getInetAddress(), options.getTcpPort());
} catch (Exception e) {
throw new RuntimeException("Failed to create TLS server socket", e);
}
} else {
serverSocket =
new ServerSocket(
options.getTcpPort(), DEFAULT_CONNECTION_BACKLOG, options.getInetAddress());
LOG.info(
"Local TCP server started on {}:{}", options.getInetAddress(), options.getTcpPort());
}

if (executor == null) {
executor = Executors.newCachedThreadPool();
Expand All @@ -195,6 +211,8 @@ void start() {
started = true;
LOG.info("Adapter started for database '{}'.", options.getDatabaseUri());

} catch (IllegalArgumentException e) {
throw e;
} catch (IOException | RuntimeException e) {
throw new AdapterStartException(e);
}
Expand Down Expand Up @@ -267,4 +285,63 @@ public AdapterStartException(Throwable cause) {
super("Failed to start the adapter.", cause);
}
}

private ServerSocket createSSLServerSocket() throws Exception {
javax.net.ssl.SSLContext sslContext =
createSSLContext(options.getProxyTLSCertPath(), options.getProxyTLSKeyPath());
return sslContext
.getServerSocketFactory()
.createServerSocket(
options.getTcpPort(), DEFAULT_CONNECTION_BACKLOG, options.getInetAddress());
}

private javax.net.ssl.SSLContext createSSLContext(String certPath, String keyPath)
throws Exception {
// 1. Read the FULL certificate chain
java.security.cert.CertificateFactory cf =
java.security.cert.CertificateFactory.getInstance("X.509");
java.util.Collection<? extends java.security.cert.Certificate> certs;
try (java.io.FileInputStream certIs = new java.io.FileInputStream(certPath)) {
// generateCertificates reads all certs in a bundled PEM file
certs = cf.generateCertificates(certIs);
}
java.security.cert.Certificate[] certChain =
certs.toArray(new java.security.cert.Certificate[0]);

// 2. Parse the Private Key (Requires unencrypted PKCS#8 format)
String keyStr =
new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(keyPath)))
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = java.util.Base64.getDecoder().decode(keyStr);
java.security.spec.PKCS8EncodedKeySpec spec =
new java.security.spec.PKCS8EncodedKeySpec(keyBytes);

// 3. Generate the key, falling back to EC if RSA fails
java.security.PrivateKey key;
try {
key = java.security.KeyFactory.getInstance("RSA").generatePrivate(spec);
} catch (java.security.spec.InvalidKeySpecException e) {
key = java.security.KeyFactory.getInstance("EC").generatePrivate(spec);
}

// 4. Build the KeyStore
java.security.KeyStore ks =
java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType());
ks.load(null, null); // Initialize empty keystore
ks.setKeyEntry("key", key, new char[0], certChain);

// 5. Initialize the SSLContext
javax.net.ssl.KeyManagerFactory kmf =
javax.net.ssl.KeyManagerFactory.getInstance(
javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, new char[0]);

javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
// Passing null for TrustManagers uses the default Java trust store
sslContext.init(kmf.getKeyManagers(), null, null);

return sslContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ static class Builder {
private String experimentalHostEndpoint = null;
private String clientCertPath = null;
private String clientKeyPath = null;
private String proxyTLSCertPath = null;
private String proxyTLSKeyPath = null;

/** The Cloud Spanner endpoint. */
Builder spannerEndpoint(String spannerEndpoint) {
Expand Down Expand Up @@ -132,6 +134,13 @@ Builder useClientCert(String clientCertPath, String clientKeyPath) {
return this;
}

/** (Optional) Use TLS connection for the proxy listener. */
Builder useProxyTLS(String proxyTLSCertPath, String proxyTLSKeyPath) {
this.proxyTLSCertPath = proxyTLSCertPath;
this.proxyTLSKeyPath = proxyTLSKeyPath;
return this;
}

private void validateHostConflict(
String spannerEndpointToCheck, String experimentalHostEndpointToCheck) {
if (!Strings.isNullOrEmpty(spannerEndpointToCheck)
Expand Down Expand Up @@ -161,6 +170,8 @@ AdapterOptions build() {
private String experimentalHostEndpoint;
private String clientCertPath;
private String clientKeyPath;
private String proxyTLSCertPath;
private String proxyTLSKeyPath;

private AdapterOptions(Builder builder) {
this.spannerEndpoint = builder.spannerEndpoint;
Expand All @@ -177,6 +188,8 @@ private AdapterOptions(Builder builder) {
this.experimentalHostEndpoint = builder.experimentalHostEndpoint;
this.clientCertPath = builder.clientCertPath;
this.clientKeyPath = builder.clientKeyPath;
this.proxyTLSCertPath = builder.proxyTLSCertPath;
this.proxyTLSKeyPath = builder.proxyTLSKeyPath;
}

static Builder newBuilder() {
Expand Down Expand Up @@ -242,4 +255,16 @@ String getClientCertPath() {
String getClientKeyPath() {
return clientKeyPath;
}

boolean useProxyTLS() {
return !Strings.isNullOrEmpty(proxyTLSCertPath) && !Strings.isNullOrEmpty(proxyTLSKeyPath);
}

String getProxyTLSCertPath() {
return proxyTLSCertPath;
}

String getProxyTLSKeyPath() {
return proxyTLSKeyPath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ private AdapterOptions buildAdapterOptions(
.metricsRecorder(metricsRecorder)
.usePlainText(config.usePlainText())
.setExperimentalHostEndpoint(config.getExperimentalHostEndpoint())
.useClientCert(config.getClientCertPath(), config.getClientKeyPath());
.useClientCert(config.getClientCertPath(), config.getClientKeyPath())
.useProxyTLS(config.getProxyTLSCertPath(), config.getProxyTLSKeyPath());
if (config.getMaxCommitDelayMillis() != null) {
opBuilder.maxCommitDelay(Duration.ofMillis(config.getMaxCommitDelayMillis()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ static LauncherConfig fromUserConfigs(UserConfigs userConfigs) throws UnknownHos
final String experimentalHostEndpoint;
final String clientCertPath;
final String clientKeyPath;
final String proxyTLSCertPath;
final String proxyTLSKeyPath;
HealthCheckConfig healthCheckConfig = null;

if (userConfigs.getGlobalClientConfigs() != null) {
Expand All @@ -84,13 +86,17 @@ static LauncherConfig fromUserConfigs(UserConfigs userConfigs) throws UnknownHos
experimentalHostEndpoint = userConfigs.getGlobalClientConfigs().getExperimentalHostEndpoint();
clientCertPath = userConfigs.getGlobalClientConfigs().getClientCertPath();
clientKeyPath = userConfigs.getGlobalClientConfigs().getClientKeyPath();
proxyTLSCertPath = userConfigs.getGlobalClientConfigs().getProxyTLSCertPath();
proxyTLSKeyPath = userConfigs.getGlobalClientConfigs().getProxyTLSKeyPath();
} else {
globalSpannerEndpoint = ConfigConstants.DEFAULT_SPANNER_ENDPOINT;
globalEnableBuiltInMetrics = false;
usePlainText = false;
experimentalHostEndpoint = null;
clientCertPath = null;
clientKeyPath = null;
proxyTLSCertPath = null;
proxyTLSKeyPath = null;
}

List<ListenerConfig> listenerConfigs = new ArrayList<>();
Expand All @@ -104,7 +110,9 @@ static LauncherConfig fromUserConfigs(UserConfigs userConfigs) throws UnknownHos
usePlainText,
experimentalHostEndpoint,
clientCertPath,
clientKeyPath));
clientKeyPath,
proxyTLSCertPath,
proxyTLSKeyPath));
}

return new LauncherConfig(listenerConfigs, healthCheckConfig);
Expand Down Expand Up @@ -150,6 +158,8 @@ final class ListenerConfig {
private final String experimentalHostEndpoint;
private String clientCertPath;
private String clientKeyPath;
private final String proxyTLSCertPath;
private final String proxyTLSKeyPath;

private ListenerConfig(Builder builder) {
this.databaseUri = builder.databaseUri;
Expand All @@ -163,6 +173,8 @@ private ListenerConfig(Builder builder) {
this.experimentalHostEndpoint = builder.experimentalHostEndpoint;
this.clientCertPath = builder.clientCertPath;
this.clientKeyPath = builder.clientKeyPath;
this.proxyTLSCertPath = builder.proxyTLSCertPath;
this.proxyTLSKeyPath = builder.proxyTLSKeyPath;
}

public String getDatabaseUri() {
Expand Down Expand Up @@ -210,14 +222,24 @@ public String getClientKeyPath() {
return clientKeyPath;
}

public String getProxyTLSCertPath() {
return proxyTLSCertPath;
}

public String getProxyTLSKeyPath() {
return proxyTLSKeyPath;
}

static ListenerConfig fromListenerConfigs(
ListenerConfigs listener,
String globalSpannerEndpoint,
boolean globalEnableBuiltInMetrics,
boolean usePlainText,
String experimentalHostEndpoint,
String clientCertPath,
String clientKeyPath)
String clientKeyPath,
String proxyTLSCertPath,
String proxyTLSKeyPath)
throws UnknownHostException {
String host = listener.getHost() != null ? listener.getHost() : ConfigConstants.DEFAULT_HOST;
int port = listener.getPort() != null ? listener.getPort() : ConfigConstants.DEFAULT_PORT;
Expand All @@ -238,6 +260,7 @@ static ListenerConfig fromListenerConfigs(
.setExperimentalHostEndpoint(experimentalHostEndpoint)
.usePlainText(usePlainText)
.useClientCert(clientCertPath, clientKeyPath)
.useProxyTLS(proxyTLSCertPath, proxyTLSKeyPath)
.build();
}

Expand Down Expand Up @@ -269,6 +292,8 @@ static ListenerConfig fromProperties(Map<String, String> properties) throws Unkn
properties.get(ConfigConstants.EXPERIMENTAL_HOST_ENDPOINT_PROP_KEY);
String clientCertPath = properties.get(ConfigConstants.CLIENT_CERT_PATH_PROP_KEY);
String clientKeyPath = properties.get(ConfigConstants.CLIENT_KEY_PATH_PROP_KEY);
String proxyTLSCertPath = properties.get(ConfigConstants.PROXY_TLS_CERT_PATH_PROP_KEY);
String proxyTLSKeyPath = properties.get(ConfigConstants.PROXY_TLS_KEY_PATH_PROP_KEY);
String databaseUri = properties.get(ConfigConstants.DATABASE_URI_PROP_KEY);
if (!Strings.isNullOrEmpty(experimentalHostEndpoint)) {
if (!DatabaseName.isParsableFrom(databaseUri)) {
Expand All @@ -288,6 +313,7 @@ static ListenerConfig fromProperties(Map<String, String> properties) throws Unkn
.usePlainText(usePlainText)
.setExperimentalHostEndpoint(experimentalHostEndpoint)
.useClientCert(clientCertPath, clientKeyPath)
.useProxyTLS(proxyTLSCertPath, proxyTLSKeyPath)
.build();
}

Expand All @@ -307,6 +333,8 @@ static class Builder {
private String experimentalHostEndpoint;
private String clientCertPath;
private String clientKeyPath;
private String proxyTLSCertPath;
private String proxyTLSKeyPath;

private void validateHostConflict(
String spannerEndpointToCheck, String experimentalHostEndpointToCheck) {
Expand Down Expand Up @@ -371,6 +399,12 @@ public Builder useClientCert(String clientCertPath, String clientKeyPath) {
return this;
}

public Builder useProxyTLS(String proxyTLSCertPath, String proxyTLSKeyPath) {
this.proxyTLSCertPath = proxyTLSCertPath;
this.proxyTLSKeyPath = proxyTLSKeyPath;
return this;
}

public ListenerConfig build() {
return new ListenerConfig(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ private ConfigConstants() {}
public static final String EXPERIMENTAL_HOST_ENDPOINT_PROP_KEY = "experimentalHostEndpoint";
public static final String CLIENT_CERT_PATH_PROP_KEY = "clientCertPath";
public static final String CLIENT_KEY_PATH_PROP_KEY = "clientKeyPath";
public static final String PROXY_TLS_CERT_PATH_PROP_KEY = "proxyTLSCertPath";
public static final String PROXY_TLS_KEY_PATH_PROP_KEY = "proxyTLSKeyPath";
}
Loading
Loading