This is an automated email from the ASF dual-hosted git repository.
fanningpj pushed a commit to branch 1.2.x
in repository https://gitbox.apache.org/repos/asf/pekko-management.git
The following commit(s) were added to refs/heads/1.2.x by this push:
new fbb0d3cb cluster-bootstrap support TLS requests in client calls (#426)
(#549)
fbb0d3cb is described below
commit fbb0d3cbe68d293517a57bc7227504191f1307d8
Author: PJ Fanning <[email protected]>
AuthorDate: Wed Nov 19 10:03:07 2025 +0100
cluster-bootstrap support TLS requests in client calls (#426) (#549)
* cluster-bootstrap support TLS requests in client calls
Update HttpContactPointBootstrap.scala
Update HttpContactPointBootstrap.scala
add cert
Update BootstrapCoordinatorSpec.scala
extra test
make TLS version configurable
cert unused
Update BootstrapCoordinatorSpec.scala
Update BootstrapCoordinatorSpec.scala
Update HttpContactPointBootstrap.scala
Revert "cert unused"
This reverts commit 83b45b537de0cef771f6e029f2663c07bbd5ec26.
add tests
* Update management-cluster-bootstrap/src/main/resources/reference.conf
* Update
management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrapSpec.scala
---------
Co-authored-by: Copilot <[email protected]>
---
build.sbt | 1 +
.../src/main/resources/reference.conf | 7 +++
.../bootstrap/ClusterBootstrapSettings.scala | 7 +++
.../internal/HttpContactPointBootstrap.scala | 39 ++++++++++++-
management-cluster-bootstrap/src/test/files/ca.crt | 18 ++++++
.../resources/{reference.conf => application.conf} | 0
.../internal/BootstrapCoordinatorSpec.scala | 9 +--
.../internal/HttpContactPointBootstrapSpec.scala | 67 +++++++++++++++++++++-
8 files changed, 142 insertions(+), 6 deletions(-)
diff --git a/build.sbt b/build.sbt
index 351e9ef9..c3e76ef5 100644
--- a/build.sbt
+++ b/build.sbt
@@ -151,6 +151,7 @@ lazy val managementClusterBootstrap =
pekkoModule("management-cluster-bootstrap"
libraryDependencies := Dependencies.managementClusterBootstrap,
mimaPreviousArtifactsSet)
.dependsOn(management)
+ .dependsOn(managementPki)
lazy val leaseKubernetes = pekkoModule("lease-kubernetes")
.enablePlugins(AutomateHeaderPlugin, ReproducibleBuildsPlugin)
diff --git a/management-cluster-bootstrap/src/main/resources/reference.conf
b/management-cluster-bootstrap/src/main/resources/reference.conf
index 5332ae2e..0f4d7844 100644
--- a/management-cluster-bootstrap/src/main/resources/reference.conf
+++ b/management-cluster-bootstrap/src/main/resources/reference.conf
@@ -134,6 +134,13 @@ pekko.management {
# Max amount of jitter to be added on retries
probe-interval-jitter = 0.2
+
+ http-client {
+ # set this to your HTTPS certificate path if you want to setup a HTTPS
trust store
+ ca-path = ""
+ # the TLS version to use when connecting to contact points
+ tls-version = "TLSv1.2"
+ }
}
join-decider {
diff --git
a/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/ClusterBootstrapSettings.scala
b/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/ClusterBootstrapSettings.scala
index ca6d47e5..ba77a437 100644
---
a/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/ClusterBootstrapSettings.scala
+++
b/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/ClusterBootstrapSettings.scala
@@ -134,6 +134,13 @@ final class ClusterBootstrapSettings(config: Config, log:
LoggingAdapter) {
object contactPoint {
private val contactPointConfig = bootConfig.getConfig("contact-point")
+ object httpClient {
+ private val httpClientConfig =
contactPointConfig.getConfig("http-client")
+
+ val caPath: String = httpClientConfig.getString("ca-path")
+ val tlsVersion: String = httpClientConfig.getString("tls-version")
+ }
+
val fallbackPort: Int =
contactPointConfig
.optDefinedValue("fallback-port")
diff --git
a/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrap.scala
b/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrap.scala
index cea30b9b..f9a2c62a 100644
---
a/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrap.scala
+++
b/management-cluster-bootstrap/src/main/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrap.scala
@@ -14,10 +14,13 @@
package org.apache.pekko.management.cluster.bootstrap.internal
import java.time.LocalDateTime
+import java.security.{ KeyStore, SecureRandom }
import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.TimeoutException
+import javax.net.ssl.{ KeyManager, KeyManagerFactory, SSLContext, TrustManager
}
import scala.concurrent.Future
import scala.concurrent.duration._
+
import org.apache.pekko
import pekko.actor.Actor
import pekko.actor.ActorLogging
@@ -29,7 +32,9 @@ import pekko.actor.Timers
import pekko.annotation.InternalApi
import pekko.cluster.Cluster
import pekko.discovery.ServiceDiscovery.ResolvedTarget
+import pekko.http.scaladsl.ConnectionContext
import pekko.http.scaladsl.Http
+import pekko.http.scaladsl.HttpsConnectionContext
import pekko.http.scaladsl.model.HttpResponse
import pekko.http.scaladsl.model.StatusCodes
import pekko.http.scaladsl.model.Uri
@@ -41,6 +46,7 @@ import
pekko.management.cluster.bootstrap.contactpoint.HttpBootstrapJsonProtocol
import pekko.management.cluster.bootstrap.contactpoint.{
ClusterBootstrapRequests, HttpBootstrapJsonProtocol }
import pekko.pattern.after
import pekko.pattern.pipe
+import pekko.pki.kubernetes.PemManagersProvider
@InternalApi
private[bootstrap] object HttpContactPointBootstrap {
@@ -56,6 +62,26 @@ private[bootstrap] object HttpContactPointBootstrap {
private case object ProbeTick extends DeadLetterSuppression
private val ProbingTimerKey = "probing-key"
+
+ def generateSSLContext(settings: ClusterBootstrapSettings): SSLContext = {
+ val factory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
+ val keyStore = KeyStore.getInstance("PKCS12")
+ keyStore.load(null)
+ factory.init(keyStore, Array.empty)
+ val km: Array[KeyManager] = factory.getKeyManagers
+ val caPath = settings.contactPoint.httpClient.caPath.trim
+ val tm: Array[TrustManager] = if (caPath.isEmpty) {
+ Array.empty
+ } else {
+ val certificates = PemManagersProvider.loadCertificates(caPath)
+ PemManagersProvider.buildTrustManagers(certificates)
+ }
+ val tlsVersion = settings.contactPoint.httpClient.tlsVersion.trim
+ val random: SecureRandom = new SecureRandom
+ val sslContext = SSLContext.getInstance(tlsVersion)
+ sslContext.init(km, tm, random)
+ sslContext
+ }
}
/**
@@ -88,7 +114,12 @@ private[bootstrap] class HttpContactPointBootstrap(
}
private implicit val sys: ActorSystem = context.system
+
+ private lazy val clientSslContext: HttpsConnectionContext =
+
ConnectionContext.httpsClient(HttpContactPointBootstrap.generateSSLContext(settings))
+
private val http = Http()
+
private val connectionPoolWithoutRetries =
ConnectionPoolSettings(context.system).withMaxRetries(0)
import context.dispatcher
@@ -111,7 +142,13 @@ private[bootstrap] class HttpContactPointBootstrap(
override def receive = {
case ProbeTick =>
log.debug("Probing [{}] for seed nodes...", probeRequest.uri)
- val reply = http.singleRequest(probeRequest, settings =
connectionPoolWithoutRetries).flatMap(handleResponse)
+ val reply = if (probeRequest.uri.scheme == "https") {
+ http.singleRequest(probeRequest, settings =
connectionPoolWithoutRetries,
+ connectionContext = clientSslContext)
+ } else {
+ http.singleRequest(probeRequest, settings =
connectionPoolWithoutRetries)
+ }.flatMap(handleResponse)
+
val afterTimeout = after(settings.contactPoint.probingFailureTimeout,
context.system.scheduler)(replyTimeout)
Future.firstCompletedOf(List(reply, afterTimeout)).pipeTo(self)
diff --git a/management-cluster-bootstrap/src/test/files/ca.crt
b/management-cluster-bootstrap/src/test/files/ca.crt
new file mode 100644
index 00000000..7fc98192
--- /dev/null
+++ b/management-cluster-bootstrap/src/test/files/ca.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
+a3ViZUNBMB4XDTE3MTIxMjEzMzY1MVoXDTI3MTIxMDEzMzY1MVowFTETMBEGA1UE
+AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMrk
+QcE8e2L3Rnm8K51y1Y4CHWwx4XwD0SqPwGq9nnygFaBsibIIrex89Im4f73QaqR5
+h87ypi0dyqlaTZdleZN7Q4hNSpWF1t/zSGanm7QOSl76FlTAFNm/eVNamfuGRf1x
+OYWGWRwdct3Six5K+R/qHh6oJ9XDli9LuV4vxHTDB/mr/2Xgyz1MDrIdRDYpiqev
+3HNJqnfXFT3eGWXk4ENZsc+I/R5LbSXA+cSQd9xrkrBhbreHLk99pif7eAKwVKNZ
+Rcsp9QBgMOUAoFgk+sU6YeVrasXIF1R4BB7g+LpqpM3F6jqmD79j2mREMIU3kjEQ
+eXMqi1W31i9ug1VxwTUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
+MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4IBAQAyRchLY4Jhu1EBFlhYebGLrEO/twZCu2NQyM0by5XUoXApJeqf
+S00Q7A67CRcQlbtRAH5vqhpCxutlKc26dF5Y1MmJmkGT7WIjujV0UIF/jJDnmwKK
++DRQl1UgA1e4WS6XOwUaSo9ltgJQ+GJfgkg3Xs3pzjjIpX94eF4V9ArJ8npRVO+w
+cCxE01P+Nm9U5H24QnlY+1IxNeszitm34SGiRy6SqoKSfYQoNyQadG9KVybs4FAs
+7aeYAB10I7FLFt4+Ji93zZjnWcKXjv59vz7NBDPtCsaXhJ82983GsfV2z+WQ3kRZ
+R2XVTsdz8yu0rgmyewxVKH7Roo5Ts+qpZFbi
+-----END CERTIFICATE-----
diff --git a/management-cluster-bootstrap/src/test/resources/reference.conf
b/management-cluster-bootstrap/src/test/resources/application.conf
similarity index 100%
rename from management-cluster-bootstrap/src/test/resources/reference.conf
rename to management-cluster-bootstrap/src/test/resources/application.conf
diff --git
a/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/BootstrapCoordinatorSpec.scala
b/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/BootstrapCoordinatorSpec.scala
index 32c72aae..9995694c 100644
---
a/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/BootstrapCoordinatorSpec.scala
+++
b/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/BootstrapCoordinatorSpec.scala
@@ -14,22 +14,23 @@
package org.apache.pekko.management.cluster.bootstrap.internal
import java.util.concurrent.atomic.AtomicReference
+
import org.apache.pekko
import pekko.actor.{ ActorRef, ActorSystem, Props }
import pekko.discovery.ServiceDiscovery.{ Resolved, ResolvedTarget }
import pekko.discovery.{ Lookup, MockDiscovery }
import pekko.http.scaladsl.model.Uri
-import com.typesafe.config.ConfigFactory
import
pekko.management.cluster.bootstrap.internal.BootstrapCoordinator.Protocol.InitiateBootstrapping
import pekko.management.cluster.bootstrap.{ ClusterBootstrapSettings,
LowestAddressJoinDecider }
-import org.scalatest.concurrent.Eventually
+import com.typesafe.config.ConfigFactory
import org.scalatest.BeforeAndAfterAll
+import org.scalatest.concurrent.Eventually
+import org.scalatest.matchers.should.Matchers
import org.scalatest.time.{ Millis, Seconds, Span }
+import org.scalatest.wordspec.AnyWordSpec
import scala.concurrent.{ Await, Future }
import scala.concurrent.duration._
-import org.scalatest.matchers.should.Matchers
-import org.scalatest.wordspec.AnyWordSpec
class BootstrapCoordinatorSpec extends AnyWordSpec with Matchers with
BeforeAndAfterAll with Eventually {
val serviceName = "bootstrap-coordinator-test-service"
diff --git
a/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrapSpec.scala
b/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrapSpec.scala
index ef80e5be..b3f8892d 100644
---
a/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrapSpec.scala
+++
b/management-cluster-bootstrap/src/test/scala/org/apache/pekko/management/cluster/bootstrap/internal/HttpContactPointBootstrapSpec.scala
@@ -13,17 +13,82 @@
package org.apache.pekko.management.cluster.bootstrap.internal
+import java.nio.file.NoSuchFileException
+
import org.apache.pekko
-import pekko.actor.ActorPath
+import pekko.actor.{ ActorPath, ActorSystem }
+import pekko.event.Logging
+import pekko.management.cluster.bootstrap.ClusterBootstrapSettings
import pekko.http.scaladsl.model.Uri.Host
+import com.typesafe.config.ConfigFactory
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class HttpContactPointBootstrapSpec extends AnyWordSpec with Matchers {
+
"HttpContactPointBootstrap" should {
"use a safe name when connecting over IPv6" in {
val name =
HttpContactPointBootstrap.name(Host("[fe80::1013:2070:258a:c662]"), 443)
ActorPath.isValidPathElement(name) should be(true)
}
+ "generate SSLContext with default config" in {
+ val sys = ActorSystem("HttpContactPointBootstrapSpec")
+ val log = Logging(sys, classOf[HttpContactPointBootstrapSpec])
+ try {
+ val settings = new ClusterBootstrapSettings(sys.settings.config, log)
+ HttpContactPointBootstrap.generateSSLContext(settings) should not be
null
+ } finally {
+ sys.terminate()
+ }
+ }
+ "generate SSLContext with cert" in {
+ val sys = ActorSystem("HttpContactPointBootstrapSpec")
+ val log = Logging(sys, classOf[HttpContactPointBootstrapSpec])
+ try {
+ val cfg = ConfigFactory.parseString("""
+ pekko.management.cluster.bootstrap.contact-point.http-client {
+ ca-path = "management-cluster-bootstrap/src/test/files/ca.crt"
+ }""").withFallback(sys.settings.config)
+ val settings = new ClusterBootstrapSettings(cfg, log)
+ HttpContactPointBootstrap.generateSSLContext(settings) should not be
null
+ } finally {
+ sys.terminate()
+ }
+ }
+ "fail to generate SSLContext with missing cert" in {
+ val sys = ActorSystem("HttpContactPointBootstrapSpec")
+ val log = Logging(sys, classOf[HttpContactPointBootstrapSpec])
+ try {
+ val cfg = ConfigFactory.parseString("""
+ pekko.management.cluster.bootstrap.contact-point.http-client {
+ ca-path =
"management-cluster-bootstrap/src/test/files/non-existent.crt"
+ }""").withFallback(sys.settings.config)
+ val settings = new ClusterBootstrapSettings(cfg, log)
+ intercept[NoSuchFileException] {
+ HttpContactPointBootstrap.generateSSLContext(settings)
+ }
+ } finally {
+ sys.terminate()
+ }
+ }
+ "fail to generate SSLContext with bad tls-version" in {
+ val sys = ActorSystem("HttpContactPointBootstrapSpec")
+ val log = Logging(sys, classOf[HttpContactPointBootstrapSpec])
+ try {
+ val cfg = ConfigFactory.parseString("""
+ pekko.management.cluster.bootstrap.contact-point.http-client {
+ ca-path = "management-cluster-bootstrap/src/test/files/ca.crt"
+ tls-version = "BAD_VERSION"
+ }""").withFallback(sys.settings.config)
+ val settings = new ClusterBootstrapSettings(cfg, log)
+ val noSuchAlgorithmException =
intercept[java.security.NoSuchAlgorithmException] {
+ HttpContactPointBootstrap.generateSSLContext(settings)
+ }
+ noSuchAlgorithmException.getMessage.contains("BAD_VERSION") should
be(true)
+ } finally {
+ sys.terminate()
+ }
+ }
+
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]