This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new f120f43a9f75 CAMEL-26109: camel-vertx-websocket - fail to start when
consumers sharing a host and port use different SSLContextParameters (#28357)
f120f43a9f75 is described below
commit f120f43a9f755a73b1c804b17cffdb820eab3252
Author: Federico Mariani <[email protected]>
AuthorDate: Mon Oct 26 14:20:32 2026 +0100
CAMEL-26109: camel-vertx-websocket - fail to start when consumers sharing a
host and port use different SSLContextParameters (#28357)
* CAMEL-26109: camel-vertx-websocket - fail to start when consumers sharing
a host and port use different SSLContextParameters
* CAMEL-26109: say whether the shared server runs without TLS in the error
message
* CAMEL-26109: only disconnect a consumer that connected, and expose only
the SSLContextParameters of the host
A consumer that fails the check is stopped by BaseService.start(), and must
not remove the route another consumer registered on the same path, nor stop
the server. Also tests that consumers without sslContextParameters share
one host.
* CAMEL-26109: add the upgrade notes for 4.22.3 and 4.18.6
---
.../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_18.adoc | 9 +
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 9 +
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 9 +
8 files changed, 285 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 8118b7a7054f..fc53ca74d361 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_18.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
index e5cedb8881d5..3a8f5a1de266 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
@@ -497,6 +497,15 @@ has an entry with a wildcard such as `https://*` or an
entry `null`, the origin
credentials. Previously credentials were allowed for every origin. A
cross-origin client that sends cookies or other
credentials must have its origin listed in `allowedOrigins`.
+=== 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.18.4 to 4.18.5
=== camel-azure-eventhubs
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 5fb06ba6af28..10ac0a1220f3 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
@@ -312,6 +312,15 @@ first ran is no longer served: its messages no longer
reach the route and it no
sends, and the connection is closed. Clients that open a connection in this
window must reconnect after the consumer
has started.
+=== 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-lra - only an LRA hosted by the configured coordinator is joined
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 9af133837d12..d289ebdbd6fe 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -296,6 +296,15 @@ default). It fails with a
`java.util.concurrent.TimeoutException` when RabbitMQ
and with an `org.springframework.amqp.AmqpTimeoutException` when RabbitMQ does
not confirm it within `confirmTimeout`.
Set `confirm=disabled` to send without waiting, as 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.
+
=== camel-jbang - camel validate and camel overview report the same endpoints
no route consumes
`camel validate source` and `camel overview` now use the same rules for a
`direct:`, `seda:` or `disruptor:` endpoint