This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch CAMEL-23259
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 12b7d4330f6d5961c4f6efb2656f5f5a1fe5093b
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Mar 27 12:53:57 2026 +0100

    CAMEL-23259: Close channel on SSL/TLS handshake failure in Netty components
    
    Add SslHandshakeFailureHandler to close channels when SSL handshake
    fails, replacing the removed Netty 3.x 
SslHandler.setCloseOnSSLException(true)
    API. The handler listens for SslHandshakeCompletionEvent and closes the
    channel on failure, preventing failed SSL connections from remaining open.
    
    Applied to all four pipeline initializer factories:
    - DefaultServerInitializerFactory (camel-netty)
    - DefaultClientInitializerFactory (camel-netty)
    - HttpServerInitializerFactory (camel-netty-http)
    - HttpClientInitializerFactory (camel-netty-http)
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../netty/http/HttpClientInitializerFactory.java   |  4 +-
 .../netty/http/HttpServerInitializerFactory.java   |  5 +-
 .../netty/DefaultClientInitializerFactory.java     |  4 +-
 .../netty/DefaultServerInitializerFactory.java     |  4 +-
 .../netty/handlers/SslHandshakeFailureHandler.java | 51 +++++++++++++++++++
 .../netty/SslHandshakeFailureHandlerTest.java      | 59 ++++++++++++++++++++++
 6 files changed, 118 insertions(+), 9 deletions(-)

diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java
index 9aa91f14807b..1460decfcd4d 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java
@@ -38,6 +38,7 @@ import org.apache.camel.component.netty.ChannelHandlerFactory;
 import org.apache.camel.component.netty.ClientInitializerFactory;
 import org.apache.camel.component.netty.NettyConfiguration;
 import org.apache.camel.component.netty.NettyProducer;
+import org.apache.camel.component.netty.handlers.SslHandshakeFailureHandler;
 import org.apache.camel.component.netty.http.handlers.HttpClientChannelHandler;
 import org.apache.camel.component.netty.http.handlers.HttpInboundStreamHandler;
 import 
org.apache.camel.component.netty.http.handlers.HttpOutboundStreamHandler;
@@ -86,10 +87,9 @@ public class HttpClientInitializerFactory extends 
ClientInitializerFactory {
 
         SslHandler sslHandler = configureClientSSLOnDemand();
         if (sslHandler != null) {
-            //TODO must close on SSL exception
-            //sslHandler.setCloseOnSSLException(true);
             LOG.debug("Client SSL handler configured and added as an 
interceptor against the ChannelPipeline: {}", sslHandler);
             pipeline.addLast("ssl", sslHandler);
+            pipeline.addLast("sslHandshakeFailure", 
SslHandshakeFailureHandler.INSTANCE);
         }
 
         pipeline.addLast("http", new HttpClientCodec());
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
index 43623e9f79aa..4d31ff93e468 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
@@ -37,6 +37,7 @@ import org.apache.camel.component.netty.ChannelHandlerFactory;
 import org.apache.camel.component.netty.NettyConsumer;
 import org.apache.camel.component.netty.NettyServerBootstrapConfiguration;
 import org.apache.camel.component.netty.ServerInitializerFactory;
+import org.apache.camel.component.netty.handlers.SslHandshakeFailureHandler;
 import org.apache.camel.component.netty.http.handlers.HttpInboundStreamHandler;
 import 
org.apache.camel.component.netty.http.handlers.HttpOutboundStreamHandler;
 import org.apache.camel.component.netty.ssl.SSLEngineFactory;
@@ -83,9 +84,6 @@ public class HttpServerInitializerFactory extends 
ServerInitializerFactory {
 
         ChannelHandler sslHandler = configureServerSSLOnDemand();
         if (sslHandler != null) {
-            //TODO must close on SSL exception
-            // sslHandler.setCloseOnSSLException(true);
-
             if (sslHandler instanceof ChannelHandlerFactory) {
                 // use the factory to create a new instance of the channel as 
it may not be shareable
                 sslHandler = ((ChannelHandlerFactory) 
sslHandler).newChannelHandler();
@@ -93,6 +91,7 @@ public class HttpServerInitializerFactory extends 
ServerInitializerFactory {
 
             LOG.debug("Server SSL handler configured and added as an 
interceptor against the ChannelPipeline: {}", sslHandler);
             pipeline.addLast("ssl", sslHandler);
+            pipeline.addLast("sslHandshakeFailure", 
SslHandshakeFailureHandler.INSTANCE);
         }
 
         pipeline.addLast("decoder", new HttpRequestDecoder(
diff --git 
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java
 
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java
index 0c897865bf9b..1b18f3f61461 100644
--- 
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java
+++ 
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java
@@ -30,6 +30,7 @@ import io.netty.handler.ssl.SslHandler;
 import io.netty.handler.timeout.ReadTimeoutHandler;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.component.netty.handlers.ClientChannelHandler;
+import org.apache.camel.component.netty.handlers.SslHandshakeFailureHandler;
 import org.apache.camel.component.netty.ssl.SSLEngineFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -56,10 +57,9 @@ public class DefaultClientInitializerFactory extends 
ClientInitializerFactory {
 
         SslHandler sslHandler = configureClientSSLOnDemand();
         if (sslHandler != null) {
-            //TODO  must close on SSL exception
-            //sslHandler.setCloseOnSSLException(true);
             LOG.debug("Client SSL handler configured and added to the 
ChannelPipeline: {}", sslHandler);
             addToPipeline("ssl", channelPipeline, sslHandler);
+            addToPipeline("sslHandshakeFailure", channelPipeline, 
SslHandshakeFailureHandler.INSTANCE);
         }
 
         List<ChannelHandler> decoders = 
producer.getConfiguration().getDecodersAsList();
diff --git 
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java
 
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java
index 638f37ad7fcd..a3d73b991c4f 100644
--- 
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java
+++ 
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java
@@ -30,6 +30,7 @@ import io.netty.util.concurrent.EventExecutorGroup;
 import org.apache.camel.CamelContext;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.component.netty.handlers.ServerChannelHandler;
+import org.apache.camel.component.netty.handlers.SslHandshakeFailureHandler;
 import org.apache.camel.component.netty.ssl.SSLEngineFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -60,10 +61,9 @@ public class DefaultServerInitializerFactory extends 
ServerInitializerFactory {
 
         SslHandler sslHandler = configureServerSSLOnDemand();
         if (sslHandler != null) {
-            //TODO  must close on SSL exception
-            //sslHandler.setCloseOnSSLException(true);
             LOG.debug("Server SSL handler configured and added as an 
interceptor against the ChannelPipeline: {}", sslHandler);
             addToPipeline("ssl", channelPipeline, sslHandler);
+            addToPipeline("sslHandshakeFailure", channelPipeline, 
SslHandshakeFailureHandler.INSTANCE);
         }
 
         List<ChannelHandler> encoders = 
consumer.getConfiguration().getEncodersAsList();
diff --git 
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/handlers/SslHandshakeFailureHandler.java
 
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/handlers/SslHandshakeFailureHandler.java
new file mode 100644
index 000000000000..139cb4834f45
--- /dev/null
+++ 
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/handlers/SslHandshakeFailureHandler.java
@@ -0,0 +1,51 @@
+/*
+ * 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.netty.handlers;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.ssl.SslHandshakeCompletionEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Closes the channel when an SSL/TLS handshake failure is detected.
+ * <p>
+ * This handler replaces the removed Netty 3.x {@code 
SslHandler.setCloseOnSSLException(true)} functionality. It listens
+ * for {@link SslHandshakeCompletionEvent} and closes the channel on failure, 
preventing failed SSL connections from
+ * remaining open.
+ */
[email protected]
+public class SslHandshakeFailureHandler extends ChannelInboundHandlerAdapter {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SslHandshakeFailureHandler.class);
+
+    public static final SslHandshakeFailureHandler INSTANCE = new 
SslHandshakeFailureHandler();
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) 
throws Exception {
+        if (evt instanceof SslHandshakeCompletionEvent handshakeEvent) {
+            if (!handshakeEvent.isSuccess()) {
+                LOG.debug("SSL/TLS handshake failed on channel {}, closing: 
{}",
+                        ctx.channel(), handshakeEvent.cause().getMessage());
+                ctx.close();
+            }
+        }
+        super.userEventTriggered(ctx, evt);
+    }
+}
diff --git 
a/components/camel-netty/src/test/java/org/apache/camel/component/netty/SslHandshakeFailureHandlerTest.java
 
b/components/camel-netty/src/test/java/org/apache/camel/component/netty/SslHandshakeFailureHandlerTest.java
new file mode 100644
index 000000000000..a868c81c0b2a
--- /dev/null
+++ 
b/components/camel-netty/src/test/java/org/apache/camel/component/netty/SslHandshakeFailureHandlerTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.netty;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.ssl.SslHandshakeCompletionEvent;
+import org.apache.camel.component.netty.handlers.SslHandshakeFailureHandler;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class SslHandshakeFailureHandlerTest {
+
+    @Test
+    public void testChannelClosedOnHandshakeFailure() {
+        EmbeddedChannel channel = new 
EmbeddedChannel(SslHandshakeFailureHandler.INSTANCE);
+        assertTrue(channel.isOpen());
+
+        SslHandshakeCompletionEvent failureEvent
+                = new SslHandshakeCompletionEvent(new 
SSLHandshakeException("test handshake failure"));
+        channel.pipeline().fireUserEventTriggered(failureEvent);
+
+        assertFalse(channel.isOpen(), "Channel should be closed after SSL 
handshake failure");
+    }
+
+    @Test
+    public void testChannelOpenOnHandshakeSuccess() {
+        EmbeddedChannel channel = new 
EmbeddedChannel(SslHandshakeFailureHandler.INSTANCE);
+        assertTrue(channel.isOpen());
+
+        
channel.pipeline().fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS);
+
+        assertTrue(channel.isOpen(), "Channel should remain open after 
successful SSL handshake");
+        channel.close();
+    }
+
+    @Test
+    public void testHandlerIsSharable() {
+        assertTrue(SslHandshakeFailureHandler.INSTANCE.isSharable(),
+                "SslHandshakeFailureHandler should be sharable");
+    }
+}

Reply via email to