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

ibessonov 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 4b188ee303 IGNITE-18828 Add ciphers support to SSL (jdbc, client, 
scalecube) (#1779)
4b188ee303 is described below

commit 4b188ee30301e8564ebff0051288795703f33699
Author: Vadim Pakhnushev <[email protected]>
AuthorDate: Wed Mar 15 11:44:55 2023 +0300

    IGNITE-18828 Add ciphers support to SSL (jdbc, client, scalecube) (#1779)
---
 .../org/apache/ignite/client/SslConfiguration.java |   3 +
 .../internal/client/SslConfigurationBuilder.java   |  12 +-
 .../internal/client/SslConfigurationImpl.java      |   9 +
 .../io/netty/NettyClientConnectionMultiplexer.java |   4 +-
 .../ignite/internal/jdbc/ConnectionProperties.java |  14 ++
 .../internal/jdbc/ConnectionPropertiesImpl.java    |  17 +-
 .../ignite/internal/jdbc/JdbcConnection.java       |   1 +
 .../AbstractSslConfigurationSchema.java            |   4 +
 .../SslConfigurationValidatorImpl.java             |  35 ++++
 .../internal/network/ssl/SslContextProvider.java   |  16 ++
 .../SslConfigurationValidatorImplTest.java         |  22 ++-
 .../network/configuration/StubSslView.java         |   9 +-
 .../apache/ignite/internal/rest/RestComponent.java |  48 ++---
 .../org/apache/ignite/internal/rest/RestNode.java  |  27 ++-
 .../ignite/internal/rest/RestNodeBuilder.java      |  10 +-
 .../ignite/internal/rest/ssl/ItRestSslTest.java    |  67 +++++--
 .../org/apache/ignite/internal/ssl/ItSslTest.java  | 207 ++++++++++++++++++++-
 17 files changed, 436 insertions(+), 69 deletions(-)

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
index 2bf4c8c25c..075aa82829 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/client/SslConfiguration.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/SslConfiguration.java
@@ -28,6 +28,9 @@ public interface SslConfiguration {
     /** Client authentication configuration. */
     ClientAuthenticationMode clientAuthenticationMode();
 
+    /** List of ciphers that will be used to setup the SSL connection. */
+    @Nullable Iterable<String> ciphers();
+
     /** Keystore path that will be used to setup the SSL connection. */
     @Nullable String keyStorePath();
 
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
index cd188009e6..5f69dc6264 100644
--- 
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
@@ -29,6 +29,8 @@ public class SslConfigurationBuilder {
 
     private ClientAuthenticationMode clientAuth = 
ClientAuthenticationMode.NONE;
 
+    private @Nullable Iterable<String> ciphers;
+
     private @Nullable String keyStorePath;
 
     private @Nullable String keyStorePassword;
@@ -58,6 +60,12 @@ public class SslConfigurationBuilder {
         return this;
     }
 
+    /** Ciphers setter. */
+    public SslConfigurationBuilder ciphers(@Nullable Iterable<String> ciphers) 
{
+        this.ciphers = ciphers;
+        return this;
+    }
+
     /** Keystore path setter. */
     public SslConfigurationBuilder keyStorePath(@Nullable String keyStorePath) 
{
         this.keyStorePath = keyStorePath;
@@ -107,7 +115,9 @@ public class SslConfigurationBuilder {
     /** Build SslConfiguration instance. */
     public SslConfiguration build() {
         return new SslConfigurationImpl(
-                enabled, clientAuth, keyStorePath, keyStorePassword, 
keyStoreType, trustStorePath, trustStorePassword, trustStoreType
+                enabled, clientAuth, ciphers,
+                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
index d7a7c4ba13..a38eaa8e2f 100644
--- 
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
@@ -27,6 +27,8 @@ public class SslConfigurationImpl implements SslConfiguration 
{
 
     private final ClientAuthenticationMode clientAuth;
 
+    private final @Nullable Iterable<String> ciphers;
+
     private final @Nullable String keyStorePath;
 
     private final @Nullable String keyStorePassword;
@@ -43,6 +45,7 @@ public class SslConfigurationImpl implements SslConfiguration 
{
     SslConfigurationImpl(
             boolean enabled,
             ClientAuthenticationMode clientAuth,
+            @Nullable Iterable<String> ciphers,
             @Nullable String keyStorePath,
             @Nullable String keyStorePassword,
             String keyStoreType,
@@ -52,6 +55,7 @@ public class SslConfigurationImpl implements SslConfiguration 
{
     ) {
         this.enabled = enabled;
         this.clientAuth = clientAuth;
+        this.ciphers = ciphers;
         this.keyStorePath = keyStorePath;
         this.keyStorePassword = keyStorePassword;
         this.keyStoreType = keyStoreType;
@@ -72,6 +76,11 @@ public class SslConfigurationImpl implements 
SslConfiguration {
         return clientAuth;
     }
 
+    @Override
+    public @Nullable Iterable<String> ciphers() {
+        return ciphers;
+    }
+
     /** {@inheritDoc} */
     @Override
     public @Nullable String keyStorePath() {
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 8406f5489c..b0c17be0c4 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
@@ -53,7 +53,6 @@ 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.
@@ -105,6 +104,7 @@ public class NettyClientConnectionMultiplexer implements 
ClientConnectionMultipl
             SslConfiguration ssl = clientCfg.ssl();
             SslContextBuilder builder = 
SslContextBuilder.forClient().trustManager(loadTrustManagerFactory(ssl));
 
+            builder.ciphers(ssl.ciphers());
             ClientAuth clientAuth = 
toNettyClientAuth(ssl.clientAuthenticationMode());
             if (ClientAuth.NONE != clientAuth) {
                 
builder.clientAuth(clientAuth).keyManager(loadKeyManagerFactory(ssl));
@@ -119,7 +119,6 @@ public class NettyClientConnectionMultiplexer implements 
ClientConnectionMultipl
 
     }
 
-    @NotNull
     private static KeyManagerFactory loadKeyManagerFactory(SslConfiguration 
ssl)
             throws KeyStoreException, IOException, NoSuchAlgorithmException, 
CertificateException, UnrecoverableKeyException {
         KeyStore ks = KeyStore.getInstance(ssl.keyStoreType());
@@ -138,7 +137,6 @@ public class NettyClientConnectionMultiplexer implements 
ClientConnectionMultipl
         return keyManagerFactory;
     }
 
-    @NotNull
     private static TrustManagerFactory 
loadTrustManagerFactory(SslConfiguration ssl)
             throws KeyStoreException, IOException, NoSuchAlgorithmException, 
CertificateException {
         KeyStore ts = KeyStore.getInstance(ssl.trustStoreType());
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
index ce29e5e572..f0d1820037 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
@@ -156,6 +156,20 @@ public interface ConnectionProperties {
      */
     ClientAuthenticationMode getClientAuth();
 
+    /**
+     * SSL ciphers.
+     *
+     * @param ciphers list of ciphers.
+     */
+    void setCiphers(String ciphers);
+
+    /**
+     * SSL ciphers.
+     *
+     * @return list of ciphers.
+     */
+    Iterable<String> getCiphers();
+
     /**
      * Set trust store path that will be used to setup SSL connection.
      *
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
index 385008f74f..44cca8524f 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
@@ -109,6 +109,10 @@ public class ConnectionPropertiesImpl implements 
ConnectionProperties, Serializa
     private final StringProperty clientAuth = new StringProperty("clientAuth",
             "SSL client authentication", "none", clientAuthValues(), false, 
null);
 
+    /** SSL ciphers list. */
+    private final StringProperty ciphers = new StringProperty("ciphers",
+            "SSL ciphers", null, null, false, null);
+
     @NotNull
     private static String[] clientAuthValues() {
         return Arrays.stream(ClientAuthenticationMode.values())
@@ -125,7 +129,7 @@ public class ConnectionPropertiesImpl implements 
ConnectionProperties, Serializa
     /** Properties array. */
     private final ConnectionProperty[] propsArray = {
             qryTimeout, connTimeout, trustStorePath, trustStorePassword, 
trustStoreType,
-            sslEnabled, clientAuth, keyStorePath, keyStorePassword, 
keyStoreType
+            sslEnabled, clientAuth, ciphers, keyStorePath, keyStorePassword, 
keyStoreType
     };
 
     /** {@inheritDoc} */
@@ -334,6 +338,17 @@ public class ConnectionPropertiesImpl implements 
ConnectionProperties, Serializa
         return 
ClientAuthenticationMode.valueOf(this.clientAuth.value().toUpperCase());
     }
 
+    @Override
+    public void setCiphers(String ciphers) {
+        this.ciphers.setValue(ciphers);
+    }
+
+    @Override
+    public Iterable<String> getCiphers() {
+        String value = ciphers.value();
+        return value != null ? Arrays.asList(value.split(",")) : null;
+    }
+
     /**
      * Init connection properties.
      *
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index 0c39eb4034..d157be23a5 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -179,6 +179,7 @@ public class JdbcConnection implements Connection {
                     .trustStorePath(connProps.getTrustStorePath())
                     .trustStorePassword(connProps.getTrustStorePassword())
                     .clientAuth(connProps.getClientAuth())
+                    .ciphers(connProps.getCiphers())
                     .keyStoreType(connProps.getKeyStoreType())
                     .keyStorePath(connProps.getKeyStorePath())
                     .keyStorePassword(connProps.getKeyStorePassword())
diff --git 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/AbstractSslConfigurationSchema.java
 
b/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/AbstractSslConfigurationSchema.java
index 958a28732d..b58890748b 100644
--- 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/AbstractSslConfigurationSchema.java
+++ 
b/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/AbstractSslConfigurationSchema.java
@@ -42,6 +42,10 @@ public class AbstractSslConfigurationSchema {
     @Value(hasDefault = true)
     public final String clientAuth = "none";
 
+    /** List of ciphers to enable, separated by comma. */
+    @Value(hasDefault = true)
+    public String ciphers = "";
+
     /** SSL keystore configuration. */
     @ConfigValue
     public KeyStoreConfigurationSchema keyStore;
diff --git 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImpl.java
 
b/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImpl.java
index 0efc970d06..ab08f9d659 100644
--- 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImpl.java
+++ 
b/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImpl.java
@@ -19,13 +19,23 @@ package org.apache.ignite.internal.network.configuration;
 
 import static org.apache.ignite.internal.util.StringUtils.nullOrBlank;
 
+import io.netty.buffer.ByteBufAllocator;
 import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.net.ssl.SSLException;
 import org.apache.ignite.configuration.validation.ValidationContext;
 import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.configuration.validation.Validator;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
 
 /**
  * SSL configuration validator implementation.
@@ -34,6 +44,8 @@ public class SslConfigurationValidatorImpl implements 
Validator<SslConfiguration
 
     public static final SslConfigurationValidatorImpl INSTANCE = new 
SslConfigurationValidatorImpl();
 
+    private static final IgniteLogger LOG = 
Loggers.forClass(SslConfigurationValidatorImpl.class);
+
     @Override
     public void validate(SslConfigurationValidator annotation, 
ValidationContext<AbstractSslView> ctx) {
         AbstractSslView ssl = ctx.getNewValue();
@@ -48,6 +60,10 @@ public class SslConfigurationValidatorImpl implements 
Validator<SslConfiguration
             } catch (IllegalArgumentException e) {
                 ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Incorrect 
client auth parameter " + ssl.clientAuth()));
             }
+
+            if (!ssl.ciphers().isBlank()) {
+                validateCiphers(ctx, ssl);
+            }
         }
     }
 
@@ -73,4 +89,23 @@ public class SslConfigurationValidatorImpl implements 
Validator<SslConfiguration
             }
         }
     }
+
+    private static void validateCiphers(ValidationContext<AbstractSslView> 
ctx, AbstractSslView ssl) {
+        try {
+            SslContext context = SslContextBuilder.forClient().build();
+            Set<String> supported = 
Arrays.stream(context.newEngine(ByteBufAllocator.DEFAULT).getSupportedCipherSuites())
+                    .filter(Objects::nonNull) // OpenSSL engine returns null 
string in the array so we need to filter them out
+                    .collect(Collectors.toSet());
+            Set<String> ciphers = Arrays.stream(ssl.ciphers().split(","))
+                    .map(String::strip)
+                    .collect(Collectors.toSet());
+            if (!supported.containsAll(ciphers)) {
+                ciphers.removeAll(supported);
+                ctx.addIssue(new ValidationIssue(ctx.currentKey(), "There are 
unsupported cipher suites: " + ciphers));
+            }
+        } catch (SSLException e) {
+            ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Can't create 
SSL engine"));
+            LOG.warn("Can't create SSL engine", e);
+        }
+    }
 }
diff --git 
a/modules/network/src/main/java/org/apache/ignite/internal/network/ssl/SslContextProvider.java
 
b/modules/network/src/main/java/org/apache/ignite/internal/network/ssl/SslContextProvider.java
index d50b1acc3f..be1e570086 100644
--- 
a/modules/network/src/main/java/org/apache/ignite/internal/network/ssl/SslContextProvider.java
+++ 
b/modules/network/src/main/java/org/apache/ignite/internal/network/ssl/SslContextProvider.java
@@ -26,6 +26,9 @@ import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.TrustManagerFactory;
 import org.apache.ignite.internal.network.configuration.SslView;
@@ -46,6 +49,8 @@ public final class SslContextProvider {
 
             var builder = 
SslContextBuilder.forClient().trustManager(trustManagerFactory);
 
+            setCiphers(builder, ssl);
+
             ClientAuth clientAuth = 
ClientAuth.valueOf(ssl.clientAuth().toUpperCase());
             if (ClientAuth.NONE == clientAuth) {
                 return builder.build();
@@ -72,6 +77,8 @@ public final class SslContextProvider {
 
             var builder = SslContextBuilder.forServer(keyManagerFactory);
 
+            setCiphers(builder, ssl);
+
             ClientAuth clientAuth = 
ClientAuth.valueOf(ssl.clientAuth().toUpperCase());
             if (ClientAuth.NONE == clientAuth) {
                 return builder.build();
@@ -89,4 +96,13 @@ public final class SslContextProvider {
             throw new IgniteException(Common.SSL_CONFIGURATION_ERR, e);
         }
     }
+
+    private static void setCiphers(SslContextBuilder builder, SslView ssl) {
+        if (!ssl.ciphers().isBlank()) {
+            List<String> ciphers = Arrays.stream(ssl.ciphers().split(","))
+                    .map(String::strip)
+                    .collect(Collectors.toList());
+            builder.ciphers(ciphers);
+        }
+    }
 }
diff --git 
a/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImplTest.java
 
b/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImplTest.java
index 2ab49b09ba..c2bc2e90da 100644
--- 
a/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImplTest.java
+++ 
b/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/SslConfigurationValidatorImplTest.java
@@ -56,6 +56,20 @@ class SslConfigurationValidatorImplTest {
                 "Key store type must not be blank", "Key store file doesn't 
exist at /path/to/keystore.p12");
     }
 
+    @Test
+    public void incorrectCipherName(@WorkDirectory Path workDir) throws 
IOException {
+        KeyStoreView keyStore = createValidKeyStoreConfig(workDir);
+        validate(new StubSslView(true, "NONE", "foo, TLS_AES_256_GCM_SHA384", 
keyStore, null),
+                "There are unsupported cipher suites: [foo]");
+    }
+
+    @Test
+    public void validCipherName(@WorkDirectory Path workDir) throws 
IOException {
+        KeyStoreView keyStore = createValidKeyStoreConfig(workDir);
+        validate(new StubSslView(true, "NONE", "TLS_AES_256_GCM_SHA384", 
keyStore, null),
+                (String[]) null);
+    }
+
     @Test
     public void nullTrustStorePath(@WorkDirectory Path workDir) throws 
IOException {
         validate(createTrustStoreConfig(workDir, "PKCS12", null, "changeIt"),
@@ -83,7 +97,7 @@ class SslConfigurationValidatorImplTest {
     @Test
     public void incorrectAuthType(@WorkDirectory Path workDir) throws 
IOException {
         KeyStoreView keyStore = createValidKeyStoreConfig(workDir);
-        StubSslView sslView = new StubSslView(true, "foo", keyStore, null);
+        StubSslView sslView = new StubSslView(true, "foo", "", keyStore, null);
 
         validate(sslView, "Incorrect client auth parameter foo");
     }
@@ -91,7 +105,7 @@ class SslConfigurationValidatorImplTest {
     @Test
     public void validKeyStoreConfig(@WorkDirectory Path workDir) throws 
IOException {
         KeyStoreView keyStore = createValidKeyStoreConfig(workDir);
-        validate(new StubSslView(true, "NONE", keyStore, null), (String[]) 
null);
+        validate(new StubSslView(true, "NONE", "", keyStore, null), (String[]) 
null);
     }
 
     @Test
@@ -109,13 +123,13 @@ class SslConfigurationValidatorImplTest {
     }
 
     private static AbstractSslView createKeyStoreConfig(String type, String 
path, String password) {
-        return new StubSslView(true, "NONE", new StubKeyStoreView(type, path, 
password), null);
+        return new StubSslView(true, "NONE", "", new StubKeyStoreView(type, 
path, password), null);
     }
 
     private static AbstractSslView createTrustStoreConfig(Path workDir, String 
type, String path, String password) throws IOException {
         KeyStoreView keyStore = createValidKeyStoreConfig(workDir);
         KeyStoreView trustStore = new StubKeyStoreView(type, path, password);
-        return new StubSslView(true, "OPTIONAL", keyStore, trustStore);
+        return new StubSslView(true, "OPTIONAL", "", keyStore, trustStore);
     }
 
     private static KeyStoreView createValidKeyStoreConfig(Path workDir) throws 
IOException {
diff --git 
a/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/StubSslView.java
 
b/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/StubSslView.java
index c074054dda..e3d6cca2f0 100644
--- 
a/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/StubSslView.java
+++ 
b/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/StubSslView.java
@@ -22,13 +22,15 @@ public class StubSslView implements SslView {
 
     private final boolean enabled;
     private final String clientAuth;
+    private String ciphers;
     private final KeyStoreView keyStore;
     private final KeyStoreView trustStore;
 
     /** Constructor. */
-    public StubSslView(boolean enabled, String clientAuth, KeyStoreView 
keyStore, KeyStoreView trustStore) {
+    public StubSslView(boolean enabled, String clientAuth, String ciphers, 
KeyStoreView keyStore, KeyStoreView trustStore) {
         this.enabled = enabled;
         this.clientAuth = clientAuth;
+        this.ciphers = ciphers;
         this.keyStore = keyStore;
         this.trustStore = trustStore;
     }
@@ -43,6 +45,11 @@ public class StubSslView implements SslView {
         return clientAuth;
     }
 
+    @Override
+    public String ciphers() {
+        return ciphers;
+    }
+
     @Override
     public KeyStoreView keyStore() {
         return keyStore;
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
index 7aa8b5bde0..518bb06ffb 100644
--- 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
+++ 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
@@ -181,7 +181,8 @@ public class RestComponent implements IgniteComponent {
         return micronaut
                 .properties(properties)
                 .banner(false)
-                .mapError(ServerStartupException.class, 
RestComponent::mapServerStartupException)
+                // -1 forces the micronaut to throw an 
ApplicationStartupException instead of doing System.exit
+                .mapError(ServerStartupException.class, ex -> -1)
                 .mapError(ApplicationStartupException.class, ex -> -1);
     }
 
@@ -191,14 +192,6 @@ public class RestComponent implements IgniteComponent {
         }
     }
 
-    private static int mapServerStartupException(ServerStartupException 
exception) {
-        if (exception.getCause() instanceof BindException) {
-            return -1; // -1 forces the micronaut to throw an 
ApplicationStartupException
-        } else {
-            return 1;
-        }
-    }
-
     private Map<String, Object> serverProperties(int port, int sslPort) {
         RestSslView restSslView = restConfiguration.ssl().value();
         boolean sslEnabled = restSslView.enabled();
@@ -207,33 +200,30 @@ public class RestComponent implements IgniteComponent {
             KeyStoreView keyStore = restSslView.keyStore();
             boolean dualProtocol = restConfiguration.dualProtocol().value();
 
-            Map<String, Object> micronautSslConfig = Map.of(
-                    "micronaut.server.port", port, // Micronaut is not going 
to handle requests on that port, but it's required
-                    "micronaut.server.dual-protocol", dualProtocol,
-                    "micronaut.server.ssl.port", sslPort,
-                    "micronaut.server.ssl.enabled", sslEnabled,
-                    "micronaut.server.ssl.key-store.path", "file:" + 
keyStore.path(),
-                    "micronaut.server.ssl.key-store.password", 
keyStore.password(),
-                    "micronaut.server.ssl.key-store.type", keyStore.type()
-            );
+            Map<String, Object> result = new HashMap<>();
+            // Micronaut is not going to handle requests on that port, but 
it's required
+            result.put("micronaut.server.port", port);
+            result.put("micronaut.server.dual-protocol", dualProtocol);
+            result.put("micronaut.server.ssl.port", sslPort);
+            if (!restSslView.ciphers().isBlank()) {
+                result.put("micronaut.server.ssl.ciphers", 
restSslView.ciphers());
+            }
+            result.put("micronaut.server.ssl.enabled", sslEnabled);
+            result.put("micronaut.server.ssl.key-store.path", "file:" + 
keyStore.path());
+            result.put("micronaut.server.ssl.key-store.password", 
keyStore.password());
+            result.put("micronaut.server.ssl.key-store.type", keyStore.type());
 
             ClientAuth clientAuth = 
ClientAuth.valueOf(restSslView.clientAuth().toUpperCase());
             if (ClientAuth.NONE == clientAuth) {
-                return micronautSslConfig;
+                return result;
             }
 
             KeyStoreView trustStore = restSslView.trustStore();
 
-            Map<String, Object> micronautClientAuthConfig = Map.of(
-                    "micronaut.server.ssl.client-authentication", 
toMicronautClientAuth(clientAuth),
-                    "micronaut.server.ssl.trust-store.path", "file:" + 
trustStore.path(),
-                    "micronaut.server.ssl.trust-store.password", 
trustStore.password(),
-                    "micronaut.server.ssl.trust-store.type", trustStore.type()
-            );
-
-            HashMap<String, Object> result = new HashMap<>();
-            result.putAll(micronautSslConfig);
-            result.putAll(micronautClientAuthConfig);
+            result.put("micronaut.server.ssl.client-authentication", 
toMicronautClientAuth(clientAuth));
+            result.put("micronaut.server.ssl.trust-store.path", "file:" + 
trustStore.path());
+            result.put("micronaut.server.ssl.trust-store.password", 
trustStore.password());
+            result.put("micronaut.server.ssl.trust-store.type", 
trustStore.type());
 
             return result;
         } else {
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNode.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNode.java
index 1a955c1154..069087de94 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNode.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNode.java
@@ -41,6 +41,7 @@ public class RestNode {
     private final boolean sslEnabled;
     private final boolean sslClientAuthEnabled;
     private final boolean dualProtocol;
+    private final String ciphers;
     private CompletableFuture<Ignite> igniteNodeFuture;
 
     /** Constructor. */
@@ -56,7 +57,8 @@ public class RestNode {
             int httpsPort,
             boolean sslEnabled,
             boolean sslClientAuthEnabled,
-            boolean dualProtocol
+            boolean dualProtocol,
+            String ciphers
     ) {
         this.keyStorePath = keyStorePath;
         this.keyStorePassword = keyStorePassword;
@@ -70,6 +72,7 @@ public class RestNode {
         this.sslEnabled = sslEnabled;
         this.sslClientAuthEnabled = sslClientAuthEnabled;
         this.dualProtocol = dualProtocol;
+        this.ciphers = ciphers;
     }
 
     public static RestNodeBuilder builder() {
@@ -115,6 +118,8 @@ public class RestNode {
     }
 
     private String bootstrapCfg() {
+        String keyStoreFilePath = 
getResourcePath(ItRestSslTest.class.getClassLoader().getResource(keyStorePath));
+        String trustStoreFilePath = 
getResourcePath(ItRestSslTest.class.getClassLoader().getResource(trustStorePath));
         return "{\n"
                 + "  network: {\n"
                 + "    port: " + networkPort + ",\n"
@@ -128,14 +133,15 @@ public class RestNode {
                 + "    ssl: {\n"
                 + "      enabled: " + sslEnabled + ",\n"
                 + "      clientAuth: " + (sslClientAuthEnabled ? "require" : 
"none") + ",\n"
+                + "      ciphers: \"" + ciphers + "\",\n"
                 + "      port: " + httpsPort + ",\n"
                 + "      keyStore: {\n"
-                + "        path: \"" + getResourcePath(keyStorePath) + "\",\n"
+                + "        path: \"" + escapeWindowsPath(keyStoreFilePath) + 
"\",\n"
                 + "        password: " + keyStorePassword + "\n"
                 + "      }, \n"
                 + "      trustStore: {\n"
                 + "        type: JKS,\n"
-                + "        path: \"" + getResourcePath(trustStorePath) + 
"\",\n"
+                + "        path: \"" + escapeWindowsPath(trustStoreFilePath) + 
"\",\n"
                 + "        password: " + trustStorePassword + "\n"
                 + "      }\n"
                 + "    }\n"
@@ -143,14 +149,19 @@ public class RestNode {
                 + "}";
     }
 
-    private static String getResourcePath(String resource) {
+    /** Converts URL gotten from classloader to proper file system path. */
+    public static String getResourcePath(URL url) {
         try {
-            URL url = 
ItRestSslTest.class.getClassLoader().getResource(resource);
-            Objects.requireNonNull(url, "Resource " + resource + " not 
found.");
-            Path path = Path.of(url.toURI()); // Properly extract file system 
path from the "file:" URL
-            return path.toString().replace("\\", "\\\\"); // Escape 
backslashes for the config parser
+            Objects.requireNonNull(url);
+            // Properly extract file system path from the "file:" URL
+            return Path.of(url.toURI()).toString();
         } catch (URISyntaxException e) {
             throw new RuntimeException(e); // Shouldn't happen since URL is 
obtained from the class loader
         }
     }
+
+    /** Use this to escape backslashes for the HOCON config parser. */
+    public static String escapeWindowsPath(String path) {
+        return path.replace("\\", "\\\\");
+    }
 }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNodeBuilder.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNodeBuilder.java
index 9b2f5eead5..6250bb1038 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNodeBuilder.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/RestNodeBuilder.java
@@ -35,6 +35,8 @@ public class RestNodeBuilder {
     private boolean sslClientAuthEnabled = false;
     private boolean dualProtocol = false;
 
+    private String ciphers = "";
+
     public RestNodeBuilder keyStorePath(String keyStorePath) {
         this.keyStorePath = keyStorePath;
         return this;
@@ -95,6 +97,11 @@ public class RestNodeBuilder {
         return this;
     }
 
+    public RestNodeBuilder ciphers(String ciphers) {
+        this.ciphers = ciphers;
+        return this;
+    }
+
     /** Builds {@link RestNode}. */
     public RestNode build() {
         return new RestNode(
@@ -109,7 +116,8 @@ public class RestNodeBuilder {
                 httpsPort,
                 sslEnabled,
                 sslClientAuthEnabled,
-                dualProtocol
+                dualProtocol,
+                ciphers
         );
     }
 }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/ItRestSslTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/ItRestSslTest.java
index 66ed790197..9455e37eb2 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/ItRestSslTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/ItRestSslTest.java
@@ -39,6 +39,7 @@ import java.security.cert.CertificateException;
 import java.util.stream.Stream;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
 import javax.net.ssl.TrustManagerFactory;
 import org.apache.ignite.internal.IgniteIntegrationTest;
 import org.apache.ignite.internal.rest.RestNode;
@@ -73,15 +74,18 @@ public class ItRestSslTest extends IgniteIntegrationTest {
     @WorkDirectory
     private static Path workDir;
 
-    /** HTTP client that is expected to be defined in subclasses. */
+    /** HTTP client. */
     private static HttpClient client;
 
-    /** SSL HTTP client that is expected to be defined in subclasses. */
+    /** SSL HTTP client. */
     private static HttpClient sslClient;
 
-    /** SSL HTTP client that is expected to be defined in subclasses. */
+    /** SSL HTTP client with client auth. */
     private static HttpClient sslClientWithClientAuth;
 
+    /** SSL HTTP client with custom cipher. */
+    private static HttpClient sslClientWithCustomCipher;
+
     private static RestNode httpNode;
 
     private static RestNode httpsNode;
@@ -90,6 +94,8 @@ public class ItRestSslTest extends IgniteIntegrationTest {
 
     private static RestNode httpsWithClientAuthNode;
 
+    private static RestNode httpsWithCustomCipherNode;
+
     @BeforeAll
     static void beforeAll(TestInfo testInfo) throws Exception {
 
@@ -104,6 +110,14 @@ public class ItRestSslTest extends IgniteIntegrationTest {
                 .sslContext(sslContextWithClientAuth())
                 .build();
 
+        SSLContext sslContext = sslContext();
+        SSLParameters sslParameters = sslContext.getDefaultSSLParameters();
+        sslParameters.setCipherSuites(new 
String[]{"TLS_DHE_RSA_WITH_AES_128_CBC_SHA"});
+        sslClientWithCustomCipher = HttpClient.newBuilder()
+                .sslContext(sslContext)
+                .sslParameters(sslParameters)
+                .build();
+
         httpNode = RestNode.builder()
                 .workDir(workDir)
                 .name(testNodeName(testInfo, 3344))
@@ -145,12 +159,23 @@ public class ItRestSslTest extends IgniteIntegrationTest {
                 .dualProtocol(false)
                 .build();
 
-        Stream.of(httpNode, httpsNode, dualProtocolNode, 
httpsWithClientAuthNode).parallel()
+        httpsWithCustomCipherNode = RestNode.builder()
+                .workDir(workDir)
+                .name(testNodeName(testInfo, 3348))
+                .networkPort(3348)
+                .httpPort(10304)
+                .httpsPort(10404)
+                .sslEnabled(true)
+                .dualProtocol(false)
+                .ciphers("TLS_AES_256_GCM_SHA384")
+                .build();
+
+        Stream.of(httpNode, httpsNode, dualProtocolNode, 
httpsWithClientAuthNode, httpsWithCustomCipherNode).parallel()
                 .forEach(RestNode::start);
     }
 
     @Test
-    void httpsProtocol() throws IOException, InterruptedException {
+    void httpsProtocol() throws Exception {
         // When GET /management/v1/configuration/node
         URI uri = URI.create(httpsNode.httpsAddress() + 
"/management/v1/configuration/node");
         HttpRequest request = HttpRequest.newBuilder(uri).build();
@@ -161,7 +186,7 @@ public class ItRestSslTest extends IgniteIntegrationTest {
     }
 
     @Test
-    void httpProtocol(TestInfo testInfo) throws IOException, 
InterruptedException {
+    void httpProtocol(TestInfo testInfo) throws Exception {
         // When GET /management/v1/configuration/node
         URI uri = URI.create(httpNode.httpAddress() + 
"/management/v1/configuration/node");
         HttpRequest request = HttpRequest.newBuilder(uri).build();
@@ -172,7 +197,7 @@ public class ItRestSslTest extends IgniteIntegrationTest {
     }
 
     @Test
-    void dualProtocol() throws IOException, InterruptedException {
+    void dualProtocol() throws Exception {
         // When GET /management/v1/configuration/node
         URI httpUri = URI.create(dualProtocolNode.httpAddress() + 
"/management/v1/configuration/node");
         HttpRequest httpRequest = HttpRequest.newBuilder(httpUri).build();
@@ -200,7 +225,7 @@ public class ItRestSslTest extends IgniteIntegrationTest {
     }
 
     @Test
-    void httpProtocolNotSslClient() throws IOException, InterruptedException {
+    void httpProtocolNotSslClient() {
         // When GET /management/v1/configuration/node
         URI uri = URI.create(httpsNode.httpAddress() + 
"/management/v1/configuration/node");
         HttpRequest request = HttpRequest.newBuilder(uri).build();
@@ -210,7 +235,7 @@ public class ItRestSslTest extends IgniteIntegrationTest {
     }
 
     @Test
-    void httpsWithClientAuthProtocol(TestInfo testInfo) throws IOException, 
InterruptedException {
+    void httpsWithClientAuthProtocol(TestInfo testInfo) throws Exception {
         // When GET /management/v1/configuration/node
         URI uri = URI.create(httpsWithClientAuthNode.httpsAddress() + 
"/management/v1/configuration/node");
         HttpRequest request = HttpRequest.newBuilder(uri).build();
@@ -221,7 +246,7 @@ public class ItRestSslTest extends IgniteIntegrationTest {
     }
 
     @Test
-    void httpsWithClientAuthProtocolButClientWithoutAuth(TestInfo testInfo) 
throws IOException, InterruptedException {
+    void httpsWithClientAuthProtocolButClientWithoutAuth(TestInfo testInfo) {
         // When GET /management/v1/configuration/node
         URI uri = URI.create(httpsWithClientAuthNode.httpsAddress() + 
"/management/v1/configuration/node");
         HttpRequest request = HttpRequest.newBuilder(uri).build();
@@ -230,9 +255,29 @@ public class ItRestSslTest extends IgniteIntegrationTest {
         assertThrows(IOException.class, () -> sslClient.send(request, 
BodyHandlers.ofString()));
     }
 
+    void httpsWithCustomCipher(TestInfo testInfo) throws Exception {
+        // When GET /management/v1/configuration/node
+        URI uri = URI.create(httpsWithCustomCipherNode.httpsAddress() + 
"/management/v1/configuration/node");
+        HttpRequest request = HttpRequest.newBuilder(uri).build();
+
+        // Then response code is 200
+        HttpResponse<String> response = sslClient.send(request, 
BodyHandlers.ofString());
+        assertEquals(200, response.statusCode());
+    }
+
+    @Test
+    void httpsWithCustomCipherButClientWithIncompatibleCipher(TestInfo 
testInfo) {
+        // When GET /management/v1/configuration/node
+        URI uri = URI.create(httpsWithCustomCipherNode.httpsAddress() + 
"/management/v1/configuration/node");
+        HttpRequest request = HttpRequest.newBuilder(uri).build();
+
+        // Expect IOException for SSL client that configures incompatible 
cipher
+        assertThrows(IOException.class, () -> 
sslClientWithCustomCipher.send(request, BodyHandlers.ofString()));
+    }
+
     @AfterAll
     static void afterAll() {
-        Stream.of(httpNode, httpsNode, dualProtocolNode, 
httpsWithClientAuthNode)
+        Stream.of(httpNode, httpsNode, dualProtocolNode, 
httpsWithClientAuthNode, httpsWithCustomCipherNode)
                 .parallel()
                 .forEach(RestNode::stop);
     }
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 4e5a4471fe..164433becf 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
@@ -18,6 +18,11 @@
 package org.apache.ignite.internal.ssl;
 
 import static org.apache.ignite.client.ClientAuthenticationMode.REQUIRE;
+import static org.apache.ignite.internal.rest.RestNode.escapeWindowsPath;
+import static org.apache.ignite.internal.rest.RestNode.getResourcePath;
+import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willTimeoutIn;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
@@ -27,11 +32,17 @@ import java.nio.file.Path;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.InitParameters;
 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.IgniteIntegrationTest;
+import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.intellij.lang.annotations.Language;
 import org.junit.jupiter.api.AfterEach;
@@ -54,8 +65,8 @@ public class ItSslTest extends IgniteIntegrationTest {
     @BeforeAll
     static void beforeAll() {
         password = "changeit";
-        trustStorePath = 
ItSslTest.class.getClassLoader().getResource("ssl/truststore.jks").getPath();
-        keyStorePath = 
ItSslTest.class.getClassLoader().getResource("ssl/keystore.p12").getPath();
+        trustStorePath = 
getResourcePath(ItSslTest.class.getClassLoader().getResource("ssl/truststore.jks"));
+        keyStorePath = 
getResourcePath(ItSslTest.class.getClassLoader().getResource("ssl/keystore.p12"));
     }
 
     @Nested
@@ -105,7 +116,7 @@ public class ItSslTest extends IgniteIntegrationTest {
 
         @Test
         @DisplayName("Client can not connect with ssl configured when ssl 
disabled on the cluster")
-        void clientCanNotConnectWithoutSsl() {
+        void clientCanNotConnectWithSsl() {
             var sslConfiguration =
                     SslConfiguration.builder()
                             .enabled(true)
@@ -156,11 +167,11 @@ public class ItSslTest extends IgniteIntegrationTest {
                 + "      enabled: true,\n"
                 + "      trustStore: {\n"
                 + "        password: \"" + password + "\","
-                + "        path: \"" + trustStorePath + "\""
+                + "        path: \"" + escapeWindowsPath(trustStorePath) + "\""
                 + "      },\n"
                 + "      keyStore: {\n"
                 + "        password: \"" + password + "\","
-                + "        path: \"" + keyStorePath + "\""
+                + "        path: \"" + escapeWindowsPath(keyStorePath) + "\""
                 + "      }\n"
                 + "    },\n"
                 + "    port: 3345,\n"
@@ -172,7 +183,7 @@ public class ItSslTest extends IgniteIntegrationTest {
                 + "  clientConnector.ssl: {\n"
                 + "    enabled: true, "
                 + "    keyStore: {\n"
-                + "      path: \"" + keyStorePath + "\",\n"
+                + "      path: \"" + escapeWindowsPath(keyStorePath) + "\",\n"
                 + "      password: \"" + password + "\"\n"
                 + "    }\n"
                 + "  }\n"
@@ -250,6 +261,120 @@ public class ItSslTest extends IgniteIntegrationTest {
         }
     }
 
+    @Nested
+    @DisplayName("Given SSL enabled on the cluster and specific cipher 
enabled")
+    class ClusterWithSslCustomCipher {
+
+        String sslEnabledWithCipherBoostrapConfig = 
createBoostrapConfig("TLS_AES_256_GCM_SHA384");
+
+        @WorkDirectory
+        private Path workDir;
+        private Cluster cluster;
+
+        @BeforeEach
+        void setUp(TestInfo testInfo) {
+            cluster = new Cluster(testInfo, workDir, 
sslEnabledWithCipherBoostrapConfig);
+            cluster.startAndInit(2);
+        }
+
+        @AfterEach
+        void tearDown() {
+            cluster.shutdown();
+        }
+
+        @Test
+        @DisplayName("SSL enabled and setup correctly then cluster starts")
+        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
+                } catch (IgniteClientConnectionException e) {
+                    throw e;
+                }
+            });
+        }
+
+        @Test
+        void jdbcCannotConnectWithoutSsl() {
+            var url = "jdbc:ignite:thin://127.0.0.1:10800";
+            assertThrows(SQLException.class, () -> 
DriverManager.getConnection(url));
+        }
+
+        @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));
+            }
+        }
+
+        @Test
+        @DisplayName("Client cannot connect with SSL configured with 
non-matcher cipher")
+        void clientCannotConnectWithSslAndWrongCipher() {
+            var sslConfiguration =
+                    SslConfiguration.builder()
+                            .enabled(true)
+                            
.ciphers(List.of("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"))
+                            .trustStoreType("JKS")
+                            .trustStorePath(trustStorePath)
+                            .trustStorePassword(password)
+                            .build();
+
+            assertThrows(IgniteClientConnectionException.class, () -> {
+                try (IgniteClient ignored = IgniteClient.builder()
+                        .addresses("localhost:10800")
+                        .ssl(sslConfiguration)
+                        .build()) {
+                    // no-op
+                }
+            });
+        }
+
+        @Test
+        void jdbcCannotConnectWithSslAndWrongCipher() {
+            var url =
+                    "jdbc:ignite:thin://127.0.0.1:10800"
+                            + "?sslEnabled=true"
+                            + 
"&ciphers=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
+                            + "&trustStorePath=" + trustStorePath
+                            + "&trustStoreType=JKS"
+                            + "&trustStorePassword=" + password;
+            assertThrows(SQLException.class, () -> 
DriverManager.getConnection(url));
+        }
+
+        @Test
+        @DisplayName("Jdbc client can connect with SSL configured")
+        void jdbcCanConnectWithSsl() throws SQLException {
+            var url =
+                    "jdbc:ignite:thin://127.0.0.1:10800"
+                            + "?sslEnabled=true"
+                            + "&trustStorePath=" + trustStorePath
+                            + "&trustStoreType=JKS"
+                            + "&trustStorePassword=" + password;
+            try (Connection conn = DriverManager.getConnection(url)) {
+                // No-op.
+            }
+        }
+    }
+
     @Nested
     @DisplayName("Given SSL enabled client auth is set to require on the 
cluster")
     class ClusterWithSslAndClientAuth {
@@ -262,11 +387,11 @@ public class ItSslTest extends IgniteIntegrationTest {
                 + "      clientAuth: \"require\",\n"
                 + "      trustStore: {\n"
                 + "        password: \"" + password + "\","
-                + "        path: \"" + trustStorePath + "\""
+                + "        path: \"" + escapeWindowsPath(trustStorePath) + "\""
                 + "      },\n"
                 + "      keyStore: {\n"
                 + "        password: \"" + password + "\","
-                + "        path: \"" + keyStorePath + "\""
+                + "        path: \"" + escapeWindowsPath(keyStorePath) + "\""
                 + "      }\n"
                 + "    },\n"
                 + "    port: 3365,\n"
@@ -279,13 +404,13 @@ public class ItSslTest extends IgniteIntegrationTest {
                 + "    enabled: true, "
                 + "    clientAuth: \"require\", "
                 + "    keyStore: {\n"
-                + "      path: \"" + keyStorePath + "\",\n"
+                + "      path: \"" + escapeWindowsPath(keyStorePath) + "\",\n"
                 + "      password: \"" + password + "\"\n"
                 + "    }, \n"
                 + "    trustStore: {\n"
                 + "      type: JKS,"
                 + "      password: \"" + password + "\","
-                + "      path: \"" + trustStorePath + "\""
+                + "      path: \"" + escapeWindowsPath(trustStorePath) + "\""
                 + "      }\n"
                 + "  }\n"
                 + "}";
@@ -387,4 +512,66 @@ public class ItSslTest extends IgniteIntegrationTest {
             }
         }
     }
+
+    @Test
+    @DisplayName("Cluster is not initialized when nodes are configured with 
incompatible ciphers")
+    void incompatibleCiphersNodes(@WorkDirectory Path workDir, TestInfo 
testInfo) {
+        Cluster cluster = new Cluster(testInfo, workDir);
+
+        String sslEnabledWithCipher1BoostrapConfig = 
createBoostrapConfig("TLS_AES_256_GCM_SHA384");
+        String sslEnabledWithCipher2BoostrapConfig = 
createBoostrapConfig("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
+
+        CompletableFuture<IgniteImpl> node1 = cluster.startClusterNode(0, 
sslEnabledWithCipher1BoostrapConfig);
+
+        String metaStorageAndCmgNodeName = testNodeName(testInfo, 0);
+
+        InitParameters initParameters = InitParameters.builder()
+                .destinationNodeName(metaStorageAndCmgNodeName)
+                .metaStorageNodeNames(List.of(metaStorageAndCmgNodeName))
+                .clusterName("cluster")
+                .build();
+
+        IgnitionManager.init(initParameters);
+
+        // First node will initialize the cluster with single node 
successfully since the second node can't connect to it.
+        assertThat(node1, willCompleteSuccessfully());
+
+        CompletableFuture<IgniteImpl> node2 = cluster.startClusterNode(1, 
sslEnabledWithCipher2BoostrapConfig);
+        assertThat(node2, willTimeoutIn(1, TimeUnit.SECONDS));
+
+        cluster.shutdown();
+    }
+
+    @Language("JSON")
+    private static String createBoostrapConfig(String ciphers) {
+        return "{\n"
+                + "  network: {\n"
+                + "    ssl : {"
+                + "      enabled: true,\n"
+                + "      ciphers: " + ciphers + ",\n"
+                + "      trustStore: {\n"
+                + "        password: \"" + password + "\","
+                + "        path: \"" + escapeWindowsPath(trustStorePath) + "\""
+                + "      },\n"
+                + "      keyStore: {\n"
+                + "        password: \"" + password + "\","
+                + "        path: \"" + escapeWindowsPath(keyStorePath) + "\""
+                + "      }\n"
+                + "    },\n"
+                + "    port: 3345,\n"
+                + "    portRange: 2,\n"
+                + "    nodeFinder:{\n"
+                + "      netClusterNodes: [ \"localhost:3345\", 
\"localhost:3346\" ]\n"
+                + "    }\n"
+                + "  },\n"
+                + "  clientConnector.ssl: {\n"
+                + "    enabled: true, "
+                + "    ciphers: " + ciphers + ",\n"
+                + "    keyStore: {\n"
+                + "      path: \"" + escapeWindowsPath(keyStorePath) + "\",\n"
+                + "      password: \"" + password + "\"\n"
+                + "    }\n"
+                + "  }\n"
+                + "}";
+    }
 }

Reply via email to