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));
+ }
+ }
}
}