Skip to content

Commit d24682e

Browse files
committed
implemented passing custom ssl config
1 parent 2cb4140 commit d24682e

File tree

6 files changed

+184
-13
lines changed

6 files changed

+184
-13
lines changed

clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseSslContextProvider.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package com.clickhouse.client;
22

3+
import java.security.KeyStore;
34
import java.util.Optional;
45
import java.util.ServiceLoader;
56

7+
import javax.net.ssl.SSLContext;
68
import javax.net.ssl.SSLException;
79

10+
import com.clickhouse.client.config.ClickHouseSslMode;
811
import com.clickhouse.data.ClickHouseChecker;
912

1013
/**
@@ -44,4 +47,26 @@ static ClickHouseSslContextProvider getProvider() {
4447
* @throws SSLException when error occured getting SSL context
4548
*/
4649
<T> Optional<T> getSslContext(Class<? extends T> sslContextClass, ClickHouseConfig config) throws SSLException;
50+
51+
/**
52+
* Use this method if trust store should be imported
53+
*
54+
* @param clientCert
55+
* @param clientKey
56+
* @param sslRootCert
57+
* @return
58+
* @throws SSLException
59+
*/
60+
SSLContext getSslContextFromCerts(String clientCert, String clientKey, String sslRootCert) throws SSLException;
61+
62+
/**
63+
* Use this method if client has separate certs
64+
*
65+
* @param truststorePath
66+
* @param truststorePassword
67+
* @param keyStoreType
68+
* @return
69+
* @throws SSLException
70+
*/
71+
SSLContext getSslContextFromKeyStore(String truststorePath, String truststorePassword, String keyStoreType) throws SSLException;
4772
}

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ static String getAlgorithm(String header, String defaultAlg) {
6565
return startIndex < endIndex ? header.substring(startIndex, endIndex) : defaultAlg;
6666
}
6767

68-
static PrivateKey getPrivateKey(String keyFile)
68+
public static PrivateKey getPrivateKey(String keyFile)
6969
throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
7070
String algorithm = (String) ClickHouseDefaults.SSL_KEY_ALGORITHM.getEffectiveDefaultValue();
7171
StringBuilder builder = new StringBuilder();
@@ -90,7 +90,7 @@ static PrivateKey getPrivateKey(String keyFile)
9090
return kf.generatePrivate(keySpec);
9191
}
9292

93-
protected KeyStore getKeyStore(String cert, String key) throws NoSuchAlgorithmException, InvalidKeySpecException,
93+
public KeyStore getKeyStore(String cert, String key) throws NoSuchAlgorithmException, InvalidKeySpecException,
9494
IOException, CertificateException, KeyStoreException {
9595
final KeyStore ks;
9696
try {
@@ -117,7 +117,7 @@ protected KeyStore getKeyStore(String cert, String key) throws NoSuchAlgorithmEx
117117
return ks;
118118
}
119119

120-
protected SSLContext getJavaSslContext(ClickHouseConfig config) throws SSLException {
120+
public SSLContext getJavaSslContext(ClickHouseConfig config) throws SSLException {
121121
ClickHouseSslMode sslMode = config.getSslMode();
122122
String clientCert = config.getSslCert();
123123
String clientKey = config.getSslKey();
@@ -126,6 +126,20 @@ protected SSLContext getJavaSslContext(ClickHouseConfig config) throws SSLExcept
126126
String truststorePassword = config.getTrustStorePassword();
127127
String keyStoreType = (!config.getKeyStoreType().isEmpty() && config.getKeyStoreType() != null) ? config.getKeyStoreType() : KeyStore.getDefaultType();
128128

129+
return getSslContextImpl(sslMode, clientCert, clientKey, sslRootCert, truststorePath, truststorePassword,
130+
keyStoreType);
131+
}
132+
133+
public SSLContext getSslContextFromCerts(String clientCert, String clientKey, String sslRootCert) throws SSLException {
134+
return getSslContextImpl(ClickHouseSslMode.STRICT,
135+
clientCert, clientKey, sslRootCert, null, null, KeyStore.getDefaultType());
136+
}
137+
138+
public SSLContext getSslContextFromKeyStore(String truststorePath, String truststorePassword, String keyStoreType) throws SSLException {
139+
return getSslContextImpl(ClickHouseSslMode.STRICT, null, null, null, truststorePath, truststorePassword, keyStoreType);
140+
}
141+
142+
private SSLContext getSslContextImpl(ClickHouseSslMode sslMode, String clientCert, String clientKey, String sslRootCert, String truststorePath, String truststorePassword, String keyStoreType) throws SSLException {
129143
SSLContext ctx;
130144
try {
131145
ctx = SSLContext.getInstance((String) ClickHouseDefaults.SSL_PROTOCOL.getEffectiveDefaultValue());

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ public class ClickHouseHttpProto {
3939
*/
4040
public static final String HEADER_DATABASE = "X-ClickHouse-Database";
4141

42+
/**
43+
* Name of user to be used to authenticate
44+
*/
45+
public static final String HEADER_DB_USER = "X-ClickHouse-User";
46+
47+
public static final String HEADER_DB_PASSWORD = "X-ClickHouse-Key";
48+
4249
/**
4350
* Query parameter to specify the query ID.
4451
*/

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,75 @@ public Builder setHttpCookiesEnabled(boolean enabled) {
456456
return this;
457457
}
458458

459+
460+
/**
461+
* Defines path to the trust store file. It cannot be combined with
462+
* certificates. Either trust store or certificates should be used.
463+
*
464+
* {@see setSSLTrustStorePassword} and {@see setSSLTrustStoreType}
465+
* @param path
466+
* @return
467+
*/
468+
public Builder setSSLTrustStore(String path) {
469+
this.configuration.put(ClickHouseClientOption.TRUST_STORE.getKey(), path);
470+
return this;
471+
}
472+
473+
/**
474+
* Password for the SSL Trust Store.
475+
*
476+
* @param password
477+
* @return
478+
*/
479+
public Builder setSSLTrustStorePassword(String password) {
480+
this.configuration.put(ClickHouseClientOption.KEY_STORE_PASSWORD.getKey(), password);
481+
return this;
482+
}
483+
484+
/**
485+
* Type of the SSL Trust Store. Usually JKS
486+
*
487+
* @param type
488+
* @return
489+
*/
490+
public Builder setSSLTrustStoreType(String type) {
491+
this.configuration.put(ClickHouseClientOption.KEY_STORE_TYPE.getKey(), type);
492+
return this;
493+
}
494+
495+
/**
496+
* Defines path to the key store file. It cannot be combined with
497+
* certificates. Either key store or certificates should be used.
498+
*
499+
* {@see setSSLKeyStorePassword} and {@see setSSLKeyStoreType}
500+
* @param path
501+
* @return
502+
*/
503+
public Builder setRootCertificate(String path) {
504+
this.configuration.put(ClickHouseClientOption.SSL_ROOT_CERTIFICATE.getKey(), path);
505+
return this;
506+
}
507+
508+
/**
509+
* Client certificate for mTLS.
510+
* @param path
511+
* @return
512+
*/
513+
public Builder setClientCertificate(String path) {
514+
this.configuration.put(ClickHouseClientOption.SSL_CERTIFICATE.getKey(), path);
515+
return this;
516+
}
517+
518+
/**
519+
* Client key for mTLS.
520+
* @param path
521+
* @return
522+
*/
523+
public Builder setClientKey(String path) {
524+
this.configuration.put(ClickHouseClientOption.SSL_KEY.getKey(), path);
525+
return this;
526+
}
527+
459528
public Client build() {
460529
// check if endpoint are empty. so can not initiate client
461530
if (this.endpoints.isEmpty()) {
@@ -466,6 +535,11 @@ public Client build() {
466535
throw new IllegalArgumentException("Username and password are required");
467536
}
468537

538+
if (this.configuration.containsKey(ClickHouseClientOption.TRUST_STORE) &&
539+
this.configuration.containsKey(ClickHouseClientOption.SSL_CERTIFICATE)) {
540+
throw new IllegalArgumentException("Trust store and certificates cannot be used together");
541+
}
542+
469543
this.configuration = setDefaults(this.configuration);
470544

471545
return new Client(this.endpoints, this.configuration, this.useNewImplementation);

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

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.clickhouse.client.api.internal;
22

33
import com.clickhouse.client.ClickHouseNode;
4+
import com.clickhouse.client.ClickHouseSslContextProvider;
45
import com.clickhouse.client.api.Client;
56
import com.clickhouse.client.api.ClientException;
67
import com.clickhouse.client.api.ClientMisconfigurationException;
78
import com.clickhouse.client.api.ServerException;
89
import com.clickhouse.client.api.enums.ProxyType;
910
import com.clickhouse.client.config.ClickHouseClientOption;
11+
import com.clickhouse.client.config.ClickHouseDefaults;
1012
import com.clickhouse.client.http.ApacheHttpConnectionImpl;
1113
import com.clickhouse.client.http.ClickHouseHttpProto;
1214
import com.clickhouse.client.http.config.ClickHouseHttpOption;
@@ -34,21 +36,29 @@
3436
import org.apache.hc.core5.http.io.entity.EntityTemplate;
3537
import org.apache.hc.core5.io.IOCallback;
3638
import org.apache.hc.core5.net.URIBuilder;
39+
import org.apache.hc.core5.ssl.SSLContexts;
3740
import org.slf4j.Logger;
3841
import org.slf4j.LoggerFactory;
3942

4043
import javax.net.ssl.HostnameVerifier;
4144
import javax.net.ssl.SSLContext;
45+
import javax.net.ssl.SSLException;
4246
import javax.net.ssl.SSLSocketFactory;
4347
import java.io.ByteArrayOutputStream;
48+
import java.io.FileInputStream;
4449
import java.io.IOException;
50+
import java.io.InputStream;
4551
import java.io.OutputStream;
4652
import java.net.ConnectException;
4753
import java.net.InetSocketAddress;
4854
import java.net.NoRouteToHostException;
4955
import java.net.URI;
5056
import java.net.URISyntaxException;
57+
import java.net.URL;
5158
import java.net.UnknownHostException;
59+
import java.security.KeyStore;
60+
import java.security.cert.Certificate;
61+
import java.security.cert.CertificateFactory;
5262
import java.util.Base64;
5363
import java.util.EnumSet;
5464
import java.util.Map;
@@ -80,27 +90,67 @@ public HttpAPIClientHelper(Map<String, String> configuration) {
8090
}
8191

8292
public CloseableHttpClient createHttpClient() {
93+
94+
// Top Level builders
8395
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
84-
CredentialsProviderBuilder credProviderBuilder = CredentialsProviderBuilder.create();
85-
SocketConfig.Builder soCfgBuilder = SocketConfig.custom();
86-
PoolingHttpClientConnectionManagerBuilder connMgrBuilder = PoolingHttpClientConnectionManagerBuilder.create();
8796

8897

98+
// Socket configuration
99+
SocketConfig.Builder soCfgBuilder = SocketConfig.custom();
89100
MapUtils.applyInt(chConfiguration, ClickHouseClientOption.SOCKET_TIMEOUT.getKey(),
90101
(t) -> soCfgBuilder.setSoTimeout(t, TimeUnit.MILLISECONDS));
91102
MapUtils.applyInt(chConfiguration, ClickHouseClientOption.SOCKET_RCVBUF.getKey(),
92103
soCfgBuilder::setRcvBufSize);
93104
MapUtils.applyInt(chConfiguration, ClickHouseClientOption.SOCKET_SNDBUF.getKey(),
94105
soCfgBuilder::setSndBufSize);
95106

107+
108+
// Connection manager
109+
PoolingHttpClientConnectionManagerBuilder connMgrBuilder = PoolingHttpClientConnectionManagerBuilder.create();
110+
111+
ClickHouseSslContextProvider sslContextProvider = ClickHouseSslContextProvider.getProvider();
112+
113+
String trustStorePath = chConfiguration.get(ClickHouseClientOption.TRUST_STORE.getKey());
114+
SSLContext sslContext = null;
115+
if (trustStorePath != null ) {
116+
try {
117+
sslContext = sslContextProvider.getSslContextFromKeyStore(
118+
trustStorePath,
119+
chConfiguration.get(ClickHouseClientOption.KEY_STORE_PASSWORD.getKey()),
120+
chConfiguration.get(ClickHouseClientOption.KEY_STORE_TYPE.getKey())
121+
);
122+
} catch (SSLException e) {
123+
throw new ClientMisconfigurationException("Failed to create SSL context from a keystore", e);
124+
}
125+
} else if (chConfiguration.get(ClickHouseClientOption.SSL_ROOT_CERTIFICATE.getKey()) != null ||
126+
chConfiguration.get(ClickHouseClientOption.SSL_CERTIFICATE.getKey()) != null ||
127+
chConfiguration.get(ClickHouseClientOption.SSL_KEY.getKey()) != null) {
128+
129+
try {
130+
sslContext = sslContextProvider.getSslContextFromCerts(
131+
chConfiguration.get(ClickHouseClientOption.SSL_CERTIFICATE.getKey()),
132+
chConfiguration.get(ClickHouseClientOption.SSL_KEY.getKey()),
133+
chConfiguration.get(ClickHouseClientOption.SSL_ROOT_CERTIFICATE.getKey())
134+
);
135+
} catch (SSLException e) {
136+
throw new ClientMisconfigurationException("Failed to create SSL context from certificates", e);
137+
}
138+
}
139+
if (sslContext !=null) {
140+
connMgrBuilder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext));
141+
}
142+
143+
connMgrBuilder.setDefaultSocketConfig(soCfgBuilder.build());
144+
clientBuilder.setConnectionManager(connMgrBuilder.build());
145+
146+
// Proxy
96147
String proxyHost = chConfiguration.get(ClickHouseClientOption.PROXY_HOST.getKey());
97148
String proxyPort = chConfiguration.get(ClickHouseClientOption.PROXY_PORT.getKey());
98149
HttpHost proxy = null;
99150
if (proxyHost != null && proxyPort != null) {
100151
proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort));
101152
}
102153

103-
104154
String proxyTypeVal = chConfiguration.get(ClickHouseClientOption.PROXY_TYPE.getKey());
105155
ProxyType proxyType = proxyTypeVal == null ? null : ProxyType.valueOf(proxyTypeVal);
106156
if (proxyType == ProxyType.HTTP) {
@@ -117,10 +167,7 @@ public CloseableHttpClient createHttpClient() {
117167
.equalsIgnoreCase("false")) {
118168
clientBuilder.disableCookieManagement();
119169
}
120-
clientBuilder.setDefaultCredentialsProvider(credProviderBuilder.build());
121170

122-
connMgrBuilder.setDefaultSocketConfig(soCfgBuilder.build());
123-
clientBuilder.setConnectionManager(connMgrBuilder.build());
124171
return clientBuilder.build();
125172
}
126173

@@ -211,6 +258,8 @@ private void addHeaders(HttpPost req, Map<String, String> chConfig, Map<String,
211258
}
212259
}
213260
req.addHeader(ClickHouseHttpProto.HEADER_DATABASE, chConfig.get(ClickHouseClientOption.DATABASE.getKey()));
261+
req.addHeader(ClickHouseHttpProto.HEADER_DB_USER, chConfig.get(ClickHouseDefaults.USER.getKey()));
262+
req.addHeader(ClickHouseHttpProto.HEADER_DB_PASSWORD, chConfig.get(ClickHouseDefaults.PASSWORD.getKey()));
214263

215264
if (proxyAuthHeaderValue != null) {
216265
req.addHeader(HttpHeaders.PROXY_AUTHORIZATION, proxyAuthHeaderValue);

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,21 @@ private static Client[] secureClientProvider() throws Exception {
6262
}
6363

6464
@Test(groups = { "integration" })
65-
public void testConnectThruHttps() {
65+
public void testSecureConnection() {
6666
ClickHouseNode secureServer = getSecureServer(ClickHouseProtocol.HTTP);
6767

6868
try (Client client = new Client.Builder()
69-
.addEndpoint(Protocol.HTTP, secureServer.getHost(), secureServer.getPort(), true)
69+
.addEndpoint("https://localhost:8443")
7070
.setUsername("default")
7171
.setPassword("")
72+
.setRootCertificate("containers/clickhouse-server/certs/localhost.crt")
73+
// .useNewImplementation(System.getProperty("client.tests.useNewImplementation", "false").equals("true"))
7274
.useNewImplementation(true)
7375
.build()) {
7476

7577
List<GenericRecord> records = client.queryAll("SELECT timezone()");
7678
Assert.assertTrue(records.size() > 0);
77-
Assert.assertEquals(records.get(0).getString(0), "UTC");
79+
Assert.assertEquals(records.get(0).getString(1), "UTC");
7880
} catch (Exception e) {
7981
e.printStackTrace();
8082
Assert.fail(e.getMessage());

0 commit comments

Comments
 (0)