diff --git a/tests/src/test/java/org/eclipse/hono/tests/EnabledIfDnsRebindingIsSupported.java b/tests/src/test/java/org/eclipse/hono/tests/EnabledIfDnsRebindingIsSupported.java
new file mode 100644
index 0000000000..5540ccdae4
--- /dev/null
+++ b/tests/src/test/java/org/eclipse/hono/tests/EnabledIfDnsRebindingIsSupported.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.hono.tests;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * An annotation which configures a test to run only if DNS rebinding for the nip.io domain works in the
+ * local environment.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(EnabledIfDnsRebindingIsSupportedCondition.class)
+public @interface EnabledIfDnsRebindingIsSupported {
+
+ /**
+ * The default domain name.
+ */
+ String DEFAULT_DOMAIN = "nip.io";
+
+ /**
+ * The domain to use for checking DNS rebinding to work.
+ *
+ * The default value of this property is {@value #DEFAULT_DOMAIN}.
+ *
+ * @return The domain name.
+ */
+ String domain() default DEFAULT_DOMAIN;
+}
diff --git a/tests/src/test/java/org/eclipse/hono/tests/EnabledIfDnsRebindingIsSupportedCondition.java b/tests/src/test/java/org/eclipse/hono/tests/EnabledIfDnsRebindingIsSupportedCondition.java
new file mode 100644
index 0000000000..8887b267d6
--- /dev/null
+++ b/tests/src/test/java/org/eclipse/hono/tests/EnabledIfDnsRebindingIsSupportedCondition.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+
+package org.eclipse.hono.tests;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ExecutionCondition;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.platform.commons.util.AnnotationUtils;
+
+
+/**
+ * A condition that checks if DNS rebinding works for host names containing a particular domain.
+ *
+ * This makes sure that a look up of a host name like {@code 127.0.0.1.nip.io} is successfully resolved to IPv4
+ * address {@code 127.0.0.1}.
+ *
+ * The domain name to check is taken from {@link EnabledIfDnsRebindingIsSupported#domain()}.
+ */
+public class EnabledIfDnsRebindingIsSupportedCondition implements ExecutionCondition {
+
+ private static final Map RESULTS = new HashMap<>();
+
+ /**
+ * Checks if {@code 127.0.0.1.nip.io} can be resolved to {@code 127.0.0.1}.
+ *
+ * @param context The context to evaluate in.
+ */
+ @Override
+ public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext context) {
+
+ final String domainName = AnnotationUtils.findAnnotation(
+ context.getElement(),
+ EnabledIfDnsRebindingIsSupported.class)
+ .map(EnabledIfDnsRebindingIsSupported::domain)
+ .orElse(EnabledIfDnsRebindingIsSupported.DEFAULT_DOMAIN);
+
+ synchronized (RESULTS) {
+ return RESULTS.computeIfAbsent(domainName, this::performLookup);
+ }
+ }
+
+ private ConditionEvaluationResult performLookup(final String domainName) {
+
+
+ final String hostname = "127.0.0.1.%s".formatted(domainName);
+ try {
+ final var address = InetAddress.getByName(hostname);
+ if (address.isLoopbackAddress()) {
+ return ConditionEvaluationResult.enabled("lookup of %s succeeded".formatted(hostname));
+ } else {
+ return ConditionEvaluationResult.disabled("lookup of %s yields non-loopback address: %s"
+ .formatted(hostname, address.getHostAddress()));
+ }
+ } catch (final UnknownHostException e) {
+ // DNS rebinding protection seems to be in place
+ return ConditionEvaluationResult.disabled("""
+ DNS rebinding protection prevents resolving of %s. You might want to configure your resolver
+ to use a DNS server that allows rebinding for domain %s as described in
+ https://github.com/IBM-Blockchain/blockchain-vscode-extension/issues/2878#issuecomment-890147917
+ """.formatted(hostname, domainName));
+ }
+ }
+}
diff --git a/tests/src/test/java/org/eclipse/hono/tests/IntegrationTestSupport.java b/tests/src/test/java/org/eclipse/hono/tests/IntegrationTestSupport.java
index 9f1b57dc20..a229055bea 100644
--- a/tests/src/test/java/org/eclipse/hono/tests/IntegrationTestSupport.java
+++ b/tests/src/test/java/org/eclipse/hono/tests/IntegrationTestSupport.java
@@ -37,6 +37,7 @@
import java.util.OptionalInt;
import java.util.Queue;
import java.util.Set;
+import java.util.StringJoiner;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -383,7 +384,7 @@ public final class IntegrationTestSupport {
/**
* The IP address of the CoAP protocol adapter.
*/
- public static final String COAP_HOST = IntegrationTestSupport.getResolvableHostname(PROPERTY_COAP_HOST);
+ public static final String COAP_HOST = System.getProperty(PROPERTY_COAP_HOST, DEFAULT_HOST);
/**
* The port number that the CoAP adapter listens on for requests.
*/
@@ -395,7 +396,7 @@ public final class IntegrationTestSupport {
/**
* The IP address of the HTTP protocol adapter.
*/
- public static final String HTTP_HOST = IntegrationTestSupport.getResolvableHostname(PROPERTY_HTTP_HOST);
+ public static final String HTTP_HOST = System.getProperty(PROPERTY_HTTP_HOST, DEFAULT_HOST);
/**
* The port number that the HTTP adapter listens on for requests.
*/
@@ -407,7 +408,7 @@ public final class IntegrationTestSupport {
/**
* The IP address of the MQTT protocol adapter.
*/
- public static final String MQTT_HOST = IntegrationTestSupport.getResolvableHostname(PROPERTY_MQTT_HOST);
+ public static final String MQTT_HOST = System.getProperty(PROPERTY_MQTT_HOST, DEFAULT_HOST);
/**
* The port number that the MQTT adapter listens on for connections.
*/
@@ -419,7 +420,7 @@ public final class IntegrationTestSupport {
/**
* The IP address of the AMQP protocol adapter.
*/
- public static final String AMQP_HOST = IntegrationTestSupport.getResolvableHostname(PROPERTY_AMQP_HOST);
+ public static final String AMQP_HOST = System.getProperty(PROPERTY_AMQP_HOST, DEFAULT_HOST);
/**
* The port number that the AMQP adapter listens on for connections.
*/
@@ -531,16 +532,20 @@ public IntegrationTestSupport(final Vertx vertx) {
}
/**
- * Gets a host name/IP address that can be resolved via DNS from the value of a Java system property.
+ * Gets a host name in the {@code nip.io} domain for a given host name.
*
- * @param systemPropertyName The name of the property to read.
+ * @param hostname The host name.
+ * @param virtualHost The virtual host name to prepend.
* @return The host name.
*/
- private static String getResolvableHostname(final String systemPropertyName) {
- return Optional.of(System.getProperty(systemPropertyName, DEFAULT_HOST))
- .map(host -> "localhost".equals(host) ? DEFAULT_HOST : host)
- .map(ipAddress -> ipAddress + ".nip.io")
- .get();
+ public static String getSniHostname(final String hostname, final String virtualHost) {
+
+ Objects.requireNonNull(hostname);
+ final String literalIpAddress = "localhost".equals(hostname) ? DEFAULT_HOST : hostname;
+ final var b = new StringJoiner(".");
+ Optional.ofNullable(virtualHost).ifPresent(b::add);
+ b.add(literalIpAddress).add("nip.io");
+ return b.toString();
}
private static ClientConfigProperties getClientConfigProperties(
diff --git a/tests/src/test/java/org/eclipse/hono/tests/amqp/AmqpConnectionIT.java b/tests/src/test/java/org/eclipse/hono/tests/amqp/AmqpConnectionIT.java
index b4b7af3629..12c1ded8d6 100644
--- a/tests/src/test/java/org/eclipse/hono/tests/amqp/AmqpConnectionIT.java
+++ b/tests/src/test/java/org/eclipse/hono/tests/amqp/AmqpConnectionIT.java
@@ -28,6 +28,7 @@
import org.eclipse.hono.client.ClientErrorException;
import org.eclipse.hono.service.management.tenant.Tenant;
+import org.eclipse.hono.tests.EnabledIfDnsRebindingIsSupported;
import org.eclipse.hono.tests.EnabledIfRegistrySupportsFeatures;
import org.eclipse.hono.tests.IntegrationTestSupport;
import org.eclipse.hono.tests.Tenants;
@@ -153,6 +154,7 @@ public void testConnectX509SucceedsForRegisteredDevice(final String tlsVersion,
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@ValueSource(strings = { IntegrationTestSupport.TLS_VERSION_1_2, IntegrationTestSupport.TLS_VERSION_1_3 })
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true)
public void testConnectX509SucceedsUsingSni(final String tlsVersion, final VertxTestContext ctx) {
@@ -171,7 +173,7 @@ public void testConnectX509SucceedsUsingSni(final String tlsVersion, final Vertx
deviceId,
cert))
.compose(ok -> connectToAdapter(
- tenantId + "." + IntegrationTestSupport.AMQP_HOST,
+ IntegrationTestSupport.getSniHostname(IntegrationTestSupport.AMQP_HOST, tenantId),
deviceCert,
tlsVersion))
.onComplete(ctx.succeeding(con -> {
@@ -189,6 +191,7 @@ public void testConnectX509SucceedsUsingSni(final String tlsVersion, final Vertx
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@ValueSource(strings = { IntegrationTestSupport.TLS_VERSION_1_2, IntegrationTestSupport.TLS_VERSION_1_3 })
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true, tenantAlias = true)
public void testConnectX509SucceedsUsingSniWithTenantAlias(final String tlsVersion, final VertxTestContext ctx) {
@@ -209,7 +212,7 @@ public void testConnectX509SucceedsUsingSniWithTenantAlias(final String tlsVersi
deviceId,
cert))
.compose(ok -> connectToAdapter(
- "test-alias." + IntegrationTestSupport.AMQP_HOST,
+ IntegrationTestSupport.getSniHostname(IntegrationTestSupport.AMQP_HOST, "test-alias"),
deviceCert,
tlsVersion))
.onComplete(ctx.succeeding(con -> {
@@ -620,6 +623,7 @@ public void testConnectFailsForNonMatchingTrustAnchor(final VertxTestContext ctx
* @param ctx The test context
*/
@Test
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true, tenantAlias = true)
public void testConnectX509FailsUsingSniWithNonExistingTenantAlias(final VertxTestContext ctx) {
@@ -640,7 +644,7 @@ public void testConnectX509FailsUsingSniWithNonExistingTenantAlias(final VertxTe
deviceId,
cert))
.compose(ok -> connectToAdapter(
- "wrong-alias." + IntegrationTestSupport.AMQP_HOST,
+ IntegrationTestSupport.getSniHostname(IntegrationTestSupport.AMQP_HOST, "wrong-alias"),
deviceCert,
IntegrationTestSupport.TLS_VERSION_1_2))
.onComplete(ctx.failing(t -> {
diff --git a/tests/src/test/java/org/eclipse/hono/tests/coap/TelemetryCoapIT.java b/tests/src/test/java/org/eclipse/hono/tests/coap/TelemetryCoapIT.java
index a79951bcde..501a80a6dc 100644
--- a/tests/src/test/java/org/eclipse/hono/tests/coap/TelemetryCoapIT.java
+++ b/tests/src/test/java/org/eclipse/hono/tests/coap/TelemetryCoapIT.java
@@ -32,6 +32,7 @@
import org.eclipse.hono.application.client.MessageContext;
import org.eclipse.hono.config.KeyLoader;
import org.eclipse.hono.service.management.tenant.Tenant;
+import org.eclipse.hono.tests.EnabledIfDnsRebindingIsSupported;
import org.eclipse.hono.tests.EnabledIfRegistrySupportsFeatures;
import org.eclipse.hono.tests.IntegrationTestSupport;
import org.eclipse.hono.tests.Tenants;
@@ -180,6 +181,7 @@ public void testRootResourceDoesNotExposeAnyInfo(final VertxTestContext ctx) thr
* @throws InterruptedException if the test fails.
*/
@Test
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true, tenantAlias = true)
public void testUploadMessagesUsingClientCertificateWithAlias(final VertxTestContext ctx) throws InterruptedException {
@@ -212,11 +214,13 @@ public void testUploadMessagesUsingClientCertificateWithAlias(final VertxTestCon
final CoapClient client = getCoapsClient(clientCertLoader);
+ final var hostname = IntegrationTestSupport.getSniHostname(IntegrationTestSupport.COAP_HOST, "test-alias");
+
testUploadMessages(ctx, tenantId,
() -> warmUp(client, createCoapsRequest(
Code.POST,
getMessageType(),
- "test-alias." + IntegrationTestSupport.COAP_HOST,
+ hostname,
getPostResource(),
"hello 0".getBytes(StandardCharsets.UTF_8))),
count -> {
@@ -225,7 +229,7 @@ public void testUploadMessagesUsingClientCertificateWithAlias(final VertxTestCon
final Request request = createCoapsRequest(
Code.POST,
getMessageType(),
- "test-alias." + IntegrationTestSupport.COAP_HOST,
+ hostname,
getPostResource(),
payload.getBytes(StandardCharsets.UTF_8));
client.advanced(getHandler(result), request);
diff --git a/tests/src/test/java/org/eclipse/hono/tests/http/TelemetryHttpIT.java b/tests/src/test/java/org/eclipse/hono/tests/http/TelemetryHttpIT.java
index 9ca198a84d..6357b61225 100644
--- a/tests/src/test/java/org/eclipse/hono/tests/http/TelemetryHttpIT.java
+++ b/tests/src/test/java/org/eclipse/hono/tests/http/TelemetryHttpIT.java
@@ -29,6 +29,7 @@
import org.eclipse.hono.client.ServiceInvocationException;
import org.eclipse.hono.service.management.tenant.Tenant;
import org.eclipse.hono.tests.AssumeMessagingSystem;
+import org.eclipse.hono.tests.EnabledIfDnsRebindingIsSupported;
import org.eclipse.hono.tests.EnabledIfRegistrySupportsFeatures;
import org.eclipse.hono.tests.IntegrationTestSupport;
import org.eclipse.hono.tests.Tenants;
@@ -297,6 +298,7 @@ public void testUploadQos1MessageFailsIfDeliveryStateNotUpdated(
* @throws InterruptedException if the test fails.
*/
@Test
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true, tenantAlias = true)
public void testUploadMessagesUsingClientCertificateWithAlias(final VertxTestContext ctx) throws InterruptedException {
@@ -326,7 +328,7 @@ public void testUploadMessagesUsingClientCertificateWithAlias(final VertxTestCon
}
final RequestOptions options = new RequestOptions()
- .setHost("test-alias." + IntegrationTestSupport.HTTP_HOST)
+ .setHost(IntegrationTestSupport.getSniHostname(IntegrationTestSupport.HTTP_HOST, "test-alias"))
.setPort(IntegrationTestSupport.HTTPS_PORT)
.setURI(getEndpointUri())
.setHeaders(requestHeaders);
diff --git a/tests/src/test/java/org/eclipse/hono/tests/mqtt/MqttConnectionIT.java b/tests/src/test/java/org/eclipse/hono/tests/mqtt/MqttConnectionIT.java
index 237aabeb9a..ac5f48c86a 100644
--- a/tests/src/test/java/org/eclipse/hono/tests/mqtt/MqttConnectionIT.java
+++ b/tests/src/test/java/org/eclipse/hono/tests/mqtt/MqttConnectionIT.java
@@ -29,6 +29,7 @@
import org.eclipse.hono.service.management.credentials.X509CertificateSecret;
import org.eclipse.hono.service.management.device.Device;
import org.eclipse.hono.service.management.tenant.Tenant;
+import org.eclipse.hono.tests.EnabledIfDnsRebindingIsSupported;
import org.eclipse.hono.tests.EnabledIfRegistrySupportsFeatures;
import org.eclipse.hono.tests.IntegrationTestSupport;
import org.eclipse.hono.tests.Tenants;
@@ -126,6 +127,7 @@ public void testConnectX509SucceedsForRegisteredDevice(final VertxTestContext ct
* @param ctx The test context
*/
@Test
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true)
public void testConnectX509SucceedsUsingSni(final VertxTestContext ctx) {
@@ -140,7 +142,9 @@ public void testConnectX509SucceedsUsingSni(final VertxTestContext ctx) {
.compose(ok -> helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, cert));
})
// WHEN the device connects to the adapter including its tenant ID in the host name
- .compose(ok -> connectToAdapter(deviceCert, tenantId + "." + IntegrationTestSupport.MQTT_HOST))
+ .compose(ok -> connectToAdapter(
+ deviceCert,
+ IntegrationTestSupport.getSniHostname(IntegrationTestSupport.MQTT_HOST, tenantId)))
.onComplete(ctx.succeeding(conAckMsg -> {
// THEN the connection attempt succeeds
ctx.verify(() -> assertThat(conAckMsg.code()).isEqualTo(MqttConnectReturnCode.CONNECTION_ACCEPTED));
@@ -155,6 +159,7 @@ public void testConnectX509SucceedsUsingSni(final VertxTestContext ctx) {
* @param ctx The test context
*/
@Test
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true, tenantAlias = true)
public void testConnectX509SucceedsUsingSniWithTenantAlias(final VertxTestContext ctx) {
@@ -174,7 +179,9 @@ public void testConnectX509SucceedsUsingSniWithTenantAlias(final VertxTestContex
deviceId,
cert))
// WHEN the device connects to the adapter including the tenant alias in the host name
- .compose(ok -> connectToAdapter(deviceCert, "test-alias." + IntegrationTestSupport.MQTT_HOST))
+ .compose(ok -> connectToAdapter(
+ deviceCert,
+ IntegrationTestSupport.getSniHostname(IntegrationTestSupport.MQTT_HOST, "test-alias")))
.onComplete(ctx.succeeding(conAckMsg -> {
// THEN the connection attempt succeeds
ctx.verify(() -> assertThat(conAckMsg.code()).isEqualTo(MqttConnectReturnCode.CONNECTION_ACCEPTED));
@@ -189,6 +196,7 @@ public void testConnectX509SucceedsUsingSniWithTenantAlias(final VertxTestContex
* @param ctx The test context
*/
@Test
+ @EnabledIfDnsRebindingIsSupported
@EnabledIfRegistrySupportsFeatures(trustAnchorGroups = true, tenantAlias = true)
public void testConnectX509FailsUsingSniWithNonExistingTenantAlias(final VertxTestContext ctx) {
@@ -208,7 +216,9 @@ public void testConnectX509FailsUsingSniWithNonExistingTenantAlias(final VertxTe
deviceId,
cert))
// WHEN the device connects to the adapter including a wrong tenant alias in the host name
- .compose(ok -> connectToAdapter(deviceCert, "wrong-alias." + IntegrationTestSupport.MQTT_HOST))
+ .compose(ok -> connectToAdapter(
+ deviceCert,
+ IntegrationTestSupport.getSniHostname(IntegrationTestSupport.MQTT_HOST, "wrong-alias")))
.onComplete(ctx.failing(t -> {
// THEN the connection is refused
ctx.verify(() -> {