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]