This is an automated email from the ASF dual-hosted git repository.
kfaraz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 9350eb90430 Migrate `ITTLSTest` to embedded-tests (#19037)
9350eb90430 is described below
commit 9350eb904306d97a86088c42079d9019abf67775
Author: Kashif Faraz <[email protected]>
AuthorDate: Mon Feb 23 22:10:08 2026 +0530
Migrate `ITTLSTest` to embedded-tests (#19037)
Changes:
- Migrate `ITTLSTest` to embedded-tests
- Add `EmbeddedSSLAuthResource`
- Update scripts in `docker/tls` so that they work with
`EmbeddedTLSAuthResource`
---
.github/workflows/cron-job-its.yml | 15 -
.github/workflows/standard-its.yml | 16 -
.../unit-and-integration-tests-unified.yml | 6 +-
embedded-tests/pom.xml | 6 +
.../embedded/auth/EmbeddedSSLAuthResource.java | 152 ++++++
.../druid/testing/embedded/auth/TLSTest.java | 491 ++++++++++++++++++
.../embedded/auth/TestTLSCertificateChecker.java | 14 +-
.../auth/TestTLSCertificateCheckerModule.java | 11 +-
.../org.apache.druid.initialization.DruidModule | 1 +
.../environment-configs/router-custom-check-tls | 1 -
.../tls/generate-client-certs-and-keystores.sh | 24 +-
.../docker/tls/generate-root-certs.sh | 16 +-
.../tls/generate-server-certs-and-keystores.sh | 30 +-
integration-tests/docker/tls/root.cnf | 2 +-
integration-tests/docker/tls/root2.cnf | 2 +-
integration-tests/pom.xml | 5 -
.../org.apache.druid.initialization.DruidModule | 16 -
.../java/org/apache/druid/tests/TestNGGroup.java | 4 -
.../org/apache/druid/tests/security/ITTLSTest.java | 552 ---------------------
.../embedded/junit5/EmbeddedClusterTestBase.java | 13 +
20 files changed, 716 insertions(+), 661 deletions(-)
diff --git a/.github/workflows/cron-job-its.yml
b/.github/workflows/cron-job-its.yml
index 25a4259f8df..877cdf067f4 100644
--- a/.github/workflows/cron-job-its.yml
+++ b/.github/workflows/cron-job-its.yml
@@ -56,21 +56,6 @@ jobs:
run: |
./it.sh ci
- integration-query-tests-middleManager:
- strategy:
- fail-fast: false
- matrix:
- testing_group: [ security ]
- uses: ./.github/workflows/reusable-standard-its.yml
- needs: build
- with:
- build_jdk: 17
- runtime_jdk: 17
- testing_groups: -Dgroups=${{ matrix.testing_group }}
- use_indexer: middleManager
- override_config_path: ./environment-configs/test-groups/prepopulated-data
- group: ${{ matrix.testing_group }}
-
security_vulnerabilities:
if: github.repository == 'apache/druid'
name: security vulnerabilities
diff --git a/.github/workflows/standard-its.yml
b/.github/workflows/standard-its.yml
index be8b19ef9a2..1a26febe3d9 100644
--- a/.github/workflows/standard-its.yml
+++ b/.github/workflows/standard-its.yml
@@ -41,19 +41,3 @@ jobs:
-
'extension-core/(mysql-metadata-storage|druid-basic-security|simple-client-sslcontext|druid-testing-tools|druid-lookups-cached-global|druid-histogram|druid-datasketches|druid-parquet-extensions|druid-avro-extensions|druid-protobuf-extensions|druid-orc-extensions|druid-kafka-indexing-service|druid-s3-extensions)/**'
core:
- '!extension*/**'
-
- integration-query-tests-middleManager:
- needs: changes
- strategy:
- fail-fast: false
- matrix:
- testing_group: [security]
- uses: ./.github/workflows/reusable-standard-its.yml
- if: ${{ needs.changes.outputs.core == 'true' ||
needs.changes.outputs.common-extensions == 'true' }}
- with:
- build_jdk: 17
- runtime_jdk: 17
- testing_groups: -Dgroups=${{ matrix.testing_group }}
- use_indexer: middleManager
- override_config_path: ./environment-configs/test-groups/prepopulated-data
- group: ${{ matrix.testing_group }}
diff --git a/.github/workflows/unit-and-integration-tests-unified.yml
b/.github/workflows/unit-and-integration-tests-unified.yml
index 57390917932..b5860b49455 100644
--- a/.github/workflows/unit-and-integration-tests-unified.yml
+++ b/.github/workflows/unit-and-integration-tests-unified.yml
@@ -55,16 +55,12 @@ jobs:
name: "unit tests"
uses: ./.github/workflows/ci.yml
- standard-its:
- needs: [unit-tests]
- uses: ./.github/workflows/standard-its.yml
-
docker-tests:
needs: [unit-tests]
uses: ./.github/workflows/docker-tests.yml
actions-timeline:
- needs: [unit-tests, standard-its, docker-tests]
+ needs: [unit-tests, docker-tests]
runs-on: ubuntu-latest
if: ${{ !cancelled() }}
steps:
diff --git a/embedded-tests/pom.xml b/embedded-tests/pom.xml
index 8548bd7668a..8a187613dd2 100644
--- a/embedded-tests/pom.xml
+++ b/embedded-tests/pom.xml
@@ -303,6 +303,12 @@
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.druid.extensions</groupId>
+ <artifactId>simple-client-sslcontext</artifactId>
+ <version>${parent.version}</version>
+ <scope>test</scope>
+ </dependency>
<!-- Some embedded tests use the router and the web-console for debugging
-->
<dependency>
diff --git
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/EmbeddedSSLAuthResource.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/EmbeddedSSLAuthResource.java
new file mode 100644
index 00000000000..900272ce3fa
--- /dev/null
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/EmbeddedSSLAuthResource.java
@@ -0,0 +1,152 @@
+/*
+ * 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.druid.testing.embedded.auth;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.druid.https.SSLContextModule;
+import org.apache.druid.java.util.common.ISE;
+import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
+import org.apache.druid.testing.embedded.EmbeddedResource;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * {@link EmbeddedResource} used to enable SSL on Druid services.
+ * This resource is responsible for the following:
+ * <ul>
+ * <li>Enable TLS port on the Druid services</li>
+ * <li>Generate client certificates using script
+ * {@code
integration-tests/docker/tls/generate-client-certs-and-keystores.sh}</li>
+ * <li>Generate server truststore using script
+ * {@code
integration-tests/docker/tls/generate-server-certs-and-keystores.sh}</li>
+ * <li>Keep the generated certificates in the {@code TestFolder} used by the
cluster</li>
+ * <li>Configure the cluster with the appropriate keystore/truststore
paths.</li>
+ * </ul>
+ */
+public class EmbeddedSSLAuthResource implements EmbeddedResource
+{
+ private EmbeddedDruidCluster cluster;
+
+ @Override
+ public void beforeStart(EmbeddedDruidCluster cluster)
+ {
+ this.cluster = cluster;
+ }
+
+ @Override
+ public void start() throws Exception
+ {
+ final File tlsDir = getTlsTempDirectory();
+
+ final File tlsScripts = new File(tlsDir, "scripts");
+ copyScriptsToDirectory(tlsScripts);
+
+ // Generate client certificates and keystores
+ final ProcessBuilder generateClientCertificates = new ProcessBuilder(
+ "/bin/bash",
+ new File(tlsScripts,
"generate-client-certs-and-keystores.sh").getAbsolutePath()
+ )
+ .directory(tlsDir)
+ .redirectErrorStream(true)
+ .redirectOutput(ProcessBuilder.Redirect.INHERIT);
+
+ int exitCode = generateClientCertificates.start().waitFor();
+ if (exitCode != 0) {
+ throw new ISE("Client certificate generation failed with code[%s]",
exitCode);
+ }
+
+ // Generate server certificates and keystores
+ final ProcessBuilder generateServerCertificates = new ProcessBuilder(
+ "/bin/bash",
+ new File(tlsScripts,
"generate-server-certs-and-keystores.sh").getAbsolutePath()
+ )
+ .directory(tlsDir)
+ .redirectErrorStream(true)
+ .redirectOutput(ProcessBuilder.Redirect.INHERIT);
+
+ exitCode = generateServerCertificates.start().waitFor();
+ if (exitCode != 0) {
+ throw new ISE("Server certificate generation failed with code[%s]",
exitCode);
+ }
+ }
+
+ private void copyScriptsToDirectory(File targetDir)
+ {
+ try {
+ FileUtils.copyDirectory(new File("../integration-tests/docker/tls"),
targetDir);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void onStarted(EmbeddedDruidCluster cluster)
+ {
+ final String truststore = getTlsFilePath("server_tls/truststore.jks");
+ final String keystore = getTlsFilePath("server_tls/server.p12");
+ final String crlPath = getTlsFilePath("server_tls/revocations.crl");
+
+ cluster.addExtension(SSLContextModule.class)
+ .addCommonProperty("druid.enableTlsPort", "true")
+
+ .addCommonProperty("druid.server.https.certAlias", "druid")
+ .addCommonProperty("druid.server.https.keyManagerPassword",
"druid123")
+ .addCommonProperty("druid.server.https.keyStorePassword",
"druid123")
+ .addCommonProperty("druid.server.https.keyStorePath", keystore)
+ .addCommonProperty("druid.server.https.keyStoreType", "PKCS12")
+ .addCommonProperty("druid.server.https.requireClientCertificate",
"true")
+ .addCommonProperty("druid.server.https.trustStoreAlgorithm", "PKIX")
+ .addCommonProperty("druid.server.https.trustStorePassword",
"druid123")
+ .addCommonProperty("druid.server.https.trustStorePath", truststore)
+ .addCommonProperty("druid.server.https.validateHostnames", "true")
+ .addCommonProperty("druid.server.https.crlPath", crlPath)
+
+ .addCommonProperty("druid.client.https.protocol", "TLSv1.2")
+ .addCommonProperty("druid.client.https.certAlias", "druid")
+ .addCommonProperty("druid.client.https.keyManagerPassword",
"druid123")
+ .addCommonProperty("druid.client.https.keyStorePassword",
"druid123")
+ .addCommonProperty("druid.client.https.keyStorePath", keystore)
+ .addCommonProperty("druid.client.https.trustStoreAlgorithm", "PKIX")
+ .addCommonProperty("druid.client.https.trustStorePassword",
"druid123")
+ .addCommonProperty("druid.client.https.trustStorePath", truststore);
+ }
+
+ @Override
+ public void stop() throws Exception
+ {
+ // do nothing
+ }
+
+ /**
+ * @return Absolute path of the given file inside the temporary TLS directory
+ * used by this resource.
+ */
+ public String getTlsFilePath(String filename)
+ {
+ return new File(getTlsTempDirectory(), filename).getAbsolutePath();
+ }
+
+ private File getTlsTempDirectory()
+ {
+ return cluster.getTestFolder().getOrCreateFolder("tls");
+ }
+}
diff --git
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TLSTest.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TLSTest.java
new file mode 100644
index 00000000000..2b04ee3a83c
--- /dev/null
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TLSTest.java
@@ -0,0 +1,491 @@
+/*
+ * 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.druid.testing.embedded.auth;
+
+import com.google.common.base.Throwables;
+import org.apache.druid.guice.http.DruidHttpClientConfig;
+import org.apache.druid.java.util.common.ISE;
+import org.apache.druid.java.util.common.RetryUtils;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.lifecycle.Lifecycle;
+import org.apache.druid.java.util.http.client.CredentialedHttpClient;
+import org.apache.druid.java.util.http.client.HttpClient;
+import org.apache.druid.java.util.http.client.HttpClientConfig;
+import org.apache.druid.java.util.http.client.HttpClientInit;
+import org.apache.druid.java.util.http.client.Request;
+import org.apache.druid.java.util.http.client.auth.BasicCredentials;
+import org.apache.druid.java.util.http.client.response.StatusResponseHandler;
+import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
+import org.apache.druid.metadata.DefaultPasswordProvider;
+import org.apache.druid.server.security.TLSCertificateChecker;
+import org.apache.druid.server.security.TLSUtils;
+import org.apache.druid.testing.embedded.EmbeddedBroker;
+import org.apache.druid.testing.embedded.EmbeddedCoordinator;
+import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
+import org.apache.druid.testing.embedded.EmbeddedDruidServer;
+import org.apache.druid.testing.embedded.EmbeddedHistorical;
+import org.apache.druid.testing.embedded.EmbeddedOverlord;
+import org.apache.druid.testing.embedded.EmbeddedRouter;
+import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpResponseStatus;
+import org.joda.time.Duration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.ClosedChannelException;
+
+/**
+ * Embedded test to verify communication with Druid services over TLS with
+ * various certificate states - expired, revoked, invalid hostname, etc.
+ */
+public class TLSTest extends EmbeddedClusterTestBase
+{
+ private static final int MAX_BROKEN_PIPE_RETRIES = 30;
+ private static final Duration SSL_HANDSHAKE_TIMEOUT = new Duration(30 *
1000);
+
+ private static final String ERROR_CERTIFICATE_UNKNOWN = "Received fatal
alert: certificate_unknown";
+
+ private DruidHttpClientConfig httpClientConfig;
+ private TLSCertificateChecker certificateChecker;
+
+ private final EmbeddedSSLAuthResource sslAuthResource = new
EmbeddedSSLAuthResource();
+ private final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
+ private final EmbeddedOverlord overlord = new EmbeddedOverlord();
+ private final EmbeddedBroker broker = new EmbeddedBroker();
+ private final EmbeddedHistorical historical = new EmbeddedHistorical();
+ private final EmbeddedRouter router = new EmbeddedRouter();
+
+ private final EmbeddedRouter routerAcceptsAnyCert = new EmbeddedRouter()
+ .addProperty("druid.plaintextPort", "8889")
+ .addProperty("druid.tlsPort", "9089")
+ .addProperty("druid.server.https.validateHostnames", "false");
+
+ private final EmbeddedRouter routerNeedsNoClientCert = new EmbeddedRouter()
+ .addProperty("druid.plaintextPort", "8890")
+ .addProperty("druid.tlsPort", "9090")
+ .addProperty("druid.server.https.requireClientCertificate", "false")
+ .addProperty("druid.server.https.validateHostnames", "false");
+
+ private final EmbeddedRouter routerUsesCustomCertChecker = new
EmbeddedRouter()
+ .addProperty("druid.plaintextPort", "8891")
+ .addProperty("druid.tlsPort", "9091")
+ .addProperty("druid.tls.certificateChecker",
TestTLSCertificateCheckerModule.CHECKER_TYPE);
+
+ @Override
+ protected EmbeddedDruidCluster createCluster()
+ {
+ return EmbeddedDruidCluster
+ .withEmbeddedDerbyAndZookeeper()
+ .addResource(new EmbeddedBasicAuthResource())
+ .addExtension(TestTLSCertificateCheckerModule.class)
+ .addResource(sslAuthResource)
+ .addServer(coordinator)
+ .addServer(overlord)
+ .addServer(broker)
+ .addServer(historical)
+ .addServer(router)
+ .addServer(routerAcceptsAnyCert)
+ .addServer(routerNeedsNoClientCert)
+ .addServer(routerUsesCustomCertChecker);
+ }
+
+ @BeforeAll
+ public void initHttpClient()
+ {
+ this.httpClientConfig =
overlord.bindings().getInstance(DruidHttpClientConfig.class);
+ this.certificateChecker =
overlord.bindings().getInstance(TLSCertificateChecker.class);
+ }
+
+ @Test
+ public void testPlaintextAccess()
+ {
+ HttpClient adminClient = new CredentialedHttpClient(
+ new BasicCredentials("admin", "priest"),
+ overlord.bindings().globalHttpClient()
+ );
+ verifyGetStatusIsOk(adminClient, coordinator);
+ verifyGetStatusIsOk(adminClient, overlord);
+ verifyGetStatusIsOk(adminClient, broker);
+ verifyGetStatusIsOk(adminClient, historical);
+ verifyGetStatusIsOk(adminClient, router);
+ verifyGetStatusIsOk(adminClient, routerAcceptsAnyCert);
+ verifyGetStatusIsOk(adminClient, routerNeedsNoClientCert);
+ }
+
+ @Test
+ public void testTLSNodeAccess()
+ {
+ HttpClient adminClient = makeCustomHttpClient(
+ "client_tls/client.jks",
+ "druid"
+ );
+ verifyGetStatusHttpsIsOk(adminClient, coordinator);
+ verifyGetStatusHttpsIsOk(adminClient, overlord);
+ verifyGetStatusHttpsIsOk(adminClient, broker);
+ verifyGetStatusHttpsIsOk(adminClient, historical);
+ verifyGetStatusHttpsIsOk(adminClient, router);
+ verifyGetStatusHttpsIsOk(adminClient, routerAcceptsAnyCert);
+ verifyGetStatusHttpsIsOk(adminClient, routerNeedsNoClientCert);
+ }
+
+ @Test
+ public void testTLSNodeAccessWithIntermediate()
+ {
+ HttpClient intermediateCertClient = makeCustomHttpClient(
+ "client_tls/intermediate_ca_client.jks",
+ "intermediate_ca_client"
+ );
+ verifyGetStatusHttpsIsOk(intermediateCertClient, coordinator);
+ verifyGetStatusHttpsIsOk(intermediateCertClient, overlord);
+ verifyGetStatusHttpsIsOk(intermediateCertClient, broker);
+ verifyGetStatusHttpsIsOk(intermediateCertClient, historical);
+ verifyGetStatusHttpsIsOk(intermediateCertClient, router);
+ verifyGetStatusHttpsIsOk(intermediateCertClient, routerAcceptsAnyCert);
+ verifyGetStatusHttpsIsOk(intermediateCertClient, routerNeedsNoClientCert);
+ }
+
+ @Test
+ public void checkAccessWithNoCert()
+ {
+ HttpClient certlessClient = makeCertlessClient();
+ verifyGetStatusHttpsIsOk(certlessClient, routerNeedsNoClientCert);
+
+ // Mac environments seem to throw a 'bad_certificate' error
+ // whereas Ubuntu environments throw a 'handshake_failure' error
+ final String sslErrorMessage = "Received fatal alert";
+ verifyGetHttpsFailsWith(sslErrorMessage, coordinator, certlessClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, overlord, certlessClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, broker, certlessClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, historical, certlessClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, router, certlessClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, routerAcceptsAnyCert,
certlessClient);
+ }
+
+ @Test
+ public void checkAccessWithWrongHostname()
+ {
+ HttpClient wrongHostnameClient = makeCustomHttpClient(
+ "client_tls/invalid_hostname_client.jks",
+ "invalid_hostname_client"
+ );
+ verifyGetStatusHttpsIsOk(wrongHostnameClient, routerAcceptsAnyCert);
+ verifyGetStatusHttpsIsOk(wrongHostnameClient, routerNeedsNoClientCert);
+
+ final String sslErrorMessage = ERROR_CERTIFICATE_UNKNOWN;
+ verifyGetHttpsFailsWith(sslErrorMessage, coordinator, wrongHostnameClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, overlord, wrongHostnameClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, broker, wrongHostnameClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, historical, wrongHostnameClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, router, wrongHostnameClient);
+ }
+
+ @Test
+ public void checkAccessWithWrongRoot()
+ {
+ HttpClient wrongRootClient = makeCustomHttpClient(
+ "client_tls/client_another_root.jks",
+ "druid_another_root"
+ );
+ verifyGetStatusHttpsIsOk(wrongRootClient, routerNeedsNoClientCert);
+
+ final String sslErrorMessage = ERROR_CERTIFICATE_UNKNOWN;
+ verifyGetHttpsFailsWith(sslErrorMessage, coordinator, wrongRootClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, overlord, wrongRootClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, broker, wrongRootClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, historical, wrongRootClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, router, wrongRootClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, routerAcceptsAnyCert,
wrongRootClient);
+ }
+
+ @Test
+ public void checkAccessWithRevokedCert()
+ {
+ HttpClient revokedClient = makeCustomHttpClient(
+ "client_tls/revoked_client.jks",
+ "revoked_druid"
+ );
+ verifyGetStatusHttpsIsOk(revokedClient, routerNeedsNoClientCert);
+
+ final String sslErrorMessage = ERROR_CERTIFICATE_UNKNOWN;
+ verifyGetHttpsFailsWith(sslErrorMessage, coordinator, revokedClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, overlord, revokedClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, broker, revokedClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, historical, revokedClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, router, revokedClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, routerAcceptsAnyCert,
revokedClient);
+ }
+
+ @Test
+ public void checkAccessWithExpiredCert()
+ {
+ HttpClient expiredClient = makeCustomHttpClient(
+ "client_tls/expired_client.jks",
+ "expired_client"
+ );
+ verifyGetStatusHttpsIsOk(expiredClient, routerNeedsNoClientCert);
+
+ final String sslErrorMessage = ERROR_CERTIFICATE_UNKNOWN;
+ verifyGetHttpsFailsWith(sslErrorMessage, coordinator, expiredClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, overlord, expiredClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, broker, expiredClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, historical, expiredClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, router, expiredClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, routerAcceptsAnyCert,
expiredClient);
+ }
+
+ @Test
+ public void checkAccessWithNotCASignedCert()
+ {
+ HttpClient notCAClient = makeCustomHttpClient(
+ "client_tls/invalid_ca_client.jks",
+ "invalid_ca_client"
+ );
+ final String sslErrorMessage = ERROR_CERTIFICATE_UNKNOWN;
+ verifyGetHttpsFailsWith(sslErrorMessage, coordinator, notCAClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, overlord, notCAClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, broker, notCAClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, historical, notCAClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, router, notCAClient);
+ verifyGetHttpsFailsWith(sslErrorMessage, routerAcceptsAnyCert,
notCAClient);
+
+ verifyGetStatusHttpsIsOk(notCAClient, routerNeedsNoClientCert);
+ }
+
+ @Test
+ public void checkAccessWithCustomCertificateChecks()
+ {
+ // Verify GET over HTTPS fails with default HttpClient
+ verifyGetHttpsFailsWith(
+ ERROR_CERTIFICATE_UNKNOWN,
+ routerUsesCustomCertChecker,
+ overlord.bindings().escalatedHttpClient()
+ );
+
+ final HttpClient wrongHostnameClient = makeCustomHttpClient(
+ "client_tls/invalid_hostname_client.jks",
+ "invalid_hostname_client",
+ new TestTLSCertificateChecker()
+ );
+ verifyGetStatusHttpsIsOk(wrongHostnameClient, routerUsesCustomCertChecker);
+
+ // Verify that access to Broker fails
+ verifyRequestFails(
+ wrongHostnameClient,
+ HttpMethod.POST,
+ getServerHttpsUrl(routerUsesCustomCertChecker) + "/druid/v2",
+ ISE.class,
+ "Error while making request to url[https://localhost:9091/druid/v2]
status[400 Bad Request]"
+ );
+
+ // Verify that access to Coordinator works
+ makeRequest(
+ wrongHostnameClient,
+ HttpMethod.GET,
+ getServerHttpsUrl(routerUsesCustomCertChecker) +
"/druid/coordinator/v1/leader"
+ );
+ }
+
+ private void verifyGetHttpsFailsWith(String expectedMessage,
EmbeddedDruidServer<?> server, HttpClient httpClient)
+ {
+ verifyRequestFails(
+ httpClient,
+ HttpMethod.GET,
+ getServerHttpsUrl(server),
+ SSLException.class,
+ expectedMessage
+ );
+ }
+
+ private HttpClientConfig.Builder getHttpClientConfigBuilder(SSLContext
sslContext)
+ {
+ return HttpClientConfig
+ .builder()
+ .withNumConnections(httpClientConfig.getNumConnections())
+ .withReadTimeout(httpClientConfig.getReadTimeout())
+ .withWorkerCount(httpClientConfig.getNumMaxThreads())
+ .withCompressionCodec(
+
HttpClientConfig.CompressionCodec.valueOf(StringUtils.toUpperCase(httpClientConfig.getCompressionCodec()))
+ )
+
.withUnusedConnectionTimeoutDuration(httpClientConfig.getUnusedConnectionTimeout())
+ .withSslHandshakeTimeout(SSL_HANDSHAKE_TIMEOUT)
+ .withSslContext(sslContext);
+ }
+
+ private HttpClient makeCustomHttpClient(String keystorePath, String
certAlias)
+ {
+ return makeCustomHttpClient(keystorePath, certAlias, certificateChecker);
+ }
+
+ private HttpClient makeCustomHttpClient(
+ String keystorePath,
+ String certAlias,
+ TLSCertificateChecker certificateChecker
+ )
+ {
+ final DefaultPasswordProvider passwordProvider = new
DefaultPasswordProvider("druid123");
+ SSLContext intermediateClientSSLContext = new
TLSUtils.ClientSSLContextBuilder()
+ .setProtocol("TLSv1.2")
+
.setTrustStorePath(sslAuthResource.getTlsFilePath("client_tls/truststore.jks"))
+ .setTrustStoreAlgorithm("PKIX")
+ .setTrustStorePasswordProvider(passwordProvider)
+ .setKeyStoreType("PKCS12")
+ .setKeyStorePath(sslAuthResource.getTlsFilePath(keystorePath))
+ .setCertAlias(certAlias)
+ .setKeyStorePasswordProvider(passwordProvider)
+ .setKeyManagerFactoryPasswordProvider(passwordProvider)
+ .setCertificateChecker(certificateChecker)
+ .build();
+
+ final HttpClientConfig.Builder builder =
getHttpClientConfigBuilder(intermediateClientSSLContext);
+
+ HttpClient client = HttpClientInit.createClient(
+ builder.build(),
+ new Lifecycle()
+ );
+
+ return new CredentialedHttpClient(
+ new BasicCredentials("admin", "priest"),
+ client
+ );
+ }
+
+ private HttpClient makeCertlessClient()
+ {
+ SSLContext certlessClientSSLContext = new
TLSUtils.ClientSSLContextBuilder()
+ .setProtocol("TLSv1.2")
+ .setTrustStoreType("JKS")
+
.setTrustStorePath(sslAuthResource.getTlsFilePath("client_tls/truststore.jks"))
+ .setTrustStoreAlgorithm("PKIX")
+ .setTrustStorePasswordProvider(new DefaultPasswordProvider("druid123"))
+ .setCertificateChecker(certificateChecker)
+ .build();
+
+ final HttpClientConfig.Builder builder =
getHttpClientConfigBuilder(certlessClientSSLContext);
+
+ HttpClient client = HttpClientInit.createClient(
+ builder.build(),
+ new Lifecycle()
+ );
+
+ return new CredentialedHttpClient(
+ new BasicCredentials("admin", "priest"),
+ client
+ );
+ }
+
+ private void verifyRequestFails(
+ HttpClient httpClient,
+ HttpMethod method,
+ String url,
+ Class<?> expectedException,
+ String expectedExceptionMsg
+ )
+ {
+ try {
+ RetryUtils.retry(
+ () -> {
+ makeRequest(httpClient, method, url);
+ return 0;
+ },
+ e -> isRetriable(Throwables.getRootCause(e)),
+ MAX_BROKEN_PIPE_RETRIES
+ );
+
+ Assertions.fail("Did not get expected exception: " + expectedException);
+ }
+ catch (Exception e) {
+ Throwable rootCause = Throwables.getRootCause(e);
+ if (expectedException.isInstance(rootCause)) {
+ Assertions.assertTrue(
+ rootCause.getMessage().contains(expectedExceptionMsg),
+ StringUtils.format(
+ "Got error message[%s] instead of expected[%s]",
+ rootCause.getMessage(), expectedExceptionMsg
+ )
+ );
+ } else {
+ Assertions.fail(
+ StringUtils.format("Got a different exception[%s] instead of
expected[%s]", rootCause, expectedException),
+ rootCause
+ );
+ }
+ }
+ }
+
+ private void verifyGetStatusIsOk(HttpClient httpClient,
EmbeddedDruidServer<?> server)
+ {
+ makeRequest(httpClient, HttpMethod.GET, getServerUrl(server) + "/status");
+ }
+
+ private void verifyGetStatusHttpsIsOk(HttpClient httpClient,
EmbeddedDruidServer<?> server)
+ {
+ makeRequest(httpClient, HttpMethod.GET, getServerHttpsUrl(server) +
"/status");
+ }
+
+ private void makeRequest(
+ HttpClient httpClient,
+ HttpMethod method,
+ String url
+ )
+ {
+ try {
+ final Request request = new Request(method, new URL(url));
+ final StatusResponseHolder response = RetryUtils.retry(
+ () -> httpClient.go(request,
StatusResponseHandler.getInstance()).get(),
+ e -> isRetriable(Throwables.getRootCause(e)),
+ MAX_BROKEN_PIPE_RETRIES
+ );
+
+ if (!response.getStatus().equals(HttpResponseStatus.OK)) {
+ String errMsg = StringUtils.format(
+ "Error while making request to url[%s] status[%s] content[%s]",
+ url,
+ response.getStatus(),
+ response.getContent()
+ );
+ throw new ISE(errMsg);
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean isRetriable(Throwable ex)
+ {
+ if (!(ex instanceof IOException)) {
+ return false;
+ }
+
+ if (ex instanceof ClosedChannelException) {
+ return true;
+ }
+
+ return null != ex.getMessage()
+ && ("Broken pipe".equals(ex.getMessage())
+ || "Connection reset by peer".contains(ex.getMessage()));
+ }
+}
diff --git
a/integration-tests/src/main/java/org/apache/druid/testing/utils/ITTLSCertificateChecker.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TestTLSCertificateChecker.java
similarity index 84%
rename from
integration-tests/src/main/java/org/apache/druid/testing/utils/ITTLSCertificateChecker.java
rename to
embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TestTLSCertificateChecker.java
index d3b39efcfe5..ade50133be2 100644
---
a/integration-tests/src/main/java/org/apache/druid/testing/utils/ITTLSCertificateChecker.java
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TestTLSCertificateChecker.java
@@ -17,9 +17,8 @@
* under the License.
*/
-package org.apache.druid.testing.utils;
+package org.apache.druid.testing.embedded.auth;
-import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.server.security.TLSCertificateChecker;
import javax.net.ssl.SSLEngine;
@@ -27,10 +26,11 @@ import javax.net.ssl.X509ExtendedTrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
-public class ITTLSCertificateChecker implements TLSCertificateChecker
+/**
+ * Certificate checker with custom behaviour used in embedded tests.
+ */
+public class TestTLSCertificateChecker implements TLSCertificateChecker
{
- private static final Logger log = new Logger(ITTLSCertificateChecker.class);
-
@Override
public void checkClient(
X509Certificate[] chain,
@@ -40,7 +40,7 @@ public class ITTLSCertificateChecker implements
TLSCertificateChecker
) throws CertificateException
{
// only the integration test client with
"thisisprobablynottherighthostname" cert is allowed to talk to me
- if (!chain[0].toString().contains("thisisprobablynottherighthostname") ||
!engine.getPeerHost().contains("172.172.172.1")) {
+ if (!chain[0].toString().contains("thisisprobablynottherighthostname")) {
throw new CertificateException("Custom check rejected request from
client.");
}
}
@@ -56,7 +56,7 @@ public class ITTLSCertificateChecker implements
TLSCertificateChecker
baseTrustManager.checkServerTrusted(chain, authType, engine);
// fail intentionally when trying to talk to the broker
- if (chain[0].toString().contains("172.172.172.60")) {
+ if (chain[0].toString().contains(":8282")) {
throw new CertificateException("Custom check intentionally terminated
request to broker.");
}
}
diff --git
a/integration-tests/src/main/java/org/apache/druid/testing/guice/ITTLSCertificateCheckerModule.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TestTLSCertificateCheckerModule.java
similarity index 75%
rename from
integration-tests/src/main/java/org/apache/druid/testing/guice/ITTLSCertificateCheckerModule.java
rename to
embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TestTLSCertificateCheckerModule.java
index e992d843743..392854ff2af 100644
---
a/integration-tests/src/main/java/org/apache/druid/testing/guice/ITTLSCertificateCheckerModule.java
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/TestTLSCertificateCheckerModule.java
@@ -17,25 +17,24 @@
* under the License.
*/
-package org.apache.druid.testing.guice;
+package org.apache.druid.testing.embedded.auth;
import com.google.inject.Binder;
import com.google.inject.name.Names;
import org.apache.druid.initialization.DruidModule;
import org.apache.druid.server.security.TLSCertificateChecker;
-import org.apache.druid.testing.utils.ITTLSCertificateChecker;
-public class ITTLSCertificateCheckerModule implements DruidModule
+public class TestTLSCertificateCheckerModule implements DruidModule
{
- private final ITTLSCertificateChecker INSTANCE = new
ITTLSCertificateChecker();
+ private final TestTLSCertificateChecker INSTANCE = new
TestTLSCertificateChecker();
- public static final String IT_CHECKER_TYPE = "integration-test";
+ public static final String CHECKER_TYPE = "embedded-test";
@Override
public void configure(Binder binder)
{
binder.bind(TLSCertificateChecker.class)
- .annotatedWith(Names.named(IT_CHECKER_TYPE))
+ .annotatedWith(Names.named(CHECKER_TYPE))
.toInstance(INSTANCE);
}
}
diff --git
a/embedded-tests/src/test/resources/META-INF/services/org.apache.druid.initialization.DruidModule
b/embedded-tests/src/test/resources/META-INF/services/org.apache.druid.initialization.DruidModule
index 12fa4663f10..55c9638f6d5 100644
---
a/embedded-tests/src/test/resources/META-INF/services/org.apache.druid.initialization.DruidModule
+++
b/embedded-tests/src/test/resources/META-INF/services/org.apache.druid.initialization.DruidModule
@@ -19,3 +19,4 @@
org.apache.druid.testing.embedded.query.ServerManagerForQueryErrorTestModule
org.apache.druid.testing.embedded.gcs.GoogleStorageTestModule
+org.apache.druid.testing.embedded.auth.TestTLSCertificateCheckerModule
diff --git
a/integration-tests/docker/environment-configs/router-custom-check-tls
b/integration-tests/docker/environment-configs/router-custom-check-tls
index 1852feac5d0..85f4f12f6fa 100644
--- a/integration-tests/docker/environment-configs/router-custom-check-tls
+++ b/integration-tests/docker/environment-configs/router-custom-check-tls
@@ -31,4 +31,3 @@ druid_router_managementProxy_enabled=true
druid_auth_basic_common_cacheDirectory=/tmp/authCache/router-custom-check-tls
druid_sql_avatica_enable=true
druid_client_https_validateHostnames=false
-druid_tls_certificateChecker=integration-test
diff --git
a/integration-tests/docker/tls/generate-client-certs-and-keystores.sh
b/integration-tests/docker/tls/generate-client-certs-and-keystores.sh
index 3d48db05d56..b88236ae2c7 100755
--- a/integration-tests/docker/tls/generate-client-certs-and-keystores.sh
+++ b/integration-tests/docker/tls/generate-client-certs-and-keystores.sh
@@ -15,20 +15,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./docker/tls/generate-root-certs.sh
+./scripts/generate-root-certs.sh
mkdir -p client_tls
rm -f client_tls/*
-cp docker/tls/root.key client_tls/root.key
-cp docker/tls/root.pem client_tls/root.pem
-cp docker/tls/untrusted_root.key client_tls/untrusted_root.key
-cp docker/tls/untrusted_root.pem client_tls/untrusted_root.pem
+cp scripts/root.key client_tls/root.key
+cp scripts/root.pem client_tls/root.pem
+cp scripts/untrusted_root.key client_tls/untrusted_root.key
+cp scripts/untrusted_root.pem client_tls/untrusted_root.pem
cd client_tls
-../docker/tls/generate-expired-client-cert.sh
-../docker/tls/generate-good-client-cert.sh
-../docker/tls/generate-incorrect-hostname-client-cert.sh
-../docker/tls/generate-invalid-intermediate-client-cert.sh
-../docker/tls/generate-to-be-revoked-client-cert.sh
-../docker/tls/generate-untrusted-root-client-cert.sh
-../docker/tls/generate-valid-intermediate-client-cert.sh
+../scripts/generate-expired-client-cert.sh
+../scripts/generate-good-client-cert.sh
+../scripts/generate-incorrect-hostname-client-cert.sh
+../scripts/generate-invalid-intermediate-client-cert.sh
+../scripts/generate-to-be-revoked-client-cert.sh
+../scripts/generate-untrusted-root-client-cert.sh
+../scripts/generate-valid-intermediate-client-cert.sh
diff --git a/integration-tests/docker/tls/generate-root-certs.sh
b/integration-tests/docker/tls/generate-root-certs.sh
index 9cc2ddc1457..c3c508ec516 100755
--- a/integration-tests/docker/tls/generate-root-certs.sh
+++ b/integration-tests/docker/tls/generate-root-certs.sh
@@ -15,14 +15,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-rm -f root.key
-rm -f untrusted_root.key
-rm -f root.pem
-rm -f untrusted_root.pem
+rm -f scripts/root.key
+rm -f scripts/untrusted_root.key
+rm -f scripts/root.pem
+rm -f scripts/untrusted_root.pem
-openssl genrsa -out docker/tls/root.key 4096
-openssl genrsa -out docker/tls/untrusted_root.key 4096
+openssl genrsa -out scripts/root.key 4096
+openssl genrsa -out scripts/untrusted_root.key 4096
-openssl req -config docker/tls/root.cnf -key docker/tls/root.key -new -x509
-days 3650 -sha256 -extensions v3_ca -out docker/tls/root.pem
-openssl req -config docker/tls/root.cnf -key docker/tls/untrusted_root.key
-new -x509 -days 3650 -sha256 -extensions v3_ca -out
docker/tls/untrusted_root.pem
+openssl req -config scripts/root.cnf -key scripts/root.key -new -x509 -days
3650 -sha256 -extensions v3_ca -out scripts/root.pem
+openssl req -config scripts/root.cnf -key scripts/untrusted_root.key -new
-x509 -days 3650 -sha256 -extensions v3_ca -out scripts/untrusted_root.pem
diff --git
a/integration-tests/docker/tls/generate-server-certs-and-keystores.sh
b/integration-tests/docker/tls/generate-server-certs-and-keystores.sh
index 635079149cb..4fc1ca8adf6 100755
--- a/integration-tests/docker/tls/generate-server-certs-and-keystores.sh
+++ b/integration-tests/docker/tls/generate-server-certs-and-keystores.sh
@@ -15,20 +15,28 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-cd /tls
+mkdir -p server_tls
+rm -f server_tls/*
-FILE_CHECK_IF_RAN=/tls/server.key
+cp scripts/root.cnf server_tls/root.cnf
+cp scripts/root2.cnf server_tls/root2.cnf
+
+cp scripts/root.key server_tls/root.key
+cp scripts/root.pem server_tls/root.pem
+cp scripts/untrusted_root.key server_tls/untrusted_root.key
+cp scripts/untrusted_root.pem server_tls/untrusted_root.pem
+
+FILE_CHECK_IF_RAN=server_tls/server.key
if [ -f "$FILE_CHECK_IF_RAN" ]; then
- echo "Using existing certs/keys since /tls/server.key exists. Skipping
generation (most likely this script was ran previously). To generate new certs,
delete /tls/server.key"
+ echo "Using existing certs/keys since server_tls/server.key exists. Skipping
generation (most likely this script was ran previously). To generate new certs,
delete server_tls/server.key"
exit
fi
+cd server_tls
rm -f cert_db.txt
touch cert_db.txt
-export DOCKER_IP=$(cat /docker_ip)
export MY_HOSTNAME=$(hostname)
-export MY_IP=$(hostname -i)
cat <<EOT > csr.conf
[req]
@@ -45,16 +53,14 @@ L=Druid City
O=Druid
OU=IntegrationTests
[email protected]
-CN = ${MY_IP}
+CN = 127.0.0.1
[ req_ext ]
subjectAltName = @alt_names
basicConstraints=CA:FALSE,pathlen:0
[ alt_names ]
-IP.1 = ${DOCKER_IP}
-IP.2 = ${MY_IP}
-IP.3 = 127.0.0.1
+IP.1 = 127.0.0.1
DNS.1 = ${MY_HOSTNAME}
DNS.2 = localhost
@@ -72,15 +78,15 @@ openssl pkcs12 -export -in server.pem -inkey server.key
-out server.p12 -name dr
keytool -import -alias druid-it-root -keystore truststore.jks -file root.pem
-storepass druid123 -noprompt
# Revoke one of the client certs
-openssl ca -revoke /client_tls/revoked_client.pem -config root.cnf -cert
root.pem -keyfile root.key
+openssl ca -revoke ../client_tls/revoked_client.pem -config root.cnf -cert
root.pem -keyfile root.key
# Create the CRL
-openssl ca -gencrl -config root.cnf -cert root.pem -keyfile root.key -out
/tls/revocations.crl
+openssl ca -gencrl -config root.cnf -cert root.pem -keyfile root.key -out
revocations.crl
# Generate empty CRLs for the intermediate cert test case
rm -f cert_db2.txt
touch cert_db2.txt
-openssl ca -gencrl -config root2.cnf -cert /client_tls/ca_intermediate.pem
-keyfile /client_tls/ca_intermediate.key -out
/tls/empty-revocations-intermediate.crl
+openssl ca -gencrl -config root2.cnf -cert ../client_tls/ca_intermediate.pem
-keyfile ../client_tls/ca_intermediate.key -out
empty-revocations-intermediate.crl
# Append CRLs
cat empty-revocations-intermediate.crl >> revocations.crl
diff --git a/integration-tests/docker/tls/root.cnf
b/integration-tests/docker/tls/root.cnf
index 75417d2dbd2..2c152dee5c6 100644
--- a/integration-tests/docker/tls/root.cnf
+++ b/integration-tests/docker/tls/root.cnf
@@ -17,7 +17,7 @@
default_ca = CA_default
[ CA_default ]
-database = /tls/cert_db.txt
+database = cert_db.txt
x509_extensions = usr_cert
name_opt = ca_default
cert_opt = ca_default
diff --git a/integration-tests/docker/tls/root2.cnf
b/integration-tests/docker/tls/root2.cnf
index 17363fc623c..dd9d4771979 100644
--- a/integration-tests/docker/tls/root2.cnf
+++ b/integration-tests/docker/tls/root2.cnf
@@ -17,7 +17,7 @@
default_ca = CA_default
[ CA_default ]
-database = /tls/cert_db2.txt
+database = cert_db2.txt
x509_extensions = usr_cert
name_opt = ca_default
cert_opt = ca_default
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 42530197fbc..b5fd9884547 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -191,11 +191,6 @@
<version>${project.parent.version}</version>
<scope>provided</scope>
</dependency>
- <dependency>
- <groupId>org.apache.druid.extensions</groupId>
- <artifactId>simple-client-sslcontext</artifactId>
- <version>${project.parent.version}</version>
- </dependency>
<dependency>
<groupId>org.apache.druid</groupId>
<artifactId>druid-multi-stage-query</artifactId>
diff --git
a/integration-tests/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule
b/integration-tests/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule
deleted file mode 100644
index fa9b4c12830..00000000000
---
a/integration-tests/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.
-
-org.apache.druid.testing.guice.ITTLSCertificateCheckerModule
diff --git
a/integration-tests/src/test/java/org/apache/druid/tests/TestNGGroup.java
b/integration-tests/src/test/java/org/apache/druid/tests/TestNGGroup.java
index e75c79350b8..a7c4b293293 100644
--- a/integration-tests/src/test/java/org/apache/druid/tests/TestNGGroup.java
+++ b/integration-tests/src/test/java/org/apache/druid/tests/TestNGGroup.java
@@ -25,10 +25,6 @@ package org.apache.druid.tests;
*/
public class TestNGGroup
{
- /**
- * This group can only be run individually using -Dgroups=security since it
requires specific test data setup.
- */
- public static final String SECURITY = "security";
/**
* This group is not part of CI. To run this group, s3 configs/credentials
for your s3 must be provided in a file.
diff --git
a/integration-tests/src/test/java/org/apache/druid/tests/security/ITTLSTest.java
b/integration-tests/src/test/java/org/apache/druid/tests/security/ITTLSTest.java
deleted file mode 100644
index 163f8da09e9..00000000000
---
a/integration-tests/src/test/java/org/apache/druid/tests/security/ITTLSTest.java
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * 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.druid.tests.security;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Throwables;
-import com.google.inject.Inject;
-import org.apache.druid.guice.annotations.Client;
-import org.apache.druid.guice.http.DruidHttpClientConfig;
-import org.apache.druid.https.SSLClientConfig;
-import org.apache.druid.java.util.common.ISE;
-import org.apache.druid.java.util.common.StringUtils;
-import org.apache.druid.java.util.common.lifecycle.Lifecycle;
-import org.apache.druid.java.util.common.logger.Logger;
-import org.apache.druid.java.util.http.client.CredentialedHttpClient;
-import org.apache.druid.java.util.http.client.HttpClient;
-import org.apache.druid.java.util.http.client.HttpClientConfig;
-import org.apache.druid.java.util.http.client.HttpClientInit;
-import org.apache.druid.java.util.http.client.Request;
-import org.apache.druid.java.util.http.client.auth.BasicCredentials;
-import org.apache.druid.java.util.http.client.response.StatusResponseHandler;
-import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
-import org.apache.druid.server.security.TLSCertificateChecker;
-import org.apache.druid.server.security.TLSUtils;
-import org.apache.druid.testing.guice.DruidTestModuleFactory;
-import org.apache.druid.testing.tools.IntegrationTestingConfig;
-import org.apache.druid.testing.utils.ITTLSCertificateChecker;
-import org.apache.druid.tests.TestNGGroup;
-import org.jboss.netty.handler.codec.http.HttpMethod;
-import org.jboss.netty.handler.codec.http.HttpResponseStatus;
-import org.joda.time.Duration;
-import org.testng.Assert;
-import org.testng.annotations.Guice;
-import org.testng.annotations.Test;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.ws.rs.core.MediaType;
-
-import java.io.IOException;
-import java.net.URL;
-import java.nio.channels.ClosedChannelException;
-
-@Test(groups = TestNGGroup.SECURITY)
-@Guice(moduleFactory = DruidTestModuleFactory.class)
-public class ITTLSTest
-{
- private static final Logger LOG = new Logger(ITTLSTest.class);
-
- private static final Duration SSL_HANDSHAKE_TIMEOUT = new Duration(30 *
1000);
-
- private static final int MAX_CONNECTION_EXCEPTION_RETRIES = 30;
-
- @Inject
- IntegrationTestingConfig config;
-
- @Inject
- ObjectMapper jsonMapper;
-
- @Inject
- SSLClientConfig sslClientConfig;
-
- @Inject
- @Client
- HttpClient httpClient;
-
- @Inject
- @Client
- DruidHttpClientConfig httpClientConfig;
-
- @Inject
- TLSCertificateChecker certificateChecker;
-
-
- @Test
- public void testPlaintextAccess()
- {
- LOG.info("---------Testing resource access without TLS---------");
- HttpClient adminClient = new CredentialedHttpClient(
- new BasicCredentials("admin", "priest"),
- httpClient
- );
- makeRequest(adminClient, HttpMethod.GET, config.getCoordinatorUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getOverlordUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getBrokerUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getHistoricalUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getRouterUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getPermissiveRouterUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getNoClientAuthRouterUrl()
+ "/status", null);
- }
-
- @Test
- public void testTLSNodeAccess()
- {
- LOG.info("---------Testing resource access with TLS enabled---------");
- HttpClient adminClient = new CredentialedHttpClient(
- new BasicCredentials("admin", "priest"),
- httpClient
- );
- makeRequest(adminClient, HttpMethod.GET, config.getCoordinatorTLSUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getOverlordTLSUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getBrokerTLSUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getHistoricalTLSUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET, config.getRouterTLSUrl() +
"/status", null);
- makeRequest(adminClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl() + "/status", null);
- makeRequest(adminClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void testTLSNodeAccessWithIntermediate()
- {
- LOG.info("---------Testing TLS resource access with 3-part cert
chain---------");
- HttpClient intermediateCertClient = makeCustomHttpClient(
- "client_tls/intermediate_ca_client.jks",
- "intermediate_ca_client"
- );
- makeRequest(intermediateCertClient, HttpMethod.GET,
config.getCoordinatorTLSUrl() + "/status", null);
- makeRequest(intermediateCertClient, HttpMethod.GET,
config.getOverlordTLSUrl() + "/status", null);
- makeRequest(intermediateCertClient, HttpMethod.GET,
config.getBrokerTLSUrl() + "/status", null);
- makeRequest(intermediateCertClient, HttpMethod.GET,
config.getHistoricalTLSUrl() + "/status", null);
- makeRequest(intermediateCertClient, HttpMethod.GET,
config.getRouterTLSUrl() + "/status", null);
- makeRequest(intermediateCertClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl() + "/status", null);
- makeRequest(intermediateCertClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void checkAccessWithNoCert()
- {
- LOG.info("---------Testing TLS resource access without a
certificate---------");
- HttpClient certlessClient = makeCertlessClient();
- checkFailedAccessNoCert(certlessClient, HttpMethod.GET,
config.getCoordinatorTLSUrl());
- checkFailedAccessNoCert(certlessClient, HttpMethod.GET,
config.getOverlordTLSUrl());
- checkFailedAccessNoCert(certlessClient, HttpMethod.GET,
config.getBrokerTLSUrl());
- checkFailedAccessNoCert(certlessClient, HttpMethod.GET,
config.getHistoricalTLSUrl());
- checkFailedAccessNoCert(certlessClient, HttpMethod.GET,
config.getRouterTLSUrl());
- checkFailedAccessNoCert(certlessClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl());
- makeRequest(certlessClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void checkAccessWithWrongHostname()
- {
- LOG.info("---------Testing TLS resource access when client certificate has
non-matching hostnames---------");
- HttpClient wrongHostnameClient = makeCustomHttpClient(
- "client_tls/invalid_hostname_client.jks",
- "invalid_hostname_client"
- );
- checkFailedAccessWrongHostname(wrongHostnameClient, HttpMethod.GET,
config.getCoordinatorTLSUrl());
- checkFailedAccessWrongHostname(wrongHostnameClient, HttpMethod.GET,
config.getOverlordTLSUrl());
- checkFailedAccessWrongHostname(wrongHostnameClient, HttpMethod.GET,
config.getBrokerTLSUrl());
- checkFailedAccessWrongHostname(wrongHostnameClient, HttpMethod.GET,
config.getHistoricalTLSUrl());
- checkFailedAccessWrongHostname(wrongHostnameClient, HttpMethod.GET,
config.getRouterTLSUrl());
- makeRequest(wrongHostnameClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl() + "/status", null);
- makeRequest(wrongHostnameClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void checkAccessWithWrongRoot()
- {
- LOG.info("---------Testing TLS resource access when client certificate is
signed by a non-trusted root CA---------");
- HttpClient wrongRootClient = makeCustomHttpClient(
- "client_tls/client_another_root.jks",
- "druid_another_root"
- );
- checkFailedAccessWrongRoot(wrongRootClient, HttpMethod.GET,
config.getCoordinatorTLSUrl());
- checkFailedAccessWrongRoot(wrongRootClient, HttpMethod.GET,
config.getOverlordTLSUrl());
- checkFailedAccessWrongRoot(wrongRootClient, HttpMethod.GET,
config.getBrokerTLSUrl());
- checkFailedAccessWrongRoot(wrongRootClient, HttpMethod.GET,
config.getHistoricalTLSUrl());
- checkFailedAccessWrongRoot(wrongRootClient, HttpMethod.GET,
config.getRouterTLSUrl());
- checkFailedAccessWrongRoot(wrongRootClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl());
- makeRequest(wrongRootClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void checkAccessWithRevokedCert()
- {
- LOG.info("---------Testing TLS resource access when client certificate has
been revoked---------");
- HttpClient revokedClient = makeCustomHttpClient(
- "client_tls/revoked_client.jks",
- "revoked_druid"
- );
- checkFailedAccessRevoked(revokedClient, HttpMethod.GET,
config.getCoordinatorTLSUrl());
- checkFailedAccessRevoked(revokedClient, HttpMethod.GET,
config.getOverlordTLSUrl());
- checkFailedAccessRevoked(revokedClient, HttpMethod.GET,
config.getBrokerTLSUrl());
- checkFailedAccessRevoked(revokedClient, HttpMethod.GET,
config.getHistoricalTLSUrl());
- checkFailedAccessRevoked(revokedClient, HttpMethod.GET,
config.getRouterTLSUrl());
- makeRequest(revokedClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl() + "/status", null);
- makeRequest(revokedClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void checkAccessWithExpiredCert()
- {
- LOG.info("---------Testing TLS resource access when client certificate has
expired---------");
- HttpClient expiredClient = makeCustomHttpClient(
- "client_tls/expired_client.jks",
- "expired_client"
- );
- checkFailedAccessExpired(expiredClient, HttpMethod.GET,
config.getCoordinatorTLSUrl());
- checkFailedAccessExpired(expiredClient, HttpMethod.GET,
config.getOverlordTLSUrl());
- checkFailedAccessExpired(expiredClient, HttpMethod.GET,
config.getBrokerTLSUrl());
- checkFailedAccessExpired(expiredClient, HttpMethod.GET,
config.getHistoricalTLSUrl());
- checkFailedAccessExpired(expiredClient, HttpMethod.GET,
config.getRouterTLSUrl());
- checkFailedAccessExpired(expiredClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl());
- makeRequest(expiredClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void checkAccessWithNotCASignedCert()
- {
- LOG.info(
- "---------Testing TLS resource access when client certificate is
signed by a non-CA intermediate cert---------");
- HttpClient notCAClient = makeCustomHttpClient(
- "client_tls/invalid_ca_client.jks",
- "invalid_ca_client"
- );
- checkFailedAccessNotCA(notCAClient, HttpMethod.GET,
config.getCoordinatorTLSUrl());
- checkFailedAccessNotCA(notCAClient, HttpMethod.GET,
config.getOverlordTLSUrl());
- checkFailedAccessNotCA(notCAClient, HttpMethod.GET,
config.getBrokerTLSUrl());
- checkFailedAccessNotCA(notCAClient, HttpMethod.GET,
config.getHistoricalTLSUrl());
- checkFailedAccessNotCA(notCAClient, HttpMethod.GET,
config.getRouterTLSUrl());
- checkFailedAccessNotCA(notCAClient, HttpMethod.GET,
config.getPermissiveRouterTLSUrl());
- makeRequest(notCAClient, HttpMethod.GET,
config.getNoClientAuthRouterTLSUrl() + "/status", null);
- }
-
- @Test
- public void checkAccessWithCustomCertificateChecks()
- {
- LOG.info("---------Testing TLS resource access with custom certificate
checks---------");
- HttpClient wrongHostnameClient = makeCustomHttpClient(
- "client_tls/invalid_hostname_client.jks",
- "invalid_hostname_client",
- new ITTLSCertificateChecker()
- );
-
- checkFailedAccessWrongHostname(httpClient, HttpMethod.GET,
config.getCustomCertCheckRouterTLSUrl());
-
- makeRequest(wrongHostnameClient, HttpMethod.GET,
config.getCustomCertCheckRouterTLSUrl() + "/status", null);
-
- checkFailedAccess(
- wrongHostnameClient,
- HttpMethod.POST,
- config.getCustomCertCheckRouterTLSUrl() + "/druid/v2",
- "Custom cert check",
- ISE.class,
- "Error while making request to url[https://127.0.0.1:9091/druid/v2]
status[400 Bad Request] content[{\"error\":\"Unknown
exception\",\"errorMessage\":\"No content to map due to end-of-input",
- true
- );
-
- makeRequest(wrongHostnameClient, HttpMethod.GET,
config.getCustomCertCheckRouterTLSUrl() + "/druid/coordinator/v1/leader", null);
- }
-
- private void checkFailedAccessNoCert(HttpClient httpClient, HttpMethod
method, String url)
- {
- checkFailedAccess(
- httpClient,
- method,
- url + "/status",
- "Certless",
- SSLException.class,
- "Received fatal alert: bad_certificate",
- false
- );
- }
-
- private void checkFailedAccessWrongHostname(HttpClient httpClient,
HttpMethod method, String url)
- {
- checkFailedAccess(
- httpClient,
- method,
- url + "/status",
- "Wrong hostname",
- SSLException.class,
- "Received fatal alert: certificate_unknown",
- false
- );
- }
-
- private void checkFailedAccessWrongRoot(HttpClient httpClient, HttpMethod
method, String url)
- {
- checkFailedAccess(
- httpClient,
- method,
- url + "/status",
- "Wrong root cert",
- SSLException.class,
- "Received fatal alert: certificate_unknown",
- false
- );
- }
-
- private void checkFailedAccessRevoked(HttpClient httpClient, HttpMethod
method, String url)
- {
- checkFailedAccess(
- httpClient,
- method,
- url + "/status",
- "Revoked cert",
- SSLException.class,
- "Received fatal alert: certificate_unknown",
- false
- );
- }
-
- private void checkFailedAccessExpired(HttpClient httpClient, HttpMethod
method, String url)
- {
- checkFailedAccess(
- httpClient,
- method,
- url + "/status",
- "Expired cert",
- SSLException.class,
- "Received fatal alert: certificate_unknown",
- false
- );
- }
-
- private void checkFailedAccessNotCA(HttpClient httpClient, HttpMethod
method, String url)
- {
- checkFailedAccess(
- httpClient,
- method,
- url + "/status",
- "Cert signed by non-CA",
- SSLException.class,
- "Received fatal alert: certificate_unknown",
- false
- );
- }
-
- private HttpClientConfig.Builder getHttpClientConfigBuilder(SSLContext
sslContext)
- {
- return HttpClientConfig
- .builder()
- .withNumConnections(httpClientConfig.getNumConnections())
- .withReadTimeout(httpClientConfig.getReadTimeout())
- .withWorkerCount(httpClientConfig.getNumMaxThreads())
- .withCompressionCodec(
-
HttpClientConfig.CompressionCodec.valueOf(StringUtils.toUpperCase(httpClientConfig.getCompressionCodec()))
- )
-
.withUnusedConnectionTimeoutDuration(httpClientConfig.getUnusedConnectionTimeout())
- .withSslHandshakeTimeout(SSL_HANDSHAKE_TIMEOUT)
- .withSslContext(sslContext);
- }
-
- private HttpClient makeCustomHttpClient(String keystorePath, String
certAlias)
- {
- return makeCustomHttpClient(keystorePath, certAlias, certificateChecker);
- }
-
- private HttpClient makeCustomHttpClient(
- String keystorePath,
- String certAlias,
- TLSCertificateChecker certificateChecker
- )
- {
- SSLContext intermediateClientSSLContext = new
TLSUtils.ClientSSLContextBuilder()
- .setProtocol(sslClientConfig.getProtocol())
- .setTrustStoreType(sslClientConfig.getTrustStoreType())
- .setTrustStorePath(sslClientConfig.getTrustStorePath())
- .setTrustStoreAlgorithm(sslClientConfig.getTrustStoreAlgorithm())
-
.setTrustStorePasswordProvider(sslClientConfig.getTrustStorePasswordProvider())
- .setKeyStoreType(sslClientConfig.getKeyStoreType())
- .setKeyStorePath(keystorePath)
- .setKeyStoreAlgorithm(sslClientConfig.getKeyManagerFactoryAlgorithm())
- .setCertAlias(certAlias)
-
.setKeyStorePasswordProvider(sslClientConfig.getKeyStorePasswordProvider())
-
.setKeyManagerFactoryPasswordProvider(sslClientConfig.getKeyManagerPasswordProvider())
- .setCertificateChecker(certificateChecker)
- .build();
-
- final HttpClientConfig.Builder builder =
getHttpClientConfigBuilder(intermediateClientSSLContext);
-
- final Lifecycle lifecycle = new Lifecycle();
-
- HttpClient client = HttpClientInit.createClient(
- builder.build(),
- lifecycle
- );
-
- HttpClient adminClient = new CredentialedHttpClient(
- new BasicCredentials("admin", "priest"),
- client
- );
- return adminClient;
- }
-
- private HttpClient makeCertlessClient()
- {
- SSLContext certlessClientSSLContext = new
TLSUtils.ClientSSLContextBuilder()
- .setProtocol(sslClientConfig.getProtocol())
- .setTrustStoreType(sslClientConfig.getTrustStoreType())
- .setTrustStorePath(sslClientConfig.getTrustStorePath())
- .setTrustStoreAlgorithm(sslClientConfig.getTrustStoreAlgorithm())
-
.setTrustStorePasswordProvider(sslClientConfig.getTrustStorePasswordProvider())
- .setCertificateChecker(certificateChecker)
- .build();
-
- final HttpClientConfig.Builder builder =
getHttpClientConfigBuilder(certlessClientSSLContext);
-
- final Lifecycle lifecycle = new Lifecycle();
-
- HttpClient client = HttpClientInit.createClient(
- builder.build(),
- lifecycle
- );
-
- HttpClient adminClient = new CredentialedHttpClient(
- new BasicCredentials("admin", "priest"),
- client
- );
- return adminClient;
- }
-
- private void checkFailedAccess(
- HttpClient httpClient,
- HttpMethod method,
- String url,
- String clientDesc,
- Class expectedException,
- String expectedExceptionMsg,
- boolean useContainsMsgCheck
- )
- {
- int retries = 0;
- while (true) {
- try {
- makeRequest(httpClient, method, url, null, -1);
- }
- catch (RuntimeException re) {
- Throwable rootCause = Throwables.getRootCause(re);
-
- if (isRetriable(rootCause)) {
- if (retries > MAX_CONNECTION_EXCEPTION_RETRIES) {
- Assert.fail(StringUtils.format(
- "Broken pipe / connection reset retries exhausted, test
failed, did not get %s.",
- expectedException
- ));
- } else {
- retries += 1;
- continue;
- }
- }
-
- Assert.assertTrue(
- expectedException.isInstance(rootCause),
- StringUtils.format("Expected %s but found %s instead.",
expectedException, Throwables.getStackTraceAsString(rootCause))
- );
-
- if (useContainsMsgCheck) {
-
Assert.assertTrue(rootCause.getMessage().contains(expectedExceptionMsg));
- } else {
- Assert.assertEquals(
- rootCause.getMessage(),
- expectedExceptionMsg
- );
- }
-
- LOG.info("%s client [%s] request failed as expected when accessing
[%s]", clientDesc, method, url);
- return;
- }
- Assert.fail(StringUtils.format("Test failed, did not get %s.",
expectedException));
- }
- }
-
- private StatusResponseHolder makeRequest(HttpClient httpClient, HttpMethod
method, String url, byte[] content)
- {
- return makeRequest(httpClient, method, url, content, 4);
- }
-
- private StatusResponseHolder makeRequest(
- HttpClient httpClient,
- HttpMethod method,
- String url,
- byte[] content,
- int maxRetries
- )
- {
- try {
- Request request = new Request(method, new URL(url));
- if (content != null) {
- request.setContent(MediaType.APPLICATION_JSON, content);
- }
- int retryCount = 0;
-
- StatusResponseHolder response;
-
- while (true) {
- response = httpClient.go(
- request,
- StatusResponseHandler.getInstance()
- ).get();
-
- if (!response.getStatus().equals(HttpResponseStatus.OK)) {
- String errMsg = StringUtils.format(
- "Error while making request to url[%s] status[%s] content[%s]",
- url,
- response.getStatus(),
- response.getContent()
- );
- if (retryCount > maxRetries) {
- throw new ISE(errMsg);
- } else {
- LOG.error(errMsg);
- LOG.error("retrying in 3000ms, retryCount: " + retryCount);
- retryCount++;
- Thread.sleep(3000);
- }
- } else {
- LOG.info("[%s] request to [%s] succeeded.", method, url);
- break;
- }
- }
- return response;
- }
- catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private boolean isRetriable(Throwable ex)
- {
- if (!(ex instanceof IOException)) {
- return false;
- }
-
- if (ex instanceof ClosedChannelException) {
- return true;
- }
-
- return null != ex.getMessage()
- && ("Broken pipe".equals(ex.getMessage())
- || "Connection reset by peer".contains(ex.getMessage()));
- }
-}
diff --git
a/services/src/test/java/org/apache/druid/testing/embedded/junit5/EmbeddedClusterTestBase.java
b/services/src/test/java/org/apache/druid/testing/embedded/junit5/EmbeddedClusterTestBase.java
index 09bbc097546..f370da1ff08 100644
---
a/services/src/test/java/org/apache/druid/testing/embedded/junit5/EmbeddedClusterTestBase.java
+++
b/services/src/test/java/org/apache/druid/testing/embedded/junit5/EmbeddedClusterTestBase.java
@@ -69,6 +69,19 @@ public abstract class EmbeddedClusterTestBase
);
}
+ /**
+ * Returns the TLS-enabled HTTPS url of the given server.
+ */
+ protected static String getServerHttpsUrl(EmbeddedDruidServer<?> server)
+ {
+ final DruidNode node = server.bindings().selfNode();
+ return StringUtils.format(
+ "https://%s:%s",
+ node.getHost(),
+ node.getTlsPort()
+ );
+ }
+
/**
* Creates the cluster to be used in this test class. This method is invoked
* only once before any of the {@code @Test} methods have run.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]