This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 123960c689 IGNITE-18578 Add SSL for Java client (#1662)
123960c689 is described below

commit 123960c689af1a721329a8078d0810926d5a5983
Author: Aleksandr <[email protected]>
AuthorDate: Wed Feb 15 12:20:08 2023 +0400

    IGNITE-18578 Add SSL for Java client (#1662)
    
    * Client side: add `IgniteClientConfiguration.ssl`
    * Server side: add `ClientConnectorConfigurationSchema.ssl`
---
 modules/client-handler/build.gradle                |   2 +
 .../ignite/client/handler/ItClientHandlerTest.java |  65 +--------
 .../client/handler/ItSslClientHandlerTest.java     | 146 +++++++++++++++++++++
 .../apache/ignite/client/handler/TestServer.java   | 106 +++++++++++++++
 .../ignite/client/handler/TestSslConfig.java       |  76 +++++++++++
 .../ignite/client/handler/ClientHandlerModule.java |   8 ++
 .../ClientConnectorConfigurationSchema.java        |   6 +
 modules/client/build.gradle                        |   1 +
 .../ignite/client/ClientAuthenticationMode.java    |  31 +++++
 .../org/apache/ignite/client/IgniteClient.java     |  18 ++-
 .../ignite/client/IgniteClientConfiguration.java   |  10 ++
 .../org/apache/ignite/client/SslConfiguration.java |  53 ++++++++
 .../client/IgniteClientConfigurationImpl.java      |  13 +-
 .../internal/client/SslConfigurationBuilder.java   | 113 ++++++++++++++++
 .../internal/client/SslConfigurationImpl.java      | 110 ++++++++++++++++
 .../io/netty/NettyClientConnectionMultiplexer.java |  92 +++++++++++++
 .../org/apache/ignite/client/RetryPolicyTest.java  |   2 +-
 .../java/org/apache/ignite/lang/ErrorGroups.java   |   3 +
 .../org/apache/ignite/internal/ssl/ItSslTest.java  | 116 ++++++++++++++++
 19 files changed, 908 insertions(+), 63 deletions(-)

diff --git a/modules/client-handler/build.gradle 
b/modules/client-handler/build.gradle
index 868c69c50f..4b083fdac5 100644
--- a/modules/client-handler/build.gradle
+++ b/modules/client-handler/build.gradle
@@ -54,7 +54,9 @@ dependencies {
     integrationTestImplementation project(':ignite-sql-engine')
     integrationTestImplementation project(':ignite-table')
     
integrationTestImplementation(testFixtures(project(':ignite-configuration')))
+    integrationTestImplementation(testFixtures(project(':ignite-core')))
     integrationTestImplementation libs.msgpack.core
+    integrationTestImplementation libs.netty.handler
 }
 
 description = 'ignite-client-handler'
diff --git 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
index 5d2bc0fd63..2daea85c7a 100644
--- 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
+++ 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.client.handler;
 
-import static 
org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static 
org.apache.ignite.lang.ErrorGroups.Client.PROTOCOL_COMPATIBILITY_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Common.UNKNOWN_ERR;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -27,32 +26,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Answers.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.mock;
 
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.Socket;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import 
org.apache.ignite.client.handler.configuration.ClientConnectorConfiguration;
-import org.apache.ignite.compute.IgniteCompute;
-import org.apache.ignite.internal.configuration.ConfigurationManager;
-import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
-import org.apache.ignite.internal.network.configuration.NetworkConfiguration;
-import org.apache.ignite.internal.sql.engine.QueryProcessor;
-import org.apache.ignite.internal.table.IgniteTablesInternal;
-import org.apache.ignite.network.ClusterService;
-import org.apache.ignite.network.NettyBootstrapFactory;
-import org.apache.ignite.sql.IgniteSql;
-import org.apache.ignite.tx.IgniteTransactions;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
-import org.mockito.Mockito;
 import org.msgpack.core.MessagePack;
 
 /**
@@ -60,27 +41,25 @@ import org.msgpack.core.MessagePack;
  */
 public class ItClientHandlerTest {
     /** Magic bytes. */
-    private static final byte[] MAGIC = new byte[]{0x49, 0x47, 0x4E, 0x49};
+    private static final byte[] MAGIC = {0x49, 0x47, 0x4E, 0x49};
 
     private ClientHandlerModule serverModule;
 
-    private ConfigurationManager configurationManager;
-
-    private NettyBootstrapFactory bootstrapFactory;
+    private TestServer testServer;
 
     private int serverPort;
 
     @BeforeEach
     public void setUp(TestInfo testInfo) {
-        serverModule = startServer(testInfo);
+        testServer = new TestServer();
+        serverModule = testServer.start(testInfo);
         serverPort = serverModule.localAddress().getPort();
     }
 
     @AfterEach
     public void tearDown() throws Exception {
         serverModule.stop();
-        configurationManager.stop();
-        bootstrapFactory.stop();
+        testServer.tearDown();
     }
 
     @Test
@@ -207,40 +186,6 @@ public class ItClientHandlerTest {
         }
     }
 
-    private ClientHandlerModule startServer(TestInfo testInfo) {
-        configurationManager = new ConfigurationManager(
-                List.of(ClientConnectorConfiguration.KEY, 
NetworkConfiguration.KEY),
-                Set.of(),
-                new TestConfigurationStorage(LOCAL),
-                List.of(),
-                List.of()
-        );
-
-        configurationManager.start();
-
-        var registry = configurationManager.configurationRegistry();
-
-        registry.getConfiguration(ClientConnectorConfiguration.KEY).change(
-                local -> local.changePort(10800).changePortRange(10)
-        ).join();
-
-        bootstrapFactory = new 
NettyBootstrapFactory(registry.getConfiguration(NetworkConfiguration.KEY), 
testInfo.getDisplayName());
-
-        bootstrapFactory.start();
-
-        ClusterService clusterService = mock(ClusterService.class, 
RETURNS_DEEP_STUBS);
-        
Mockito.when(clusterService.topologyService().localMember().id()).thenReturn("id");
-        
Mockito.when(clusterService.topologyService().localMember().name()).thenReturn("consistent-id");
-
-        var module = new ClientHandlerModule(mock(QueryProcessor.class), 
mock(IgniteTablesInternal.class), mock(IgniteTransactions.class),
-                registry, mock(IgniteCompute.class), clusterService, 
bootstrapFactory, mock(IgniteSql.class),
-                () -> CompletableFuture.completedFuture(UUID.randomUUID()));
-
-        module.start();
-
-        return module;
-    }
-
     private void writeAndFlushLoop(Socket socket) throws Exception {
         var stop = System.currentTimeMillis() + 5000;
         var out = socket.getOutputStream();
diff --git 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItSslClientHandlerTest.java
 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItSslClientHandlerTest.java
new file mode 100644
index 0000000000..585167418d
--- /dev/null
+++ 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItSslClientHandlerTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.client.handler;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import io.netty.handler.ssl.util.SelfSignedCertificate;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.msgpack.core.MessagePack;
+
+/** SSL client integration test. */
+@ExtendWith(WorkDirectoryExtension.class)
+public class ItSslClientHandlerTest {
+
+    /** Magic bytes. */
+    private static final byte[] MAGIC = {0x49, 0x47, 0x4E, 0x49};
+
+    private ClientHandlerModule serverModule;
+
+    private TestServer testServer;
+
+    private String keyStorePkcs12Path;
+
+    @WorkDirectory
+    private Path workDir;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        keyStorePkcs12Path = 
workDir.resolve("keystore.p12").toAbsolutePath().toString();
+        generateKeystore(new SelfSignedCertificate("localhost"));
+    }
+
+    private void generateKeystore(SelfSignedCertificate cert)
+            throws KeyStoreException, IOException, NoSuchAlgorithmException, 
CertificateException {
+        KeyStore ks = KeyStore.getInstance("PKCS12");
+        ks.load(null, null);
+        ks.setKeyEntry("key", cert.key(), null, new 
Certificate[]{cert.cert()});
+        try (FileOutputStream fos = new FileOutputStream(keyStorePkcs12Path)) {
+            ks.store(fos, "changeit".toCharArray());
+        }
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        if (testServer != null) {
+            testServer.tearDown();
+        }
+    }
+
+    @Test
+    @DisplayName("When SSL not configured (by default) the client can connect")
+    void sslNotConfigured(TestInfo testInfo) throws IOException {
+        // Given server started
+        testServer = new TestServer();
+        serverModule = testServer.start(testInfo);
+
+        // Then
+        performAndCheckMagic();
+    }
+
+    @Test
+    @DisplayName("When SSL configured on the server but not configured on the 
client then exception is thrown")
+    void sslConfiguredOnlyForServer(TestInfo testInfo) {
+        // Given server started
+        testServer = new TestServer(
+                TestSslConfig.builder()
+                        .keyStorePath(keyStorePkcs12Path)
+                        .keyStorePassword("changeit")
+                        .build()
+        );
+        serverModule = testServer.start(testInfo);
+
+        // Then
+        assertThrows(SocketException.class, this::performAndCheckMagic);
+    }
+
+    private void performAndCheckMagic() throws IOException {
+        int serverPort = serverModule.localAddress().getPort();
+
+        try (var sock = new Socket("127.0.0.1", serverPort)) {
+            OutputStream out = sock.getOutputStream();
+
+            // Magic: IGNI
+            out.write(MAGIC);
+
+            // Handshake.
+            var packer = MessagePack.newDefaultBufferPacker();
+            packer.packInt(0);
+            packer.packInt(0);
+            packer.packInt(0);
+            packer.packInt(7); // Size.
+
+            packer.packInt(3); // Major.
+            packer.packInt(0); // Minor.
+            packer.packInt(0); // Patch.
+
+            packer.packInt(2); // Client type: general purpose.
+
+            packer.packBinaryHeader(0); // Features.
+            packer.packMapHeader(0); // Extensions.
+
+            out.write(packer.toByteArray());
+            out.flush();
+
+            // Read response.
+            var unpacker = 
MessagePack.newDefaultUnpacker(sock.getInputStream());
+            var magic = unpacker.readPayload(4);
+
+            assertArrayEquals(MAGIC, magic);
+        }
+    }
+}
diff --git 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
new file mode 100644
index 0000000000..fda5782751
--- /dev/null
+++ 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.client.handler;
+
+import static 
org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import javax.annotation.Nullable;
+import 
org.apache.ignite.client.handler.configuration.ClientConnectorConfiguration;
+import org.apache.ignite.compute.IgniteCompute;
+import org.apache.ignite.internal.configuration.ConfigurationManager;
+import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.apache.ignite.internal.network.configuration.NetworkConfiguration;
+import org.apache.ignite.internal.sql.engine.QueryProcessor;
+import org.apache.ignite.internal.table.IgniteTablesInternal;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.NettyBootstrapFactory;
+import org.apache.ignite.sql.IgniteSql;
+import org.apache.ignite.tx.IgniteTransactions;
+import org.junit.jupiter.api.TestInfo;
+import org.mockito.Mockito;
+
+/** Test server that can be started with SSL configuration. */
+public class TestServer {
+    private final ConfigurationManager configurationManager;
+
+    private NettyBootstrapFactory bootstrapFactory;
+
+    private final TestSslConfig testSslConfig;
+
+    TestServer(@Nullable TestSslConfig testSslConfig) {
+        this.testSslConfig = testSslConfig;
+        this.configurationManager = new ConfigurationManager(
+                List.of(ClientConnectorConfiguration.KEY, 
NetworkConfiguration.KEY),
+                Set.of(),
+                new TestConfigurationStorage(LOCAL),
+                List.of(),
+                List.of()
+        );
+    }
+
+    TestServer() {
+        this(null);
+    }
+
+    void tearDown() throws Exception {
+        configurationManager.stop();
+        bootstrapFactory.stop();
+    }
+
+    ClientHandlerModule start(TestInfo testInfo) {
+        configurationManager.start();
+
+        clientConnectorConfig().change(
+                local -> local.changePort(10800).changePortRange(10)
+        ).join();
+
+        if (testSslConfig != null) {
+            clientConnectorConfig().ssl().enabled().update(true).join();
+            
clientConnectorConfig().ssl().keyStore().path().update(testSslConfig.keyStorePath()).join();
+            
clientConnectorConfig().ssl().keyStore().password().update(testSslConfig.keyStorePassword()).join();
+        }
+
+        var registry = configurationManager.configurationRegistry();
+        bootstrapFactory = new 
NettyBootstrapFactory(registry.getConfiguration(NetworkConfiguration.KEY), 
testInfo.getDisplayName());
+
+        bootstrapFactory.start();
+
+        ClusterService clusterService = mock(ClusterService.class, 
RETURNS_DEEP_STUBS);
+        
Mockito.when(clusterService.topologyService().localMember().id()).thenReturn("id");
+        
Mockito.when(clusterService.topologyService().localMember().name()).thenReturn("consistent-id");
+
+        var module = new ClientHandlerModule(mock(QueryProcessor.class), 
mock(IgniteTablesInternal.class), mock(IgniteTransactions.class),
+                registry, mock(IgniteCompute.class), clusterService, 
bootstrapFactory, mock(IgniteSql.class),
+                () -> CompletableFuture.completedFuture(UUID.randomUUID()));
+
+        module.start();
+
+        return module;
+    }
+
+    private ClientConnectorConfiguration clientConnectorConfig() {
+        var registry = configurationManager.configurationRegistry();
+        return registry.getConfiguration(ClientConnectorConfiguration.KEY);
+    }
+}
diff --git 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestSslConfig.java
 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestSslConfig.java
new file mode 100644
index 0000000000..6bfd9338be
--- /dev/null
+++ 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestSslConfig.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.client.handler;
+
+/** Test SSL configuration. It is needed for grouping parameters. */
+public class TestSslConfig {
+    private final String algorithm;
+
+    private final String keyStorePassword;
+
+    private final String keyStorePath;
+
+    private TestSslConfig(String algorithm, String keyStorePassword, String 
keyStorePath) {
+        this.algorithm = algorithm;
+        this.keyStorePassword = keyStorePassword;
+        this.keyStorePath = keyStorePath;
+    }
+
+    public String algorithm() {
+        return algorithm;
+    }
+
+    public String keyStorePassword() {
+        return keyStorePassword;
+    }
+
+    public String keyStorePath() {
+        return keyStorePath;
+    }
+
+    static TestSslConfigBuilder builder() {
+        return new TestSslConfigBuilder();
+    }
+
+    static class TestSslConfigBuilder {
+        private String algorithm;
+
+        private String keyStorePassword;
+
+        private String keyStorePath;
+
+        public TestSslConfigBuilder algorithm(String algorithm) {
+            this.algorithm = algorithm;
+            return this;
+        }
+
+        public TestSslConfigBuilder keyStorePassword(String keyStorePassword) {
+            this.keyStorePassword = keyStorePassword;
+            return this;
+        }
+
+        public TestSslConfigBuilder keyStorePath(String keyStorePath) {
+            this.keyStorePath = keyStorePath;
+            return this;
+        }
+
+        public TestSslConfig build() {
+            return new TestSslConfig(algorithm, keyStorePassword, 
keyStorePath);
+        }
+    }
+}
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
index 4c1c17fd1e..9b20db8306 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
@@ -24,6 +24,7 @@ import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
+import io.netty.handler.ssl.SslContext;
 import io.netty.handler.timeout.IdleState;
 import io.netty.handler.timeout.IdleStateEvent;
 import io.netty.handler.timeout.IdleStateHandler;
@@ -40,6 +41,7 @@ import 
org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.manager.IgniteComponent;
+import org.apache.ignite.internal.network.ssl.SslContextProvider;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.table.IgniteTablesInternal;
 import org.apache.ignite.lang.IgniteException;
@@ -198,6 +200,12 @@ public class ClientHandlerModule implements 
IgniteComponent {
                             ch.pipeline().addLast(new IdleChannelHandler());
                         }
 
+                        if (configuration.ssl().enabled()) {
+                            SslContext sslContext =  
SslContextProvider.createServerSslContext(configuration.ssl());
+
+                            ch.pipeline().addFirst("ssl", 
sslContext.newHandler(ch.alloc()));
+                        }
+
                         ch.pipeline().addLast(
                                 new ClientMessageDecoder(),
                                 new ClientInboundMessageHandler(
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/configuration/ClientConnectorConfigurationSchema.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/configuration/ClientConnectorConfigurationSchema.java
index 6cd5458d31..22525e0572 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/configuration/ClientConnectorConfigurationSchema.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/configuration/ClientConnectorConfigurationSchema.java
@@ -17,10 +17,12 @@
 
 package org.apache.ignite.client.handler.configuration;
 
+import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.configuration.validation.Range;
+import org.apache.ignite.internal.network.configuration.SslConfigurationSchema;
 
 /**
  * Configuration schema for thin client connector.
@@ -51,4 +53,8 @@ public class ClientConnectorConfigurationSchema {
     /** Server exception stack trace visibility. */
     @Value(hasDefault = true)
     public final boolean sendServerExceptionStackTraceToClient = false;
+
+    /** SSL configuration schema. */
+    @ConfigValue
+    public SslConfigurationSchema ssl;
 }
diff --git a/modules/client/build.gradle b/modules/client/build.gradle
index bc3d1abe7a..e2b8ca1cc8 100644
--- a/modules/client/build.gradle
+++ b/modules/client/build.gradle
@@ -32,6 +32,7 @@ dependencies {
     implementation libs.netty.common
     implementation libs.netty.buffer
     implementation libs.netty.codec
+    implementation libs.netty.handler
     implementation libs.auto.service.annotations
 
     annotationProcessor libs.auto.service
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/ClientAuthenticationMode.java
 
b/modules/client/src/main/java/org/apache/ignite/client/ClientAuthenticationMode.java
new file mode 100644
index 0000000000..57cb50a166
--- /dev/null
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/ClientAuthenticationMode.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.client;
+
+/** Ignite SSL client authentication enum. */
+public enum ClientAuthenticationMode {
+    /** Indicates that the client authentication will not be requested. */
+    NONE,
+
+    /** Indicates that the client authentication will be requested. */
+    OPTIONAL,
+
+    /** Indicates that the client authentication will be requested and the 
connection will be closed if the client
+     * certificate is not trusted. */
+    REQUIRE
+}
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
index 045baf2578..29cbf2137a 100644
--- a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
+++ b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
@@ -97,6 +97,9 @@ public interface IgniteClient extends Ignite {
         /** Logger factory. */
         private @Nullable LoggerFactory loggerFactory;
 
+        /** SSL configuration. */
+        private @Nullable SslConfiguration sslConfiguration;
+
         /**
          * Sets the addresses of Ignite server nodes within a cluster. An 
address can be an IP address or a hostname, with or without port.
          * If port is not set then Ignite will generate multiple addresses for 
default port range. See {@link
@@ -260,6 +263,18 @@ public interface IgniteClient extends Ignite {
             return this;
         }
 
+        /**
+         * Sets the SSL configuration.
+         *
+         * @param sslConfiguration SSL configuration.
+         * @return This instance.
+         */
+        public Builder ssl(@Nullable SslConfiguration sslConfiguration) {
+            this.sslConfiguration = sslConfiguration;
+
+            return this;
+        }
+
         /**
          * Builds the client.
          *
@@ -285,7 +300,8 @@ public interface IgniteClient extends Ignite {
                     heartbeatInterval,
                     heartbeatTimeout,
                     retryPolicy,
-                    loggerFactory
+                    loggerFactory,
+                    sslConfiguration
             );
 
             return TcpIgniteClient.startAsync(cfg);
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
index d957492bb1..646c9a86cd 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
@@ -141,4 +141,14 @@ public interface IgniteClientConfiguration {
      * @return Configured logger factory.
      */
     @Nullable LoggerFactory loggerFactory();
+
+    /**
+     * Returns the client SSL configuration. This configuration will be used 
to setup the SSL connection with
+     * the Ignite 3 nodes.
+     *
+     * <p>When {@code null} then no SSL is used.
+     *
+     * @return Client SSL configuration.
+     */
+    @Nullable SslConfiguration ssl();
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/SslConfiguration.java 
b/modules/client/src/main/java/org/apache/ignite/client/SslConfiguration.java
new file mode 100644
index 0000000000..2bf4c8c25c
--- /dev/null
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/SslConfiguration.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.client;
+
+import org.apache.ignite.internal.client.SslConfigurationBuilder;
+import org.jetbrains.annotations.Nullable;
+
+/** Client SSL configuration. */
+public interface SslConfiguration {
+    /** If set to {@code true} then the SSL connection will be used to 
interact with Ignite 3 node. */
+    boolean enabled();
+
+    /** Client authentication configuration. */
+    ClientAuthenticationMode clientAuthenticationMode();
+
+    /** Keystore path that will be used to setup the SSL connection. */
+    @Nullable String keyStorePath();
+
+    /** Keystore password that will be used to setup the SSL connection. */
+    @Nullable String keyStorePassword();
+
+    /** Keystore type that will be used to setup the SSL connection. */
+    String keyStoreType();
+
+    /** Truststore path that will be used to setup the SSL connection. */
+    @Nullable String trustStorePath();
+
+    /** Truststore password that will be used to setup the SSL connection. */
+    @Nullable String trustStorePassword();
+
+    /** Truststore type that will be used to setup the SSL connection. */
+    String trustStoreType();
+
+    /** SSL configuration builder. */
+    static SslConfigurationBuilder builder() {
+        return new SslConfigurationBuilder();
+    }
+}
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
index 2fa413b2c2..777e6dcd9d 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
@@ -21,6 +21,7 @@ import java.util.concurrent.Executor;
 import org.apache.ignite.client.IgniteClientAddressFinder;
 import org.apache.ignite.client.IgniteClientConfiguration;
 import org.apache.ignite.client.RetryPolicy;
+import org.apache.ignite.client.SslConfiguration;
 import org.apache.ignite.lang.LoggerFactory;
 import org.jetbrains.annotations.Nullable;
 
@@ -57,6 +58,8 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
 
     private final LoggerFactory loggerFactory;
 
+    private final SslConfiguration sslConfiguration;
+
     /**
      * Constructor.
      *
@@ -81,7 +84,8 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
             long heartbeatInterval,
             long heartbeatTimeout,
             @Nullable RetryPolicy retryPolicy,
-            @Nullable LoggerFactory loggerFactory
+            @Nullable LoggerFactory loggerFactory,
+            @Nullable SslConfiguration sslConfiguration
     ) {
         this.addressFinder = addressFinder;
 
@@ -96,6 +100,7 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
         this.heartbeatTimeout = heartbeatTimeout;
         this.retryPolicy = retryPolicy;
         this.loggerFactory = loggerFactory;
+        this.sslConfiguration = sslConfiguration;
     }
 
     /** {@inheritDoc} */
@@ -157,4 +162,10 @@ public final class IgniteClientConfigurationImpl 
implements IgniteClientConfigur
     public @Nullable RetryPolicy retryPolicy() {
         return retryPolicy;
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public @Nullable SslConfiguration ssl() {
+        return sslConfiguration;
+    }
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/SslConfigurationBuilder.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/SslConfigurationBuilder.java
new file mode 100644
index 0000000000..cd188009e6
--- /dev/null
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/SslConfigurationBuilder.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.client;
+
+import org.apache.ignite.client.ClientAuthenticationMode;
+import org.apache.ignite.client.SslConfiguration;
+import org.jetbrains.annotations.Nullable;
+
+/** SSL configuration builder. */
+public class SslConfigurationBuilder {
+    private static final String DEFAULT_KEYSTORE_TYPE = "PKCS12";
+
+    private boolean enabled = false;
+
+    private ClientAuthenticationMode clientAuth = 
ClientAuthenticationMode.NONE;
+
+    private @Nullable String keyStorePath;
+
+    private @Nullable String keyStorePassword;
+
+    private String keyStoreType = DEFAULT_KEYSTORE_TYPE;
+
+    private @Nullable String trustStorePath;
+
+    private @Nullable String trustStorePassword;
+
+    private String trustStoreType = DEFAULT_KEYSTORE_TYPE;
+
+    /** Enabled/disabled setter. */
+    public SslConfigurationBuilder enabled(boolean enabled) {
+        this.enabled = enabled;
+        return this;
+    }
+
+    /** SSL client authentication setter. */
+    public SslConfigurationBuilder clientAuth(@Nullable 
ClientAuthenticationMode clientAuth) {
+        if (clientAuth == null) {
+            this.clientAuth = ClientAuthenticationMode.NONE;
+            return this;
+        }
+
+        this.clientAuth = clientAuth;
+        return this;
+    }
+
+    /** Keystore path setter. */
+    public SslConfigurationBuilder keyStorePath(@Nullable String keyStorePath) 
{
+        this.keyStorePath = keyStorePath;
+        return this;
+    }
+
+    /** Keystore password setter. */
+    public SslConfigurationBuilder keyStorePassword(@Nullable String 
keyStorePassword) {
+        this.keyStorePassword = keyStorePassword;
+        return this;
+    }
+
+    /** Keystore type setter. If set to {@code null} then the default PKCS12 
type is used. */
+    public SslConfigurationBuilder keyStoreType(@Nullable String keyStoreType) 
{
+        if (keyStoreType == null) {
+            this.keyStoreType = DEFAULT_KEYSTORE_TYPE;
+            return this;
+        }
+
+        this.keyStoreType = keyStoreType;
+        return this;
+    }
+
+    /** Truststore path setter. */
+    public SslConfigurationBuilder trustStorePath(@Nullable String 
trustStorePath) {
+        this.trustStorePath = trustStorePath;
+        return this;
+    }
+
+    /** Truststore password setter. */
+    public SslConfigurationBuilder trustStorePassword(@Nullable String 
trustStorePassword) {
+        this.trustStorePassword = trustStorePassword;
+        return this;
+    }
+
+    /** Truststore type setter. If set to {@code null} then the default PKCS12 
type is used. */
+    public SslConfigurationBuilder trustStoreType(@Nullable String 
trustStoreType) {
+        if (trustStoreType == null) {
+            this.trustStoreType = DEFAULT_KEYSTORE_TYPE;
+            return this;
+        }
+
+        this.trustStoreType = trustStoreType;
+        return this;
+    }
+
+    /** Build SslConfiguration instance. */
+    public SslConfiguration build() {
+        return new SslConfigurationImpl(
+                enabled, clientAuth, keyStorePath, keyStorePassword, 
keyStoreType, trustStorePath, trustStorePassword, trustStoreType
+        );
+    }
+}
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/SslConfigurationImpl.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/SslConfigurationImpl.java
new file mode 100644
index 0000000000..d7a7c4ba13
--- /dev/null
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/SslConfigurationImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.client;
+
+import org.apache.ignite.client.ClientAuthenticationMode;
+import org.apache.ignite.client.SslConfiguration;
+import org.jetbrains.annotations.Nullable;
+
+/** SSL configuration. */
+public class SslConfigurationImpl implements SslConfiguration {
+    private final boolean enabled;
+
+    private final ClientAuthenticationMode clientAuth;
+
+    private final @Nullable String keyStorePath;
+
+    private final @Nullable String keyStorePassword;
+
+    private final String keyStoreType;
+
+    private final @Nullable String trustStorePath;
+
+    private final @Nullable String trustStorePassword;
+
+    private final String trustStoreType;
+
+    /** Main constructor. */
+    SslConfigurationImpl(
+            boolean enabled,
+            ClientAuthenticationMode clientAuth,
+            @Nullable String keyStorePath,
+            @Nullable String keyStorePassword,
+            String keyStoreType,
+            @Nullable String trustStorePath,
+            @Nullable String trustStorePassword,
+            String trustStoreType
+    ) {
+        this.enabled = enabled;
+        this.clientAuth = clientAuth;
+        this.keyStorePath = keyStorePath;
+        this.keyStorePassword = keyStorePassword;
+        this.keyStoreType = keyStoreType;
+        this.trustStorePath = trustStorePath;
+        this.trustStorePassword = trustStorePassword;
+        this.trustStoreType = trustStoreType;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean enabled() {
+        return enabled;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ClientAuthenticationMode clientAuthenticationMode() {
+        return clientAuth;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @Nullable String keyStorePath() {
+        return keyStorePath;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @Nullable String keyStorePassword() {
+        return keyStorePassword;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String keyStoreType() {
+        return keyStoreType;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @Nullable String trustStorePath() {
+        return trustStorePath;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @Nullable String trustStorePassword() {
+        return trustStorePassword;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String trustStoreType() {
+        return trustStoreType;
+    }
+}
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java
index 24b8acc613..8406f5489c 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.client.io.netty;
 
+import static 
org.apache.ignite.lang.ErrorGroups.Client.CLIENT_SSL_CONFIGURATION_ERR;
+
 import io.netty.bootstrap.Bootstrap;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelInitializer;
@@ -24,16 +26,34 @@ import io.netty.channel.ChannelOption;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
 import java.util.concurrent.CompletableFuture;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManagerFactory;
+import org.apache.ignite.client.ClientAuthenticationMode;
 import org.apache.ignite.client.IgniteClientConfiguration;
 import org.apache.ignite.client.IgniteClientConnectionException;
+import org.apache.ignite.client.SslConfiguration;
 import org.apache.ignite.internal.client.io.ClientConnection;
 import org.apache.ignite.internal.client.io.ClientConnectionMultiplexer;
 import org.apache.ignite.internal.client.io.ClientConnectionStateHandler;
 import org.apache.ignite.internal.client.io.ClientMessageHandler;
 import org.apache.ignite.internal.client.proto.ClientMessageDecoder;
 import org.apache.ignite.lang.ErrorGroups.Client;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Netty-based multiplexer.
@@ -62,6 +82,7 @@ public class NettyClientConnectionMultiplexer implements 
ClientConnectionMultipl
             bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) {
+                    setupSsl(ch, clientCfg);
                     ch.pipeline().addLast(
                             new ClientMessageDecoder(),
                             new NettyClientMessageHandler());
@@ -75,6 +96,77 @@ public class NettyClientConnectionMultiplexer implements 
ClientConnectionMultipl
         }
     }
 
+    private void setupSsl(SocketChannel ch, IgniteClientConfiguration 
clientCfg) {
+        if (clientCfg.ssl() == null || !clientCfg.ssl().enabled()) {
+            return;
+        }
+
+        try {
+            SslConfiguration ssl = clientCfg.ssl();
+            SslContextBuilder builder = 
SslContextBuilder.forClient().trustManager(loadTrustManagerFactory(ssl));
+
+            ClientAuth clientAuth = 
toNettyClientAuth(ssl.clientAuthenticationMode());
+            if (ClientAuth.NONE != clientAuth) {
+                
builder.clientAuth(clientAuth).keyManager(loadKeyManagerFactory(ssl));
+            }
+
+            SslContext context = builder.build();
+
+            ch.pipeline().addFirst("ssl", context.newHandler(ch.alloc()));
+        } catch (NoSuchAlgorithmException | KeyStoreException | 
CertificateException | IOException | UnrecoverableKeyException e) {
+            throw new IgniteException(CLIENT_SSL_CONFIGURATION_ERR, "Client 
SSL configuration error: " + e.getMessage(), e);
+        }
+
+    }
+
+    @NotNull
+    private static KeyManagerFactory loadKeyManagerFactory(SslConfiguration 
ssl)
+            throws KeyStoreException, IOException, NoSuchAlgorithmException, 
CertificateException, UnrecoverableKeyException {
+        KeyStore ks = KeyStore.getInstance(ssl.keyStoreType());
+
+        char[] ksPassword = ssl.keyStorePassword() == null ? null : 
ssl.keyStorePassword().toCharArray();
+        if (ssl.keyStorePath() != null) {
+            try (InputStream is = 
Files.newInputStream(Path.of(ssl.keyStorePath()))) {
+                ks.load(is, ksPassword);
+            }
+        } else {
+            ks.load(null, ksPassword);
+        }
+
+        KeyManagerFactory keyManagerFactory = 
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        keyManagerFactory.init(ks, ksPassword);
+        return keyManagerFactory;
+    }
+
+    @NotNull
+    private static TrustManagerFactory 
loadTrustManagerFactory(SslConfiguration ssl)
+            throws KeyStoreException, IOException, NoSuchAlgorithmException, 
CertificateException {
+        KeyStore ts = KeyStore.getInstance(ssl.trustStoreType());
+        char[] tsPassword = ssl.trustStorePassword() == null ? null : 
ssl.trustStorePassword().toCharArray();
+        if (ssl.trustStorePath() != null) {
+            try (InputStream is = 
Files.newInputStream(Path.of(ssl.trustStorePath()))) {
+                ts.load(is, tsPassword);
+            }
+        } else {
+            ts.load(null, tsPassword);
+        }
+
+        TrustManagerFactory trustManagerFactory = 
TrustManagerFactory.getInstance(
+                TrustManagerFactory.getDefaultAlgorithm()
+        );
+        trustManagerFactory.init(ts);
+        return trustManagerFactory;
+    }
+
+    private static ClientAuth toNettyClientAuth(ClientAuthenticationMode 
igniteClientAuth) {
+        switch (igniteClientAuth) {
+            case NONE: return ClientAuth.NONE;
+            case REQUIRE: return ClientAuth.REQUIRE;
+            case OPTIONAL: return ClientAuth.OPTIONAL;
+            default: throw new IllegalArgumentException("Client authentication 
type is not supported");
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public void stop() {
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java 
b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
index c0bd49c23a..d3ba937871 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
@@ -228,7 +228,7 @@ public class RetryPolicyTest {
     @Test
     public void testRetryReadPolicyAllOperationsSupported() {
         var plc = new RetryReadPolicy();
-        var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, 0, null, 
0, 0, null, null);
+        var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, 0, null, 
0, 0, null, null, null);
 
         for (var op : ClientOperationType.values()) {
             var ctx = new RetryPolicyContextImpl(cfg, op, 0, null);
diff --git a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java 
b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
index 2df1939577..c96b81101b 100755
--- a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
+++ b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
@@ -96,6 +96,9 @@ public class ErrorGroups {
 
         /** Cluster ID mismatch error. */
         public static final int CLUSTER_ID_MISMATCH_ERR = 
CLIENT_ERR_GROUP.registerErrorCode(8);
+
+        /** Client SSL configuration error. */
+        public static final int CLIENT_SSL_CONFIGURATION_ERR = 
CLIENT_ERR_GROUP.registerErrorCode(9);
     }
 
     /** SQL error group. */
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
index f24e9deb29..9d4054eb73 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/ssl/ItSslTest.java
@@ -17,10 +17,16 @@
 
 package org.apache.ignite.internal.ssl;
 
+import static org.apache.ignite.client.ClientAuthenticationMode.REQUIRE;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.nio.file.Path;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.IgniteClientConnectionException;
+import org.apache.ignite.client.SslConfiguration;
 import org.apache.ignite.internal.Cluster;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
@@ -90,6 +96,14 @@ public class ItSslTest {
         void clusterStartsWithDisabledSsl(TestInfo testInfo) {
             assertThat(cluster.runningNodes().count(), is(2L));
         }
+
+        @Test
+        @DisplayName("Client can connect without ssl")
+        void clientCouldConnectWithoutSsl() throws Exception {
+            try (IgniteClient client = 
IgniteClient.builder().addresses("localhost:10800").build()) {
+                assertThat(client.clusterNodes(), hasSize(2));
+            }
+        }
     }
 
     @Nested
@@ -120,6 +134,13 @@ public class ItSslTest {
                 + "    nodeFinder:{\n"
                 + "      netClusterNodes: [ \"localhost:3345\", 
\"localhost:3346\" ]\n"
                 + "    }\n"
+                + "  },\n"
+                + "  clientConnector.ssl: {\n"
+                + "    enabled: true, "
+                + "    keyStore: {\n"
+                + "      path: \"" + keyStorePath + "\",\n"
+                + "      password: \"" + password + "\"\n"
+                + "    }\n"
                 + "  }\n"
                 + "}";
 
@@ -139,6 +160,36 @@ public class ItSslTest {
         void clusterStartsWithEnabledSsl(TestInfo testInfo) {
             assertThat(cluster.runningNodes().count(), is(2L));
         }
+
+        @Test
+        @DisplayName("Client cannot connect without SSL configured")
+        void clientCannotConnectWithoutSsl() {
+            assertThrows(IgniteClientConnectionException.class, () -> {
+                try (IgniteClient ignored = 
IgniteClient.builder().addresses("localhost:10800").build()) {
+                    // no-op
+                }
+            });
+        }
+
+        @Test
+        @DisplayName("Client can connect with SSL configured")
+        void clientCanConnectWithSsl() throws Exception {
+            var sslConfiguration =
+                    SslConfiguration.builder()
+                            .enabled(true)
+                            .trustStoreType("JKS")
+                            .trustStorePath(trustStorePath)
+                            .trustStorePassword(password)
+                            .build();
+
+            try (IgniteClient client = IgniteClient.builder()
+                    .addresses("localhost:10800")
+                    .ssl(sslConfiguration)
+                    .build()
+            ) {
+                assertThat(client.clusterNodes(), hasSize(2));
+            }
+        }
     }
 
     @Nested
@@ -170,6 +221,19 @@ public class ItSslTest {
                 + "    nodeFinder:{\n"
                 + "      netClusterNodes: [ \"localhost:3365\", 
\"localhost:3366\" ]\n"
                 + "    }\n"
+                + "  },\n"
+                + "  clientConnector.ssl: {\n"
+                + "    enabled: true, "
+                + "    clientAuth: \"require\", "
+                + "    keyStore: {\n"
+                + "      path: \"" + keyStorePath + "\",\n"
+                + "      password: \"" + password + "\"\n"
+                + "    }, \n"
+                + "    trustStore: {\n"
+                + "      type: JKS,"
+                + "      password: \"" + password + "\","
+                + "      path: \"" + trustStorePath + "\""
+                + "      },\n"
                 + "  }\n"
                 + "}";
 
@@ -189,5 +253,57 @@ public class ItSslTest {
         void clusterStartsWithEnabledSsl(TestInfo testInfo) {
             assertThat(cluster.runningNodes().count(), is(2L));
         }
+
+        @Test
+        @DisplayName("Client cannot connect without SSL configured")
+        void clientCannotConnectWithoutSsl() {
+            assertThrows(IgniteClientConnectionException.class, () -> {
+                try (IgniteClient ignored = 
IgniteClient.builder().addresses("localhost:10800").build()) {
+                    // no-op
+                }
+            });
+        }
+
+        @Test
+        @DisplayName("Client can not connect without client authentication 
configured")
+        void clientCanNotConnectWithoutClientAuth() {
+            var sslConfiguration =
+                    SslConfiguration.builder()
+                            .enabled(true)
+                            .trustStoreType("JKS")
+                            .trustStorePath(trustStorePath)
+                            .trustStorePassword(password)
+                            .build();
+
+            assertThrows(IgniteClientConnectionException.class,
+                    () -> IgniteClient.builder()
+                            .addresses("localhost:10800")
+                            .ssl(sslConfiguration)
+                            .build()
+            );
+        }
+
+        @Test
+        @DisplayName("Client can connect with SSL and client authentication 
configured")
+        void clientCanConnectWithSslAndClientAuth() throws Exception {
+            var sslConfiguration =
+                    SslConfiguration.builder()
+                            .enabled(true)
+                            .trustStoreType("JKS")
+                            .trustStorePath(trustStorePath)
+                            .trustStorePassword(password)
+                            .clientAuth(REQUIRE)
+                            .keyStorePath(keyStorePath)
+                            .keyStorePassword(password)
+                            .build();
+
+            try (IgniteClient client = IgniteClient.builder()
+                    .addresses("localhost:10800")
+                    .ssl(sslConfiguration)
+                    .build()
+            ) {
+                assertThat(client.clusterNodes(), hasSize(2));
+            }
+        }
     }
 }

Reply via email to