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"); + } +}
