This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 5181a3f57a9b CAMEL-23259: Close channel on SSL/TLS handshake failure
in Netty comp… (#22295)
5181a3f57a9b is described below
commit 5181a3f57a9b796246df16df4c1696fa2c07aea7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Mar 27 14:39:50 2026 +0100
CAMEL-23259: Close channel on SSL/TLS handshake failure in Netty comp…
(#22295)
* 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]>
* CAMEL-23259: Address gnodet's review on PR #22295
- Use NettyHelper.close(ctx.channel()) instead of ctx.close() for
consistency with other handlers in the package
- Update Javadoc to clarify this is a defense-in-depth safety net,
since Netty 4.x already closes channels on handshake failure natively
Signed-off-by: Andrea Cosentino <[email protected]>
---------
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 | 54 ++++++++++++++++++++
.../netty/SslHandshakeFailureHandlerTest.java | 59 ++++++++++++++++++++++
6 files changed, 121 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..a5eb6fde70ff
--- /dev/null
+++
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/handlers/SslHandshakeFailureHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.apache.camel.component.netty.NettyHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Ensures channel closure on SSL/TLS handshake failure as a defense-in-depth
safety net, complementing Netty's built-in
+ * behavior.
+ * <p>
+ * Netty 4.x's {@code SslHandler} already closes the channel on handshake
failure natively. This handler provides an
+ * explicit safety net by listening for {@link SslHandshakeCompletionEvent}
and closing the channel on failure,
+ * replacing the removed Netty 3.x {@code
SslHandler.setCloseOnSSLException(true)} API that was commented out since the
+ * Netty 4 migration (CAMEL-6555).
+ */
[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());
+ NettyHelper.close(ctx.channel());
+ }
+ }
+ 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");
+ }
+}