From 8e7a7968fb4f06d788ab27be0db95680baaa08eb Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 6 Jun 2025 14:54:31 -0700 Subject: [PATCH 1/5] added test harness --- client-v2/pom.xml | 5 + .../com/clickhouse/client/NetworkTests.java | 110 ++++++++++++++++++ client-v2/src/test/resources/certs/node1.crt | 19 +++ client-v2/src/test/resources/certs/node1.key | 28 +++++ client-v2/src/test/resources/certs/node2.crt | 19 +++ client-v2/src/test/resources/certs/node2.key | 28 +++++ client-v2/src/test/resources/nginx.conf | 53 +++++++++ 7 files changed, 262 insertions(+) create mode 100644 client-v2/src/test/java/com/clickhouse/client/NetworkTests.java create mode 100644 client-v2/src/test/resources/certs/node1.crt create mode 100644 client-v2/src/test/resources/certs/node1.key create mode 100644 client-v2/src/test/resources/certs/node2.crt create mode 100644 client-v2/src/test/resources/certs/node2.key create mode 100644 client-v2/src/test/resources/nginx.conf diff --git a/client-v2/pom.xml b/client-v2/pom.xml index a610c0ef5..5bf2d3e02 100644 --- a/client-v2/pom.xml +++ b/client-v2/pom.xml @@ -129,6 +129,11 @@ 1.18.36 test + + org.slf4j + slf4j-simple + 2.0.16 + diff --git a/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java b/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java new file mode 100644 index 000000000..eb3931994 --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java @@ -0,0 +1,110 @@ +package com.clickhouse.client; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.MountableFile; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; +import java.util.Collections; + +@Test(groups = {"integration"}) +public class NetworkTests { + private static final String NGINX_IMAGE = "nginx:alpine"; + private static final int NGINX_SSL_PORT = 8443; + private static final String NODE1_HOST = "node1.test"; + private static final String NODE2_HOST = "node2.test"; + + private GenericContainer nginxContainer; + + @BeforeClass + public void setUp() throws Exception { + + // Create Nginx container with custom configuration + nginxContainer = new GenericContainer<>(NGINX_IMAGE) + .withCopyFileToContainer( + MountableFile.forClasspathResource("nginx.conf"), + "/etc/nginx/nginx.conf" + ) + .withCopyFileToContainer( + MountableFile.forClasspathResource("certs"), + "/etc/nginx/certs/" + ) + .withExposedPorts(NGINX_SSL_PORT) + .waitingFor(Wait.forListeningPort()); + + nginxContainer.start(); + } + + @AfterClass + public void tearDown() { + if (nginxContainer != null) { + nginxContainer.stop(); + } + } + + private String getNginxHost() { + return nginxContainer.getHost(); + } + + private int getNginxPort() { + return nginxContainer.getMappedPort(NGINX_SSL_PORT); + } + + @Test + public void testSNI() { + // Test will be implemented here + String host = getNginxHost(); + int port = getNginxPort(); + System.out.println("Nginx container running at: " + host + ":" + port); + } + + @Test(dataProvider = "testSNINodesDP") + void testSNINodes(String host) throws Exception { + int port = nginxContainer.getMappedPort(NGINX_SSL_PORT); + SSLSocket socket = createSniSocket(host, "localhost", port); + socket.startHandshake(); + + SSLSession session = socket.getSession(); + X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0]; + Assert.assertTrue(cert.getSubjectX500Principal().getName().contains("CN=" + host)); + } + + @DataProvider + static Object[][] testSNINodesDP() { + return new Object[][] { + {NODE1_HOST}, + {NODE2_HOST} + }; + } + + private SSLSocket createSniSocket(String sniHost, String serverHost, int port) throws Exception { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new TrustManager[] { new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String authType) {} + public void checkServerTrusted(X509Certificate[] chain, String authType) {} + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + } }, null); + + SSLSocketFactory factory = context.getSocketFactory(); + SSLSocket socket = (SSLSocket) factory.createSocket(serverHost, port); + + SSLParameters sslParams = socket.getSSLParameters(); + sslParams.setServerNames(Collections.singletonList(new SNIHostName(sniHost))); + socket.setSSLParameters(sslParams); + + return socket; + } +} diff --git a/client-v2/src/test/resources/certs/node1.crt b/client-v2/src/test/resources/certs/node1.crt new file mode 100644 index 000000000..8956880a4 --- /dev/null +++ b/client-v2/src/test/resources/certs/node1.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIjCCAgqgAwIBAgIUPgsC1txpblBP8ybZlyOE/iwm3GAwDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKbm9kZTEudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 +MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUxLnRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC16EcQY0+5s2sF4WDFcQgExmuIDn3v8gxqklv4BTXV +1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75gnm7nDd/5yCEbuhOGjHZcKWLx0ff7 +MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJaHyDQRbK3Uu896K3wm5YC8FArj83 +XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9TrhyxMWaviFVHk9Lvvj8ajrJQ6nyFNOYL +hTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93XbrGFu6bstL0V6MeAFV7u+oE6uaJpzw +23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4uxNztVhtAgMBAAGjajBoMB0GA1Ud +DgQWBBSFVEPrB2BgUiQ2VuyBbpcASR+HujAfBgNVHSMEGDAWgBSFVEPrB2BgUiQ2 +VuyBbpcASR+HujAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUxLnRl +c3QwDQYJKoZIhvcNAQELBQADggEBACDcZV0fYLyWHcY2lRRp+XfCC9pku2QCLKtb +UYF2sAzMm/ND5U6MFu5BB9EcveFVDDe00DgdkSTC/7LgljhZvTLFxaqmcHO6bz9N +woh1sljzlxDWymPCFg812lTt+KF6dvRswVYVY8ZkF9mrf0oVTYFNeX29G65/GlWQ +ykN8VvjtnMZpphJYTaMNzOsAvEWLxSppVYYIhNqjwIifJjiWGptqtmeycQrInJp0 +5KgecVqEffxm1NcZyTlcoDvcm2ayQblpq17rcQ6H5Y1LF9vVffA1UwAgLPQmSNO2 +5hbfGo2ySmRzBv5goYdhR4/KVj+IXL0ASgzp6lHh+UhSLFeTFWY= +-----END CERTIFICATE----- diff --git a/client-v2/src/test/resources/certs/node1.key b/client-v2/src/test/resources/certs/node1.key new file mode 100644 index 000000000..b385eac9c --- /dev/null +++ b/client-v2/src/test/resources/certs/node1.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC16EcQY0+5s2sF +4WDFcQgExmuIDn3v8gxqklv4BTXV1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75g +nm7nDd/5yCEbuhOGjHZcKWLx0ff7MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJ +aHyDQRbK3Uu896K3wm5YC8FArj83XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9Trhyx +MWaviFVHk9Lvvj8ajrJQ6nyFNOYLhTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93Xbr +GFu6bstL0V6MeAFV7u+oE6uaJpzw23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4 +uxNztVhtAgMBAAECggEADi6hV7w+a357AhbbuhkBZInpuU5T8EChSi/F/tS9KUIj +453JEJt6evgFJXgmydFgLjjXGPdwcMQUSCkiAV6aDznUpvo/anaKBc5uOedCW7GY +/lu47DBYhHf8Yt3w8NuHevymaQVETWspunNsfbk14zQUoxN1erksbC3ib74GDLU8 +U6c08y1A+xFgybIUqTwyKLjBk6rUSwcXBwkmVPCvpoeY1b7jpDK71uMQsuA9Y0Fn +Ks7i5n3868nUe46SwR3aaZMz1ca1UGsQNTX1ZXr6rYB6PbsOBi7GCPaUKKBmzhtZ +pvQBRDESXvTgZ3uL1NUxgBYlpunTdyu8sCKhnoTZsQKBgQD/Aopz/CmB7Jm7Yr81 +Jw1fOUMCQlzVDFkXRgbGqDruQhvrgx1eNVhIUJGX1VffW1xFyut6G9h8eBQ5Na/g +XQC9Ea/B4HeovhIF1/n0owhBfVmBMWf5GOngguXPDVScehZY/58t2CM4C4wBs7hP +whJlPss40Ftox5X63ZRVO/CECQKBgQC2nRQm+K0qR8sEkWfj4iviWpAZjCmCkFAI +fl89WL5UWObwQBNrRlfOQDuncHYgfcRitxnyqqsfr4DbmkSGh+XBmSIAVqUx+b6i +VkXtq6LA/uJ/BuALyKQmR+UH9C3kyCEOkqialy1pcfQF5ZnDT7/CoPtaheknR5kj +6BMKN1gyRQKBgQCRNhovN81PDbLw8KcfFlDYA5xzweRo8TIePaMIJq4AFKcfcjcb ++VZ/P1nY0wTJzJV7rLRwgUDCiAJEHZ08LtmH0HV7+l1JOoq6xySvuNKvsDhyh/bD +8vri/MbuI5Il1KO7JDy5d1V+yZx9L6Dp0gJ1os9IV+VlghmWk+yuqIYqOQKBgQCP +55bi4yKq5+p/jgpdlXTZql5WE5L+lbTMDLIGyPErzOcoOoZTChrVqN6Zo1EjZ5ij +5gCnr4CstoQICjin/126Q2987sq9aD6m9O+kTJY0GmfojEVwY8ufcxZ0PyMPX/pU +3pPJRwr9RlRzdSzHsge+W1fYTSCf2JdAqXifm1+ANQKBgBVU0Z8Eeeab7RslPiDJ +kPd1okVJMMSI1jLANsla4WWL93lHDTQ/c6yhO6as3j4f4Bp13k6vtWypolnhCDWc +OTNPZYHPD9qQYevQlpFK2pERIEWH5BgeYLCy2YLWlKpUprHGyOUW3Ji9Gukha3pk +KVkxTzYFuXegiqZyKWFimVrs +-----END PRIVATE KEY----- diff --git a/client-v2/src/test/resources/certs/node2.crt b/client-v2/src/test/resources/certs/node2.crt new file mode 100644 index 000000000..3d495d448 --- /dev/null +++ b/client-v2/src/test/resources/certs/node2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIjCCAgqgAwIBAgIUbBm4zP62LDBki+SOV7K/a3RGoQQwDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKbm9kZTIudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 +MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUyLnRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDXZDmSiRTjmVNMOF3p58Fg34+SvCoohYO0pB9LaY5d +H/iPHssTm/spD15sGMTwmnFKNgLKOCHCDgltTPJTCiVteddh944K+9drxnfck9Eb +FQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT4Hq0Tj37awIEPYcT2gjMrbsbXjAZ +nyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6tjJWbfgWy1DsdPuTadJAAaaa35CA +CgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2eaYx9Y8K3MqM4KouMeykO9CX04+3pg +bDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh8CRz2dmPAgMBAAGjajBoMB0GA1Ud +DgQWBBQAsXC8lOD5OjHgmNpWYYbAjI+1EjAfBgNVHSMEGDAWgBQAsXC8lOD5OjHg +mNpWYYbAjI+1EjAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUyLnRl +c3QwDQYJKoZIhvcNAQELBQADggEBAKzBUiof/5yrmebVf+Arrur2/96QnEQJkGQm +1RwcClGOwhUtdbxcdoXgYqOLtQSiTGNAKcVyOKATOz6tc7qU5BkIga9d2FSGSV9c +6gU/Ij9+Y2AAHpElnHBGkjiuBAugv5Tt4NxnJXss5raML9/ATi0y4wcRSAeSCnDy +wFX66SEoWVkQcti7/TrxJRgstubWplTPtGQK/lcgWFNIKJDHft0nlXNsUK0CrfLq +wdWtmhWuadg66JuKvJABVtJIr2RQ5rYVWPyuS3sbSVIA9CB9dPjb/3/lSwmdSBbL +0WesfGryPK4aZXFlUEzzr/P/euwHhss0KUYjIpdGNQ7sBdwcFKg= +-----END CERTIFICATE----- diff --git a/client-v2/src/test/resources/certs/node2.key b/client-v2/src/test/resources/certs/node2.key new file mode 100644 index 000000000..14115f3d1 --- /dev/null +++ b/client-v2/src/test/resources/certs/node2.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDXZDmSiRTjmVNM +OF3p58Fg34+SvCoohYO0pB9LaY5dH/iPHssTm/spD15sGMTwmnFKNgLKOCHCDglt +TPJTCiVteddh944K+9drxnfck9EbFQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT +4Hq0Tj37awIEPYcT2gjMrbsbXjAZnyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6 +tjJWbfgWy1DsdPuTadJAAaaa35CACgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2ea +Yx9Y8K3MqM4KouMeykO9CX04+3pgbDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh +8CRz2dmPAgMBAAECggEAUvRaSgn6nheooaPZ46Kq8xzXBlvq3rVhPrqxBNHQDySn +jbN94f0Ck9ND+dzmSIYq2LqJg4lv0vWmAs8OSxYOySWSYrw9nBRXaL5E3pKdh9Ek +fBSUWdlBbJngv94Es0SZk33wQzh1ls2lBl7f0z6JF6IkYi6yLd6c4fuNIuK5IrxX +0ahgqfiHrNizm8rfnIuHVQh/wBQk3cseXFMSnq5Y9/ZaFzqVKKauhLr8vFYRHK0Y +UCcLFCMZxvOpdVlOa8Ej4kZWQNd5yuvmEaaTcFgxkADmHF53jFjNVYluT16KQeVr +hzylv+rdrXgTpSb+uXmYzff1JTC7i8Jk9NQ5CaVCmQKBgQD3Gdn7rRU85aNkwaA5 +k2ykOP0ixQyLvYA0h8ajlidbxbfFm1IbrDmcOGgDIRwkwpD8GfZoKc41cjgaP9Fq +tWjdXNJaf0ogTWT9mcJFX1KGLU0yk3c8VPgtbhYf67u08H05ZF2O9TszBdxqy7lb +cfMIgiQXJ9gn86HMdL+t1hrZSQKBgQDfJgcZHHTCSW1o+wwWnPNrGjM5/vxUw293 +6d93m8nsUnZtYsRfqOpZKKyvuCbFhLlF5QkfpR19WUbCfqSRd2Ibq03CTN8MBa+0 +7yleHyDTYe48OGXyKzW0SdyM9T6oy7C0Fx1s4mB2Pa/iEovyZThxBPf3tINiU7QI +aMgo8mG0FwKBgQCTMVwPNpBDIUvOliSah34c6TGpB5Ysm6qGICcshSJW4ZVR6e7k +OxhqPZN/4bZBE4GIGvMUI2sJnUtcH43gscWQr11CuTZvvYqeInx+FIU1FkE5Emzh +jH/1l/En/KVo6CTADlBI+z3Ta/dbypVUqrEp7VQQUxTI37EO+LDBnZuKuQKBgQCo +2MhkKtD6Nb8wwUzbS3UwZEnAp3zc0mhkzrUFdv7p71em2yYFz/VK82j/KRNFP3FF +PtGRfUl8EHJnNpZlb3TvxMX4sFvRZ/gUqyadDmazK5WtQWt6O1HOkljs26DG7iFN +k3PzcLNWpMSSBkCYcBiuA/lxF8iWPQmrl0VlHknpjwKBgQCEPI+tuaw0fHsiHAK2 +w8rTB8KO5Qh7p5d1BSGmefM2HfJGialT5cCKABQX/kK4TxtLX7Pi1LPhJuGuJoi+ +AAwlznz2O1PUPCnBEc8mB/8p8ZrY89q0+xaqoyW1hnsNi20nXe6eHXlGu+J3zLlX +rEDrZRwY7ldkYFWh467fqFEl1w== +-----END PRIVATE KEY----- diff --git a/client-v2/src/test/resources/nginx.conf b/client-v2/src/test/resources/nginx.conf new file mode 100644 index 000000000..fe8bff763 --- /dev/null +++ b/client-v2/src/test/resources/nginx.conf @@ -0,0 +1,53 @@ +# nginx.conf +events {} + +http { + server { + listen 8443 ssl; + server_name node1.test; + + ssl_certificate /etc/nginx/certs/node1.crt; + ssl_certificate_key /etc/nginx/certs/node1.key; + + location = /default { + if ($request_method != POST) { + return 405; + } + + if ($arg_query = "SELECT hostName()") { + return 200 "node1.test\n"; + } + + return 400 "Unsupported query\n"; + } + + location / { + return 200 "node1.test served\n"; + } + } + + server { + listen 8443 ssl; + server_name node2.test; + + ssl_certificate /etc/nginx/certs/node2.crt; + ssl_certificate_key /etc/nginx/certs/node2.key; + + location = /default { + if ($request_method != POST) { + return 405; + } + + if ($arg_query = "SELECT hostName()") { + return 200 "node2.test\n"; + } + + return 400 "Unsupported query\n"; + } + + location / { + return 200 "node2.test served\n"; + } + } +} + From df123606d6997d6f9a15f4ad57467467d9aeead5 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 18 Jun 2025 11:44:04 -0700 Subject: [PATCH 2/5] drafted SNI config? --- .../java/com/clickhouse/client/api/Client.java | 5 +++++ .../clickhouse/client/internal/SmallTests.java | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) 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 b014dc99f..6b8bb89da 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 @@ -594,6 +594,11 @@ public Builder useHttpCompression(boolean enabled) { return this; } + /** + * Tell client that compression will be handled by application. + * @param enabled - indicates that feature is enabled. + * @return + */ public Builder appCompressedData(boolean enabled) { this.configuration.put(ClientConfigProperties.APP_COMPRESSED_DATA.getKey(), String.valueOf(enabled)); return this; diff --git a/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java b/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java index 5a1755777..ff42c0ced 100644 --- a/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/internal/SmallTests.java @@ -1,5 +1,6 @@ package com.clickhouse.client.internal; +import com.clickhouse.client.api.ClientConfigProperties; import com.clickhouse.client.api.data_formats.internal.ProcessParser; import com.clickhouse.client.api.metrics.OperationMetrics; import com.clickhouse.client.api.metrics.ServerMetrics; @@ -45,4 +46,19 @@ public void testTimezoneConvertion() { ZonedDateTime utcSameLocalDt = dt.withZoneSameLocal(ZoneId.of("UTC")); System.out.println("withZoneSameLocal: " + utcSameLocalDt); } + + @Test + public void testGenConfigParameters() { + System.out.println("

Default: `none`
Enum: `none`
Key: `none` " + + + ); + for (ClientConfigProperties p : ClientConfigProperties.values()) { + String defaultValue = p.getDefaultValue() == null ? "-" : "`" + p.getDefaultValue() + "`"; + System.out.println("

Default: " +defaultValue + "
Enum: `ClientConfigProperties." + p.name() + "`
Key: `" + p.getKey() +"` " + + + ); + } + } } From a65e60f3789e3b469227560efbab4fce29e873f3 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Mon, 23 Jun 2025 22:47:26 -0700 Subject: [PATCH 3/5] implemented SNI mapping in both clients --- .../client/config/ClickHouseClientOption.java | 10 +- .../client/http/ApacheHttpConnectionImpl.java | 37 ++- .../clickhouse/client/http/NetworkTests.java | 269 ++++++++++++++++++ .../src/test/resources/certs/node1.crt | 19 ++ .../src/test/resources/certs/node1.key | 28 ++ .../src/test/resources/certs/node2.crt | 19 ++ .../src/test/resources/certs/node2.key | 28 ++ .../src/test/resources/nginx.conf | 53 ++++ .../client/api/ClientConfigProperties.java | 61 ++++ .../api/internal/HttpAPIClientHelper.java | 58 +++- .../com/clickhouse/client/NetworkTests.java | 11 +- client-v2/src/test/resources/certs/README.md | 41 +++ 12 files changed, 626 insertions(+), 8 deletions(-) create mode 100644 clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java create mode 100644 clickhouse-http-client/src/test/resources/certs/node1.crt create mode 100644 clickhouse-http-client/src/test/resources/certs/node1.key create mode 100644 clickhouse-http-client/src/test/resources/certs/node2.crt create mode 100644 clickhouse-http-client/src/test/resources/certs/node2.key create mode 100644 clickhouse-http-client/src/test/resources/nginx.conf create mode 100644 client-v2/src/test/resources/certs/README.md diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java index 95187b541..c12d9eb46 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java @@ -451,7 +451,15 @@ public enum ClickHouseClientOption implements ClickHouseOption { */ CONNECTION_TTL("connection_ttl", 0L, "Connection time to live in milliseconds. 0 or negative number means no limit."), - MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode."); + MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode."), + + /** + * Comma separated key-value pairs of IP address/host to SNI mapping. + * Special mapping {@code _default_} - for default SNI when no match found. Without default mapping only matched targets will have SNI parameter. + */ + SSL_SNI_MAPPING("ssl_sni_map", "", "Comma separated key-value pairs of IP address/host to SNI mapping. Special mapping _default_ - for default SNI when no match found. Without default mapping only matched targets will have SNI parameter.") + + ; private final String key; private final Serializable defaultValue; diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java index e5689b4f9..092487777 100644 --- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java +++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java @@ -11,6 +11,7 @@ import com.clickhouse.client.config.ClickHouseProxyType; import com.clickhouse.client.config.ClickHouseSslMode; import com.clickhouse.client.http.config.ClickHouseHttpOption; +import com.clickhouse.config.ClickHouseOption; import com.clickhouse.data.ClickHouseChecker; import com.clickhouse.data.ClickHouseExternalTable; import com.clickhouse.data.ClickHouseFormat; @@ -54,8 +55,11 @@ import org.apache.hc.core5.util.Timeout; import org.apache.hc.core5.util.VersionInfo; +import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -66,10 +70,9 @@ import java.io.UncheckedIOException; import java.net.ConnectException; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.SocketOption; -import java.net.SocketOptions; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; @@ -394,6 +397,8 @@ public static SocketFactory create(ClickHouseConfig config) { static class SSLSocketFactory extends SSLConnectionSocketFactory { private final ClickHouseConfig config; + private final Map sniMapping; + private final String defaultSNI; private SSLSocketFactory(ClickHouseConfig config) throws SSLException { super(ClickHouseSslContextProvider.getProvider().getSslContext(SSLContext.class, config) @@ -402,6 +407,9 @@ private SSLSocketFactory(ClickHouseConfig config) throws SSLException { ? new DefaultHostnameVerifier() : (hostname, session) -> true); // NOSONAR this.config = config; + String sniMappingStr = config.getStrOption(ClickHouseClientOption.SSL_SNI_MAPPING); + sniMapping = ClickHouseOption.toKeyValuePairs(sniMappingStr); + defaultSNI = sniMapping.get("_default_"); } @Override @@ -409,6 +417,31 @@ public Socket createSocket(HttpContext context) throws IOException { return AbstractSocketClient.setSocketOptions(config, new Socket()); } + @Override + protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException { + super.prepareSocket(socket, context); + + if (!sniMapping.isEmpty()) { + InetAddress remote = socket.getInetAddress(); + if (remote != null) { // actually should be not null here + String sni = sniMapping.get(remote.getHostAddress()); + if (sni == null) { + sni = sniMapping.get(remote.getHostName()); + if (sni == null) { + sni = defaultSNI; + } + } + if (sni != null && !sni.isEmpty()) { + SSLParameters sslParams = socket.getSSLParameters(); + sslParams.setServerNames(Collections.singletonList(new SNIHostName(sni))); + socket.setSSLParameters(sslParams); + } + } else { + log.warn("Failed to apply SNI - remote address is null"); + } + } + } + public static SSLSocketFactory create(ClickHouseConfig config) throws SSLException { return new SSLSocketFactory(config); } diff --git a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java new file mode 100644 index 000000000..5130d9dcd --- /dev/null +++ b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java @@ -0,0 +1,269 @@ +package com.clickhouse.client.http; + +import com.clickhouse.client.AbstractSocketClient; +import com.clickhouse.client.ClickHouseClient; +import com.clickhouse.client.ClickHouseConfig; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseNodeSelector; +import com.clickhouse.client.ClickHouseProtocol; +import com.clickhouse.client.ClickHouseResponse; +import com.clickhouse.client.ClickHouseSocketFactory; +import com.clickhouse.client.ClickHouseSslContextProvider; +import com.clickhouse.client.config.ClickHouseClientOption; +import com.clickhouse.client.config.ClickHouseDefaults; +import com.clickhouse.client.config.ClickHouseSslMode; +import com.clickhouse.config.ClickHouseOption; +import com.clickhouse.data.ClickHouseFormat; +import com.clickhouse.data.ClickHouseRecord; +import com.clickhouse.data.ClickHouseUtils; +import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.ssl.SSLContexts; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.MountableFile; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import wiremock.Run; + +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.IOException; +import java.net.Socket; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Map; + +/** + * This tests is only for development and manual testing + */ +@Test(groups = {"integration"}) +public class NetworkTests { + private static final String NGINX_IMAGE = "nginx:alpine"; + private static final int NGINX_SSL_PORT = 8443; + private static final String NODE1_HOST = "node1.test"; + private static final String NODE2_HOST = "node2.test"; + + private GenericContainer nginxContainer; + + static { + System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "DEBUG"); + } + + @BeforeClass + public void setUp() throws Exception { + + // Create Nginx container with custom configuration + nginxContainer = new GenericContainer<>(NGINX_IMAGE) + .withCopyFileToContainer( + MountableFile.forClasspathResource("nginx.conf"), + "/etc/nginx/nginx.conf" + ) + .withCopyFileToContainer( + MountableFile.forClasspathResource("certs"), + "/etc/nginx/certs/" + ) + .withExposedPorts(NGINX_SSL_PORT) + .waitingFor(Wait.forListeningPort()); + + nginxContainer.start(); + } + + @AfterClass + public void tearDown() { + if (nginxContainer != null) { + nginxContainer.stop(); + } + } + + private String getNginxHost() { + return nginxContainer.getHost(); + } + + private int getNginxPort() { + return nginxContainer.getMappedPort(NGINX_SSL_PORT); + } + + @Test + public void testSNI() { + // Test will be implemented here + String host = getNginxHost(); + int port = getNginxPort(); + System.out.println("Nginx container running at: " + host + ":" + port); + } + + @Test(dataProvider = "testSNINodesDP") + void testSNINodes(String host) throws Exception { + int port = nginxContainer.getMappedPort(NGINX_SSL_PORT); + SSLSocket socket = createSniSocket(host, "localhost", port); + socket.startHandshake(); + + SSLSession session = socket.getSession(); + X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0]; + Assert.assertTrue(cert.getSubjectX500Principal().getName().contains("CN=" + host)); + } + + @DataProvider + static Object[][] testSNINodesDP() { + return new Object[][]{ + {NODE1_HOST}, + {NODE2_HOST} + }; + } + + private SSLSocket createSniSocket(String sniHost, String serverHost, int port) throws Exception { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new TrustManager[]{new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }}, null); + + SSLSocketFactory factory = context.getSocketFactory(); + SSLSocket socket = (SSLSocket) factory.createSocket(serverHost, port); + + SSLParameters sslParams = socket.getSSLParameters(); + sslParams.setServerNames(Collections.singletonList(new SNIHostName(sniHost))); + socket.setSSLParameters(sslParams); + + return socket; + } + + @Test(groups = {"integration"}) + void testClientConfiguration() throws Exception { + ClickHouseNode gateway = ClickHouseNode.of("https://" + getNginxHost() + ":" + getNginxPort() + "/?sslmode=none"); + try (ClickHouseClient client = ClickHouseClient.builder() + .option(ClickHouseDefaults.USER, "default") + .option(ClickHouseDefaults.PASSWORD, "") + .option(ClickHouseClientOption.SSL_MODE, ClickHouseSslMode.NONE) + .option(ClickHouseClientOption.COMPRESS, false) /// we emulate servers so need plain text + .option(ClickHouseClientOption.SSL_SNI_MAPPING, "127.0.1.1=nodeX.test,localhost=node2.test") + .nodeSelector(ClickHouseNodeSelector.of(ClickHouseProtocol.HTTP)) + .build()) { + + + + try (ClickHouseResponse response = client.read(gateway) + .format(ClickHouseFormat.TabSeparated) + .query("SELECT hostname()").executeAndWait()) { +// ClickHouseRecord record = response.firstRecord(); + String serverHostname = response.firstRecord().getValue(0).asString(); + Assert.assertEquals(serverHostname, "node2.test"); + }; + } + } + + + @Test(groups = {"integration"}) + void testCustomSSLConnectionFactory() throws Exception { + ClickHouseNode gateway = ClickHouseNode.of("https://" + getNginxHost() + ":" + getNginxPort() + "/?sslmode=none"); + try (ClickHouseClient client = ClickHouseClient.builder() + .option(ClickHouseDefaults.USER, "default") + .option(ClickHouseDefaults.PASSWORD, "") + .option(ClickHouseClientOption.CUSTOM_SOCKET_FACTORY_OPTIONS, "default_sni=node1.sni") + .option(ClickHouseClientOption.CUSTOM_SOCKET_FACTORY, SNIAwareSSLSocketFactory.class.getName()) + .option(ClickHouseClientOption.SSL_MODE, ClickHouseSslMode.NONE) + .option(ClickHouseClientOption.COMPRESS, false) /// we emulate servers so need plain text + + .nodeSelector(ClickHouseNodeSelector.of(ClickHouseProtocol.HTTP)) + .build()) { + + + + try (ClickHouseResponse response = client.read(gateway) + .format(ClickHouseFormat.TabSeparated) + .query("SELECT hostname()").executeAndWait()) { +// ClickHouseRecord record = response.firstRecord(); + String serverHostname = response.firstRecord().getValue(0).asString(); + Assert.assertEquals(serverHostname, "node2.test"); + }; + } + } + + public static class SNIAwareSSLSocketFactory implements ClickHouseSocketFactory { + + @Override + public T create(ClickHouseConfig config, Class clazz) throws IOException, UnsupportedOperationException { + if (config == null || clazz == null) { + throw new IllegalArgumentException("Non-null configuration and class are required"); + } else if (SSLConnectionSocketFactory.class.equals(clazz)) { + return clazz.cast(new CustomSSLSocketFactory(config)); + } else if (PlainConnectionSocketFactory.class.equals(clazz)) { + return clazz.cast(new CustomPlainSocketFactory(config)); + } + + throw new UnsupportedOperationException(ClickHouseUtils.format("Class %s is not supported", clazz)); + } + + @Override + public boolean supports(Class clazz) { + return PlainConnectionSocketFactory.class.equals(clazz) || SSLConnectionSocketFactory.class.equals(clazz); + } + } + + static class CustomPlainSocketFactory extends PlainConnectionSocketFactory { + private final ClickHouseConfig config; + + public CustomPlainSocketFactory(ClickHouseConfig config) { + this.config = config; + } + + @Override + public Socket createSocket(final HttpContext context) throws IOException { + // Use AbstractSockerClient.setSockerOptions to propagate socket configuration + return AbstractSocketClient.setSocketOptions(config, new Socket()); + } + } + + static class CustomSSLSocketFactory extends SSLConnectionSocketFactory { + private static final Logger LOG = LoggerFactory.getLogger(CustomSSLSocketFactory.class); + private final ClickHouseConfig config; + + private final SNIHostName defaultSNIHostName; + + public CustomSSLSocketFactory(ClickHouseConfig config) throws SSLException { + super(ClickHouseSslContextProvider.getProvider().getSslContext(SSLContext.class, config) + .orElse(SSLContexts.createDefault()), + config.getSslMode() == ClickHouseSslMode.STRICT + ? new DefaultHostnameVerifier() + : (hostname, session) -> true); // NOSONAR + this.config = config; + String configStr = config.getStrOption(ClickHouseClientOption.CUSTOM_SOCKET_FACTORY_OPTIONS); + if (configStr != null) { + Map configMap = ClickHouseOption.toKeyValuePairs(configStr); + defaultSNIHostName = new SNIHostName(configMap.get("default_sni")); + } else { + throw new RuntimeException("Missing configuration for the factory"); + } + } + + @Override + protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException { + LOG.debug("Preparing socket: {}", socket); + LOG.debug("Remote address: {}", socket.getInetAddress()); + SSLParameters sslParams = socket.getSSLParameters(); + sslParams.setServerNames(Collections.singletonList(defaultSNIHostName)); + socket.setSSLParameters(sslParams); + } + } +} diff --git a/clickhouse-http-client/src/test/resources/certs/node1.crt b/clickhouse-http-client/src/test/resources/certs/node1.crt new file mode 100644 index 000000000..8956880a4 --- /dev/null +++ b/clickhouse-http-client/src/test/resources/certs/node1.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIjCCAgqgAwIBAgIUPgsC1txpblBP8ybZlyOE/iwm3GAwDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKbm9kZTEudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 +MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUxLnRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC16EcQY0+5s2sF4WDFcQgExmuIDn3v8gxqklv4BTXV +1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75gnm7nDd/5yCEbuhOGjHZcKWLx0ff7 +MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJaHyDQRbK3Uu896K3wm5YC8FArj83 +XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9TrhyxMWaviFVHk9Lvvj8ajrJQ6nyFNOYL +hTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93XbrGFu6bstL0V6MeAFV7u+oE6uaJpzw +23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4uxNztVhtAgMBAAGjajBoMB0GA1Ud +DgQWBBSFVEPrB2BgUiQ2VuyBbpcASR+HujAfBgNVHSMEGDAWgBSFVEPrB2BgUiQ2 +VuyBbpcASR+HujAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUxLnRl +c3QwDQYJKoZIhvcNAQELBQADggEBACDcZV0fYLyWHcY2lRRp+XfCC9pku2QCLKtb +UYF2sAzMm/ND5U6MFu5BB9EcveFVDDe00DgdkSTC/7LgljhZvTLFxaqmcHO6bz9N +woh1sljzlxDWymPCFg812lTt+KF6dvRswVYVY8ZkF9mrf0oVTYFNeX29G65/GlWQ +ykN8VvjtnMZpphJYTaMNzOsAvEWLxSppVYYIhNqjwIifJjiWGptqtmeycQrInJp0 +5KgecVqEffxm1NcZyTlcoDvcm2ayQblpq17rcQ6H5Y1LF9vVffA1UwAgLPQmSNO2 +5hbfGo2ySmRzBv5goYdhR4/KVj+IXL0ASgzp6lHh+UhSLFeTFWY= +-----END CERTIFICATE----- diff --git a/clickhouse-http-client/src/test/resources/certs/node1.key b/clickhouse-http-client/src/test/resources/certs/node1.key new file mode 100644 index 000000000..b385eac9c --- /dev/null +++ b/clickhouse-http-client/src/test/resources/certs/node1.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC16EcQY0+5s2sF +4WDFcQgExmuIDn3v8gxqklv4BTXV1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75g +nm7nDd/5yCEbuhOGjHZcKWLx0ff7MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJ +aHyDQRbK3Uu896K3wm5YC8FArj83XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9Trhyx +MWaviFVHk9Lvvj8ajrJQ6nyFNOYLhTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93Xbr +GFu6bstL0V6MeAFV7u+oE6uaJpzw23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4 +uxNztVhtAgMBAAECggEADi6hV7w+a357AhbbuhkBZInpuU5T8EChSi/F/tS9KUIj +453JEJt6evgFJXgmydFgLjjXGPdwcMQUSCkiAV6aDznUpvo/anaKBc5uOedCW7GY +/lu47DBYhHf8Yt3w8NuHevymaQVETWspunNsfbk14zQUoxN1erksbC3ib74GDLU8 +U6c08y1A+xFgybIUqTwyKLjBk6rUSwcXBwkmVPCvpoeY1b7jpDK71uMQsuA9Y0Fn +Ks7i5n3868nUe46SwR3aaZMz1ca1UGsQNTX1ZXr6rYB6PbsOBi7GCPaUKKBmzhtZ +pvQBRDESXvTgZ3uL1NUxgBYlpunTdyu8sCKhnoTZsQKBgQD/Aopz/CmB7Jm7Yr81 +Jw1fOUMCQlzVDFkXRgbGqDruQhvrgx1eNVhIUJGX1VffW1xFyut6G9h8eBQ5Na/g +XQC9Ea/B4HeovhIF1/n0owhBfVmBMWf5GOngguXPDVScehZY/58t2CM4C4wBs7hP +whJlPss40Ftox5X63ZRVO/CECQKBgQC2nRQm+K0qR8sEkWfj4iviWpAZjCmCkFAI +fl89WL5UWObwQBNrRlfOQDuncHYgfcRitxnyqqsfr4DbmkSGh+XBmSIAVqUx+b6i +VkXtq6LA/uJ/BuALyKQmR+UH9C3kyCEOkqialy1pcfQF5ZnDT7/CoPtaheknR5kj +6BMKN1gyRQKBgQCRNhovN81PDbLw8KcfFlDYA5xzweRo8TIePaMIJq4AFKcfcjcb ++VZ/P1nY0wTJzJV7rLRwgUDCiAJEHZ08LtmH0HV7+l1JOoq6xySvuNKvsDhyh/bD +8vri/MbuI5Il1KO7JDy5d1V+yZx9L6Dp0gJ1os9IV+VlghmWk+yuqIYqOQKBgQCP +55bi4yKq5+p/jgpdlXTZql5WE5L+lbTMDLIGyPErzOcoOoZTChrVqN6Zo1EjZ5ij +5gCnr4CstoQICjin/126Q2987sq9aD6m9O+kTJY0GmfojEVwY8ufcxZ0PyMPX/pU +3pPJRwr9RlRzdSzHsge+W1fYTSCf2JdAqXifm1+ANQKBgBVU0Z8Eeeab7RslPiDJ +kPd1okVJMMSI1jLANsla4WWL93lHDTQ/c6yhO6as3j4f4Bp13k6vtWypolnhCDWc +OTNPZYHPD9qQYevQlpFK2pERIEWH5BgeYLCy2YLWlKpUprHGyOUW3Ji9Gukha3pk +KVkxTzYFuXegiqZyKWFimVrs +-----END PRIVATE KEY----- diff --git a/clickhouse-http-client/src/test/resources/certs/node2.crt b/clickhouse-http-client/src/test/resources/certs/node2.crt new file mode 100644 index 000000000..3d495d448 --- /dev/null +++ b/clickhouse-http-client/src/test/resources/certs/node2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIjCCAgqgAwIBAgIUbBm4zP62LDBki+SOV7K/a3RGoQQwDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKbm9kZTIudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 +MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUyLnRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDXZDmSiRTjmVNMOF3p58Fg34+SvCoohYO0pB9LaY5d +H/iPHssTm/spD15sGMTwmnFKNgLKOCHCDgltTPJTCiVteddh944K+9drxnfck9Eb +FQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT4Hq0Tj37awIEPYcT2gjMrbsbXjAZ +nyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6tjJWbfgWy1DsdPuTadJAAaaa35CA +CgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2eaYx9Y8K3MqM4KouMeykO9CX04+3pg +bDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh8CRz2dmPAgMBAAGjajBoMB0GA1Ud +DgQWBBQAsXC8lOD5OjHgmNpWYYbAjI+1EjAfBgNVHSMEGDAWgBQAsXC8lOD5OjHg +mNpWYYbAjI+1EjAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUyLnRl +c3QwDQYJKoZIhvcNAQELBQADggEBAKzBUiof/5yrmebVf+Arrur2/96QnEQJkGQm +1RwcClGOwhUtdbxcdoXgYqOLtQSiTGNAKcVyOKATOz6tc7qU5BkIga9d2FSGSV9c +6gU/Ij9+Y2AAHpElnHBGkjiuBAugv5Tt4NxnJXss5raML9/ATi0y4wcRSAeSCnDy +wFX66SEoWVkQcti7/TrxJRgstubWplTPtGQK/lcgWFNIKJDHft0nlXNsUK0CrfLq +wdWtmhWuadg66JuKvJABVtJIr2RQ5rYVWPyuS3sbSVIA9CB9dPjb/3/lSwmdSBbL +0WesfGryPK4aZXFlUEzzr/P/euwHhss0KUYjIpdGNQ7sBdwcFKg= +-----END CERTIFICATE----- diff --git a/clickhouse-http-client/src/test/resources/certs/node2.key b/clickhouse-http-client/src/test/resources/certs/node2.key new file mode 100644 index 000000000..14115f3d1 --- /dev/null +++ b/clickhouse-http-client/src/test/resources/certs/node2.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDXZDmSiRTjmVNM +OF3p58Fg34+SvCoohYO0pB9LaY5dH/iPHssTm/spD15sGMTwmnFKNgLKOCHCDglt +TPJTCiVteddh944K+9drxnfck9EbFQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT +4Hq0Tj37awIEPYcT2gjMrbsbXjAZnyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6 +tjJWbfgWy1DsdPuTadJAAaaa35CACgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2ea +Yx9Y8K3MqM4KouMeykO9CX04+3pgbDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh +8CRz2dmPAgMBAAECggEAUvRaSgn6nheooaPZ46Kq8xzXBlvq3rVhPrqxBNHQDySn +jbN94f0Ck9ND+dzmSIYq2LqJg4lv0vWmAs8OSxYOySWSYrw9nBRXaL5E3pKdh9Ek +fBSUWdlBbJngv94Es0SZk33wQzh1ls2lBl7f0z6JF6IkYi6yLd6c4fuNIuK5IrxX +0ahgqfiHrNizm8rfnIuHVQh/wBQk3cseXFMSnq5Y9/ZaFzqVKKauhLr8vFYRHK0Y +UCcLFCMZxvOpdVlOa8Ej4kZWQNd5yuvmEaaTcFgxkADmHF53jFjNVYluT16KQeVr +hzylv+rdrXgTpSb+uXmYzff1JTC7i8Jk9NQ5CaVCmQKBgQD3Gdn7rRU85aNkwaA5 +k2ykOP0ixQyLvYA0h8ajlidbxbfFm1IbrDmcOGgDIRwkwpD8GfZoKc41cjgaP9Fq +tWjdXNJaf0ogTWT9mcJFX1KGLU0yk3c8VPgtbhYf67u08H05ZF2O9TszBdxqy7lb +cfMIgiQXJ9gn86HMdL+t1hrZSQKBgQDfJgcZHHTCSW1o+wwWnPNrGjM5/vxUw293 +6d93m8nsUnZtYsRfqOpZKKyvuCbFhLlF5QkfpR19WUbCfqSRd2Ibq03CTN8MBa+0 +7yleHyDTYe48OGXyKzW0SdyM9T6oy7C0Fx1s4mB2Pa/iEovyZThxBPf3tINiU7QI +aMgo8mG0FwKBgQCTMVwPNpBDIUvOliSah34c6TGpB5Ysm6qGICcshSJW4ZVR6e7k +OxhqPZN/4bZBE4GIGvMUI2sJnUtcH43gscWQr11CuTZvvYqeInx+FIU1FkE5Emzh +jH/1l/En/KVo6CTADlBI+z3Ta/dbypVUqrEp7VQQUxTI37EO+LDBnZuKuQKBgQCo +2MhkKtD6Nb8wwUzbS3UwZEnAp3zc0mhkzrUFdv7p71em2yYFz/VK82j/KRNFP3FF +PtGRfUl8EHJnNpZlb3TvxMX4sFvRZ/gUqyadDmazK5WtQWt6O1HOkljs26DG7iFN +k3PzcLNWpMSSBkCYcBiuA/lxF8iWPQmrl0VlHknpjwKBgQCEPI+tuaw0fHsiHAK2 +w8rTB8KO5Qh7p5d1BSGmefM2HfJGialT5cCKABQX/kK4TxtLX7Pi1LPhJuGuJoi+ +AAwlznz2O1PUPCnBEc8mB/8p8ZrY89q0+xaqoyW1hnsNi20nXe6eHXlGu+J3zLlX +rEDrZRwY7ldkYFWh467fqFEl1w== +-----END PRIVATE KEY----- diff --git a/clickhouse-http-client/src/test/resources/nginx.conf b/clickhouse-http-client/src/test/resources/nginx.conf new file mode 100644 index 000000000..fe8bff763 --- /dev/null +++ b/clickhouse-http-client/src/test/resources/nginx.conf @@ -0,0 +1,53 @@ +# nginx.conf +events {} + +http { + server { + listen 8443 ssl; + server_name node1.test; + + ssl_certificate /etc/nginx/certs/node1.crt; + ssl_certificate_key /etc/nginx/certs/node1.key; + + location = /default { + if ($request_method != POST) { + return 405; + } + + if ($arg_query = "SELECT hostName()") { + return 200 "node1.test\n"; + } + + return 400 "Unsupported query\n"; + } + + location / { + return 200 "node1.test served\n"; + } + } + + server { + listen 8443 ssl; + server_name node2.test; + + ssl_certificate /etc/nginx/certs/node2.crt; + ssl_certificate_key /etc/nginx/certs/node2.key; + + location = /default { + if ($request_method != POST) { + return 405; + } + + if ($arg_query = "SELECT hostName()") { + return 200 "node2.test\n"; + } + + return 400 "Unsupported query\n"; + } + + location / { + return 200 "node2.test served\n"; + } + } +} + 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 06e0a9153..f9d7123a2 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 @@ -5,8 +5,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.stream.Collectors; /** @@ -140,6 +142,11 @@ public enum ClientConfigProperties { * Name of the group under which client metrics appear */ METRICS_GROUP_NAME("metrics_name"), + + /** + * Comma separated key-value pairs of IP address/host to SNI mapping. Special mapping {@code _default_} - for default SNI when no match found. Without default mapping only matched targets will have SNI parameter. + */ + SSL_SNI_MAPPING("ssl_sni_mapping", ""), ; private final String key; @@ -179,6 +186,9 @@ public String getDefaultValue() { public static final String SERVER_SETTING_PREFIX = "clickhouse_setting_"; + // Key used to identify default value in configuration map + public static final String DEFAULT_KEY = "_default_"; + public static String serverSetting(String key) { return SERVER_SETTING_PREFIX + key; } @@ -207,4 +217,55 @@ public static List valuesFromCommaSeparated(String value) { return Arrays.stream(value.split("(? s.replaceAll("\\\\,", ",")) .collect(Collectors.toList()); } + + /** + * Converts given string to key value pairs. + * + * @param str string + * @return non-null key value pairs + */ + public static Map toKeyValuePairs(String str) { + if (str == null || str.isEmpty()) { + return Collections.emptyMap(); + } + + Map map = new LinkedHashMap<>(); + String key = null; + StringBuilder builder = new StringBuilder(); + for (int i = 0, len = str.length(); i < len; i++) { + char ch = str.charAt(i); + if (ch == '\\' && i + 1 < len) { + ch = str.charAt(++i); + builder.append(ch); + continue; + } + + if (Character.isWhitespace(ch)) { + if (builder.length() > 0) { + builder.append(ch); + } + } else if (ch == '=' && key == null) { + key = builder.toString().trim(); + builder.setLength(0); + } else if (ch == ',' && key != null) { + String value = builder.toString().trim(); + builder.setLength(0); + if (!key.isEmpty() && !value.isEmpty()) { + map.put(key, value); + } + key = null; + } else { + builder.append(ch); + } + } + + if (key != null && builder.length() > 0) { + String value = builder.toString().trim(); + if (!key.isEmpty() && !value.isEmpty()) { + map.put(key, value); + } + } + + return Collections.unmodifiableMap(map); + } } 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 970a18f03..94b48efaf 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 @@ -13,6 +13,8 @@ import com.clickhouse.client.api.enums.ProxyType; import com.clickhouse.client.api.http.ClickHouseHttpProto; import com.clickhouse.client.api.transport.Endpoint; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; import net.jpountz.lz4.LZ4Factory; import org.apache.hc.client5.http.ConnectTimeoutException; import org.apache.hc.client5.http.classic.methods.HttpPost; @@ -58,13 +60,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.net.ConnectException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NoRouteToHostException; import java.net.Socket; @@ -256,8 +263,18 @@ public CloseableHttpClient createHttpClient(boolean initSslContext) { // Top Level builders HttpClientBuilder clientBuilder = HttpClientBuilder.create(); SSLContext sslContext = initSslContext ? createSSLContext() : null; - LayeredConnectionSocketFactory sslConnectionSocketFactory = sslContext == null ? new DummySSLConnectionSocketFactory() - : new SSLConnectionSocketFactory(sslContext); + LayeredConnectionSocketFactory sslConnectionSocketFactory; + if (sslContext != null) { + String sniMappingStr = chConfiguration.get(ClientConfigProperties.SSL_SNI_MAPPING.getKey()); + if (sniMappingStr != null && !sniMappingStr.isEmpty()) { + sslConnectionSocketFactory = new CustomSSLConnectionFactory(ClientConfigProperties.toKeyValuePairs(sniMappingStr), sslContext, (hostname, session) -> true); + } else { + sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext); + } + } else { + sslConnectionSocketFactory = new DummySSLConnectionSocketFactory(); + } + // Socket configuration SocketConfig.Builder soCfgBuilder = SocketConfig.custom(); MapUtils.applyInt(chConfiguration, ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey(), @@ -827,4 +844,41 @@ public long getTime() { return count > 0 ? runningAverage / count : 0; } } + + public static class CustomSSLConnectionFactory extends SSLConnectionSocketFactory { + + private final Map sniMapping; + private final String defaultSNI; + + public CustomSSLConnectionFactory(Map sniMapping, SSLContext sslContext, HostnameVerifier hostnameVerifier) { + super(sslContext, hostnameVerifier); + this.sniMapping = ImmutableMap.copyOf(sniMapping); + this.defaultSNI = sniMapping.get("_default_"); + } + + @Override + protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException { + super.prepareSocket(socket, context); + + if (!sniMapping.isEmpty()) { + InetAddress remote = socket.getInetAddress(); + if (remote != null) { // actually should be not null here + String sni = sniMapping.get(remote.getHostAddress()); + if (sni == null) { + sni = sniMapping.get(remote.getHostName()); + if (sni == null) { + sni = defaultSNI; + } + } + if (sni != null && !sni.isEmpty()) { + SSLParameters sslParams = socket.getSSLParameters(); + sslParams.setServerNames(Collections.singletonList(new SNIHostName(sni))); + socket.setSSLParameters(sslParams); + } + } else { + LOG.warn("Failed to apply SNI - remote address is null"); + } + } + } + } } diff --git a/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java b/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java index eb3931994..92060ecb9 100644 --- a/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java @@ -93,9 +93,14 @@ static Object[][] testSNINodesDP() { private SSLSocket createSniSocket(String sniHost, String serverHost, int port) throws Exception { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, new TrustManager[] { new X509TrustManager() { - public void checkClientTrusted(X509Certificate[] chain, String authType) {} - public void checkServerTrusted(X509Certificate[] chain, String authType) {} - public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + public void checkServerTrusted(X509Certificate[] chain, String authType) { + + } + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } } }, null); SSLSocketFactory factory = context.getSocketFactory(); diff --git a/client-v2/src/test/resources/certs/README.md b/client-v2/src/test/resources/certs/README.md new file mode 100644 index 000000000..fe3b9e60e --- /dev/null +++ b/client-v2/src/test/resources/certs/README.md @@ -0,0 +1,41 @@ + + + +## How to work with self-signed certificates + +1. Create a self-signed Root CA. This should be used for signing node certificates and adding to a truststore +2. Create node self-signed certificates +3. Sign node certificates with root CA + +Create root key for CA cert +```shell +openssl genrsa -out rootCA.key 4096 +``` + +Create root CA +```shell +openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 65535 -out rootCA.crt +``` + + +Create node certificates +```shell + +openssl req -x509 -nodes -days 65536 -newkey rsa:2048 -keyout node1.key -out node1.crt -subj "/CN=node1.test" -addext "subjectAltName=DNS:node1.test" + +openssl req -x509 -nodes -days 65536 -newkey rsa:2048 -keyout node2.key -out node2.crt -subj "/CN=node2.test" -addext "subjectAltName=DNS:node2.test" +``` + +```shell +openssl x509 -req -in node1.crt -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out node1-signed.crt -days 65536 -sha256 + +openssl x509 -req -in node2.crt -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out node2-signed.crt -days 65536 -sha256 +``` + + + +### Useful commands + +```shell +keytool -printcert -file rootCA.crt # read content of the certificate +``` \ No newline at end of file From 0fbe95ce08eba0f4035c6abc459c795b69344242 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 26 Jun 2025 22:53:55 -0700 Subject: [PATCH 4/5] fixed comments for pr --- client-v2/pom.xml | 1 + .../clickhouse/client/api/internal/HttpAPIClientHelper.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client-v2/pom.xml b/client-v2/pom.xml index 5bf2d3e02..7e64b8a8a 100644 --- a/client-v2/pom.xml +++ b/client-v2/pom.xml @@ -133,6 +133,7 @@ org.slf4j slf4j-simple 2.0.16 + test 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 94b48efaf..38f8fedf4 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 @@ -14,7 +14,6 @@ import com.clickhouse.client.api.http.ClickHouseHttpProto; import com.clickhouse.client.api.transport.Endpoint; import com.google.common.collect.ImmutableMap; -import com.google.errorprone.annotations.Immutable; import net.jpountz.lz4.LZ4Factory; import org.apache.hc.client5.http.ConnectTimeoutException; import org.apache.hc.client5.http.classic.methods.HttpPost; @@ -853,7 +852,7 @@ public static class CustomSSLConnectionFactory extends SSLConnectionSocketFactor public CustomSSLConnectionFactory(Map sniMapping, SSLContext sslContext, HostnameVerifier hostnameVerifier) { super(sslContext, hostnameVerifier); this.sniMapping = ImmutableMap.copyOf(sniMapping); - this.defaultSNI = sniMapping.get("_default_"); + this.defaultSNI = sniMapping.get(ClientConfigProperties.DEFAULT_KEY); } @Override From 07808d6588202a5cce3e39457f9a12173d91e374 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 8 Jul 2025 14:20:41 -0700 Subject: [PATCH 5/5] made SNI a single string. added setter for client builder. removed unneeded files --- .../client/config/ClickHouseClientOption.java | 5 +- .../client/http/ApacheHttpConnectionImpl.java | 31 +- .../clickhouse/client/http/NetworkTests.java | 269 ------------------ .../src/test/resources/certs/node1.crt | 19 -- .../src/test/resources/certs/node1.key | 28 -- .../src/test/resources/certs/node2.crt | 19 -- .../src/test/resources/certs/node2.key | 28 -- .../src/test/resources/nginx.conf | 53 ---- .../com/clickhouse/client/api/Client.java | 13 + .../client/api/ClientConfigProperties.java | 9 +- .../api/internal/HttpAPIClientHelper.java | 38 +-- .../com/clickhouse/client/ClientTests.java | 6 +- .../clickhouse/client/HttpTransportTests.java | 18 ++ .../com/clickhouse/client/NetworkTests.java | 115 -------- client-v2/src/test/resources/certs/README.md | 41 --- client-v2/src/test/resources/certs/node1.crt | 19 -- client-v2/src/test/resources/certs/node1.key | 28 -- client-v2/src/test/resources/certs/node2.crt | 19 -- client-v2/src/test/resources/certs/node2.key | 28 -- client-v2/src/test/resources/nginx.conf | 53 ---- 20 files changed, 57 insertions(+), 782 deletions(-) delete mode 100644 clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java delete mode 100644 clickhouse-http-client/src/test/resources/certs/node1.crt delete mode 100644 clickhouse-http-client/src/test/resources/certs/node1.key delete mode 100644 clickhouse-http-client/src/test/resources/certs/node2.crt delete mode 100644 clickhouse-http-client/src/test/resources/certs/node2.key delete mode 100644 clickhouse-http-client/src/test/resources/nginx.conf delete mode 100644 client-v2/src/test/java/com/clickhouse/client/NetworkTests.java delete mode 100644 client-v2/src/test/resources/certs/README.md delete mode 100644 client-v2/src/test/resources/certs/node1.crt delete mode 100644 client-v2/src/test/resources/certs/node1.key delete mode 100644 client-v2/src/test/resources/certs/node2.crt delete mode 100644 client-v2/src/test/resources/certs/node2.key delete mode 100644 client-v2/src/test/resources/nginx.conf diff --git a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java index c12d9eb46..1f410f2ab 100644 --- a/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java +++ b/clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java @@ -454,10 +454,9 @@ public enum ClickHouseClientOption implements ClickHouseOption { MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode."), /** - * Comma separated key-value pairs of IP address/host to SNI mapping. - * Special mapping {@code _default_} - for default SNI when no match found. Without default mapping only matched targets will have SNI parameter. + * SNI SSL parameter that will be set for each outbound SSL socket. */ - SSL_SNI_MAPPING("ssl_sni_map", "", "Comma separated key-value pairs of IP address/host to SNI mapping. Special mapping _default_ - for default SNI when no match found. Without default mapping only matched targets will have SNI parameter.") + SSL_SOCKET_SNI("ssl_socket_sni", "", " SNI SSL parameter that will be set for each outbound SSL socket.") ; diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java index 092487777..0c00f7015 100644 --- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java +++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java @@ -397,8 +397,7 @@ public static SocketFactory create(ClickHouseConfig config) { static class SSLSocketFactory extends SSLConnectionSocketFactory { private final ClickHouseConfig config; - private final Map sniMapping; - private final String defaultSNI; + private final SNIHostName defaultSNI; private SSLSocketFactory(ClickHouseConfig config) throws SSLException { super(ClickHouseSslContextProvider.getProvider().getSslContext(SSLContext.class, config) @@ -407,9 +406,8 @@ private SSLSocketFactory(ClickHouseConfig config) throws SSLException { ? new DefaultHostnameVerifier() : (hostname, session) -> true); // NOSONAR this.config = config; - String sniMappingStr = config.getStrOption(ClickHouseClientOption.SSL_SNI_MAPPING); - sniMapping = ClickHouseOption.toKeyValuePairs(sniMappingStr); - defaultSNI = sniMapping.get("_default_"); + String sni = config.getStrOption(ClickHouseClientOption.SSL_SOCKET_SNI); + defaultSNI = sni == null || sni.trim().isEmpty() ? null : new SNIHostName(sni); } @Override @@ -420,25 +418,10 @@ public Socket createSocket(HttpContext context) throws IOException { @Override protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException { super.prepareSocket(socket, context); - - if (!sniMapping.isEmpty()) { - InetAddress remote = socket.getInetAddress(); - if (remote != null) { // actually should be not null here - String sni = sniMapping.get(remote.getHostAddress()); - if (sni == null) { - sni = sniMapping.get(remote.getHostName()); - if (sni == null) { - sni = defaultSNI; - } - } - if (sni != null && !sni.isEmpty()) { - SSLParameters sslParams = socket.getSSLParameters(); - sslParams.setServerNames(Collections.singletonList(new SNIHostName(sni))); - socket.setSSLParameters(sslParams); - } - } else { - log.warn("Failed to apply SNI - remote address is null"); - } + if (defaultSNI != null) { + SSLParameters sslParams = socket.getSSLParameters(); + sslParams.setServerNames(Collections.singletonList(defaultSNI)); + socket.setSSLParameters(sslParams); } } diff --git a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java deleted file mode 100644 index 5130d9dcd..000000000 --- a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/NetworkTests.java +++ /dev/null @@ -1,269 +0,0 @@ -package com.clickhouse.client.http; - -import com.clickhouse.client.AbstractSocketClient; -import com.clickhouse.client.ClickHouseClient; -import com.clickhouse.client.ClickHouseConfig; -import com.clickhouse.client.ClickHouseNode; -import com.clickhouse.client.ClickHouseNodeSelector; -import com.clickhouse.client.ClickHouseProtocol; -import com.clickhouse.client.ClickHouseResponse; -import com.clickhouse.client.ClickHouseSocketFactory; -import com.clickhouse.client.ClickHouseSslContextProvider; -import com.clickhouse.client.config.ClickHouseClientOption; -import com.clickhouse.client.config.ClickHouseDefaults; -import com.clickhouse.client.config.ClickHouseSslMode; -import com.clickhouse.config.ClickHouseOption; -import com.clickhouse.data.ClickHouseFormat; -import com.clickhouse.data.ClickHouseRecord; -import com.clickhouse.data.ClickHouseUtils; -import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.ssl.SSLContexts; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.utility.MountableFile; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import wiremock.Run; - -import javax.net.ssl.SNIHostName; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import java.io.IOException; -import java.net.Socket; -import java.security.cert.X509Certificate; -import java.util.Collections; -import java.util.Map; - -/** - * This tests is only for development and manual testing - */ -@Test(groups = {"integration"}) -public class NetworkTests { - private static final String NGINX_IMAGE = "nginx:alpine"; - private static final int NGINX_SSL_PORT = 8443; - private static final String NODE1_HOST = "node1.test"; - private static final String NODE2_HOST = "node2.test"; - - private GenericContainer nginxContainer; - - static { - System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "DEBUG"); - } - - @BeforeClass - public void setUp() throws Exception { - - // Create Nginx container with custom configuration - nginxContainer = new GenericContainer<>(NGINX_IMAGE) - .withCopyFileToContainer( - MountableFile.forClasspathResource("nginx.conf"), - "/etc/nginx/nginx.conf" - ) - .withCopyFileToContainer( - MountableFile.forClasspathResource("certs"), - "/etc/nginx/certs/" - ) - .withExposedPorts(NGINX_SSL_PORT) - .waitingFor(Wait.forListeningPort()); - - nginxContainer.start(); - } - - @AfterClass - public void tearDown() { - if (nginxContainer != null) { - nginxContainer.stop(); - } - } - - private String getNginxHost() { - return nginxContainer.getHost(); - } - - private int getNginxPort() { - return nginxContainer.getMappedPort(NGINX_SSL_PORT); - } - - @Test - public void testSNI() { - // Test will be implemented here - String host = getNginxHost(); - int port = getNginxPort(); - System.out.println("Nginx container running at: " + host + ":" + port); - } - - @Test(dataProvider = "testSNINodesDP") - void testSNINodes(String host) throws Exception { - int port = nginxContainer.getMappedPort(NGINX_SSL_PORT); - SSLSocket socket = createSniSocket(host, "localhost", port); - socket.startHandshake(); - - SSLSession session = socket.getSession(); - X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0]; - Assert.assertTrue(cert.getSubjectX500Principal().getName().contains("CN=" + host)); - } - - @DataProvider - static Object[][] testSNINodesDP() { - return new Object[][]{ - {NODE1_HOST}, - {NODE2_HOST} - }; - } - - private SSLSocket createSniSocket(String sniHost, String serverHost, int port) throws Exception { - SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, new TrustManager[]{new X509TrustManager() { - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) { - } - - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - }}, null); - - SSLSocketFactory factory = context.getSocketFactory(); - SSLSocket socket = (SSLSocket) factory.createSocket(serverHost, port); - - SSLParameters sslParams = socket.getSSLParameters(); - sslParams.setServerNames(Collections.singletonList(new SNIHostName(sniHost))); - socket.setSSLParameters(sslParams); - - return socket; - } - - @Test(groups = {"integration"}) - void testClientConfiguration() throws Exception { - ClickHouseNode gateway = ClickHouseNode.of("https://" + getNginxHost() + ":" + getNginxPort() + "/?sslmode=none"); - try (ClickHouseClient client = ClickHouseClient.builder() - .option(ClickHouseDefaults.USER, "default") - .option(ClickHouseDefaults.PASSWORD, "") - .option(ClickHouseClientOption.SSL_MODE, ClickHouseSslMode.NONE) - .option(ClickHouseClientOption.COMPRESS, false) /// we emulate servers so need plain text - .option(ClickHouseClientOption.SSL_SNI_MAPPING, "127.0.1.1=nodeX.test,localhost=node2.test") - .nodeSelector(ClickHouseNodeSelector.of(ClickHouseProtocol.HTTP)) - .build()) { - - - - try (ClickHouseResponse response = client.read(gateway) - .format(ClickHouseFormat.TabSeparated) - .query("SELECT hostname()").executeAndWait()) { -// ClickHouseRecord record = response.firstRecord(); - String serverHostname = response.firstRecord().getValue(0).asString(); - Assert.assertEquals(serverHostname, "node2.test"); - }; - } - } - - - @Test(groups = {"integration"}) - void testCustomSSLConnectionFactory() throws Exception { - ClickHouseNode gateway = ClickHouseNode.of("https://" + getNginxHost() + ":" + getNginxPort() + "/?sslmode=none"); - try (ClickHouseClient client = ClickHouseClient.builder() - .option(ClickHouseDefaults.USER, "default") - .option(ClickHouseDefaults.PASSWORD, "") - .option(ClickHouseClientOption.CUSTOM_SOCKET_FACTORY_OPTIONS, "default_sni=node1.sni") - .option(ClickHouseClientOption.CUSTOM_SOCKET_FACTORY, SNIAwareSSLSocketFactory.class.getName()) - .option(ClickHouseClientOption.SSL_MODE, ClickHouseSslMode.NONE) - .option(ClickHouseClientOption.COMPRESS, false) /// we emulate servers so need plain text - - .nodeSelector(ClickHouseNodeSelector.of(ClickHouseProtocol.HTTP)) - .build()) { - - - - try (ClickHouseResponse response = client.read(gateway) - .format(ClickHouseFormat.TabSeparated) - .query("SELECT hostname()").executeAndWait()) { -// ClickHouseRecord record = response.firstRecord(); - String serverHostname = response.firstRecord().getValue(0).asString(); - Assert.assertEquals(serverHostname, "node2.test"); - }; - } - } - - public static class SNIAwareSSLSocketFactory implements ClickHouseSocketFactory { - - @Override - public T create(ClickHouseConfig config, Class clazz) throws IOException, UnsupportedOperationException { - if (config == null || clazz == null) { - throw new IllegalArgumentException("Non-null configuration and class are required"); - } else if (SSLConnectionSocketFactory.class.equals(clazz)) { - return clazz.cast(new CustomSSLSocketFactory(config)); - } else if (PlainConnectionSocketFactory.class.equals(clazz)) { - return clazz.cast(new CustomPlainSocketFactory(config)); - } - - throw new UnsupportedOperationException(ClickHouseUtils.format("Class %s is not supported", clazz)); - } - - @Override - public boolean supports(Class clazz) { - return PlainConnectionSocketFactory.class.equals(clazz) || SSLConnectionSocketFactory.class.equals(clazz); - } - } - - static class CustomPlainSocketFactory extends PlainConnectionSocketFactory { - private final ClickHouseConfig config; - - public CustomPlainSocketFactory(ClickHouseConfig config) { - this.config = config; - } - - @Override - public Socket createSocket(final HttpContext context) throws IOException { - // Use AbstractSockerClient.setSockerOptions to propagate socket configuration - return AbstractSocketClient.setSocketOptions(config, new Socket()); - } - } - - static class CustomSSLSocketFactory extends SSLConnectionSocketFactory { - private static final Logger LOG = LoggerFactory.getLogger(CustomSSLSocketFactory.class); - private final ClickHouseConfig config; - - private final SNIHostName defaultSNIHostName; - - public CustomSSLSocketFactory(ClickHouseConfig config) throws SSLException { - super(ClickHouseSslContextProvider.getProvider().getSslContext(SSLContext.class, config) - .orElse(SSLContexts.createDefault()), - config.getSslMode() == ClickHouseSslMode.STRICT - ? new DefaultHostnameVerifier() - : (hostname, session) -> true); // NOSONAR - this.config = config; - String configStr = config.getStrOption(ClickHouseClientOption.CUSTOM_SOCKET_FACTORY_OPTIONS); - if (configStr != null) { - Map configMap = ClickHouseOption.toKeyValuePairs(configStr); - defaultSNIHostName = new SNIHostName(configMap.get("default_sni")); - } else { - throw new RuntimeException("Missing configuration for the factory"); - } - } - - @Override - protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException { - LOG.debug("Preparing socket: {}", socket); - LOG.debug("Remote address: {}", socket.getInetAddress()); - SSLParameters sslParams = socket.getSSLParameters(); - sslParams.setServerNames(Collections.singletonList(defaultSNIHostName)); - socket.setSSLParameters(sslParams); - } - } -} diff --git a/clickhouse-http-client/src/test/resources/certs/node1.crt b/clickhouse-http-client/src/test/resources/certs/node1.crt deleted file mode 100644 index 8956880a4..000000000 --- a/clickhouse-http-client/src/test/resources/certs/node1.crt +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDIjCCAgqgAwIBAgIUPgsC1txpblBP8ybZlyOE/iwm3GAwDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKbm9kZTEudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 -MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUxLnRlc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQC16EcQY0+5s2sF4WDFcQgExmuIDn3v8gxqklv4BTXV -1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75gnm7nDd/5yCEbuhOGjHZcKWLx0ff7 -MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJaHyDQRbK3Uu896K3wm5YC8FArj83 -XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9TrhyxMWaviFVHk9Lvvj8ajrJQ6nyFNOYL -hTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93XbrGFu6bstL0V6MeAFV7u+oE6uaJpzw -23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4uxNztVhtAgMBAAGjajBoMB0GA1Ud -DgQWBBSFVEPrB2BgUiQ2VuyBbpcASR+HujAfBgNVHSMEGDAWgBSFVEPrB2BgUiQ2 -VuyBbpcASR+HujAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUxLnRl -c3QwDQYJKoZIhvcNAQELBQADggEBACDcZV0fYLyWHcY2lRRp+XfCC9pku2QCLKtb -UYF2sAzMm/ND5U6MFu5BB9EcveFVDDe00DgdkSTC/7LgljhZvTLFxaqmcHO6bz9N -woh1sljzlxDWymPCFg812lTt+KF6dvRswVYVY8ZkF9mrf0oVTYFNeX29G65/GlWQ -ykN8VvjtnMZpphJYTaMNzOsAvEWLxSppVYYIhNqjwIifJjiWGptqtmeycQrInJp0 -5KgecVqEffxm1NcZyTlcoDvcm2ayQblpq17rcQ6H5Y1LF9vVffA1UwAgLPQmSNO2 -5hbfGo2ySmRzBv5goYdhR4/KVj+IXL0ASgzp6lHh+UhSLFeTFWY= ------END CERTIFICATE----- diff --git a/clickhouse-http-client/src/test/resources/certs/node1.key b/clickhouse-http-client/src/test/resources/certs/node1.key deleted file mode 100644 index b385eac9c..000000000 --- a/clickhouse-http-client/src/test/resources/certs/node1.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC16EcQY0+5s2sF -4WDFcQgExmuIDn3v8gxqklv4BTXV1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75g -nm7nDd/5yCEbuhOGjHZcKWLx0ff7MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJ -aHyDQRbK3Uu896K3wm5YC8FArj83XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9Trhyx -MWaviFVHk9Lvvj8ajrJQ6nyFNOYLhTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93Xbr -GFu6bstL0V6MeAFV7u+oE6uaJpzw23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4 -uxNztVhtAgMBAAECggEADi6hV7w+a357AhbbuhkBZInpuU5T8EChSi/F/tS9KUIj -453JEJt6evgFJXgmydFgLjjXGPdwcMQUSCkiAV6aDznUpvo/anaKBc5uOedCW7GY -/lu47DBYhHf8Yt3w8NuHevymaQVETWspunNsfbk14zQUoxN1erksbC3ib74GDLU8 -U6c08y1A+xFgybIUqTwyKLjBk6rUSwcXBwkmVPCvpoeY1b7jpDK71uMQsuA9Y0Fn -Ks7i5n3868nUe46SwR3aaZMz1ca1UGsQNTX1ZXr6rYB6PbsOBi7GCPaUKKBmzhtZ -pvQBRDESXvTgZ3uL1NUxgBYlpunTdyu8sCKhnoTZsQKBgQD/Aopz/CmB7Jm7Yr81 -Jw1fOUMCQlzVDFkXRgbGqDruQhvrgx1eNVhIUJGX1VffW1xFyut6G9h8eBQ5Na/g -XQC9Ea/B4HeovhIF1/n0owhBfVmBMWf5GOngguXPDVScehZY/58t2CM4C4wBs7hP -whJlPss40Ftox5X63ZRVO/CECQKBgQC2nRQm+K0qR8sEkWfj4iviWpAZjCmCkFAI -fl89WL5UWObwQBNrRlfOQDuncHYgfcRitxnyqqsfr4DbmkSGh+XBmSIAVqUx+b6i -VkXtq6LA/uJ/BuALyKQmR+UH9C3kyCEOkqialy1pcfQF5ZnDT7/CoPtaheknR5kj -6BMKN1gyRQKBgQCRNhovN81PDbLw8KcfFlDYA5xzweRo8TIePaMIJq4AFKcfcjcb -+VZ/P1nY0wTJzJV7rLRwgUDCiAJEHZ08LtmH0HV7+l1JOoq6xySvuNKvsDhyh/bD -8vri/MbuI5Il1KO7JDy5d1V+yZx9L6Dp0gJ1os9IV+VlghmWk+yuqIYqOQKBgQCP -55bi4yKq5+p/jgpdlXTZql5WE5L+lbTMDLIGyPErzOcoOoZTChrVqN6Zo1EjZ5ij -5gCnr4CstoQICjin/126Q2987sq9aD6m9O+kTJY0GmfojEVwY8ufcxZ0PyMPX/pU -3pPJRwr9RlRzdSzHsge+W1fYTSCf2JdAqXifm1+ANQKBgBVU0Z8Eeeab7RslPiDJ -kPd1okVJMMSI1jLANsla4WWL93lHDTQ/c6yhO6as3j4f4Bp13k6vtWypolnhCDWc -OTNPZYHPD9qQYevQlpFK2pERIEWH5BgeYLCy2YLWlKpUprHGyOUW3Ji9Gukha3pk -KVkxTzYFuXegiqZyKWFimVrs ------END PRIVATE KEY----- diff --git a/clickhouse-http-client/src/test/resources/certs/node2.crt b/clickhouse-http-client/src/test/resources/certs/node2.crt deleted file mode 100644 index 3d495d448..000000000 --- a/clickhouse-http-client/src/test/resources/certs/node2.crt +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDIjCCAgqgAwIBAgIUbBm4zP62LDBki+SOV7K/a3RGoQQwDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKbm9kZTIudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 -MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUyLnRlc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDXZDmSiRTjmVNMOF3p58Fg34+SvCoohYO0pB9LaY5d -H/iPHssTm/spD15sGMTwmnFKNgLKOCHCDgltTPJTCiVteddh944K+9drxnfck9Eb -FQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT4Hq0Tj37awIEPYcT2gjMrbsbXjAZ -nyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6tjJWbfgWy1DsdPuTadJAAaaa35CA -CgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2eaYx9Y8K3MqM4KouMeykO9CX04+3pg -bDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh8CRz2dmPAgMBAAGjajBoMB0GA1Ud -DgQWBBQAsXC8lOD5OjHgmNpWYYbAjI+1EjAfBgNVHSMEGDAWgBQAsXC8lOD5OjHg -mNpWYYbAjI+1EjAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUyLnRl -c3QwDQYJKoZIhvcNAQELBQADggEBAKzBUiof/5yrmebVf+Arrur2/96QnEQJkGQm -1RwcClGOwhUtdbxcdoXgYqOLtQSiTGNAKcVyOKATOz6tc7qU5BkIga9d2FSGSV9c -6gU/Ij9+Y2AAHpElnHBGkjiuBAugv5Tt4NxnJXss5raML9/ATi0y4wcRSAeSCnDy -wFX66SEoWVkQcti7/TrxJRgstubWplTPtGQK/lcgWFNIKJDHft0nlXNsUK0CrfLq -wdWtmhWuadg66JuKvJABVtJIr2RQ5rYVWPyuS3sbSVIA9CB9dPjb/3/lSwmdSBbL -0WesfGryPK4aZXFlUEzzr/P/euwHhss0KUYjIpdGNQ7sBdwcFKg= ------END CERTIFICATE----- diff --git a/clickhouse-http-client/src/test/resources/certs/node2.key b/clickhouse-http-client/src/test/resources/certs/node2.key deleted file mode 100644 index 14115f3d1..000000000 --- a/clickhouse-http-client/src/test/resources/certs/node2.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDXZDmSiRTjmVNM -OF3p58Fg34+SvCoohYO0pB9LaY5dH/iPHssTm/spD15sGMTwmnFKNgLKOCHCDglt -TPJTCiVteddh944K+9drxnfck9EbFQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT -4Hq0Tj37awIEPYcT2gjMrbsbXjAZnyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6 -tjJWbfgWy1DsdPuTadJAAaaa35CACgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2ea -Yx9Y8K3MqM4KouMeykO9CX04+3pgbDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh -8CRz2dmPAgMBAAECggEAUvRaSgn6nheooaPZ46Kq8xzXBlvq3rVhPrqxBNHQDySn -jbN94f0Ck9ND+dzmSIYq2LqJg4lv0vWmAs8OSxYOySWSYrw9nBRXaL5E3pKdh9Ek -fBSUWdlBbJngv94Es0SZk33wQzh1ls2lBl7f0z6JF6IkYi6yLd6c4fuNIuK5IrxX -0ahgqfiHrNizm8rfnIuHVQh/wBQk3cseXFMSnq5Y9/ZaFzqVKKauhLr8vFYRHK0Y -UCcLFCMZxvOpdVlOa8Ej4kZWQNd5yuvmEaaTcFgxkADmHF53jFjNVYluT16KQeVr -hzylv+rdrXgTpSb+uXmYzff1JTC7i8Jk9NQ5CaVCmQKBgQD3Gdn7rRU85aNkwaA5 -k2ykOP0ixQyLvYA0h8ajlidbxbfFm1IbrDmcOGgDIRwkwpD8GfZoKc41cjgaP9Fq -tWjdXNJaf0ogTWT9mcJFX1KGLU0yk3c8VPgtbhYf67u08H05ZF2O9TszBdxqy7lb -cfMIgiQXJ9gn86HMdL+t1hrZSQKBgQDfJgcZHHTCSW1o+wwWnPNrGjM5/vxUw293 -6d93m8nsUnZtYsRfqOpZKKyvuCbFhLlF5QkfpR19WUbCfqSRd2Ibq03CTN8MBa+0 -7yleHyDTYe48OGXyKzW0SdyM9T6oy7C0Fx1s4mB2Pa/iEovyZThxBPf3tINiU7QI -aMgo8mG0FwKBgQCTMVwPNpBDIUvOliSah34c6TGpB5Ysm6qGICcshSJW4ZVR6e7k -OxhqPZN/4bZBE4GIGvMUI2sJnUtcH43gscWQr11CuTZvvYqeInx+FIU1FkE5Emzh -jH/1l/En/KVo6CTADlBI+z3Ta/dbypVUqrEp7VQQUxTI37EO+LDBnZuKuQKBgQCo -2MhkKtD6Nb8wwUzbS3UwZEnAp3zc0mhkzrUFdv7p71em2yYFz/VK82j/KRNFP3FF -PtGRfUl8EHJnNpZlb3TvxMX4sFvRZ/gUqyadDmazK5WtQWt6O1HOkljs26DG7iFN -k3PzcLNWpMSSBkCYcBiuA/lxF8iWPQmrl0VlHknpjwKBgQCEPI+tuaw0fHsiHAK2 -w8rTB8KO5Qh7p5d1BSGmefM2HfJGialT5cCKABQX/kK4TxtLX7Pi1LPhJuGuJoi+ -AAwlznz2O1PUPCnBEc8mB/8p8ZrY89q0+xaqoyW1hnsNi20nXe6eHXlGu+J3zLlX -rEDrZRwY7ldkYFWh467fqFEl1w== ------END PRIVATE KEY----- diff --git a/clickhouse-http-client/src/test/resources/nginx.conf b/clickhouse-http-client/src/test/resources/nginx.conf deleted file mode 100644 index fe8bff763..000000000 --- a/clickhouse-http-client/src/test/resources/nginx.conf +++ /dev/null @@ -1,53 +0,0 @@ -# nginx.conf -events {} - -http { - server { - listen 8443 ssl; - server_name node1.test; - - ssl_certificate /etc/nginx/certs/node1.crt; - ssl_certificate_key /etc/nginx/certs/node1.key; - - location = /default { - if ($request_method != POST) { - return 405; - } - - if ($arg_query = "SELECT hostName()") { - return 200 "node1.test\n"; - } - - return 400 "Unsupported query\n"; - } - - location / { - return 200 "node1.test served\n"; - } - } - - server { - listen 8443 ssl; - server_name node2.test; - - ssl_certificate /etc/nginx/certs/node2.crt; - ssl_certificate_key /etc/nginx/certs/node2.key; - - location = /default { - if ($request_method != POST) { - return 405; - } - - if ($arg_query = "SELECT hostName()") { - return 200 "node2.test\n"; - } - - return 400 "Unsupported query\n"; - } - - location / { - return 200 "node2.test served\n"; - } - } -} - 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 a976ed09d..344643d44 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 @@ -1030,6 +1030,19 @@ public Builder typeHintMapping(Map> typeHintMapping return this; } + + /** + * SNI SSL parameter that will be set for each outbound SSL socket. + * SNI stands for Server Name Indication - an extension to the TLS protocol that allows multiple domains to share the same IP address. + * + * @param sni - SNI parameter + * @return this builder instance + */ + public Builder sslSocketSNI(String sni) { + this.configuration.put(ClientConfigProperties.SSL_SOCKET_SNI.getKey(), sni); + return this; + } + public Client build() { // check if endpoint are empty. so can not initiate client if (this.endpoints.isEmpty()) { 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 a882437aa..89e0f5a12 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 @@ -11,16 +11,15 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.function.Function; -import java.util.Map; import java.util.TimeZone; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -180,9 +179,9 @@ public Object parseValue(String value) { TYPE_HINT_MAPPING("type_hint_mapping", Map.class), /** - * Comma separated key-value pairs of IP address/host to SNI mapping. Special mapping {@code _default_} - for default SNI when no match found. Without default mapping only matched targets will have SNI parameter. + * SNI SSL parameter that will be set for each outbound SSL socket. */ - SSL_SNI_MAPPING("ssl_sni_mapping", ""), + SSL_SOCKET_SNI("ssl_socket_sni", String.class,""), ; private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class); 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 d1c082c6c..a9aadd66a 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 @@ -15,7 +15,6 @@ import com.clickhouse.client.api.http.ClickHouseHttpProto; import com.clickhouse.client.api.transport.Endpoint; import com.clickhouse.data.ClickHouseFormat; -import com.google.common.collect.ImmutableMap; import net.jpountz.lz4.LZ4Factory; import org.apache.hc.client5.http.ConnectTimeoutException; import org.apache.hc.client5.http.classic.methods.HttpPost; @@ -73,7 +72,6 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.net.ConnectException; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NoRouteToHostException; import java.net.Socket; @@ -264,9 +262,9 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map true); + String socketSNI = (String)configuration.get(ClientConfigProperties.SSL_SOCKET_SNI.getKey()); + if (socketSNI != null && !socketSNI.trim().isEmpty()) { + sslConnectionSocketFactory = new CustomSSLConnectionFactory(socketSNI, sslContext, (hostname, session) -> true); } else { sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext); } @@ -852,37 +850,21 @@ public long getTime() { public static class CustomSSLConnectionFactory extends SSLConnectionSocketFactory { - private final Map sniMapping; - private final String defaultSNI; + private final SNIHostName defaultSNI; - public CustomSSLConnectionFactory(Map sniMapping, SSLContext sslContext, HostnameVerifier hostnameVerifier) { + public CustomSSLConnectionFactory(String defaultSNI, SSLContext sslContext, HostnameVerifier hostnameVerifier) { super(sslContext, hostnameVerifier); - this.sniMapping = ImmutableMap.copyOf(sniMapping); - this.defaultSNI = sniMapping.get(ClientConfigProperties.DEFAULT_KEY); + this.defaultSNI = defaultSNI == null || defaultSNI.trim().isEmpty() ? null : new SNIHostName(defaultSNI); } @Override protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException { super.prepareSocket(socket, context); - if (!sniMapping.isEmpty()) { - InetAddress remote = socket.getInetAddress(); - if (remote != null) { // actually should be not null here - String sni = sniMapping.get(remote.getHostAddress()); - if (sni == null) { - sni = sniMapping.get(remote.getHostName()); - if (sni == null) { - sni = defaultSNI; - } - } - if (sni != null && !sni.isEmpty()) { - SSLParameters sslParams = socket.getSSLParameters(); - sslParams.setServerNames(Collections.singletonList(new SNIHostName(sni))); - socket.setSSLParameters(sslParams); - } - } else { - LOG.warn("Failed to apply SNI - remote address is null"); - } + if (defaultSNI != null) { + SSLParameters sslParams = socket.getSSLParameters(); + sslParams.setServerNames(Collections.singletonList(defaultSNI)); + socket.setSSLParameters(sslParams); } } } 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 2d56941e9..da6b38658 100644 --- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java @@ -206,7 +206,7 @@ public void testDefaultSettings() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 31); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added. } try (Client client = new Client.Builder() @@ -239,7 +239,7 @@ public void testDefaultSettings() { .setSocketSndbuf(100000) .build()) { Map config = client.getConfiguration(); - Assert.assertEquals(config.size(), 32); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 33); // 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"); @@ -306,7 +306,7 @@ public void testWithOldDefaults() { Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match"); } } - Assert.assertEquals(config.size(), 31); // to check everything is set. Increment when new added. + Assert.assertEquals(config.size(), 32); // 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 9ff751068..ae495c786 100644 --- a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java @@ -36,6 +36,7 @@ import org.testng.annotations.Test; import java.io.ByteArrayInputStream; +import java.net.InetAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -1100,6 +1101,23 @@ public void testTimeoutsWithRetry() { } } + @Test(groups = {"integration"}) + public void testSNIWithCloud() throws Exception { + if (!isCloud()) { + // skip for local env + return; + } + + ClickHouseNode node = getServer(ClickHouseProtocol.HTTP); + String ip = InetAddress.getByName(node.getHost()).getHostAddress(); + try (Client c = new Client.Builder() + .addEndpoint(Protocol.HTTP, ip, node.getPort(), true) + .setUsername("default") + .setPassword(ClickHouseServerForTest.getPassword()) + .sslSocketSNI(node.getHost()).build()) { + c.execute("SELECT 1"); + } + } protected Client.Builder newClient() { ClickHouseNode node = getServer(ClickHouseProtocol.HTTP); diff --git a/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java b/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java deleted file mode 100644 index 92060ecb9..000000000 --- a/client-v2/src/test/java/com/clickhouse/client/NetworkTests.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.clickhouse.client; - -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.utility.MountableFile; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import javax.net.ssl.SNIHostName; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import java.security.cert.X509Certificate; -import java.util.Collections; - -@Test(groups = {"integration"}) -public class NetworkTests { - private static final String NGINX_IMAGE = "nginx:alpine"; - private static final int NGINX_SSL_PORT = 8443; - private static final String NODE1_HOST = "node1.test"; - private static final String NODE2_HOST = "node2.test"; - - private GenericContainer nginxContainer; - - @BeforeClass - public void setUp() throws Exception { - - // Create Nginx container with custom configuration - nginxContainer = new GenericContainer<>(NGINX_IMAGE) - .withCopyFileToContainer( - MountableFile.forClasspathResource("nginx.conf"), - "/etc/nginx/nginx.conf" - ) - .withCopyFileToContainer( - MountableFile.forClasspathResource("certs"), - "/etc/nginx/certs/" - ) - .withExposedPorts(NGINX_SSL_PORT) - .waitingFor(Wait.forListeningPort()); - - nginxContainer.start(); - } - - @AfterClass - public void tearDown() { - if (nginxContainer != null) { - nginxContainer.stop(); - } - } - - private String getNginxHost() { - return nginxContainer.getHost(); - } - - private int getNginxPort() { - return nginxContainer.getMappedPort(NGINX_SSL_PORT); - } - - @Test - public void testSNI() { - // Test will be implemented here - String host = getNginxHost(); - int port = getNginxPort(); - System.out.println("Nginx container running at: " + host + ":" + port); - } - - @Test(dataProvider = "testSNINodesDP") - void testSNINodes(String host) throws Exception { - int port = nginxContainer.getMappedPort(NGINX_SSL_PORT); - SSLSocket socket = createSniSocket(host, "localhost", port); - socket.startHandshake(); - - SSLSession session = socket.getSession(); - X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0]; - Assert.assertTrue(cert.getSubjectX500Principal().getName().contains("CN=" + host)); - } - - @DataProvider - static Object[][] testSNINodesDP() { - return new Object[][] { - {NODE1_HOST}, - {NODE2_HOST} - }; - } - - private SSLSocket createSniSocket(String sniHost, String serverHost, int port) throws Exception { - SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, new TrustManager[] { new X509TrustManager() { - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - public void checkServerTrusted(X509Certificate[] chain, String authType) { - - } - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } }, null); - - SSLSocketFactory factory = context.getSocketFactory(); - SSLSocket socket = (SSLSocket) factory.createSocket(serverHost, port); - - SSLParameters sslParams = socket.getSSLParameters(); - sslParams.setServerNames(Collections.singletonList(new SNIHostName(sniHost))); - socket.setSSLParameters(sslParams); - - return socket; - } -} diff --git a/client-v2/src/test/resources/certs/README.md b/client-v2/src/test/resources/certs/README.md deleted file mode 100644 index fe3b9e60e..000000000 --- a/client-v2/src/test/resources/certs/README.md +++ /dev/null @@ -1,41 +0,0 @@ - - - -## How to work with self-signed certificates - -1. Create a self-signed Root CA. This should be used for signing node certificates and adding to a truststore -2. Create node self-signed certificates -3. Sign node certificates with root CA - -Create root key for CA cert -```shell -openssl genrsa -out rootCA.key 4096 -``` - -Create root CA -```shell -openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 65535 -out rootCA.crt -``` - - -Create node certificates -```shell - -openssl req -x509 -nodes -days 65536 -newkey rsa:2048 -keyout node1.key -out node1.crt -subj "/CN=node1.test" -addext "subjectAltName=DNS:node1.test" - -openssl req -x509 -nodes -days 65536 -newkey rsa:2048 -keyout node2.key -out node2.crt -subj "/CN=node2.test" -addext "subjectAltName=DNS:node2.test" -``` - -```shell -openssl x509 -req -in node1.crt -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out node1-signed.crt -days 65536 -sha256 - -openssl x509 -req -in node2.crt -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out node2-signed.crt -days 65536 -sha256 -``` - - - -### Useful commands - -```shell -keytool -printcert -file rootCA.crt # read content of the certificate -``` \ No newline at end of file diff --git a/client-v2/src/test/resources/certs/node1.crt b/client-v2/src/test/resources/certs/node1.crt deleted file mode 100644 index 8956880a4..000000000 --- a/client-v2/src/test/resources/certs/node1.crt +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDIjCCAgqgAwIBAgIUPgsC1txpblBP8ybZlyOE/iwm3GAwDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKbm9kZTEudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 -MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUxLnRlc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQC16EcQY0+5s2sF4WDFcQgExmuIDn3v8gxqklv4BTXV -1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75gnm7nDd/5yCEbuhOGjHZcKWLx0ff7 -MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJaHyDQRbK3Uu896K3wm5YC8FArj83 -XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9TrhyxMWaviFVHk9Lvvj8ajrJQ6nyFNOYL -hTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93XbrGFu6bstL0V6MeAFV7u+oE6uaJpzw -23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4uxNztVhtAgMBAAGjajBoMB0GA1Ud -DgQWBBSFVEPrB2BgUiQ2VuyBbpcASR+HujAfBgNVHSMEGDAWgBSFVEPrB2BgUiQ2 -VuyBbpcASR+HujAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUxLnRl -c3QwDQYJKoZIhvcNAQELBQADggEBACDcZV0fYLyWHcY2lRRp+XfCC9pku2QCLKtb -UYF2sAzMm/ND5U6MFu5BB9EcveFVDDe00DgdkSTC/7LgljhZvTLFxaqmcHO6bz9N -woh1sljzlxDWymPCFg812lTt+KF6dvRswVYVY8ZkF9mrf0oVTYFNeX29G65/GlWQ -ykN8VvjtnMZpphJYTaMNzOsAvEWLxSppVYYIhNqjwIifJjiWGptqtmeycQrInJp0 -5KgecVqEffxm1NcZyTlcoDvcm2ayQblpq17rcQ6H5Y1LF9vVffA1UwAgLPQmSNO2 -5hbfGo2ySmRzBv5goYdhR4/KVj+IXL0ASgzp6lHh+UhSLFeTFWY= ------END CERTIFICATE----- diff --git a/client-v2/src/test/resources/certs/node1.key b/client-v2/src/test/resources/certs/node1.key deleted file mode 100644 index b385eac9c..000000000 --- a/client-v2/src/test/resources/certs/node1.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC16EcQY0+5s2sF -4WDFcQgExmuIDn3v8gxqklv4BTXV1B4MqOFhwKytLiAXHL8wyILZJXfCzSQb+75g -nm7nDd/5yCEbuhOGjHZcKWLx0ff7MBunje4b3t4LzKMtYmrPGSS540LLpsh+1VhJ -aHyDQRbK3Uu896K3wm5YC8FArj83XAV7UmEOjS4JeTWmFgfXcH04e6tSfZ9Trhyx -MWaviFVHk9Lvvj8ajrJQ6nyFNOYLhTxSU3hrEixJ/Y8h7PPEfUFjVBixCjh93Xbr -GFu6bstL0V6MeAFV7u+oE6uaJpzw23qgKq8q93niq4MLhv+sfUX5j5mHF4OookY4 -uxNztVhtAgMBAAECggEADi6hV7w+a357AhbbuhkBZInpuU5T8EChSi/F/tS9KUIj -453JEJt6evgFJXgmydFgLjjXGPdwcMQUSCkiAV6aDznUpvo/anaKBc5uOedCW7GY -/lu47DBYhHf8Yt3w8NuHevymaQVETWspunNsfbk14zQUoxN1erksbC3ib74GDLU8 -U6c08y1A+xFgybIUqTwyKLjBk6rUSwcXBwkmVPCvpoeY1b7jpDK71uMQsuA9Y0Fn -Ks7i5n3868nUe46SwR3aaZMz1ca1UGsQNTX1ZXr6rYB6PbsOBi7GCPaUKKBmzhtZ -pvQBRDESXvTgZ3uL1NUxgBYlpunTdyu8sCKhnoTZsQKBgQD/Aopz/CmB7Jm7Yr81 -Jw1fOUMCQlzVDFkXRgbGqDruQhvrgx1eNVhIUJGX1VffW1xFyut6G9h8eBQ5Na/g -XQC9Ea/B4HeovhIF1/n0owhBfVmBMWf5GOngguXPDVScehZY/58t2CM4C4wBs7hP -whJlPss40Ftox5X63ZRVO/CECQKBgQC2nRQm+K0qR8sEkWfj4iviWpAZjCmCkFAI -fl89WL5UWObwQBNrRlfOQDuncHYgfcRitxnyqqsfr4DbmkSGh+XBmSIAVqUx+b6i -VkXtq6LA/uJ/BuALyKQmR+UH9C3kyCEOkqialy1pcfQF5ZnDT7/CoPtaheknR5kj -6BMKN1gyRQKBgQCRNhovN81PDbLw8KcfFlDYA5xzweRo8TIePaMIJq4AFKcfcjcb -+VZ/P1nY0wTJzJV7rLRwgUDCiAJEHZ08LtmH0HV7+l1JOoq6xySvuNKvsDhyh/bD -8vri/MbuI5Il1KO7JDy5d1V+yZx9L6Dp0gJ1os9IV+VlghmWk+yuqIYqOQKBgQCP -55bi4yKq5+p/jgpdlXTZql5WE5L+lbTMDLIGyPErzOcoOoZTChrVqN6Zo1EjZ5ij -5gCnr4CstoQICjin/126Q2987sq9aD6m9O+kTJY0GmfojEVwY8ufcxZ0PyMPX/pU -3pPJRwr9RlRzdSzHsge+W1fYTSCf2JdAqXifm1+ANQKBgBVU0Z8Eeeab7RslPiDJ -kPd1okVJMMSI1jLANsla4WWL93lHDTQ/c6yhO6as3j4f4Bp13k6vtWypolnhCDWc -OTNPZYHPD9qQYevQlpFK2pERIEWH5BgeYLCy2YLWlKpUprHGyOUW3Ji9Gukha3pk -KVkxTzYFuXegiqZyKWFimVrs ------END PRIVATE KEY----- diff --git a/client-v2/src/test/resources/certs/node2.crt b/client-v2/src/test/resources/certs/node2.crt deleted file mode 100644 index 3d495d448..000000000 --- a/client-v2/src/test/resources/certs/node2.crt +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDIjCCAgqgAwIBAgIUbBm4zP62LDBki+SOV7K/a3RGoQQwDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKbm9kZTIudGVzdDAeFw0yNTA2MDYyMTI4NDJaFw0yNjA2 -MDYyMTI4NDJaMBUxEzARBgNVBAMMCm5vZGUyLnRlc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDXZDmSiRTjmVNMOF3p58Fg34+SvCoohYO0pB9LaY5d -H/iPHssTm/spD15sGMTwmnFKNgLKOCHCDgltTPJTCiVteddh944K+9drxnfck9Eb -FQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT4Hq0Tj37awIEPYcT2gjMrbsbXjAZ -nyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6tjJWbfgWy1DsdPuTadJAAaaa35CA -CgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2eaYx9Y8K3MqM4KouMeykO9CX04+3pg -bDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh8CRz2dmPAgMBAAGjajBoMB0GA1Ud -DgQWBBQAsXC8lOD5OjHgmNpWYYbAjI+1EjAfBgNVHSMEGDAWgBQAsXC8lOD5OjHg -mNpWYYbAjI+1EjAPBgNVHRMBAf8EBTADAQH/MBUGA1UdEQQOMAyCCm5vZGUyLnRl -c3QwDQYJKoZIhvcNAQELBQADggEBAKzBUiof/5yrmebVf+Arrur2/96QnEQJkGQm -1RwcClGOwhUtdbxcdoXgYqOLtQSiTGNAKcVyOKATOz6tc7qU5BkIga9d2FSGSV9c -6gU/Ij9+Y2AAHpElnHBGkjiuBAugv5Tt4NxnJXss5raML9/ATi0y4wcRSAeSCnDy -wFX66SEoWVkQcti7/TrxJRgstubWplTPtGQK/lcgWFNIKJDHft0nlXNsUK0CrfLq -wdWtmhWuadg66JuKvJABVtJIr2RQ5rYVWPyuS3sbSVIA9CB9dPjb/3/lSwmdSBbL -0WesfGryPK4aZXFlUEzzr/P/euwHhss0KUYjIpdGNQ7sBdwcFKg= ------END CERTIFICATE----- diff --git a/client-v2/src/test/resources/certs/node2.key b/client-v2/src/test/resources/certs/node2.key deleted file mode 100644 index 14115f3d1..000000000 --- a/client-v2/src/test/resources/certs/node2.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDXZDmSiRTjmVNM -OF3p58Fg34+SvCoohYO0pB9LaY5dH/iPHssTm/spD15sGMTwmnFKNgLKOCHCDglt -TPJTCiVteddh944K+9drxnfck9EbFQDCA+OQodNCoj3qvTZ5CvUSjPeWpdOo8yxT -4Hq0Tj37awIEPYcT2gjMrbsbXjAZnyWEh76vx3ydMN5VxJAUk4V0V8IpEUJWPyA6 -tjJWbfgWy1DsdPuTadJAAaaa35CACgAVO+aM8OFdSYw1SDmjgrgyBV9MU/XZN2ea -Yx9Y8K3MqM4KouMeykO9CX04+3pgbDJDy5CywGsWzqsdMNH3M++z9P7iBAyDL2Yh -8CRz2dmPAgMBAAECggEAUvRaSgn6nheooaPZ46Kq8xzXBlvq3rVhPrqxBNHQDySn -jbN94f0Ck9ND+dzmSIYq2LqJg4lv0vWmAs8OSxYOySWSYrw9nBRXaL5E3pKdh9Ek -fBSUWdlBbJngv94Es0SZk33wQzh1ls2lBl7f0z6JF6IkYi6yLd6c4fuNIuK5IrxX -0ahgqfiHrNizm8rfnIuHVQh/wBQk3cseXFMSnq5Y9/ZaFzqVKKauhLr8vFYRHK0Y -UCcLFCMZxvOpdVlOa8Ej4kZWQNd5yuvmEaaTcFgxkADmHF53jFjNVYluT16KQeVr -hzylv+rdrXgTpSb+uXmYzff1JTC7i8Jk9NQ5CaVCmQKBgQD3Gdn7rRU85aNkwaA5 -k2ykOP0ixQyLvYA0h8ajlidbxbfFm1IbrDmcOGgDIRwkwpD8GfZoKc41cjgaP9Fq -tWjdXNJaf0ogTWT9mcJFX1KGLU0yk3c8VPgtbhYf67u08H05ZF2O9TszBdxqy7lb -cfMIgiQXJ9gn86HMdL+t1hrZSQKBgQDfJgcZHHTCSW1o+wwWnPNrGjM5/vxUw293 -6d93m8nsUnZtYsRfqOpZKKyvuCbFhLlF5QkfpR19WUbCfqSRd2Ibq03CTN8MBa+0 -7yleHyDTYe48OGXyKzW0SdyM9T6oy7C0Fx1s4mB2Pa/iEovyZThxBPf3tINiU7QI -aMgo8mG0FwKBgQCTMVwPNpBDIUvOliSah34c6TGpB5Ysm6qGICcshSJW4ZVR6e7k -OxhqPZN/4bZBE4GIGvMUI2sJnUtcH43gscWQr11CuTZvvYqeInx+FIU1FkE5Emzh -jH/1l/En/KVo6CTADlBI+z3Ta/dbypVUqrEp7VQQUxTI37EO+LDBnZuKuQKBgQCo -2MhkKtD6Nb8wwUzbS3UwZEnAp3zc0mhkzrUFdv7p71em2yYFz/VK82j/KRNFP3FF -PtGRfUl8EHJnNpZlb3TvxMX4sFvRZ/gUqyadDmazK5WtQWt6O1HOkljs26DG7iFN -k3PzcLNWpMSSBkCYcBiuA/lxF8iWPQmrl0VlHknpjwKBgQCEPI+tuaw0fHsiHAK2 -w8rTB8KO5Qh7p5d1BSGmefM2HfJGialT5cCKABQX/kK4TxtLX7Pi1LPhJuGuJoi+ -AAwlznz2O1PUPCnBEc8mB/8p8ZrY89q0+xaqoyW1hnsNi20nXe6eHXlGu+J3zLlX -rEDrZRwY7ldkYFWh467fqFEl1w== ------END PRIVATE KEY----- diff --git a/client-v2/src/test/resources/nginx.conf b/client-v2/src/test/resources/nginx.conf deleted file mode 100644 index fe8bff763..000000000 --- a/client-v2/src/test/resources/nginx.conf +++ /dev/null @@ -1,53 +0,0 @@ -# nginx.conf -events {} - -http { - server { - listen 8443 ssl; - server_name node1.test; - - ssl_certificate /etc/nginx/certs/node1.crt; - ssl_certificate_key /etc/nginx/certs/node1.key; - - location = /default { - if ($request_method != POST) { - return 405; - } - - if ($arg_query = "SELECT hostName()") { - return 200 "node1.test\n"; - } - - return 400 "Unsupported query\n"; - } - - location / { - return 200 "node1.test served\n"; - } - } - - server { - listen 8443 ssl; - server_name node2.test; - - ssl_certificate /etc/nginx/certs/node2.crt; - ssl_certificate_key /etc/nginx/certs/node2.key; - - location = /default { - if ($request_method != POST) { - return 405; - } - - if ($arg_query = "SELECT hostName()") { - return 200 "node2.test\n"; - } - - return 400 "Unsupported query\n"; - } - - location / { - return 200 "node2.test served\n"; - } - } -} -