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 9c48f83e7e IGNITE-18791 Add SSL Client authentication to REST (#1690) 9c48f83e7e is described below commit 9c48f83e7e22e097a7db10937db57a2a87202d59 Author: Aleksandr <apk...@gmail.com> AuthorDate: Tue Feb 21 11:47:47 2023 +0400 IGNITE-18791 Add SSL Client authentication to REST (#1690) TCP Client and JDBC support this feature, now REST does it too. --- modules/rest/build.gradle | 6 + .../apache/ignite/internal/rest/RestComponent.java | 96 +++++++++++++-- .../configuration/RestSslConfigurationSchema.java | 15 +-- .../ignite/internal/rest/RestComponentTest.java | 134 +++++++++++++++++++++ .../ignite/internal/rest/TestController.java | 33 +++++ .../ignite/internal/rest/ItPortRangeTest.java | 1 + .../ignite/internal/rest/ssl/ItRestSslTest.java | 87 +++++++++++-- .../apache/ignite/internal/rest/ssl/RestNode.java | 19 ++- 8 files changed, 356 insertions(+), 35 deletions(-) diff --git a/modules/rest/build.gradle b/modules/rest/build.gradle index 3e1dea30d4..c9b2951bd4 100644 --- a/modules/rest/build.gradle +++ b/modules/rest/build.gradle @@ -40,8 +40,14 @@ dependencies { annotationProcessor libs.auto.service + testAnnotationProcessor libs.micronaut.inject.annotation.processor + testImplementation project(':ignite-configuration') + testImplementation(testFixtures(project(':ignite-core'))) + testImplementation(testFixtures(project(':ignite-configuration'))) + testImplementation libs.hamcrest.core testImplementation libs.slf4j.jdk14 + } compileJava { 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 d5fc16c084..755a253f1d 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 @@ -21,9 +21,11 @@ import static io.micronaut.context.env.Environment.BARE_METAL; import io.micronaut.context.ApplicationContext; import io.micronaut.http.server.exceptions.ServerStartupException; +import io.micronaut.http.ssl.ClientAuthentication; import io.micronaut.openapi.annotation.OpenAPIInclude; import io.micronaut.runtime.Micronaut; import io.micronaut.runtime.exceptions.ApplicationStartupException; +import io.netty.handler.ssl.ClientAuth; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; @@ -31,6 +33,9 @@ import io.swagger.v3.oas.annotations.info.License; import java.net.BindException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.ignite.internal.logger.IgniteLogger; @@ -43,8 +48,11 @@ import org.apache.ignite.internal.rest.api.configuration.NodeConfigurationApi; import org.apache.ignite.internal.rest.api.metric.NodeMetricApi; import org.apache.ignite.internal.rest.api.node.NodeManagementApi; import org.apache.ignite.internal.rest.configuration.RestConfiguration; +import org.apache.ignite.internal.rest.configuration.RestSslConfiguration; import org.apache.ignite.internal.rest.configuration.RestSslView; import org.apache.ignite.internal.rest.configuration.RestView; +import org.apache.ignite.lang.ErrorGroups.Common; +import org.apache.ignite.lang.IgniteException; import org.apache.ignite.lang.IgniteInternalException; import org.jetbrains.annotations.Nullable; @@ -147,7 +155,7 @@ public class RestComponent implements IgniteComponent { + " [HTTP ports=[" + desiredHttpPort + ", " + desiredHttpPort + portRange + "]]," + " [HTTPS ports=[" + desiredHttpsPort + ", " + desiredHttpsPort + httpsPortRange + "]]"; - throw new RuntimeException(msg); + throw new IgniteException(Common.UNEXPECTED_ERR, msg); } /** Starts Micronaut application using the provided ports. @@ -171,7 +179,7 @@ public class RestComponent implements IgniteComponent { if (bindException != null) { return false; } - throw new RuntimeException(e); + throw new IgniteException(Common.UNEXPECTED_ERR, e); } } @@ -212,14 +220,20 @@ public class RestComponent implements IgniteComponent { } private Map<String, Object> properties(int port, int sslPort) { - boolean dualProtocol = restConfiguration.dualProtocol().value(); - boolean sslEnabled = restConfiguration.ssl().enabled().value(); - String keyStoreType = restConfiguration.ssl().keyStore().type().value(); - String keyStorePath = restConfiguration.ssl().keyStore().path().value(); - String keyStorePassword = restConfiguration.ssl().keyStore().password().value(); + RestSslConfiguration sslCfg = restConfiguration.ssl(); + boolean sslEnabled = sslCfg.enabled().value(); if (sslEnabled) { - return Map.of( + String keyStorePath = sslCfg.keyStore().path().value(); + // todo: replace with configuration-level validation https://issues.apache.org/jira/browse/IGNITE-18850 + validateKeyStorePath(keyStorePath); + + String keyStoreType = sslCfg.keyStore().type().value(); + String keyStorePassword = sslCfg.keyStore().password().value(); + + 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, @@ -228,11 +242,77 @@ public class RestComponent implements IgniteComponent { "micronaut.server.ssl.key-store.password", keyStorePassword, "micronaut.server.ssl.key-store.type", keyStoreType ); + + ClientAuth clientAuth = ClientAuth.valueOf(sslCfg.clientAuth().value().toUpperCase()); + if (ClientAuth.NONE == clientAuth) { + return micronautSslConfig; + } + + + String trustStorePath = sslCfg.trustStore().path().value(); + // todo: replace with configuration-level validation https://issues.apache.org/jira/browse/IGNITE-18850 + validateTrustStore(trustStorePath); + + String trustStoreType = sslCfg.trustStore().type().value(); + String trustStorePassword = sslCfg.trustStore().password().value(); + + Map<String, Object> micronautClientAuthConfig = Map.of( + "micronaut.server.ssl.client-authentication", toMicronautClientAuth(clientAuth), + "micronaut.server.ssl.trust-store.path", "file:" + trustStorePath, + "micronaut.server.ssl.trust-store.password", trustStorePassword, + "micronaut.server.ssl.trust-store.type", trustStoreType + ); + + HashMap<String, Object> result = new HashMap<>(); + result.putAll(micronautSslConfig); + result.putAll(micronautClientAuthConfig); + + return result; } else { return Map.of("micronaut.server.port", port); } } + private static void validateKeyStorePath(String keyStorePath) { + if (keyStorePath.trim().isEmpty()) { + throw new IgniteException( + Common.SSL_CONFIGURATION_ERR, + "Trust store path is not configured. Please check your rest.ssl.keyStore.path configuration." + ); + } + + if (!Files.exists(Path.of(keyStorePath))) { + throw new IgniteException( + Common.SSL_CONFIGURATION_ERR, + "Trust store file not found: " + keyStorePath + ". Please check your rest.ssl.keyStore.path configuration." + ); + } + } + + private static void validateTrustStore(String trustStorePath) { + if (trustStorePath.trim().isEmpty()) { + throw new IgniteException( + Common.SSL_CONFIGURATION_ERR, + "Key store path is not configured. Please check your rest.ssl.trustStore.path configuration." + ); + } + + if (!Files.exists(Path.of(trustStorePath))) { + throw new IgniteException( + Common.SSL_CONFIGURATION_ERR, + "Key store file not found: " + trustStorePath + ". Please check your rest.ssl.trustStore.path configuration." + ); + } + } + + private String toMicronautClientAuth(ClientAuth clientAuth) { + switch (clientAuth) { + case OPTIONAL: return ClientAuthentication.WANT.name().toLowerCase(); + case REQUIRE: return ClientAuthentication.NEED.name().toLowerCase(); + default: throw new IllegalArgumentException("Can not convert " + clientAuth.name() + " to micronaut type"); + } + } + /** {@inheritDoc} */ @Override public synchronized void stop() throws Exception { diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/RestSslConfigurationSchema.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/RestSslConfigurationSchema.java index 00da64cad5..dbd2908d41 100644 --- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/RestSslConfigurationSchema.java +++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/RestSslConfigurationSchema.java @@ -18,19 +18,13 @@ package org.apache.ignite.internal.rest.configuration; import org.apache.ignite.configuration.annotation.Config; -import org.apache.ignite.configuration.annotation.ConfigValue; import org.apache.ignite.configuration.annotation.Value; import org.apache.ignite.configuration.validation.Range; -import org.apache.ignite.internal.network.configuration.KeyStoreConfigurationSchema; -import org.apache.ignite.internal.network.configuration.KeyStoreConfigurationValidator; +import org.apache.ignite.internal.network.configuration.AbstractSslConfigurationSchema; /** REST SSL configuration. */ @Config -public class RestSslConfigurationSchema { - - /** Whether SSL is enabled. */ - @Value(hasDefault = true) - public final boolean enabled = false; +public class RestSslConfigurationSchema extends AbstractSslConfigurationSchema { /** SSL port. */ @Range(min = 1024, max = 0xFFFF) @@ -41,9 +35,4 @@ public class RestSslConfigurationSchema { @Range(min = 0) @Value(hasDefault = true) public final int portRange = 100; - - /** SSL keystore. */ - @KeyStoreConfigurationValidator - @ConfigValue - public KeyStoreConfigurationSchema keyStore; } diff --git a/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java new file mode 100644 index 0000000000..a1eaa2d6fd --- /dev/null +++ b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java @@ -0,0 +1,134 @@ +/* + * 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.rest; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +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.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +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 java.util.List; +import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension; +import org.apache.ignite.internal.configuration.testframework.InjectConfiguration; +import org.apache.ignite.internal.rest.configuration.RestConfiguration; +import org.apache.ignite.internal.testframework.WorkDirectory; +import org.apache.ignite.internal.testframework.WorkDirectoryExtension; +import org.apache.ignite.lang.ErrorGroups.Common; +import org.apache.ignite.lang.IgniteException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(ConfigurationExtension.class) +@ExtendWith(WorkDirectoryExtension.class) +class RestComponentTest { + + private String keyStorePath; + + @WorkDirectory + private Path workDir; + + private static void pingOk(RestComponent component) throws IOException, InterruptedException { + HttpClient client = HttpClient.newBuilder().build(); + HttpResponse<String> response = client.send( + HttpRequest.newBuilder() + .uri(URI.create("http://" + component.host() + ":" + component.httpPort() + "/ping")).GET() + .build(), + BodyHandlers.ofString() + ); + + assertThat(response.statusCode(), is(200)); + assertThat(response.body(), is("pong")); + } + + @BeforeEach + void setUp() { + keyStorePath = workDir.resolve("keystore.p12").toAbsolutePath().toString(); + } + + @Test + @DisplayName("REST component stars with default configuration") + void defaultConfiguration(@InjectConfiguration RestConfiguration restConfiguration) throws Exception { + // Given + RestComponent component = new RestComponent(List.of(), restConfiguration); + + // When + component.start(); + + // Then + pingOk(component); + } + + @Test + @DisplayName("REST component does not start with ssl.enabled=true and no keystore") + void sslConfiguration(@InjectConfiguration("mock.ssl.enabled: true") RestConfiguration restConfiguration) { + // Given + RestComponent component = new RestComponent(List.of(), restConfiguration); + + // When + IgniteException thrown = assertThrows(IgniteException.class, component::start); + + // Then + assertThat(thrown.code(), is(Common.SSL_CONFIGURATION_ERR)); + } + + @Test + @DisplayName("REST component does not start with ssl.clientAuth=require and no truststore") + void clientAuthConfiguration(@InjectConfiguration RestConfiguration restConfiguration) throws Exception { + // Given correct keystore + generateKeystore(new SelfSignedCertificate()); + restConfiguration.ssl().enabled().update(true).get(); + restConfiguration.ssl().keyStore().path().update(keyStorePath).get(); + restConfiguration.ssl().keyStore().password().update("changeit").get(); + // And clientAuth=require But no truststore + restConfiguration.ssl().clientAuth().update("require").get(); + + RestComponent component = new RestComponent(List.of(), restConfiguration); + + // When + IgniteException thrown = assertThrows(IgniteException.class, component::start); + + // Then + assertThat(thrown.code(), is(Common.SSL_CONFIGURATION_ERR)); + } + + 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(keyStorePath)) { + ks.store(fos, "changeit".toCharArray()); + } + } +} diff --git a/modules/rest/src/test/java/org/apache/ignite/internal/rest/TestController.java b/modules/rest/src/test/java/org/apache/ignite/internal/rest/TestController.java new file mode 100644 index 0000000000..214c22d401 --- /dev/null +++ b/modules/rest/src/test/java/org/apache/ignite/internal/rest/TestController.java @@ -0,0 +1,33 @@ +/* + * 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.rest; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; + +/** + * Test controller. Exposes a single ping endpoint. + */ +@Controller +public class TestController { + @Get("ping") + public String ping() { + return "pong"; + } + +} diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItPortRangeTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItPortRangeTest.java index fb5f7078ad..ed5e48165f 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItPortRangeTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItPortRangeTest.java @@ -99,6 +99,7 @@ public class ItPortRangeTest { 10300, 10400, true, + false, true )) .collect(Collectors.toList()); 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 e2e608cf31..82e213b829 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 @@ -34,8 +34,10 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.stream.Stream; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.apache.ignite.internal.testframework.WorkDirectory; @@ -62,6 +64,12 @@ public class ItRestSslTest { /** Trust store password. */ private static final String trustStorePassword = "changeit"; + /** Key store path. */ + private static final String keyStorePath = "ssl/keystore.p12"; + + /** Key store password. */ + private static final String keyStorePassword = "changeit"; + /** Path to the working directory. */ @WorkDirectory private static Path workDir; @@ -72,12 +80,17 @@ public class ItRestSslTest { /** SSL HTTP client that is expected to be defined in subclasses. */ private static HttpClient sslClient; + /** SSL HTTP client that is expected to be defined in subclasses. */ + private static HttpClient sslClientWithClientAuth; + private static RestNode httpNode; private static RestNode httpsNode; private static RestNode dualProtocolNode; + private static RestNode httpsWithClientAuthNode; + @BeforeAll static void beforeAll(TestInfo testInfo) throws Exception { @@ -88,11 +101,16 @@ public class ItRestSslTest { .sslContext(sslContext()) .build(); - httpNode = new RestNode(workDir, testNodeName(testInfo, 3344), 3344, 10300, 10400, false, false); - httpsNode = new RestNode(workDir, testNodeName(testInfo, 3345), 3345, 10301, 10401, true, false); - dualProtocolNode = new RestNode(workDir, testNodeName(testInfo, 3346), 3346, 10302, 10402, true, true); - Stream.of(httpNode, httpsNode, dualProtocolNode) - .forEach(RestNode::start); + sslClientWithClientAuth = HttpClient.newBuilder() + .sslContext(sslContextWithClientAuth()) + .build(); + + httpNode = new RestNode(workDir, testNodeName(testInfo, 3344), 3344, 10300, 10400, false, false, false); + httpsNode = new RestNode(workDir, testNodeName(testInfo, 3345), 3345, 10301, 10401, true, false, false); + dualProtocolNode = new RestNode(workDir, testNodeName(testInfo, 3346), 3346, 10302, 10402, true, false, true); + httpsWithClientAuthNode = new RestNode(workDir, testNodeName(testInfo, 3347), 3347, 10303, 10403, true, true, false); + + Stream.of(httpNode, httpsNode, dualProtocolNode, httpsWithClientAuthNode).forEach(RestNode::start); } @Test @@ -143,7 +161,6 @@ public class ItRestSslTest { // Then IOException assertThrows(IOException.class, () -> client.send(request, BodyHandlers.ofString())); - } @Test @@ -156,20 +173,64 @@ public class ItRestSslTest { assertThrows(IOException.class, () -> client.send(request, BodyHandlers.ofString())); } + @Test + void httpsWithClientAuthProtocol(TestInfo testInfo) throws IOException, InterruptedException { + // When GET /management/v1/configuration/node + URI uri = URI.create(httpsWithClientAuthNode.httpsAddress() + "/management/v1/configuration/node"); + HttpRequest request = HttpRequest.newBuilder(uri).build(); + + // Then response code is 200 + HttpResponse<String> response = sslClientWithClientAuth.send(request, BodyHandlers.ofString()); + assertEquals(200, response.statusCode()); + } + + @Test + void httpsWithClientAuthProtocolButClientWithoutAuth(TestInfo testInfo) throws IOException, InterruptedException { + // When GET /management/v1/configuration/node + URI uri = URI.create(httpsWithClientAuthNode.httpsAddress() + "/management/v1/configuration/node"); + HttpRequest request = HttpRequest.newBuilder(uri).build(); + + // Expect IOException for SSL client that does not configure client auth + assertThrows(IOException.class, () -> sslClient.send(request, BodyHandlers.ofString())); + } + @AfterAll static void afterAll() { - Stream.of(httpNode, httpsNode, dualProtocolNode) - .forEach(RestNode::stop); + Stream.of(httpNode, httpsNode, dualProtocolNode, httpsWithClientAuthNode).forEach(RestNode::stop); } - private static SSLContext sslContext() - throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException { - String path = ItRestSslTest.class.getClassLoader().getResource(trustStorePath).getPath(); + private static SSLContext sslContext() throws CertificateException, KeyStoreException, IOException, + NoSuchAlgorithmException, KeyManagementException { + + String tsPath = ItRestSslTest.class.getClassLoader().getResource(trustStorePath).getPath(); + + KeyStore trustStore = KeyStore.getInstance(new File(tsPath), trustStorePassword.toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - KeyStore keyStore = KeyStore.getInstance(new File(path), trustStorePassword.toCharArray()); - trustManagerFactory.init(keyStore); + trustManagerFactory.init(trustStore); + SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); + + return sslContext; + } + + private static SSLContext sslContextWithClientAuth() throws CertificateException, KeyStoreException, IOException, + NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException { + + String tsPath = ItRestSslTest.class.getClassLoader().getResource(trustStorePath).getPath(); + String ksPath = ItRestSslTest.class.getClassLoader().getResource(keyStorePath).getPath(); + + KeyStore trustStore = KeyStore.getInstance(new File(tsPath), trustStorePassword.toCharArray()); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + + KeyStore keyStore = KeyStore.getInstance(new File(ksPath), keyStorePassword.toCharArray()); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom()); + return sslContext; } } diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/RestNode.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/RestNode.java index ca19ddb61f..ca3f9cb06f 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/RestNode.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ssl/RestNode.java @@ -29,12 +29,19 @@ public class RestNode { /** Key store password. */ private static final String keyStorePassword = "changeit"; + /** Trust store path. */ + private static final String trustStorePath = "ssl/truststore.jks"; + + /** Trust store password. */ + private static final String trustStorePassword = "changeit"; + private final Path workDir; private final String name; private final int networkPort; private final int httpPort; private final int httpsPort; private final boolean sslEnabled; + private final boolean sslClientAuthEnabled; private final boolean dualProtocol; /** Constructor. */ @@ -45,6 +52,7 @@ public class RestNode { int httpPort, int httpsPort, boolean sslEnabled, + boolean sslClientAuthEnabled, boolean dualProtocol ) { this.workDir = workDir; @@ -53,6 +61,7 @@ public class RestNode { this.httpPort = httpPort; this.httpsPort = httpsPort; this.sslEnabled = sslEnabled; + this.sslClientAuthEnabled = sslClientAuthEnabled; this.dualProtocol = dualProtocol; } @@ -75,6 +84,8 @@ public class RestNode { private String bootstrapCfg() { String keyStoreAbsolutPath = ItRestSslTest.class.getClassLoader().getResource(keyStorePath).getPath(); + String trustStoreAbsolutPath = ItRestSslTest.class.getClassLoader().getResource(trustStorePath).getPath(); + return "{\n" + " network: {\n" + " port: " + networkPort + ",\n" @@ -87,10 +98,16 @@ public class RestNode { + " dualProtocol: " + dualProtocol + ",\n" + " ssl: {\n" + " enabled: " + sslEnabled + ",\n" + + " clientAuth: " + (sslClientAuthEnabled ? "require" : "none") + ",\n" + " port: " + httpsPort + ",\n" + " keyStore: {\n" + " path: " + keyStoreAbsolutPath + ",\n" - + " password: " + keyStorePassword + "\n" + + " password: " + keyStorePassword + "\n" + + " }, \n" + + " trustStore: {\n" + + " type: JKS, " + + " path: " + trustStoreAbsolutPath + ",\n" + + " password: " + trustStorePassword + "\n" + " }\n" + " }\n" + " }"