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

Reply via email to