|
12 | 12 | import com.clickhouse.client.api.data_formats.internal.ProcessParser; |
13 | 13 | import com.clickhouse.client.api.enums.Protocol; |
14 | 14 | import com.clickhouse.client.api.enums.ProxyType; |
| 15 | +import com.clickhouse.client.api.enums.SSLMode; |
15 | 16 | import com.clickhouse.client.api.http.ClickHouseHttpProto; |
16 | 17 | import com.clickhouse.client.api.insert.InsertResponse; |
17 | 18 | import com.clickhouse.client.api.insert.InsertSettings; |
@@ -756,6 +757,34 @@ public Builder setClientKey(String path) { |
756 | 757 | return this; |
757 | 758 | } |
758 | 759 |
|
| 760 | + /** |
| 761 | + * Defines how strictly the client verifies a server identity on secure connections. |
| 762 | + * |
| 763 | + * <p>Supported modes:</p> |
| 764 | + * <ul> |
| 765 | + * <li>{@link SSLMode#DISABLED} - SSL is not used; only meaningful with plain protocols</li> |
| 766 | + * <li>{@link SSLMode#TRUST} - encrypt, but accept any server certificate and skip |
| 767 | + * hostname verification; a configured trust store or CA certificate is ignored (a warning |
| 768 | + * is logged), while a client certificate/key is still applied for mTLS</li> |
| 769 | + * <li>{@link SSLMode#VERIFY_CA} - validate the server certificate chain, but skip |
| 770 | + * hostname verification</li> |
| 771 | + * <li>{@link SSLMode#STRICT} - full verification of the certificate chain and the |
| 772 | + * hostname (default)</li> |
| 773 | + * </ul> |
| 774 | + * |
| 775 | + * <p>The mode applies only when a secure protocol is in use - for the HTTP transport that |
| 776 | + * means an {@code https://} endpoint. Setting any mode does <b>not</b> make the client use |
| 777 | + * encryption on a plain HTTP endpoint: the endpoint scheme always decides whether the |
| 778 | + * connection is encrypted.</p> |
| 779 | + * |
| 780 | + * @param sslMode ssl mode |
| 781 | + * @return same instance of the builder |
| 782 | + */ |
| 783 | + public Builder setSSLMode(SSLMode sslMode) { |
| 784 | + this.configuration.put(ClientConfigProperties.SSL_MODE.getKey(), sslMode.name()); |
| 785 | + return this; |
| 786 | + } |
| 787 | + |
759 | 788 | /** |
760 | 789 | * Configure client to use server timezone for date/datetime columns. Default is true. |
761 | 790 | * If this options is selected then server timezone should be set as well. |
@@ -1141,6 +1170,36 @@ public Client build() { |
1141 | 1170 | throw new ClientMisconfigurationException("Trust store and certificates cannot be used together"); |
1142 | 1171 | } |
1143 | 1172 |
|
| 1173 | + // A trust store and a CA certificate are not rejected here: for VERIFY_CA/STRICT the trust |
| 1174 | + // store takes precedence and the CA certificate is ignored with a warning (see createSSLContext). |
| 1175 | + |
| 1176 | + // Resolve ssl_mode case-insensitively and normalize it to the canonical enum name so that |
| 1177 | + // downstream parsing is consistent and an unknown value is reported as a misconfiguration |
| 1178 | + // here instead of failing later with a generic enum-parsing error. |
| 1179 | + String sslModeValue = configuration.get(ClientConfigProperties.SSL_MODE.getKey()); |
| 1180 | + if (sslModeValue != null) { |
| 1181 | + SSLMode sslMode; |
| 1182 | + try { |
| 1183 | + sslMode = SSLMode.fromValue(sslModeValue); |
| 1184 | + } catch (IllegalArgumentException e) { |
| 1185 | + throw new ClientMisconfigurationException("Invalid value '" + sslModeValue + "' for '" |
| 1186 | + + ClientConfigProperties.SSL_MODE.getKey() + "'", e); |
| 1187 | + } |
| 1188 | + configuration.put(ClientConfigProperties.SSL_MODE.getKey(), sslMode.name()); |
| 1189 | + |
| 1190 | + // SSLMode.DISABLED does not turn encryption off - the endpoint scheme decides that. So it |
| 1191 | + // contradicts a secure (https) endpoint and must be rejected here, before the client is created. |
| 1192 | + if (sslMode == SSLMode.DISABLED) { |
| 1193 | + for (Endpoint endpoint : this.endpoints) { |
| 1194 | + if ("https".equalsIgnoreCase(endpoint.getURI().getScheme())) { |
| 1195 | + throw new ClientMisconfigurationException("SSL mode '" + SSLMode.DISABLED |
| 1196 | + + "' cannot be used with a secure (https) endpoint. Use '" + SSLMode.TRUST |
| 1197 | + + "' to trust all certificates or use plain HTTP."); |
| 1198 | + } |
| 1199 | + } |
| 1200 | + } |
| 1201 | + } |
| 1202 | + |
1144 | 1203 | // Check timezone settings |
1145 | 1204 | String useTimeZoneValue = this.configuration.get(ClientConfigProperties.USE_TIMEZONE.getKey()); |
1146 | 1205 | String serverTimeZoneValue = this.configuration.get(ClientConfigProperties.SERVER_TIMEZONE.getKey()); |
@@ -1641,6 +1700,7 @@ public CompletableFuture<QueryResponse> query(String sqlQuery, Map<String, Objec |
1641 | 1700 | if (requestSettings.getFormat() == null) { |
1642 | 1701 | requestSettings.setFormat(ClickHouseFormat.RowBinaryWithNamesAndTypes); |
1643 | 1702 | } |
| 1703 | + applyFormatSpecificSettings(requestSettings); |
1644 | 1704 | ClientStatisticsHolder clientStats = new ClientStatisticsHolder(); |
1645 | 1705 | clientStats.start(ClientMetrics.OP_DURATION); |
1646 | 1706 |
|
@@ -2226,6 +2286,30 @@ private Map<String, Object> buildRequestSettings(Map<String, Object> opSettings) |
2226 | 2286 | return requestSettings; |
2227 | 2287 | } |
2228 | 2288 |
|
| 2289 | + /** |
| 2290 | + * Applies format-specific server-side settings to the already merged request settings. |
| 2291 | + * Must be called after {@link #buildRequestSettings(Map)} and after the request format has been resolved |
| 2292 | + * (either provided by the caller or defaulted), so that the inspected format reflects the final value. |
| 2293 | + * |
| 2294 | + * <p>For {@link ClickHouseFormat#JSONEachRow}, callers may opt in to plain JSON numbers by setting |
| 2295 | + * {@link ClientConfigProperties#JSON_DISABLE_NUMBER_QUOTING}. Explicit server settings are otherwise |
| 2296 | + * left untouched.</p> |
| 2297 | + * <ul> |
| 2298 | + * <li>{@code output_format_json_quote_64bit_integers}</li> |
| 2299 | + * <li>{@code output_format_json_quote_64bit_floats}</li> |
| 2300 | + * <li>{@code output_format_json_quote_decimals}</li> |
| 2301 | + * </ul> |
| 2302 | + */ |
| 2303 | + private static void applyFormatSpecificSettings(QuerySettings requestSettings) { |
| 2304 | + boolean disableNumberQuoting = ClientConfigProperties.JSON_DISABLE_NUMBER_QUOTING |
| 2305 | + .getOrDefault(requestSettings.getAllSettings()); |
| 2306 | + if (requestSettings.getFormat() == ClickHouseFormat.JSONEachRow && disableNumberQuoting) { |
| 2307 | + requestSettings.serverSetting("output_format_json_quote_64bit_integers", "0"); |
| 2308 | + requestSettings.serverSetting("output_format_json_quote_64bit_floats", "0"); |
| 2309 | + requestSettings.serverSetting("output_format_json_quote_decimals", "0"); |
| 2310 | + } |
| 2311 | + } |
| 2312 | + |
2229 | 2313 | private Duration durationSince(long sinceNanos) { |
2230 | 2314 | return Duration.ofNanos(System.nanoTime() - sinceNanos); |
2231 | 2315 | } |
|
0 commit comments