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

He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new 08ca126ca9 fix: improve TLS observability - handshake failure logging 
and fail-fast SSL init (#3165)
08ca126ca9 is described below

commit 08ca126ca97506c08044d451cc52a326a2ec66a8
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Thu Jun 25 11:22:50 2026 +0800

    fix: improve TLS observability - handshake failure logging and fail-fast 
SSL init (#3165)
    
    * fix: improve TLS observability - handshake failure logging and fail-fast 
SSL init
    
    Motivation:
    Classic remoting TLS handshake failures were silently swallowed: the
    NettySSLSupport handshake listener closed the channel without logging
    the failure reason or peer address. Additionally, SSLContext was lazily
    initialized, meaning keystore/truststore errors were only discovered on
    the first connection attempt rather than at startup.
    
    Modification:
    - NettySSLSupport: add MarkerLoggingAdapter parameter, log TLS handshake
      failures with peer address and cause
    - NettyTransport: pass log to NettySSLSupport
    - ConfigSSLEngineProvider: change sslContext from lazy val to val for
      eager initialization (fail-fast at provider construction time)
    
    Result:
    TLS handshake failures now produce actionable warning logs with peer
    address and error cause. Keystore/truststore configuration errors are
    detected at startup rather than on first connection.
    
    Tests:
    - remote / Test / testOnly 
org.apache.pekko.remote.ConfigSSLEngineProviderSpec
    - remote / Test / testOnly org.apache.pekko.remote.Ticket1978ConfigSpec
    
    References:
    Fixes #3162
    
    * fix: simplify redundant null check in TLS handshake failure handler
    
    Address review feedback: Netty guarantees cause() is non-null when
    isSuccess=false, so simplify the null check logic.
    
    * fix: log actual TLS handshake remote address
    
    Motivation:
    TLS handshake failure logging used SSLEngine.getPeerHost, but classic Netty 
creates engines without a peer host, so the new warning can report a null 
remote address.
    
    Modification:
    Derive the address from the Netty channel during failed-handshake cleanup. 
Centralize fallback formatting for missing remote addresses and missing cause 
messages, and add focused NettySSLSupport coverage.
    
    Result:
    Handshake failure logs include the actual channel remote address when 
available and stable fallback text otherwise.
    
    Tests:
    - scalafmt --list --mode diff-ref=origin/main
    - git diff --check
    - sbt "remote / Test / testOnly 
org.apache.pekko.remote.ConfigSSLEngineProviderSpec 
org.apache.pekko.remote.transport.netty.NettySSLSupportSpec"
    - sbt "remote / Test / testOnly 
org.apache.pekko.remote.Ticket1978ConfigSpec"
    - sbt "remote / Test / testOnly 
org.apache.pekko.remote.transport.netty.NettySSLSupportSpec"
    - sbt +headerCheckAll
    - sbt +mimaReportBinaryIssues
    - qodercli -p --output-format stream-json --cwd 
"/Users/hepin/IdeaProjects/pekkos/pekko/.qoder/worktrees/fix-tls-observability-3162"
 --attachment /tmp/project-review.diff
    
    References:
    Refs #3162
---
 .../remote/transport/netty/NettySSLSupport.scala   | 18 ++++++++-
 .../remote/transport/netty/NettyTransport.scala    |  2 +-
 .../remote/transport/netty/SSLEngineProvider.scala |  2 +-
 .../pekko/remote/ConfigSSLEngineProviderSpec.scala | 47 ++++++++++++++++++++++
 .../transport/netty/NettySSLSupportSpec.scala      | 43 ++++++++++++++++++++
 5 files changed, 108 insertions(+), 4 deletions(-)

diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala
 
b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala
index c127e4b0e8..06d723655e 100644
--- 
a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala
+++ 
b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala
@@ -13,6 +13,8 @@
 
 package org.apache.pekko.remote.transport.netty
 
+import java.net.SocketAddress
+
 import scala.annotation.nowarn
 import scala.jdk.CollectionConverters._
 
@@ -21,6 +23,7 @@ import io.netty.handler.ssl.SslHandler
 import io.netty.util.concurrent.Future
 
 import com.typesafe.config.Config
+import org.apache.pekko.event.MarkerLoggingAdapter
 
 /**
  * INTERNAL API
@@ -60,16 +63,27 @@ private[pekko] object NettySSLSupport {
   /**
    * Construct a SSLHandler which can be inserted into a Netty server/client 
pipeline
    */
-  def apply(sslEngineProvider: SSLEngineProvider, isClient: Boolean): 
SslHandler = {
+  def apply(sslEngineProvider: SSLEngineProvider, isClient: Boolean, log: 
MarkerLoggingAdapter): SslHandler = {
     val sslEngine =
       if (isClient) sslEngineProvider.createClientSSLEngine()
       else sslEngineProvider.createServerSSLEngine()
     val handler = new SslHandler(sslEngine)
     handler.handshakeFuture().addListener((future: Future[Channel]) => {
       if (!future.isSuccess) {
-        handler.closeOutbound().channel().close()
+        val channel = handler.closeOutbound().channel()
+        log.warning(
+          "TLS handshake failed for remote address [{}]: {}",
+          formatRemoteAddress(channel.remoteAddress()),
+          formatCause(future.cause()))
+        channel.close()
       }
     })
     handler
   }
+
+  private[netty] def formatRemoteAddress(remoteAddress: SocketAddress): String 
=
+    Option(remoteAddress).map(_.toString).getOrElse("unknown")
+
+  private[netty] def formatCause(cause: Throwable): String =
+    Option(cause).flatMap(t => 
Option(t.getMessage).orElse(Some(t.toString))).getOrElse("unknown cause")
 }
diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala
 
b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala
index f64a39ac02..bba302ce03 100644
--- 
a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala
+++ 
b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala
@@ -423,7 +423,7 @@ class NettyTransport(val settings: NettyTransportSettings, 
val system: ExtendedA
   private def sslHandler(isClient: Boolean): SslHandler = {
     sslEngineProvider match {
       case OptionVal.Some(sslProvider) =>
-        NettySSLSupport(sslProvider, isClient)
+        NettySSLSupport(sslProvider, isClient, log)
       case _ =>
         throw new IllegalStateException("Expected enable-ssl=on")
     }
diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala
 
b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala
index ecab23e25a..88619cb8fa 100644
--- 
a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala
+++ 
b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala
@@ -62,7 +62,7 @@ class ConfigSSLEngineProvider(protected val log: 
MarkerLoggingAdapter, private v
 
   import settings._
 
-  private lazy val sslContext: SSLContext = {
+  private val sslContext: SSLContext = {
     try {
       val rng = createSecureRandom()
       val ctx = SSLContext.getInstance(SSLProtocol)
diff --git 
a/remote/src/test/scala/org/apache/pekko/remote/ConfigSSLEngineProviderSpec.scala
 
b/remote/src/test/scala/org/apache/pekko/remote/ConfigSSLEngineProviderSpec.scala
new file mode 100644
index 0000000000..08e21e5c75
--- /dev/null
+++ 
b/remote/src/test/scala/org/apache/pekko/remote/ConfigSSLEngineProviderSpec.scala
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+package org.apache.pekko.remote
+
+import scala.annotation.nowarn
+
+import org.apache.pekko
+import pekko.remote.transport.netty.ConfigSSLEngineProvider
+import pekko.testkit.PekkoSpec
+
+import com.typesafe.config.ConfigFactory
+
+@nowarn("msg=deprecated")
+class ConfigSSLEngineProviderSpec
+    extends PekkoSpec(
+      ConfigFactory.parseString("""
+    pekko {
+      actor.provider = remote
+      remote.classic.netty.ssl.security {
+        key-store = "/nonexistent/keystore.jks"
+        key-store-password = "changeme"
+        key-password = "changeme"
+        trust-store = "/nonexistent/truststore.jks"
+        trust-store-password = "changeme"
+        protocol = "TLSv1.3"
+        enabled-algorithms = [TLS_AES_128_GCM_SHA256]
+        random-number-generator = ""
+        require-mutual-authentication = off
+      }
+    }
+  """)) {
+
+  "ConfigSSLEngineProvider" must {
+    "fail fast when keystore cannot be loaded" in {
+      intercept[RemoteTransportException] {
+        new ConfigSSLEngineProvider(system)
+      }.getMessage should include("could not be established")
+    }
+  }
+}
diff --git 
a/remote/src/test/scala/org/apache/pekko/remote/transport/netty/NettySSLSupportSpec.scala
 
b/remote/src/test/scala/org/apache/pekko/remote/transport/netty/NettySSLSupportSpec.scala
new file mode 100644
index 0000000000..da0957fa67
--- /dev/null
+++ 
b/remote/src/test/scala/org/apache/pekko/remote/transport/netty/NettySSLSupportSpec.scala
@@ -0,0 +1,43 @@
+/*
+ * 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.pekko.remote.transport.netty
+
+import java.net.InetSocketAddress
+
+import scala.annotation.nowarn
+
+import org.apache.pekko.testkit.PekkoSpec
+
+@nowarn("msg=deprecated")
+class NettySSLSupportSpec extends PekkoSpec {
+
+  "NettySSLSupport" must {
+    "format channel remote address for handshake failure logs" in {
+      NettySSLSupport.formatRemoteAddress(new InetSocketAddress("127.0.0.1", 
2552)) should ===("/127.0.0.1:2552")
+    }
+
+    "fall back when channel remote address is unavailable" in {
+      NettySSLSupport.formatRemoteAddress(null) should ===("unknown")
+    }
+
+    "fall back when handshake failure cause is unavailable or has no message" 
in {
+      NettySSLSupport.formatCause(null) should ===("unknown cause")
+      NettySSLSupport.formatCause(new RuntimeException(null: String)) should 
===("java.lang.RuntimeException")
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to