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

Reply via email to