This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new ce1ca2ac64e8 CAMEL-26109: camel-vertx-websocket - fail to start when
consumers sharing a host and port use different SSLContextParameters
ce1ca2ac64e8 is described below
commit ce1ca2ac64e8077c0080180d118b4df23b8715e4
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)
---
.../src/main/docs/vertx-websocket-component.adoc | 3 +
.../vertx/websocket/VertxWebsocketComponent.java | 22 ++-
.../vertx/websocket/VertxWebsocketHost.java | 27 ++-
...ebsocketSharedHostSSLContextParametersTest.java | 214 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 9 +
5 files changed, 270 insertions(+), 5 deletions(-)
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 31f7e2b04aa0..a72a9a7a4e6b 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
@@ -123,5 +123,8 @@ from("direct:broadcastMessage")
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.
include::spring-boot:partial$starter.adoc[]
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 9b057cb8c8a8..6475ed742fd0 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();
@@ -202,10 +217,11 @@ public class VertxWebsocketComponent extends
DefaultComponent implements SSLCont
VertxWebsocketEndpoint endpoint = consumer.getEndpoint();
VertxWebsocketConfiguration configuration =
endpoint.getConfiguration();
VertxWebsocketHostKey hostKey =
createHostKey(configuration.getWebsocketURI());
- VertxWebsocketHost vertxWebsocketHost =
vertxHostRegistry.remove(hostKey);
+ VertxWebsocketHost vertxWebsocketHost = vertxHostRegistry.get(hostKey);
- if (vertxWebsocketHost != null) {
-
vertxWebsocketHost.disconnect(configuration.getWebsocketURI().getPath());
+ // a consumer that never connected, for example one that failed to
start, leaves the host to the consumers on it
+ if (vertxWebsocketHost != null &&
vertxWebsocketHost.disconnect(consumer)) {
+ vertxHostRegistry.remove(hostKey);
}
}
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 91b465cc7b34..cfa9f07f45f4 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;
@@ -52,6 +54,8 @@ public class VertxWebsocketHost {
private final VertxWebsocketHostConfiguration hostConfiguration;
private final VertxWebsocketHostKey hostKey;
private final Map<String, Route> routeRegistry = new HashMap<>();
+ // 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 HttpServer server;
@@ -64,6 +68,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
*/
@@ -154,12 +166,21 @@ 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.
+ *
+ * @return whether the consumer was connected to this host
*/
- public void disconnect(String path) {
+ public boolean disconnect(VertxWebsocketConsumer consumer) {
+ if (!connectedConsumers.remove(consumer)) {
+ return false;
+ }
+ String path =
consumer.getEndpoint().getConfiguration().getWebsocketURI().getPath();
LOG.info("Disconnected consumer for path {}", path);
Route route = routeRegistry.remove(path);
route.remove();
@@ -170,6 +191,7 @@ public class VertxWebsocketHost {
throw new RuntimeException(e);
}
}
+ return true;
}
/**
@@ -233,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..b62d74aeb89f
--- /dev/null
+++
b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketSharedHostSSLContextParametersTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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());
+ // this line's Vert.x SSL setup expects key managers on the client as
well
+ clientParameters.setKeyManagers(keyManagers());
+ }
+
+ @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 +
"/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 +
"/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)
+ .routeId("tls")
+ .to("mock:tls");
+
+ fromF("vertx-websocket:localhost:%d/same", port)
+ .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 +
"/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, firstOptions)
+ .routeId("a").startupOrder(1)
+ .to("mock:a");
+
+ fromF("vertx-websocket:localhost:%d/b%s", port, 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 4cb7a34343d5..216fe257f20e 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
@@ -71,6 +71,15 @@ Use `0.0.0.0` as the host to accept connections on all
network interfaces.
`AvroRpcHttpServerFactory` has a new method `create(SpecificResponder, String,
int)` that receives the host. A custom factory should implement it.
+=== 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-docling - output path handling