This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new 12148625b46a CAMEL-26109: camel-vertx-websocket - fail to start when
consumers sharing a host and port use different SSLContextParameters
12148625b46a is described below
commit 12148625b46ad07e07a55ab3abc24855f2d050ed
Author: croway <[email protected]>
AuthorDate: Fri Oct 23 15:59:24 2026 +0200
CAMEL-26109: camel-vertx-websocket - fail to start when consumers sharing a
host and port use different SSLContextParameters
(cherry picked from commit f120f43a9f755a73b1c804b17cffdb820eab3252)
---
.../catalog/docs/vertx-websocket-component.adoc | 4 +
.../src/main/docs/vertx-websocket-component.adoc | 4 +
.../vertx/websocket/VertxWebsocketComponent.java | 17 +-
.../vertx/websocket/VertxWebsocketHost.java | 24 ++-
...ebsocketSharedHostSSLContextParametersTest.java | 212 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 9 +
6 files changed, 267 insertions(+), 3 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/vertx-websocket-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/vertx-websocket-component.adoc
index 896f146b659f..739c26d068dd 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/vertx-websocket-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/vertx-websocket-component.adoc
@@ -341,3 +341,7 @@ YAML::
By default, the `ws://` protocol is used, but secure connections with `wss://`
are supported by configuring the consumer or producer
via the `sslContextParameters` URI parameter and the
xref:manual::camel-configuration-utilities.adoc[Camel JSSE Configuration
Utility]
+
+All consumers on the same host and port are served by one HTTP server, so they
must use the same `sslContextParameters`
+instance. A consumer whose `sslContextParameters` differ from those of the
consumers already started on its host and
+port fails to start.
diff --git
a/components/camel-vertx/camel-vertx-websocket/src/main/docs/vertx-websocket-component.adoc
b/components/camel-vertx/camel-vertx-websocket/src/main/docs/vertx-websocket-component.adoc
index 896f146b659f..739c26d068dd 100644
---
a/components/camel-vertx/camel-vertx-websocket/src/main/docs/vertx-websocket-component.adoc
+++
b/components/camel-vertx/camel-vertx-websocket/src/main/docs/vertx-websocket-component.adoc
@@ -341,3 +341,7 @@ YAML::
By default, the `ws://` protocol is used, but secure connections with `wss://`
are supported by configuring the consumer or producer
via the `sslContextParameters` URI parameter and the
xref:manual::camel-configuration-utilities.adoc[Camel JSSE Configuration
Utility]
+
+All consumers on the same host and port are served by one HTTP server, so they
must use the same `sslContextParameters`
+instance. A consumer whose `sslContextParameters` differ from those of the
consumers already started on its host and
+port fails to start.
diff --git
a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java
b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java
index a0ea300240f2..564a81767759 100644
---
a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java
+++
b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java
@@ -29,6 +29,7 @@ import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
+import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.PropertiesHelper;
import org.apache.camel.util.URISupport;
@@ -190,6 +191,20 @@ public class VertxWebsocketComponent extends
DefaultComponent implements SSLCont
return createVertxWebsocketHost(hostConfiguration, hostKey);
});
+ // every consumer on this host and port is served by the one server
the first consumer configured, so a consumer
+ // with other SSLContextParameters cannot be served with them, and
must not start as if it were
+ SSLContextParameters hostSslContextParameters =
host.getSslContextParameters();
+ if (configuration.getSslContextParameters() !=
hostSslContextParameters) {
+ throw new IllegalArgumentException(
+ "Cannot start the consumer for " +
configuration.getWebsocketURI() + " because the server on "
+ + hostKey
+ + (hostSslContextParameters ==
null
+ ? " was started without
TLS"
+ : " was started with
other sslContextParameters")
+ + ". All consumers on the same
host and port must use the same "
+ + "SSLContextParameters
instance, or none.");
+ }
+
host.connect(consumer);
try {
host.start();
@@ -205,7 +220,7 @@ public class VertxWebsocketComponent extends
DefaultComponent implements SSLCont
VertxWebsocketHost vertxWebsocketHost = vertxHostRegistry.get(hostKey);
if (vertxWebsocketHost != null) {
-
vertxWebsocketHost.disconnect(configuration.getWebsocketURI().getPath());
+ vertxWebsocketHost.disconnect(consumer);
// every consumer on this host and port shares the one host, which
stops its server as its last
// consumer goes. Forgetting it any earlier would leave the
consumers still on it unable to disconnect.
diff --git
a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHost.java
b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHost.java
index 630d85f1ad20..2edfd39a51c4 100644
---
a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHost.java
+++
b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHost.java
@@ -18,8 +18,10 @@ package org.apache.camel.component.vertx.websocket;
import java.net.URI;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
@@ -53,6 +55,8 @@ public class VertxWebsocketHost {
private final VertxWebsocketHostKey hostKey;
// guarded by this host's monitor, together with the server it decides the
lifecycle of
private final Map<String, Route> routeRegistry = new HashMap<>();
+ // guarded by this host's monitor, the consumers that got as far as
connect, and so may disconnect
+ private final Set<VertxWebsocketConsumer> connectedConsumers = new
HashSet<>();
private final List<VertxWebsocketPeer> connectedPeers = new
CopyOnWriteArrayList<>(); // thread-safe
private final CamelContext camelContext;
private volatile HttpServer server;
@@ -65,6 +69,14 @@ public class VertxWebsocketHost {
this.hostKey = key;
}
+ /**
+ * The SSLContextParameters of the server that every consumer on this host
and port is served by, or null when it
+ * runs without TLS
+ */
+ public SSLContextParameters getSslContextParameters() {
+ return hostConfiguration.getSslContextParameters();
+ }
+
/**
* Sets up a Vert.x route and handler for the WebSocket path specified by
the consumer configuration
*/
@@ -155,12 +167,19 @@ public class VertxWebsocketHost {
});
routeRegistry.put(websocketURI.getPath(), route);
+ connectedConsumers.add(consumer);
}
/**
- * Removes the Vert.x route and handler for the WebSocket path specified
by the consumer configuration
+ * Removes the Vert.x route and handler for the WebSocket path specified
by the consumer configuration. A consumer
+ * that never connected, for example one that failed to start, owns no
route, and must not remove the route another
+ * consumer registered on the same path.
*/
- public synchronized void disconnect(String path) {
+ public synchronized void disconnect(VertxWebsocketConsumer consumer) {
+ if (!connectedConsumers.remove(consumer)) {
+ return;
+ }
+ String path =
consumer.getEndpoint().getConfiguration().getWebsocketURI().getPath();
LOG.info("Disconnected consumer for path {}", path);
Route route = routeRegistry.remove(path);
if (route != null) {
@@ -236,6 +255,7 @@ public class VertxWebsocketHost {
}
connectedPeers.clear();
routeRegistry.clear();
+ connectedConsumers.clear();
port = VertxWebsocketConstants.DEFAULT_VERTX_SERVER_PORT;
}
diff --git
a/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketSharedHostSSLContextParametersTest.java
b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketSharedHostSSLContextParametersTest.java
new file mode 100644
index 000000000000..1493669d798e
--- /dev/null
+++
b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketSharedHostSSLContextParametersTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.camel.component.vertx.websocket;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.jsse.KeyManagersParameters;
+import org.apache.camel.support.jsse.KeyStoreParameters;
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.apache.camel.support.jsse.SSLContextServerParameters;
+import org.apache.camel.support.jsse.TrustManagersParameters;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Consumers sharing a host and port are served by one server, so they must
use the same SSLContextParameters.
+ */
+public class VertxWebsocketSharedHostSSLContextParametersTest extends
VertxWebSocketTestSupport {
+
+ private SSLContextParameters serverParameters;
+ private SSLContextParameters clientAuthServerParameters;
+ private SSLContextParameters clientParameters;
+
+ @BeforeEach
+ public void setupSSLContextParameters() {
+ serverParameters = new SSLContextParameters();
+ serverParameters.setKeyManagers(keyManagers());
+
+ clientAuthServerParameters = new SSLContextParameters();
+ clientAuthServerParameters.setKeyManagers(keyManagers());
+ clientAuthServerParameters.setTrustManagers(trustManagers());
+ SSLContextServerParameters clientAuthentication = new
SSLContextServerParameters();
+ clientAuthentication.setClientAuthentication("REQUIRE");
+ clientAuthServerParameters.setServerParameters(clientAuthentication);
+
+ clientParameters = new SSLContextParameters();
+ clientParameters.setTrustManagers(trustManagers());
+ }
+
+ @Test
+ void consumerWithSSLContextParametersAfterConsumerWithoutFailsToStart()
throws Exception {
+ assertFailsToStart(createContext("",
"?sslContextParameters=#serverParameters"));
+ }
+
+ @Test
+ void consumerWithoutSSLContextParametersAfterConsumerWithFailsToStart()
throws Exception {
+
assertFailsToStart(createContext("?sslContextParameters=#serverParameters",
""));
+ }
+
+ @Test
+ void consumerWithOtherSSLContextParametersFailsToStart() throws Exception {
+
assertFailsToStart(createContext("?sslContextParameters=#serverParameters",
+ "?sslContextParameters=#clientAuthServerParameters"));
+ }
+
+ @Test
+ void consumersWithTheSameSSLContextParametersShareTheHost() throws
Exception {
+ CamelContext context =
createContext("?sslContextParameters=#serverParameters",
+ "?sslContextParameters=#serverParameters");
+ context.start();
+ try {
+ MockEndpoint mockEndpoint = context.getEndpoint("mock:b",
MockEndpoint.class);
+ mockEndpoint.expectedBodiesReceived("Hello");
+ mockEndpoint.setResultWaitTime(TimeUnit.SECONDS.toMillis(5));
+
+ context.createProducerTemplate().sendBody(
+ "vertx-websocket:localhost:" + port.getPort() +
"/b?sslContextParameters=#clientParameters", "Hello");
+
+ mockEndpoint.assertIsSatisfied();
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ void consumersWithoutSSLContextParametersShareTheHost() throws Exception {
+ CamelContext context = createContext("", "");
+ context.start();
+ try {
+ VertxWebsocketComponent component =
context.getComponent("vertx-websocket", VertxWebsocketComponent.class);
+ assertEquals(1, component.getVertxHostRegistry().size());
+
+ MockEndpoint mockEndpoint = context.getEndpoint("mock:b",
MockEndpoint.class);
+ mockEndpoint.expectedBodiesReceived("Hello");
+ mockEndpoint.setResultWaitTime(TimeUnit.SECONDS.toMillis(5));
+
+
context.createProducerTemplate().sendBody("vertx-websocket:localhost:" +
port.getPort() + "/b", "Hello");
+
+ mockEndpoint.assertIsSatisfied();
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ void rejectedConsumerOnTheSamePathKeepsTheRouteOfTheStartedConsumer()
throws Exception {
+ CamelContext context = createContext();
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+
fromF("vertx-websocket:localhost:%d/same?sslContextParameters=#serverParameters",
port.getPort())
+ .routeId("tls")
+ .to("mock:tls");
+
+ fromF("vertx-websocket:localhost:%d/same", port.getPort())
+ .routeId("plain").autoStartup(false)
+ .to("mock:plain");
+ }
+ });
+ context.start();
+ try {
+ Exception exception = assertThrows(Exception.class, () ->
context.getRouteController().startRoute("plain"));
+ assertSSLContextParametersMismatch(exception);
+
+ MockEndpoint mockEndpoint = context.getEndpoint("mock:tls",
MockEndpoint.class);
+ mockEndpoint.expectedBodiesReceived("Hello");
+ mockEndpoint.setResultWaitTime(TimeUnit.SECONDS.toMillis(5));
+
+ context.createProducerTemplate().sendBody(
+ "vertx-websocket:localhost:" + port.getPort() +
"/same?sslContextParameters=#clientParameters", "Hello");
+
+ mockEndpoint.assertIsSatisfied();
+ } finally {
+ context.stop();
+ }
+ }
+
+ private CamelContext createContext(String firstOptions, String
secondOptions) throws Exception {
+ CamelContext context = createContext();
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ fromF("vertx-websocket:localhost:%d/a%s", port.getPort(),
firstOptions)
+ .routeId("a").startupOrder(1)
+ .to("mock:a");
+
+ fromF("vertx-websocket:localhost:%d/b%s", port.getPort(),
secondOptions)
+ .routeId("b").startupOrder(2)
+ .to("mock:b");
+ }
+ });
+ return context;
+ }
+
+ private CamelContext createContext() {
+ CamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind("serverParameters", serverParameters);
+ context.getRegistry().bind("clientAuthServerParameters",
clientAuthServerParameters);
+ context.getRegistry().bind("clientParameters", clientParameters);
+ return context;
+ }
+
+ private static void assertFailsToStart(CamelContext context) {
+ try {
+ assertSSLContextParametersMismatch(assertThrows(Exception.class,
context::start));
+ } finally {
+ context.stop();
+ }
+ }
+
+ private static void assertSSLContextParametersMismatch(Exception
exception) {
+ Throwable cause = exception;
+ while (cause != null && !(cause instanceof IllegalArgumentException)) {
+ cause = cause.getCause();
+ }
+ assertNotNull(cause, "Expected an IllegalArgumentException, got " +
exception);
+ assertTrue(cause.getMessage().contains("localhost:"),
cause.getMessage());
+ assertTrue(cause.getMessage().contains("same SSLContextParameters"),
cause.getMessage());
+ }
+
+ private static KeyManagersParameters keyManagers() {
+ KeyStoreParameters keyStore = new KeyStoreParameters();
+ keyStore.setResource("server.jks");
+ keyStore.setPassword("security");
+ KeyManagersParameters keyManagers = new KeyManagersParameters();
+ keyManagers.setKeyPassword("security");
+ keyManagers.setKeyStore(keyStore);
+ return keyManagers;
+ }
+
+ private static TrustManagersParameters trustManagers() {
+ KeyStoreParameters trustStore = new KeyStoreParameters();
+ trustStore.setResource("client.jks");
+ trustStore.setPassword("storepass");
+ TrustManagersParameters trustManagers = new TrustManagersParameters();
+ trustManagers.setKeyStore(trustStore);
+ return trustManagers;
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 0b6c386f05e1..49986b416b50 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -41,6 +41,15 @@ With `includeTrace=true`, the URI of the from endpoint in
the SQL comment is now
an endpoint URI, and it is left out when the exchange has no from endpoint,
which failed with a `NullPointerException`
before.
+=== camel-vertx-websocket - consumers on the same host and port must use the
same SSLContextParameters
+
+All `vertx-websocket` consumers on the same host and port are served by one
HTTP server, which is created from the
+`sslContextParameters` of the first consumer to start. A consumer started
later with different `sslContextParameters`
+(including one with none while the server uses some, or the other way round)
used to start and be served with the
+first consumer's settings. Such a consumer now fails to start with an
`IllegalArgumentException` naming the host and
+port. Give every consumer on a host and port the same `SSLContextParameters`
instance (for example the same bean
+reference, or the global SSL context parameters), or move consumers that need
other settings to another port.
+
== Upgrading from 4.22.1 to 4.22.2
=== camel-management - Route group exchange counters count once per entry into
the group again