|
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; |
@@ -755,6 +756,34 @@ public Builder setClientKey(String path) { |
755 | 756 | return this; |
756 | 757 | } |
757 | 758 |
|
| 759 | + /** |
| 760 | + * Defines how strictly the client verifies a server identity on secure connections. |
| 761 | + * |
| 762 | + * <p>Supported modes:</p> |
| 763 | + * <ul> |
| 764 | + * <li>{@link SSLMode#DISABLED} - SSL is not used; only meaningful with plain protocols</li> |
| 765 | + * <li>{@link SSLMode#TRUST} - encrypt, but accept any server certificate and skip |
| 766 | + * hostname verification; a configured trust store or CA certificate is ignored (a warning |
| 767 | + * is logged), while a client certificate/key is still applied for mTLS</li> |
| 768 | + * <li>{@link SSLMode#VERIFY_CA} - validate the server certificate chain, but skip |
| 769 | + * hostname verification</li> |
| 770 | + * <li>{@link SSLMode#STRICT} - full verification of the certificate chain and the |
| 771 | + * hostname (default)</li> |
| 772 | + * </ul> |
| 773 | + * |
| 774 | + * <p>The mode applies only when a secure protocol is in use - for the HTTP transport that |
| 775 | + * means an {@code https://} endpoint. Setting any mode does <b>not</b> make the client use |
| 776 | + * encryption on a plain HTTP endpoint: the endpoint scheme always decides whether the |
| 777 | + * connection is encrypted.</p> |
| 778 | + * |
| 779 | + * @param sslMode ssl mode |
| 780 | + * @return same instance of the builder |
| 781 | + */ |
| 782 | + public Builder setSSLMode(SSLMode sslMode) { |
| 783 | + this.configuration.put(ClientConfigProperties.SSL_MODE.getKey(), sslMode.name()); |
| 784 | + return this; |
| 785 | + } |
| 786 | + |
758 | 787 | /** |
759 | 788 | * Configure client to use server timezone for date/datetime columns. Default is true. |
760 | 789 | * If this options is selected then server timezone should be set as well. |
@@ -1140,6 +1169,36 @@ public Client build() { |
1140 | 1169 | throw new ClientMisconfigurationException("Trust store and certificates cannot be used together"); |
1141 | 1170 | } |
1142 | 1171 |
|
| 1172 | + // A trust store and a CA certificate are not rejected here: for VERIFY_CA/STRICT the trust |
| 1173 | + // store takes precedence and the CA certificate is ignored with a warning (see createSSLContext). |
| 1174 | + |
| 1175 | + // Resolve ssl_mode case-insensitively and normalize it to the canonical enum name so that |
| 1176 | + // downstream parsing is consistent and an unknown value is reported as a misconfiguration |
| 1177 | + // here instead of failing later with a generic enum-parsing error. |
| 1178 | + String sslModeValue = configuration.get(ClientConfigProperties.SSL_MODE.getKey()); |
| 1179 | + if (sslModeValue != null) { |
| 1180 | + SSLMode sslMode; |
| 1181 | + try { |
| 1182 | + sslMode = SSLMode.fromValue(sslModeValue); |
| 1183 | + } catch (IllegalArgumentException e) { |
| 1184 | + throw new ClientMisconfigurationException("Invalid value '" + sslModeValue + "' for '" |
| 1185 | + + ClientConfigProperties.SSL_MODE.getKey() + "'", e); |
| 1186 | + } |
| 1187 | + configuration.put(ClientConfigProperties.SSL_MODE.getKey(), sslMode.name()); |
| 1188 | + |
| 1189 | + // SSLMode.DISABLED does not turn encryption off - the endpoint scheme decides that. So it |
| 1190 | + // contradicts a secure (https) endpoint and must be rejected here, before the client is created. |
| 1191 | + if (sslMode == SSLMode.DISABLED) { |
| 1192 | + for (Endpoint endpoint : this.endpoints) { |
| 1193 | + if ("https".equalsIgnoreCase(endpoint.getURI().getScheme())) { |
| 1194 | + throw new ClientMisconfigurationException("SSL mode '" + SSLMode.DISABLED |
| 1195 | + + "' cannot be used with a secure (https) endpoint. Use '" + SSLMode.TRUST |
| 1196 | + + "' to trust all certificates or use plain HTTP."); |
| 1197 | + } |
| 1198 | + } |
| 1199 | + } |
| 1200 | + } |
| 1201 | + |
1143 | 1202 | // Check timezone settings |
1144 | 1203 | String useTimeZoneValue = this.configuration.get(ClientConfigProperties.USE_TIMEZONE.getKey()); |
1145 | 1204 | String serverTimeZoneValue = this.configuration.get(ClientConfigProperties.SERVER_TIMEZONE.getKey()); |
@@ -1671,15 +1730,9 @@ public CompletableFuture<QueryResponse> query(String sqlQuery, Map<String, Objec |
1671 | 1730 | for (int i = 0; i <= retries; i++) { |
1672 | 1731 | ClassicHttpResponse httpResponse = null; |
1673 | 1732 | try { |
1674 | | - boolean useMultipart = ClientConfigProperties.HTTP_SEND_PARAMS_IN_BODY.getOrDefault(requestSettings.getAllSettings()); |
1675 | | - if (queryParams != null && useMultipart) { |
1676 | | - httpResponse = httpClientHelper.executeMultiPartRequest(selectedEndpoint, |
1677 | | - requestSettings.getAllSettings(), sqlQuery); |
1678 | | - } else { |
1679 | | - httpResponse = httpClientHelper.executeRequest(selectedEndpoint, |
| 1733 | + httpResponse = httpClientHelper.executeRequest(selectedEndpoint, |
1680 | 1734 | requestSettings.getAllSettings(), |
1681 | 1735 | sqlQuery); |
1682 | | - } |
1683 | 1736 | // Check response |
1684 | 1737 | if (httpResponse.getCode() == HttpStatus.SC_SERVICE_UNAVAILABLE) { |
1685 | 1738 | LOG.warn("Failed to get response. Server returned {}. Retrying. (Duration: {})", httpResponse.getCode(), durationSince(startTime)); |
|
0 commit comments