diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java
index cf114cb26..9b0a2a12b 100644
--- a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java
+++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseDefaultSslContextProvider.java
@@ -154,14 +154,38 @@ public SSLContext getJavaSslContext(ClickHouseConfig config) throws SSLException
}
public SSLContext getSslContextFromCerts(String clientCert, String clientKey, String sslRootCert) throws SSLException {
- return getSslContextImpl(ClickHouseSslMode.STRICT,
- clientCert, clientKey, sslRootCert, null, null, KeyStore.getDefaultType());
+ return getSslContextFromCerts(ClickHouseSslMode.STRICT, clientCert, clientKey, sslRootCert);
+ }
+
+ /**
+ * Creates an SSL context from certificates with an explicit SSL mode.
+ * With {@link ClickHouseSslMode#NONE} the server certificate is not validated, while client
+ * certificate and key are still used (if provided) so that mTLS keeps working.
+ *
+ * @param sslMode ssl mode
+ * @param clientCert client certificate for mTLS, file path or PEM content; may be null
+ * @param clientKey client private key for mTLS, file path or PEM content; may be null
+ * @param sslRootCert CA certificate to validate the server certificate, file path or PEM content; may be null
+ * @return SSL context
+ * @throws SSLException when the context cannot be created
+ */
+ public SSLContext getSslContextFromCerts(ClickHouseSslMode sslMode, String clientCert, String clientKey,
+ String sslRootCert) throws SSLException {
+ return getSslContextImpl(sslMode, clientCert, clientKey, sslRootCert, null, null, KeyStore.getDefaultType());
}
public SSLContext getSslContextFromKeyStore(String truststorePath, String truststorePassword, String keyStoreType) throws SSLException {
return getSslContextImpl(ClickHouseSslMode.STRICT, null, null, null, truststorePath, truststorePassword, keyStoreType);
}
+ private KeyManager[] getKeyManagers(String clientCert, String clientKey)
+ throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, CertificateException,
+ KeyStoreException, UnrecoverableKeyException {
+ KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ factory.init(getKeyStore(clientCert, clientKey), null);
+ return factory.getKeyManagers();
+ }
+
private SSLContext getSslContextImpl(ClickHouseSslMode sslMode, String clientCert, String clientKey, String sslRootCert, String truststorePath, String truststorePassword, String keyStoreType) throws SSLException {
SSLContext ctx;
try {
@@ -172,34 +196,29 @@ private SSLContext getSslContextImpl(ClickHouseSslMode sslMode, String clientCer
if (sslMode == ClickHouseSslMode.NONE) {
tms = new TrustManager[]{new NonValidatingTrustManager()};
- kms = new KeyManager[0];
+ // client certificate and key are independent from server verification - keep mTLS working
+ kms = clientCert != null && !clientCert.isEmpty() ? getKeyManagers(clientCert, clientKey)
+ : new KeyManager[0];
sr = new SecureRandom();
} else if (sslMode == ClickHouseSslMode.STRICT) {
- if (truststorePath != null && !truststorePath.isEmpty()) {
+ if (clientCert != null && !clientCert.isEmpty()) {
+ kms = getKeyManagers(clientCert, clientKey);
+ }
+ if (truststorePath != null && !truststorePath.isEmpty()) {
try (InputStream in = ClickHouseUtils.getFileInputStream(truststorePath)) {
KeyStore myTrustStore = KeyStore.getInstance(keyStoreType);
- myTrustStore.load(in, truststorePassword.toCharArray());
+ myTrustStore.load(in, truststorePassword == null ? null : truststorePassword.toCharArray());
TrustManagerFactory factory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init(myTrustStore);
tms = factory.getTrustManagers();
-
- }
- } else {
- if (clientCert != null && !clientCert.isEmpty()) {
- KeyManagerFactory factory = KeyManagerFactory
- .getInstance(KeyManagerFactory.getDefaultAlgorithm());
- factory.init(getKeyStore(clientCert, clientKey), null);
- kms = factory.getKeyManagers();
- }
-
- if (sslRootCert != null && !sslRootCert.isEmpty()) {
- TrustManagerFactory factory = TrustManagerFactory
- .getInstance(TrustManagerFactory.getDefaultAlgorithm());
- factory.init(getKeyStore(sslRootCert, null));
- tms = factory.getTrustManagers();
}
+ } else if (sslRootCert != null && !sslRootCert.isEmpty()) {
+ TrustManagerFactory factory = TrustManagerFactory
+ .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ factory.init(getKeyStore(sslRootCert, null));
+ tms = factory.getTrustManagers();
}
sr = new SecureRandom();
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java
index 58d94da9b..2df5ced77 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java
@@ -12,6 +12,7 @@
import com.clickhouse.client.api.data_formats.internal.ProcessParser;
import com.clickhouse.client.api.enums.Protocol;
import com.clickhouse.client.api.enums.ProxyType;
+import com.clickhouse.client.api.enums.SSLMode;
import com.clickhouse.client.api.http.ClickHouseHttpProto;
import com.clickhouse.client.api.insert.InsertResponse;
import com.clickhouse.client.api.insert.InsertSettings;
@@ -755,6 +756,33 @@ public Builder setClientKey(String path) {
return this;
}
+ /**
+ * Defines how strictly the client verifies a server identity on secure connections.
+ *
+ *
Supported modes:
+ *
+ * - {@link SSLMode#Disabled} - SSL is not used; only meaningful with plain protocols
+ * - {@link SSLMode#Trust} - encrypt, but accept any server certificate and skip
+ * hostname verification
+ * - {@link SSLMode#VerifyCa} - validate the server certificate chain, but skip
+ * hostname verification
+ * - {@link SSLMode#Strict} - full verification of the certificate chain and the
+ * hostname (default)
+ *
+ *
+ * The mode applies only when a secure protocol is in use - for the HTTP transport that
+ * means an {@code https://} endpoint. Setting any mode does not make the client use
+ * encryption on a plain HTTP endpoint: the endpoint scheme always decides whether the
+ * connection is encrypted.
+ *
+ * @param sslMode ssl mode
+ * @return same instance of the builder
+ */
+ public Builder setSSLMode(SSLMode sslMode) {
+ this.configuration.put(ClientConfigProperties.SSL_MODE.getKey(), sslMode.name());
+ return this;
+ }
+
/**
* Configure client to use server timezone for date/datetime columns. Default is true.
* If this options is selected then server timezone should be set as well.
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java
index e548a90f9..68b86b694 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java
@@ -1,6 +1,7 @@
package com.clickhouse.client.api;
import com.clickhouse.client.api.data_formats.internal.AbstractBinaryFormatReader;
+import com.clickhouse.client.api.enums.SSLMode;
import com.clickhouse.client.api.internal.ClickHouseLZ4OutputStream;
import com.clickhouse.data.ClickHouseDataType;
import com.clickhouse.data.ClickHouseFormat;
@@ -115,6 +116,8 @@ public enum ClientConfigProperties {
SSL_CERTIFICATE("sslcert", String.class),
+ SSL_MODE("ssl_mode", SSLMode.class, SSLMode.Strict.name()),
+
RETRY_ON_FAILURE("retry", Integer.class, "3"),
INPUT_OUTPUT_FORMAT("format", ClickHouseFormat.class),
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/enums/SSLMode.java b/client-v2/src/main/java/com/clickhouse/client/api/enums/SSLMode.java
new file mode 100644
index 000000000..03a3bacae
--- /dev/null
+++ b/client-v2/src/main/java/com/clickhouse/client/api/enums/SSLMode.java
@@ -0,0 +1,62 @@
+package com.clickhouse.client.api.enums;
+
+/**
+ * Defines how strictly the client verifies a server identity when a secure protocol is used.
+ *
+ * The mode affects only connections that are already using a secure transport (for example,
+ * an {@code https://} endpoint). It does not enable encryption for plain protocols - an
+ * {@code http://} endpoint stays unencrypted whatever the mode is.
+ *
+ * Modes from the least to the most strict:
+ *
+ * - {@link #Disabled} - SSL is not used. Plain protocols only.
+ * - {@link #Trust} - encryption is used, but the server certificate chain is not validated
+ * and the hostname is not verified. Susceptible to MITM attacks - use only for testing or in
+ * fully trusted environments.
+ * - {@link #VerifyCa} - the server certificate chain is validated against the trust material
+ * (default JVM trust store, configured trust store, or a CA certificate), but the hostname is
+ * not checked against the certificate.
+ * - {@link #Strict} - full verification (default): certificate chain is validated and the
+ * hostname must match the certificate.
+ *
+ */
+public enum SSLMode {
+
+ /**
+ * SSL is not used. Connection is not encrypted.
+ */
+ Disabled,
+
+ /**
+ * Encryption without verification: any server certificate is accepted and
+ * the hostname is not verified.
+ */
+ Trust,
+
+ /**
+ * Server certificate chain is validated, but the hostname is not verified.
+ */
+ VerifyCa,
+
+ /**
+ * Full verification: certificate chain is validated and the hostname must match
+ * the certificate. Default mode.
+ */
+ Strict;
+
+ /**
+ * Case-insensitive variant of {@link #valueOf(String)}.
+ *
+ * @param value mode name in any case
+ * @return matching mode
+ * @throws IllegalArgumentException when the value does not match any mode
+ */
+ public static SSLMode fromValue(String value) {
+ for (SSLMode mode : values()) {
+ if (mode.name().equalsIgnoreCase(value)) {
+ return mode;
+ }
+ }
+ throw new IllegalArgumentException("Unknown SSL mode '" + value + "'");
+ }
+}
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
index 5ae4730b7..4f5698537 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
@@ -11,6 +11,8 @@
import com.clickhouse.client.api.DataTransferException;
import com.clickhouse.client.api.ServerException;
import com.clickhouse.client.api.enums.ProxyType;
+import com.clickhouse.client.api.enums.SSLMode;
+import com.clickhouse.client.config.ClickHouseSslMode;
import com.clickhouse.client.api.http.ClickHouseHttpProto;
import com.clickhouse.client.api.transport.Endpoint;
import com.clickhouse.client.config.ClickHouseDefaultSslContextProvider;
@@ -165,11 +167,33 @@ public SSLContext createSSLContext(Map configuration) {
} catch (NoSuchAlgorithmException e) {
throw new ClientException("Failed to create default SSL context", e);
}
+ final SSLMode sslMode = ClientConfigProperties.SSL_MODE.getOrDefault(configuration);
final String trustStorePath = (String) configuration.get(ClientConfigProperties.SSL_TRUST_STORE.getKey());
final String caCertificate = (String) configuration.get(ClientConfigProperties.CA_CERTIFICATE.getKey());
final String sslCertificate = (String) configuration.get(ClientConfigProperties.SSL_CERTIFICATE.getKey());
final String sslKey = (String) configuration.get(ClientConfigProperties.SSL_KEY.getKey());
- if (trustStorePath != null) {
+
+ // This method is only reached when a secure (https) endpoint is configured, so SSLMode.Disabled
+ // contradicts the endpoint scheme. The mode does not turn encryption off - the scheme decides it.
+ if (sslMode == SSLMode.Disabled) {
+ throw new ClientMisconfigurationException("SSL mode '" + SSLMode.Disabled
+ + "' cannot be used with a secure (https) endpoint. Use SSLMode.Trust to trust all certificates or use plain HTTP");
+ }
+
+ if (sslMode == SSLMode.Trust) {
+ // Server certificate is not validated. Trust material (trust store or CA certificate)
+ // is not needed, but client certificate and key are still applied for mTLS.
+ try {
+ sslContext = sslContextProvider.getSslContextFromCerts(ClickHouseSslMode.NONE,
+ sslCertificate, sslKey, null);
+ } catch (SSLException e) {
+ throw new ClientMisconfigurationException("Failed to create SSL context for the Trust SSL mode", e);
+ }
+ } else if (trustStorePath != null) {
+ if (caCertificate != null) {
+ throw new ClientMisconfigurationException("CA certificate cannot be used together with a trust store."
+ + " The CA certificate should be imported into the trust store instead.");
+ }
try {
sslContext = sslContextProvider.getSslContextFromKeyStore(
trustStorePath,
@@ -272,7 +296,11 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map true);
} else {
sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
index eaa675349..862dcb12a 100644
--- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
+++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
@@ -332,7 +332,7 @@ public void testDefaultSettings() {
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
}
}
- Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added.
}
try (Client client = new Client.Builder()
@@ -365,7 +365,7 @@ public void testDefaultSettings() {
.setSocketSndbuf(100000)
.build()) {
Map config = client.getConfiguration();
- Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 36); // to check everything is set. Increment when new added.
Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb");
Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10");
Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000");
@@ -389,6 +389,7 @@ public void testDefaultSettings() {
Assert.assertEquals(config.get(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey()), "20000");
Assert.assertEquals(config.get(ClientConfigProperties.SOCKET_RCVBUF_OPT.getKey()), "100000");
Assert.assertEquals(config.get(ClientConfigProperties.SOCKET_SNDBUF_OPT.getKey()), "100000");
+ Assert.assertEquals(config.get(ClientConfigProperties.SSL_MODE.getKey()), "Strict");
}
}
@@ -432,7 +433,7 @@ public void testWithOldDefaults() {
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
}
}
- Assert.assertEquals(config.size(), 34); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 35); // to check everything is set. Increment when new added.
}
}
diff --git a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java
index 5c6384a60..7b2bdb569 100644
--- a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java
+++ b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java
@@ -14,6 +14,7 @@
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
import com.clickhouse.client.api.enums.Protocol;
import com.clickhouse.client.api.enums.ProxyType;
+import com.clickhouse.client.api.enums.SSLMode;
import com.clickhouse.client.api.http.ClickHouseHttpProto;
import com.clickhouse.client.api.insert.InsertResponse;
import com.clickhouse.client.api.insert.InsertSettings;
@@ -291,6 +292,132 @@ public void testSecureConnection() {
}
}
+ @Test(groups = { "integration" })
+ public void testSSLModeTrust() {
+ if (isCloud()) {
+ return; // test uses self-signed cert
+ }
+
+ ClickHouseNode secureServer = getSecureServer(ClickHouseProtocol.HTTP);
+
+ // Default mode (Strict) without any trust material - the self-signed certificate must be rejected
+ try (Client client = new Client.Builder()
+ .addEndpoint("https://localhost:" + secureServer.getPort())
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .build()) {
+ Assert.expectThrows(Exception.class, () -> client.queryAll("SELECT 1"));
+ }
+
+ // Trust mode - the same certificate is accepted without any trust material
+ try (Client client = new Client.Builder()
+ .addEndpoint("https://localhost:" + secureServer.getPort())
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setOption(ClientConfigProperties.SSL_MODE.getKey(), SSLMode.Trust.name())
+ .build()) {
+ List records = client.queryAll("SELECT timezone()");
+ Assert.assertEquals(records.get(0).getString(1), "UTC");
+ } catch (Exception e) {
+ Assert.fail("Trust SSL mode should accept a self-signed certificate", e);
+ }
+ }
+
+ @Test(groups = { "integration" })
+ public void testSSLModeVerifyCa() {
+ if (isCloud()) {
+ return; // test uses self-signed cert
+ }
+
+ ClickHouseNode secureServer = getSecureServer(ClickHouseProtocol.HTTP);
+ // server certificate has CN=localhost, so connecting via 127.0.0.1 fails hostname verification
+ final String endpointByIp = "https://127.0.0.1:" + secureServer.getPort();
+ final String serverCertificate = "containers/clickhouse-server/certs/localhost.crt";
+
+ // Strict mode (default): certificate chain is trusted, but the hostname does not match
+ try (Client client = new Client.Builder()
+ .addEndpoint(endpointByIp)
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setRootCertificate(serverCertificate)
+ .build()) {
+ Assert.expectThrows(Exception.class, () -> client.queryAll("SELECT 1"));
+ }
+
+ // VerifyCa mode: certificate chain is validated, hostname mismatch is ignored
+ try (Client client = new Client.Builder()
+ .addEndpoint(endpointByIp)
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setRootCertificate(serverCertificate)
+ .setSSLMode(SSLMode.VerifyCa)
+ .build()) {
+ List records = client.queryAll("SELECT timezone()");
+ Assert.assertEquals(records.get(0).getString(1), "UTC");
+ } catch (Exception e) {
+ Assert.fail("VerifyCa SSL mode should ignore hostname mismatch", e);
+ }
+
+ // VerifyCa mode still validates the certificate chain - without the CA it must fail
+ try (Client client = new Client.Builder()
+ .addEndpoint(endpointByIp)
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setSSLMode(SSLMode.VerifyCa)
+ .build()) {
+ Assert.expectThrows(Exception.class, () -> client.queryAll("SELECT 1"));
+ }
+ }
+
+ @Test(groups = { "integration" })
+ public void testSSLModeDisabled() {
+ if (isCloud()) {
+ return; // plain HTTP is not available in cloud
+ }
+
+ ClickHouseNode server = getServer(ClickHouseProtocol.HTTP);
+
+ // Disabled mode with a plain HTTP endpoint - SSL is simply not used
+ try (Client client = new Client.Builder()
+ .addEndpoint("http://" + server.getHost() + ":" + server.getPort())
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setSSLMode(SSLMode.Disabled)
+ .build()) {
+ List records = client.queryAll("SELECT timezone()");
+ Assert.assertEquals(records.get(0).getString(1), "UTC");
+ } catch (Exception e) {
+ Assert.fail("Disabled SSL mode should work with a plain HTTP endpoint", e);
+ }
+
+ ClickHouseNode secureServer = getSecureServer(ClickHouseProtocol.HTTP);
+ // Disabled mode contradicts a secure (https) endpoint - the scheme decides encryption, not the mode
+ Assert.expectThrows(ClientMisconfigurationException.class, () -> new Client.Builder()
+ .addEndpoint("https://localhost:" + secureServer.getPort())
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setSSLMode(SSLMode.Disabled)
+ .build());
+ }
+
+ @Test(groups = { "integration" })
+ public void testSSLModeStrictWithTrustStoreAndCaCertificate() {
+ if (isCloud()) {
+ return;
+ }
+
+ ClickHouseNode secureServer = getSecureServer(ClickHouseProtocol.HTTP);
+
+ // CA certificate cannot be combined with a trust store - it should be in the trust store already
+ Assert.expectThrows(ClientMisconfigurationException.class, () -> new Client.Builder()
+ .addEndpoint("https://localhost:" + secureServer.getPort())
+ .setUsername("default")
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setSSLTrustStore("containers/clickhouse-server/certs/KeyStore.jks")
+ .setRootCertificate("containers/clickhouse-server/certs/localhost.crt")
+ .build());
+ }
+
@Test(groups = { "integration" }, dataProvider = "NoResponseFailureProvider")
public void testInsertAndNoHttpResponseFailure(String body, int maxRetries, ThrowingFunction function,
boolean shouldFail) {
diff --git a/docs/features.md b/docs/features.md
index be63e9f99..13bb50807 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -5,7 +5,8 @@ This document lists stable, user-visible behavior in `client-v2` and `jdbc-v2` t
## `client-v2`
- HTTP and HTTPS connectivity: Connects to ClickHouse over HTTP(S), supports endpoint paths, and exposes a basic `ping` health check.
-- TLS configuration: Supports trust stores, client certificates/keys, SSL certificate authentication, and SNI for HTTPS connections.
+- TLS configuration: Supports trust stores, client certificates/keys, SSL certificate authentication, and SNI for HTTPS connections. Trust material (root CA and client certificate/key) can be supplied either as a file path or directly as PEM content.
+- SSL verification modes: `Client.Builder.setSSLMode(SSLMode)` (or the `ssl_mode` property) controls how strictly the server identity is verified on secure connections: `Disabled` (SSL not used; plain protocols only), `Trust` (encrypt but accept any server certificate and skip hostname verification, while still applying a client certificate/key for mTLS if configured), `VerifyCa` (validate the certificate chain but skip hostname verification), and `Strict` (full chain and hostname verification, default).
- Authentication modes: Supports username/password credentials, ClickHouse auth headers, bearer tokens, and optional HTTP Basic authentication.
- Runtime credential updates: Existing `Client` instances can update username/password or bearer-token credentials for subsequent requests without rebuilding the client.
- Proxy support: Can send requests through configured HTTP proxies, including proxy credentials.
@@ -41,13 +42,15 @@ Compatibility-sensitive traits:
- `Geometry` handling is shape-sensitive: supported values are 1D through 4D Java arrays representing the nested geometry variants, and unsupported shapes or non-array values are rejected during serialization.
- `Geometry` write inference is dimension-based rather than fully type-specific: point, ring/line string, polygon/multi-line string, and multi-polygon are selected from array depth, so writing `Geometry` cannot currently distinguish `Ring` from `LineString` or `Polygon` from `MultiLineString`.
- Session precedence is part of the contract: client session defaults apply to each request, operation settings may override them, and only the client `session_id` is mutable at runtime while other client session properties remain fixed for the lifetime of the client.
+- SSL mode behavior is compatibility-sensitive: the default is `Strict`. `ssl_mode` does not enable or disable encryption - the endpoint scheme decides that. `Disabled` is only valid with a plain `http://` endpoint; combining it with an `https://` endpoint throws `ClientMisconfigurationException`. A CA certificate and a trust store cannot be configured together (the CA certificate must be imported into the trust store), and that combination also throws `ClientMisconfigurationException`. When reading the `ssl_mode` value through the client configuration map, enum names are matched case-sensitively (`Disabled`, `Trust`, `VerifyCa`, `Strict`).
+- Certificate-as-content support is compatibility-sensitive: any certificate or key value containing a PEM begin marker (`-----BEGIN`) is treated as inline PEM content, otherwise it is treated as a file path (also searched in the home directory and on the classpath).
## `jdbc-v2`
- JDBC driver registration: Registers through the standard JDBC service mechanism and is available through `DriverManager`.
- JDBC URL parsing: Accepts `jdbc:clickhouse:` and `jdbc:ch:` URLs with host, port, optional HTTP path, optional database, and query parameters.
-- SSL URL support: Supports HTTPS connections through URL and property configuration, including default protocol and port handling.
+- SSL URL support: Supports HTTPS connections through URL and property configuration, including default protocol and port handling. The `ssl_mode` property selects the verification strictness (`disabled`, `trust`, `verifyca`, `strict`); values are case-insensitive and the traditional JDBC value `none` is accepted as an alias for `trust`. Root CA and client certificate/key may be supplied as a file path or as inline PEM content.
- Driver and client properties: Separates JDBC-specific properties from passthrough client options used by the underlying `client-v2` transport.
- DataSource support: Provides a JDBC `DataSource` implementation backed by the same driver configuration model.
- Connection lifecycle: Supports connection close, validity checks, ping-based health checks, and network timeout management.
@@ -85,4 +88,5 @@ Compatibility-sensitive traits:
- `getString()` formatting for temporal values is stable output: `Date` uses `yyyy-MM-dd`, `DateTime` uses `yyyy-MM-dd HH:mm:ss`, and `DateTime64` preserves fractional precision, all interpreted in server timezone context where applicable.
- Date and timestamp setters with `Calendar` are timezone-sensitive by design. Preserving the current day-shift and instant-preserving behavior is important for compatibility.
- `setObject()` temporal behavior is specific and should not drift: `LocalDateTime` and `Instant` are rendered through `fromUnixTimestamp64Nano(...)`, while `Timestamp` and `Date` use quoted textual forms.
+- JDBC `ssl_mode` handling is compatibility-sensitive: values are case-insensitive, `none` is aliased to `trust` (the no-verification mode), and an unrecognized value throws `SQLException` during connection configuration. The normalized canonical mode name is forwarded to the underlying `client-v2` transport.
- INSERT result semantics depend on server-side `async_insert` and `wait_for_async_insert`. The driver does not override these settings, so it follows whatever the server profile or user configuration sets. When `async_insert=1` and `wait_for_async_insert=0`, `Statement.executeUpdate(...)` and `PreparedStatement.executeUpdate(...)` may return `0` (or an under-counted value), and parsing/data errors in the INSERT body may not be reported synchronously as a `SQLException`. Set `async_insert=0` (or `wait_for_async_insert=1`) per connection or statement to restore synchronous row counts and error reporting.
diff --git a/examples/client-v2/README.md b/examples/client-v2/README.md
index 0f6bab14e..4ed978c00 100644
--- a/examples/client-v2/README.md
+++ b/examples/client-v2/README.md
@@ -78,10 +78,15 @@ Notes:
## SSL Examples
-`com.clickhouse.examples.client_v2.SSLExamples` shows how to connect securely to a server whose
-certificate is signed by a custom (private) CA. Only the CA certificate is passed to the client
-with `Client.Builder.setRootCertificate()` - no trust store configuration is required, and the JVM
-default trust store stays untouched.
+`com.clickhouse.examples.client_v2.SSLExamples` shows how to connect securely to a server:
+
+- **Custom CA certificate** - the server certificate is signed by a custom (private) CA. Only the
+ CA certificate is passed to the client with `Client.Builder.setRootCertificate()` (as a file path
+ or directly as a PEM string) - no trust store configuration is required, and the JVM default
+ trust store stays untouched.
+- **Self-signed certificate without verification** - `Client.Builder.setSSLMode(SSLMode.Trust)`
+ accepts any server certificate and skips hostname verification. The connection is encrypted, but
+ the server identity is not verified - use it only for testing or in fully trusted environments.
The example runs in one of two modes.
@@ -113,7 +118,8 @@ mvn exec:java -Dexec.mainClass="com.clickhouse.examples.client_v2.SSLExamples" \
-DchRootCert="/path/to/ca.crt"
```
-`-DchRootCert` is required in this mode and must point to the CA certificate in PEM format.
+`-DchRootCert` must point to the CA certificate in PEM format. When it is omitted, only the
+self-signed (`SSLMode.Trust`) example runs - useful when you do not have the CA certificate at hand.
### Setting up a Docker dev instance with a self-signed certificate manually
diff --git a/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java b/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java
index dc62b65c2..2f87f281d 100644
--- a/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java
+++ b/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java
@@ -1,6 +1,7 @@
package com.clickhouse.examples.client_v2;
import com.clickhouse.client.api.Client;
+import com.clickhouse.client.api.enums.SSLMode;
import com.clickhouse.client.api.query.GenericRecord;
import lombok.extern.slf4j.Slf4j;
@@ -22,6 +23,9 @@
* Passing the CA certificate as a PEM string instead of a file path - useful when the
* certificate comes from an environment variable or a secret manager (typical for
* Kubernetes/cloud deployments) and you do not want to write it to disk.
+ * Connecting to a server with a self-signed certificate without any trust material -
+ * {@link SSLMode#Trust} accepts any server certificate and skips hostname verification.
+ * Use it only for testing or in fully trusted environments.
*
*
* More SSL examples (mTLS, trust stores, SNI) will be added to this class later.
@@ -41,7 +45,8 @@
* {@code chPort} - ClickHouse HTTPS port, default {@code 8443}
* {@code chDatabase} - database name, default {@code default}
* {@code chUser} and {@code chPassword} - credentials (standalone mode)
- * {@code chRootCert} - path to the root CA certificate in PEM format (required in standalone mode)
+ * {@code chRootCert} - path to the root CA certificate in PEM format. When omitted in
+ * standalone mode, only the self-signed (Trust) example runs
* {@code chImage} - Docker image for local mode, default {@code clickhouse/clickhouse-server:latest}
*
*/
@@ -58,16 +63,17 @@ public static void main(String[] args) {
final String user = System.getProperty("chUser", "default");
final String password = System.getProperty("chPassword", "");
final String rootCert = trimToNull(System.getProperty("chRootCert"));
- if (rootCert == null) {
- log.error("chRootCert is required when chHost is set. "
- + "Pass the path to the CA certificate (PEM) that signed the server certificate.");
- return;
- }
log.info("Running in standalone mode against {}:{}", host, port);
String endpoint = "https://" + host + ":" + port;
- connectWithCustomRootCertificate(endpoint, database, user, password, rootCert);
- connectWithRootCertificateAsString(endpoint, database, user, password, rootCert);
+ connectToSelfSignedServer(endpoint, database, user, password);
+ if (rootCert != null) {
+ connectWithCustomRootCertificate(endpoint, database, user, password, rootCert);
+ connectWithRootCertificateAsString(endpoint, database, user, password, rootCert);
+ } else {
+ log.info("chRootCert is not set - skipping the custom CA certificate examples. "
+ + "Pass the path to the CA certificate (PEM) that signed the server certificate to run them.");
+ }
return;
}
@@ -76,6 +82,8 @@ public static void main(String[] args) {
final String image = System.getProperty("chImage", "clickhouse/clickhouse-server:latest");
log.info("Running in local mode (set -DchHost to verify your own server)");
try (SecureServerSupport server = SecureServerSupport.start(image)) {
+ connectToSelfSignedServer(server.getEndpoint(), database,
+ SecureServerSupport.USER, SecureServerSupport.PASSWORD);
connectWithCustomRootCertificate(server.getEndpoint(), database,
SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath());
connectWithRootCertificateAsString(server.getEndpoint(), database,
@@ -85,6 +93,35 @@ public static void main(String[] args) {
}
}
+ /**
+ * Connects to a ClickHouse server with a self-signed certificate without providing
+ * any trust material. {@link SSLMode#Trust} makes the client accept any server
+ * certificate and skip hostname verification.
+ *
+ * Warning: the connection is encrypted, but the server identity is NOT verified,
+ * which makes it susceptible to man-in-the-middle attacks. Use this mode only for testing
+ * or in fully trusted environments. Prefer {@link Client.Builder#setRootCertificate(String)}
+ * with the signing CA certificate whenever possible.
+ */
+ static void connectToSelfSignedServer(String endpoint, String database, String user, String password) {
+ log.info("Connecting to {} accepting any server certificate (SSLMode.Trust)", endpoint);
+ try (Client client = new Client.Builder()
+ .addEndpoint(endpoint)
+ .setUsername(user)
+ .setPassword(password)
+ .setDefaultDatabase(database)
+ // Accept the self-signed certificate and skip hostname verification.
+ .setSSLMode(SSLMode.Trust)
+ .build()) {
+
+ List rows = client.queryAll("SELECT currentUser() AS user, version() AS version");
+ log.info("Connected (server certificate not verified) as '{}' to ClickHouse {}",
+ rows.get(0).getString("user"), rows.get(0).getString("version"));
+ } catch (Exception e) {
+ log.error("Connection with SSLMode.Trust failed", e);
+ }
+ }
+
/**
* Connects to a ClickHouse server using a custom root CA certificate.
* Use this when the server certificate is signed by a private CA (corporate CA,
diff --git a/examples/jdbc/README.md b/examples/jdbc/README.md
index 1fedefa5d..4cb796317 100644
--- a/examples/jdbc/README.md
+++ b/examples/jdbc/README.md
@@ -24,10 +24,16 @@ Addition options can be passed to the application:
## SSL Examples
-`com.clickhouse.examples.jdbc.SSLExamples` shows how to connect securely to a server whose
-certificate is signed by a custom (private) CA. Only the CA certificate is passed with the
-`sslrootcert` connection property - no trust store configuration is required, and the JVM default
-trust store stays untouched.
+`com.clickhouse.examples.jdbc.SSLExamples` shows how to connect securely to a server:
+
+- **Custom CA certificate** - the server certificate is signed by a custom (private) CA. Only the
+ CA certificate is passed with the `sslrootcert` connection property (as a file path or directly
+ as a PEM string) - no trust store configuration is required, and the JVM default trust store
+ stays untouched.
+- **Self-signed certificate without verification** - the `ssl_mode=trust` connection property
+ (`ssl_mode=none` is accepted as an alias) accepts any server certificate and skips hostname
+ verification. The connection is encrypted, but the server identity is not verified - use it only
+ for testing or in fully trusted environments.
The example runs in one of two modes.
@@ -57,7 +63,8 @@ mvn exec:java -Dexec.mainClass="com.clickhouse.examples.jdbc.SSLExamples" \
-DchRootCert="/path/to/ca.crt"
```
-`-DchRootCert` is required in this mode and must point to the CA certificate in PEM format.
+`-DchRootCert` must point to the CA certificate in PEM format. When it is omitted, only the
+self-signed (`ssl_mode=trust`) example runs - useful when you do not have the CA certificate at hand.
### Setting up a Docker dev instance with a self-signed certificate manually
diff --git a/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java b/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java
index 11a022fb5..f6a1cef1f 100644
--- a/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java
+++ b/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java
@@ -27,6 +27,10 @@
* Passing the CA certificate as a PEM string instead of a file path - useful when the
* certificate comes from an environment variable or a secret manager (typical for
* Kubernetes/cloud deployments) and you do not want to write it to disk.
+ * Connecting to a server with a self-signed certificate without any trust material -
+ * the {@code ssl_mode=trust} connection property accepts any server certificate and skips
+ * hostname verification ({@code ssl_mode=none} is accepted as an alias). Use it only for
+ * testing or in fully trusted environments.
*
*
* More SSL examples (mTLS, trust stores, SNI) will be added to this class later.
@@ -45,7 +49,8 @@
* {@code chUrl} - ClickHouse JDBC URL, e.g. {@code jdbc:clickhouse://my-host:8443/default}.
* When set, standalone mode is used
* {@code chUser} and {@code chPassword} - credentials (standalone mode)
- * {@code chRootCert} - path to the root CA certificate in PEM format (required in standalone mode)
+ * {@code chRootCert} - path to the root CA certificate in PEM format. When omitted in
+ * standalone mode, only the self-signed (trust) example runs
* {@code chImage} - Docker image for local mode, default {@code clickhouse/clickhouse-server:latest}
*
*/
@@ -60,18 +65,19 @@ public static void main(String[] args) {
final String user = System.getProperty("chUser", "default");
final String password = System.getProperty("chPassword", "");
final String rootCert = trimToNull(System.getProperty("chRootCert"));
- if (rootCert == null) {
- log.error("chRootCert is required when chUrl is set. "
- + "Pass the path to the CA certificate (PEM) that signed the server certificate.");
- return;
- }
log.info("Running in standalone mode against {}", url);
try {
- connectWithCustomRootCertificate(url, user, password, rootCert);
- connectWithRootCertificateAsString(url, user, password, rootCert);
+ connectToSelfSignedServer(url, user, password);
+ if (rootCert != null) {
+ connectWithCustomRootCertificate(url, user, password, rootCert);
+ connectWithRootCertificateAsString(url, user, password, rootCert);
+ } else {
+ log.info("chRootCert is not set - skipping the custom CA certificate examples. "
+ + "Pass the path to the CA certificate (PEM) that signed the server certificate to run them.");
+ }
} catch (SQLException | IOException e) {
- log.error("Secure connection with a custom root CA certificate failed", e);
+ log.error("Secure connection failed", e);
}
return;
}
@@ -81,6 +87,8 @@ public static void main(String[] args) {
final String image = System.getProperty("chImage", "clickhouse/clickhouse-server:latest");
log.info("Running in local mode (set -DchUrl to verify your own server)");
try (SecureServerSupport server = SecureServerSupport.start(image)) {
+ connectToSelfSignedServer(server.getJdbcUrl(),
+ SecureServerSupport.USER, SecureServerSupport.PASSWORD);
connectWithCustomRootCertificate(server.getJdbcUrl(),
SecureServerSupport.USER, SecureServerSupport.PASSWORD, server.getCaCertPath());
connectWithRootCertificateAsString(server.getJdbcUrl(),
@@ -93,6 +101,37 @@ public static void main(String[] args) {
Runtime.getRuntime().exit(0);
}
+ /**
+ * Connects to a ClickHouse server with a self-signed certificate without providing
+ * any trust material. The {@code ssl_mode=trust} connection property makes the driver
+ * accept any server certificate and skip hostname verification. The traditional JDBC
+ * value {@code ssl_mode=none} is accepted as an alias.
+ *
+ * Warning: the connection is encrypted, but the server identity is NOT verified,
+ * which makes it susceptible to man-in-the-middle attacks. Use this mode only for testing
+ * or in fully trusted environments. Prefer {@code sslrootcert} with the signing CA
+ * certificate whenever possible.
+ */
+ static void connectToSelfSignedServer(String url, String user, String password) throws SQLException {
+ log.info("Connecting to {} accepting any server certificate (ssl_mode=trust)", url);
+
+ Properties properties = new Properties();
+ properties.setProperty(ClientConfigProperties.USER.getKey(), user); // user
+ properties.setProperty(ClientConfigProperties.PASSWORD.getKey(), password); // password
+ properties.setProperty("ssl", "true"); // enable TLS even if the URL has no https scheme
+ // Accept the self-signed certificate and skip hostname verification.
+ properties.setProperty(ClientConfigProperties.SSL_MODE.getKey(), "trust"); // ssl_mode
+
+ try (Connection connection = DriverManager.getConnection(url, properties);
+ Statement stmt = connection.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT currentUser() AS user, version() AS version")) {
+ if (rs.next()) {
+ log.info("Connected (server certificate not verified) as '{}' to ClickHouse {}",
+ rs.getString("user"), rs.getString("version"));
+ }
+ }
+ }
+
/**
* Connects to a ClickHouse server using a custom root CA certificate.
* Use this when the server certificate is signed by a private CA (corporate CA,
diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java
index eca74aa53..0dd15e2da 100644
--- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java
+++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java
@@ -2,6 +2,7 @@
import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.ClientConfigProperties;
+import com.clickhouse.client.api.enums.SSLMode;
import com.clickhouse.client.api.http.ClickHouseHttpProto;
import com.clickhouse.data.ClickHouseDataType;
import com.clickhouse.jdbc.Driver;
@@ -332,7 +333,8 @@ private Map parseUrl(String url) throws SQLException {
* @param urlProperties - properties parsed from URL
* @param providedProperties - properties object provided by application
*/
- private void buildFinalProperties(Map urlProperties, Properties providedProperties) {
+ private void buildFinalProperties(Map urlProperties, Properties providedProperties)
+ throws SQLException {
// Copy provided properties
Map props = new HashMap<>();
@@ -379,6 +381,22 @@ private void buildFinalProperties(Map urlProperties, Properties
}
}
+ String sslMode = clientProperties.get(ClientConfigProperties.SSL_MODE.getKey());
+ if (sslMode != null) {
+ if ("none".equalsIgnoreCase(sslMode)) {
+ // JDBC drivers traditionally use 'none' for the no-verification SSL mode - alias it to 'trust'
+ clientProperties.put(ClientConfigProperties.SSL_MODE.getKey(), SSLMode.Trust.name());
+ } else {
+ try {
+ // values are case-insensitive in JDBC - normalize before passing to the client
+ clientProperties.put(ClientConfigProperties.SSL_MODE.getKey(), SSLMode.fromValue(sslMode).name());
+ } catch (IllegalArgumentException e) {
+ throw new SQLException("Unknown value '" + sslMode + "' for property '"
+ + ClientConfigProperties.SSL_MODE.getKey() + "'", e);
+ }
+ }
+ }
+
// Fill list of client properties information, add not specified properties (doesn't affect client properties)
for (ClientConfigProperties clientProp : ClientConfigProperties.values()) {
DriverPropertyInfo propertyInfo = propertyInfos.get(clientProp.getKey());
diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java
index 0ce66c84c..8972ff834 100644
--- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java
+++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java
@@ -763,6 +763,78 @@ public void testSecureConnection() throws Exception {
}
}
+ @Test(groups = { "integration" })
+ public void testSSLModeTrust() throws Exception {
+ if (isCloud()) {
+ return; // this test uses self-signed cert
+ }
+ ClickHouseNode secureServer = getSecureServer(ClickHouseProtocol.HTTP);
+ String jdbcUrl = "jdbc:clickhouse:" + secureServer.getBaseUri();
+
+ Properties properties = new Properties();
+ properties.put(ClientConfigProperties.USER.getKey(), "default");
+ properties.put(ClientConfigProperties.PASSWORD.getKey(), ClickHouseServerForTest.getPassword());
+
+ // Default mode (strict) without any trust material - the self-signed certificate must be rejected
+ Assert.expectThrows(Exception.class, () -> {
+ try (Connection conn = new ConnectionImpl(jdbcUrl, properties);
+ Statement stmt = conn.createStatement()) {
+ stmt.executeQuery("SELECT 1");
+ }
+ });
+
+ // 'none' is a JDBC alias for the 'trust' mode
+ for (String mode : new String[] { "none", "trust", "Trust" }) {
+ Properties trustProperties = new Properties();
+ trustProperties.putAll(properties);
+ trustProperties.put(ClientConfigProperties.SSL_MODE.getKey(), mode);
+
+ try (Connection conn = new ConnectionImpl(jdbcUrl, trustProperties);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT number FROM system.numbers LIMIT 10")) {
+
+ int count = 0;
+ while (rs.next()) { count++; }
+ Assert.assertEquals(count, 10, "Failed for ssl_mode '" + mode + "'");
+ }
+ }
+ }
+
+ @Test(groups = { "integration" })
+ public void testSSLModeVerifyCa() throws Exception {
+ if (isCloud()) {
+ return; // this test uses self-signed cert
+ }
+ ClickHouseNode secureServer = getSecureServer(ClickHouseProtocol.HTTP);
+ // server certificate has CN=localhost, so connecting via 127.0.0.1 fails hostname verification
+ String jdbcUrl = "jdbc:clickhouse://127.0.0.1:" + secureServer.getPort() + "/";
+
+ Properties properties = new Properties();
+ properties.put(ClientConfigProperties.USER.getKey(), "default");
+ properties.put(ClientConfigProperties.PASSWORD.getKey(), ClickHouseServerForTest.getPassword());
+ properties.put(DriverProperties.SECURE_CONNECTION.getKey(), "true");
+ properties.put(ClientConfigProperties.CA_CERTIFICATE.getKey(), "containers/clickhouse-server/certs/localhost.crt");
+
+ // Default mode (strict): certificate chain is trusted, but the hostname does not match
+ Assert.expectThrows(Exception.class, () -> {
+ try (Connection conn = new ConnectionImpl(jdbcUrl, properties);
+ Statement stmt = conn.createStatement()) {
+ stmt.executeQuery("SELECT 1");
+ }
+ });
+
+ // verify_ca: certificate chain is validated, hostname mismatch is ignored
+ properties.put(ClientConfigProperties.SSL_MODE.getKey(), "verifyca");
+ try (Connection conn = new ConnectionImpl(jdbcUrl, properties);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT number FROM system.numbers LIMIT 10")) {
+
+ int count = 0;
+ while (rs.next()) { count++; }
+ Assert.assertEquals(count, 10);
+ }
+ }
+
@Test(groups = { "integration" })
public void testSelectingDatabase() throws Exception {
if (isCloud()) {
diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java
index 9e0abe8bc..08d80748f 100644
--- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java
+++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java
@@ -2,6 +2,7 @@
import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.ClientConfigProperties;
+import com.clickhouse.client.api.enums.SSLMode;
import com.clickhouse.data.ClickHouseDataType;
import com.clickhouse.jdbc.DriverProperties;
@@ -17,6 +18,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
@@ -169,6 +172,54 @@ public void testConfigurationProperties() throws Exception {
assertEquals(p.value, "default1");
}
+ @DataProvider(name = "sslModeValues")
+ public Object[][] sslModeValues() {
+ return new Object[][] {
+ // input value, expected client property value
+ { "none", SSLMode.Trust.name() }, // JDBC alias for the no-verification mode
+ { "NONE", SSLMode.Trust.name() },
+ { "disabled", SSLMode.Disabled.name() },
+ { "Disabled", SSLMode.Disabled.name() },
+ { "trust", SSLMode.Trust.name() },
+ { "Trust", SSLMode.Trust.name() },
+ { "verifyca", SSLMode.VerifyCa.name() },
+ { "VERIFYCA", SSLMode.VerifyCa.name() },
+ { "strict", SSLMode.Strict.name() },
+ { "Strict", SSLMode.Strict.name() },
+ };
+ }
+
+ @Test
+ public void testSSLModeDatasetCoversAllModes() {
+ Set covered = Arrays.stream(sslModeValues())
+ .map(row -> (String) row[1])
+ .collect(Collectors.toSet());
+ Set allModes = Arrays.stream(SSLMode.values())
+ .map(Enum::name)
+ .collect(Collectors.toSet());
+ assertEquals(covered, allModes,
+ "SSLMode constants changed - update the 'sslModeValues' dataset and the ssl_mode handling in JdbcConfiguration");
+ }
+
+ @Test(dataProvider = "sslModeValues")
+ public void testSSLModeProperty(String value, String expected) throws Exception {
+ // passed via Properties
+ Properties properties = new Properties();
+ properties.setProperty(ClientConfigProperties.SSL_MODE.getKey(), value);
+ JdbcConfiguration configuration = new JdbcConfiguration("jdbc:clickhouse://localhost:8123/", properties);
+ assertEquals(configuration.getClientProperties().get(ClientConfigProperties.SSL_MODE.getKey()), expected);
+
+ // passed as a URL parameter
+ configuration = new JdbcConfiguration("jdbc:clickhouse://localhost:8123/?ssl_mode=" + value, new Properties());
+ assertEquals(configuration.getClientProperties().get(ClientConfigProperties.SSL_MODE.getKey()), expected);
+ }
+
+ @Test
+ public void testSSLModeInvalidValue() {
+ assertThrows(SQLException.class,
+ () -> new JdbcConfiguration("jdbc:clickhouse://localhost:8123/?ssl_mode=insecure", new Properties()));
+ }
+
@DataProvider(name = "typeMappingsPropertyKey")
public Object[][] typeMappingsPropertyKey() {
return new Object[][] {