Skip to content

Commit d4792b7

Browse files
authored
Merge pull request #2467 from ClickHouse/impl_SNI_config
[client-v2] SNI configuration options
2 parents 04e2c1d + 07808d6 commit d4792b7

File tree

9 files changed

+133
-11
lines changed

9 files changed

+133
-11
lines changed

clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,14 @@ public enum ClickHouseClientOption implements ClickHouseOption {
451451
*/
452452
CONNECTION_TTL("connection_ttl", 0L,
453453
"Connection time to live in milliseconds. 0 or negative number means no limit."),
454-
MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode.");
454+
MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode."),
455+
456+
/**
457+
* SNI SSL parameter that will be set for each outbound SSL socket.
458+
*/
459+
SSL_SOCKET_SNI("ssl_socket_sni", "", " SNI SSL parameter that will be set for each outbound SSL socket.")
460+
461+
;
455462

456463
private final String key;
457464
private final Serializable defaultValue;

clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.clickhouse.client.config.ClickHouseProxyType;
1212
import com.clickhouse.client.config.ClickHouseSslMode;
1313
import com.clickhouse.client.http.config.ClickHouseHttpOption;
14+
import com.clickhouse.config.ClickHouseOption;
1415
import com.clickhouse.data.ClickHouseChecker;
1516
import com.clickhouse.data.ClickHouseExternalTable;
1617
import com.clickhouse.data.ClickHouseFormat;
@@ -54,8 +55,11 @@
5455
import org.apache.hc.core5.util.Timeout;
5556
import org.apache.hc.core5.util.VersionInfo;
5657

58+
import javax.net.ssl.SNIHostName;
5759
import javax.net.ssl.SSLContext;
5860
import javax.net.ssl.SSLException;
61+
import javax.net.ssl.SSLParameters;
62+
import javax.net.ssl.SSLSocket;
5963
import java.io.BufferedReader;
6064
import java.io.ByteArrayInputStream;
6165
import java.io.ByteArrayOutputStream;
@@ -66,10 +70,9 @@
6670
import java.io.UncheckedIOException;
6771
import java.net.ConnectException;
6872
import java.net.HttpURLConnection;
73+
import java.net.InetAddress;
6974
import java.net.InetSocketAddress;
7075
import java.net.Socket;
71-
import java.net.SocketOption;
72-
import java.net.SocketOptions;
7376
import java.nio.charset.StandardCharsets;
7477
import java.util.Collections;
7578
import java.util.List;
@@ -394,6 +397,7 @@ public static SocketFactory create(ClickHouseConfig config) {
394397

395398
static class SSLSocketFactory extends SSLConnectionSocketFactory {
396399
private final ClickHouseConfig config;
400+
private final SNIHostName defaultSNI;
397401

398402
private SSLSocketFactory(ClickHouseConfig config) throws SSLException {
399403
super(ClickHouseSslContextProvider.getProvider().getSslContext(SSLContext.class, config)
@@ -402,13 +406,25 @@ private SSLSocketFactory(ClickHouseConfig config) throws SSLException {
402406
? new DefaultHostnameVerifier()
403407
: (hostname, session) -> true); // NOSONAR
404408
this.config = config;
409+
String sni = config.getStrOption(ClickHouseClientOption.SSL_SOCKET_SNI);
410+
defaultSNI = sni == null || sni.trim().isEmpty() ? null : new SNIHostName(sni);
405411
}
406412

407413
@Override
408414
public Socket createSocket(HttpContext context) throws IOException {
409415
return AbstractSocketClient.setSocketOptions(config, new Socket());
410416
}
411417

418+
@Override
419+
protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException {
420+
super.prepareSocket(socket, context);
421+
if (defaultSNI != null) {
422+
SSLParameters sslParams = socket.getSSLParameters();
423+
sslParams.setServerNames(Collections.singletonList(defaultSNI));
424+
socket.setSSLParameters(sslParams);
425+
}
426+
}
427+
412428
public static SSLSocketFactory create(ClickHouseConfig config) throws SSLException {
413429
return new SSLSocketFactory(config);
414430
}

client-v2/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@
129129
<version>1.18.36</version>
130130
<scope>test</scope>
131131
</dependency>
132+
<dependency>
133+
<groupId>org.slf4j</groupId>
134+
<artifactId>slf4j-simple</artifactId>
135+
<version>2.0.16</version>
136+
<scope>test</scope>
137+
</dependency>
132138
</dependencies>
133139

134140
<build>

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,11 @@ public Builder useHttpCompression(boolean enabled) {
602602
return this;
603603
}
604604

605+
/**
606+
* Tell client that compression will be handled by application.
607+
* @param enabled - indicates that feature is enabled.
608+
* @return
609+
*/
605610
public Builder appCompressedData(boolean enabled) {
606611
this.configuration.put(ClientConfigProperties.APP_COMPRESSED_DATA.getKey(), String.valueOf(enabled));
607612
return this;
@@ -1025,6 +1030,19 @@ public Builder typeHintMapping(Map<ClickHouseDataType, Class<?>> typeHintMapping
10251030
return this;
10261031
}
10271032

1033+
1034+
/**
1035+
* SNI SSL parameter that will be set for each outbound SSL socket.
1036+
* SNI stands for Server Name Indication - an extension to the TLS protocol that allows multiple domains to share the same IP address.
1037+
*
1038+
* @param sni - SNI parameter
1039+
* @return this builder instance
1040+
*/
1041+
public Builder sslSocketSNI(String sni) {
1042+
this.configuration.put(ClientConfigProperties.SSL_SOCKET_SNI.getKey(), sni);
1043+
return this;
1044+
}
1045+
10281046
public Client build() {
10291047
// check if endpoint are empty. so can not initiate client
10301048
if (this.endpoints.isEmpty()) {

client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,15 @@
1111
import java.util.Arrays;
1212
import java.util.Collection;
1313
import java.util.Collections;
14-
import java.util.LinkedHashMap;
1514
import java.util.HashMap;
1615
import java.util.HashSet;
16+
import java.util.LinkedHashMap;
1717
import java.util.List;
1818
import java.util.Locale;
1919
import java.util.Map;
20-
import java.util.function.Function;
21-
import java.util.Map;
2220
import java.util.TimeZone;
2321
import java.util.function.Consumer;
22+
import java.util.function.Function;
2423
import java.util.stream.Collectors;
2524

2625
/**
@@ -178,6 +177,11 @@ public Object parseValue(String value) {
178177
* Used by binary readers to convert values into desired Java type.
179178
*/
180179
TYPE_HINT_MAPPING("type_hint_mapping", Map.class),
180+
181+
/**
182+
* SNI SSL parameter that will be set for each outbound SSL socket.
183+
*/
184+
SSL_SOCKET_SNI("ssl_socket_sni", String.class,""),
181185
;
182186

183187
private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class);
@@ -218,6 +222,9 @@ public <T> T getDefObjVal() {
218222

219223
public static final String SERVER_SETTING_PREFIX = "clickhouse_setting_";
220224

225+
// Key used to identify default value in configuration map
226+
public static final String DEFAULT_KEY = "_default_";
227+
221228
public static String serverSetting(String key) {
222229
return SERVER_SETTING_PREFIX + key;
223230
}

client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@
6060
import org.slf4j.Logger;
6161
import org.slf4j.LoggerFactory;
6262

63+
import javax.net.ssl.HostnameVerifier;
64+
import javax.net.ssl.SNIHostName;
6365
import javax.net.ssl.SSLContext;
6466
import javax.net.ssl.SSLException;
67+
import javax.net.ssl.SSLParameters;
68+
import javax.net.ssl.SSLSocket;
6569
import java.io.IOException;
6670
import java.io.InputStream;
6771
import java.io.OutputStream;
@@ -256,8 +260,17 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map<String,
256260
// Top Level builders
257261
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
258262
SSLContext sslContext = initSslContext ? createSSLContext(configuration) : null;
259-
LayeredConnectionSocketFactory sslConnectionSocketFactory = sslContext == null ? new DummySSLConnectionSocketFactory()
260-
: new SSLConnectionSocketFactory(sslContext);
263+
LayeredConnectionSocketFactory sslConnectionSocketFactory;
264+
if (sslContext != null) {
265+
String socketSNI = (String)configuration.get(ClientConfigProperties.SSL_SOCKET_SNI.getKey());
266+
if (socketSNI != null && !socketSNI.trim().isEmpty()) {
267+
sslConnectionSocketFactory = new CustomSSLConnectionFactory(socketSNI, sslContext, (hostname, session) -> true);
268+
} else {
269+
sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
270+
}
271+
} else {
272+
sslConnectionSocketFactory = new DummySSLConnectionSocketFactory();
273+
}
261274
// Socket configuration
262275
SocketConfig.Builder soCfgBuilder = SocketConfig.custom();
263276
ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.<Integer>applyIfSet(configuration,
@@ -834,4 +847,25 @@ public long getTime() {
834847
return count > 0 ? runningAverage / count : 0;
835848
}
836849
}
850+
851+
public static class CustomSSLConnectionFactory extends SSLConnectionSocketFactory {
852+
853+
private final SNIHostName defaultSNI;
854+
855+
public CustomSSLConnectionFactory(String defaultSNI, SSLContext sslContext, HostnameVerifier hostnameVerifier) {
856+
super(sslContext, hostnameVerifier);
857+
this.defaultSNI = defaultSNI == null || defaultSNI.trim().isEmpty() ? null : new SNIHostName(defaultSNI);
858+
}
859+
860+
@Override
861+
protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException {
862+
super.prepareSocket(socket, context);
863+
864+
if (defaultSNI != null) {
865+
SSLParameters sslParams = socket.getSSLParameters();
866+
sslParams.setServerNames(Collections.singletonList(defaultSNI));
867+
socket.setSSLParameters(sslParams);
868+
}
869+
}
870+
}
837871
}

client-v2/src/test/java/com/clickhouse/client/ClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public void testDefaultSettings() {
206206
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
207207
}
208208
}
209-
Assert.assertEquals(config.size(), 31); // to check everything is set. Increment when new added.
209+
Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added.
210210
}
211211

212212
try (Client client = new Client.Builder()
@@ -239,7 +239,7 @@ public void testDefaultSettings() {
239239
.setSocketSndbuf(100000)
240240
.build()) {
241241
Map<String, String> config = client.getConfiguration();
242-
Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added.
242+
Assert.assertEquals(config.size(), 33); // to check everything is set. Increment when new added.
243243
Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb");
244244
Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10");
245245
Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000");
@@ -306,7 +306,7 @@ public void testWithOldDefaults() {
306306
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
307307
}
308308
}
309-
Assert.assertEquals(config.size(), 31); // to check everything is set. Increment when new added.
309+
Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added.
310310
}
311311
}
312312

client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.testng.annotations.Test;
3737

3838
import java.io.ByteArrayInputStream;
39+
import java.net.InetAddress;
3940
import java.net.Socket;
4041
import java.nio.ByteBuffer;
4142
import java.nio.charset.StandardCharsets;
@@ -1100,6 +1101,23 @@ public void testTimeoutsWithRetry() {
11001101
}
11011102
}
11021103

1104+
@Test(groups = {"integration"})
1105+
public void testSNIWithCloud() throws Exception {
1106+
if (!isCloud()) {
1107+
// skip for local env
1108+
return;
1109+
}
1110+
1111+
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
1112+
String ip = InetAddress.getByName(node.getHost()).getHostAddress();
1113+
try (Client c = new Client.Builder()
1114+
.addEndpoint(Protocol.HTTP, ip, node.getPort(), true)
1115+
.setUsername("default")
1116+
.setPassword(ClickHouseServerForTest.getPassword())
1117+
.sslSocketSNI(node.getHost()).build()) {
1118+
c.execute("SELECT 1");
1119+
}
1120+
}
11031121

11041122
protected Client.Builder newClient() {
11051123
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);

client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.clickhouse.client.internal;
22

3+
import com.clickhouse.client.api.ClientConfigProperties;
34
import com.clickhouse.client.api.data_formats.internal.ProcessParser;
45
import com.clickhouse.client.api.metrics.OperationMetrics;
56
import com.clickhouse.client.api.metrics.ServerMetrics;
@@ -45,4 +46,19 @@ public void testTimezoneConvertion() {
4546
ZonedDateTime utcSameLocalDt = dt.withZoneSameLocal(ZoneId.of("UTC"));
4647
System.out.println("withZoneSameLocal: " + utcSameLocalDt);
4748
}
49+
50+
@Test
51+
public void testGenConfigParameters() {
52+
System.out.println("<br/> <br/> Default: `none` <br/> Enum: `none` <br/> Key: `none` "
53+
54+
55+
);
56+
for (ClientConfigProperties p : ClientConfigProperties.values()) {
57+
String defaultValue = p.getDefaultValue() == null ? "-" : "`" + p.getDefaultValue() + "`";
58+
System.out.println("<br/> <br/> Default: " +defaultValue + " <br/> Enum: `ClientConfigProperties." + p.name() + "` <br/> Key: `" + p.getKey() +"` "
59+
60+
61+
);
62+
}
63+
}
4864
}

0 commit comments

Comments
 (0)