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

zaynt4606 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/celeborn.git


The following commit(s) were added to refs/heads/main by this push:
     new 0bf4b44314 [CELEBORN-2388] Decouple inter-master Ratis TLS into its 
own SSL module
0bf4b44314 is described below

commit 0bf4b44314b8f5b89367e9051a572109b3f1486a
Author: hzheng182 <[email protected]>
AuthorDate: Sun Nov 1 16:04:55 2026 +0800

    [CELEBORN-2388] Decouple inter-master Ratis TLS into its own SSL module
    
    ### What changes were proposed in this pull request?
    
    Adds a dedicated `ratis` SSL module so inter-master Ratis (Raft consensus) 
gRPC TLS can use its own certificate/keystore, independent of the client-facing 
`rpc_service` module — with a fully backward-compatible fallback.
    
    - `TransportModuleConstants`: add `RATIS_MODULE = "ratis"`.
    - `CelebornConf`: add `sslEnabledExplicitly(module)`, true only when 
`celeborn.ssl.<module>.enabled` itself is set to `true`, ignoring the global 
`celeborn.ssl.enabled` default and parent module fallbacks.
    - `MasterClusterInfo.scala`: select the Ratis SSL module in one place:
      - `ratisSslModule = ratis` only if `sslEnabledExplicitly(ratis)`, i.e. 
`celeborn.ssl.ratis.enabled` is explicitly `true`, else `rpc_service`. The 
global `celeborn.ssl.enabled` default does not select `ratis`, because the 
`ratis` module does not inherit the `rpc_service` keystore.
      - Ratis TLS is enabled when `sslEnabled(ratisSslModule)` is true.
    - `HARaftServer.configureSsl`: build the `SSLFactory` from 
`MasterClusterInfo.ratisSslModule`.
    - Docs: add the `ratis` module to `docs/configuration/network-module.md` 
and describe opting in under `docs/security.md`.
    
    Config keys (`celeborn.ssl.ratis.enabled/.keyStore/.trustStore/...`) come 
from the existing `celeborn.ssl.<module>.*` machinery — no enumerated module 
list needed.
    
    ### Why are the changes needed?
    
    Master↔master Ratis TLS is currently derived from the `rpc_service` module 
(`MasterClusterInfo.scala` → `HARaftServer.configureSsl`). But `rpc_service` is 
also the client-facing control cert, so Ratis and external clients are forced 
to share **one** presented cert. That cert must simultaneously satisfy:
    
    - **clients**: signed by a CA they trust (e.g. a corporate CA) + the 
gateway/SNI hostname, and
    - **Ratis peers**: gRPC hostname verification against the internal master 
pod FQDNs (`*.<svc>-master-svc.<ns>.svc.cluster.local`).
    
    In a cross-cluster / TLS-passthrough-gateway deployment these requirements 
conflict (the client cert has no internal SANs), so HA masters can't complete 
the Ratis TLS handshake ("UNAVAILABLE: io exception" during leader election). 
Decoupling lets operators give Ratis its own cert (internal SANs) while leaving 
the client-facing cert untouched.
    
    Note: CELEBORN-1356 already split the unified `rpc` module into 
`rpc_app`/`rpc_service` for client-vs-server separation, but never separated 
Ratis inter-master traffic from `rpc_service` specifically — this PR closes 
that remaining gap.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Default behavior is unchanged: unless `celeborn.ssl.ratis.enabled=true` is 
explicitly set, Ratis TLS enablement and certificates come from `rpc_service` 
exactly as today, including when SSL is enabled through the global 
`celeborn.ssl.enabled` default. The new `ratis` module only takes effect when 
`celeborn.ssl.ratis.enabled=true` is explicitly set.
    
    ### How was this patch tested?
    
    `CelebornConfSuite` covers `sslEnabledExplicitly` against the global and 
parent module defaults. `MasterClusterInfoSuite` covers module selection and 
enablement, including:
    - global `celeborn.ssl.enabled=true` with the keystore only under 
`rpc_service` selects `rpc_service` and its certificate;
    - global `celeborn.ssl.enabled=true` with `rpc_service.enabled=false` 
leaves Ratis TLS disabled;
    - an explicitly disabled `ratis` module falls back to `rpc_service`;
    - an explicitly enabled `ratis` module uses its own certificate rather than 
the `rpc_service` one.
    
    The certificate cases assert the certificate presented by the selected key 
manager. Existing `SSLRatisMasterStatusSystemSuiteJ` and 
`MasterRatisServerSuiteJ` pass.
    
    Closes #3767 from shellfish007/upstream-pr/ratis-tls-module.
    
    Authored-by: hzheng182 <[email protected]>
    Signed-off-by: zhengtao <[email protected]>
    
    AI-Contributed/Feature: 0/36
    AI-Contributed/UT: 0/155
---
 .../common/protocol/TransportModuleConstants.java  |   5 +
 .../org/apache/celeborn/common/CelebornConf.scala  |   8 ++
 .../apache/celeborn/common/CelebornConfSuite.scala |  15 +++
 docs/configuration/network-module.md               |   1 +
 docs/security.md                                   |   2 +
 .../deploy/master/clustermeta/ha/HARaftServer.java |   3 +-
 .../master/clustermeta/ha/MasterClusterInfo.scala  |  17 ++-
 .../clustermeta/ha/MasterClusterInfoSuite.scala    | 140 +++++++++++++++++++++
 8 files changed, 187 insertions(+), 4 deletions(-)

diff --git 
a/common/src/main/java/org/apache/celeborn/common/protocol/TransportModuleConstants.java
 
b/common/src/main/java/org/apache/celeborn/common/protocol/TransportModuleConstants.java
index ae4e77ed54..befcead4df 100644
--- 
a/common/src/main/java/org/apache/celeborn/common/protocol/TransportModuleConstants.java
+++ 
b/common/src/main/java/org/apache/celeborn/common/protocol/TransportModuleConstants.java
@@ -45,6 +45,11 @@ public class TransportModuleConstants {
 
   public static final String DATA_MODULE = "data";
 
+  // SSL module for the inter-master Ratis channel, letting it use a 
certificate separate from
+  // the client-facing RPC_SERVICE_MODULE. Used only when 
celeborn.ssl.ratis.enabled is explicitly
+  // true, otherwise Ratis keeps using RPC_SERVICE_MODULE.
+  public static final String RATIS_MODULE = "ratis";
+
   // When using null, InetSocketAddress will resolve to a wildcard address for 
bind operations.
   public static final String WILDCARD_BIND_ADDRESS = null;
 }
diff --git 
a/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala 
b/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
index 23bb955c58..f66e4f251f 100644
--- a/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
+++ b/common/src/main/scala/org/apache/celeborn/common/CelebornConf.scala
@@ -1594,6 +1594,14 @@ class CelebornConf(loadDefaults: Boolean) extends 
Cloneable with Logging with Se
     getSslConfig(SSL_ENABLED, module)
   }
 
+  /**
+   * Whether SSL is explicitly enabled for the module itself, ignoring the 
global
+   * `celeborn.ssl.enabled` default and parent module fallbacks.
+   */
+  def sslEnabledExplicitly(module: String): Boolean = {
+    getOption(SSL_ENABLED.key.replace("<module>", 
module)).exists(SSL_ENABLED.valueConverter)
+  }
+
   /**
    * SSL protocol (remember that SSLv3 was compromised) supported by Java
    */
diff --git 
a/common/src/test/scala/org/apache/celeborn/common/CelebornConfSuite.scala 
b/common/src/test/scala/org/apache/celeborn/common/CelebornConfSuite.scala
index 392d5edcda..faa15ff26d 100644
--- a/common/src/test/scala/org/apache/celeborn/common/CelebornConfSuite.scala
+++ b/common/src/test/scala/org/apache/celeborn/common/CelebornConfSuite.scala
@@ -423,6 +423,21 @@ class CelebornConfSuite extends CelebornFunSuite {
     validateDefaultTransportConfValue(conf, "rpc_app")
   }
 
+  test("sslEnabledExplicitly ignores global and parent module defaults") {
+    val conf = new CelebornConf()
+    conf.set("celeborn.ssl.enabled", "true")
+    conf.set("celeborn.ssl.rpc.enabled", "true")
+    assert(conf.sslEnabled("rpc_service"))
+    assert(!conf.sslEnabledExplicitly("rpc_service"))
+    assert(!conf.sslEnabledExplicitly("ratis"))
+
+    conf.set("celeborn.ssl.ratis.enabled", "true")
+    assert(conf.sslEnabledExplicitly("ratis"))
+
+    conf.set("celeborn.ssl.ratis.enabled", "false")
+    assert(!conf.sslEnabledExplicitly("ratis"))
+  }
+
   test("Test fallback config works even with parent") {
     // Using NETWORK_IO_CONNECT_TIMEOUT since it has fallback to 
NETWORK_CONNECT_TIMEOUT
 
diff --git a/docs/configuration/network-module.md 
b/docs/configuration/network-module.md
index 18cee80982..8b66c7f3b8 100644
--- a/docs/configuration/network-module.md
+++ b/docs/configuration/network-module.md
@@ -29,6 +29,7 @@ The various transport modules which can be configured are:
 | fetch | - | Configure transport module for handling data fetch at Celeborn 
workers |
 | data | - | Configure transport module for handling data push and fetch at 
Celeborn apps |
 | replicate | - | Configure transport module for handling data replication 
between Celeborn workers |
+| ratis | - | Configure SSL for Raft communication between Celeborn masters. 
Used only when `celeborn.ssl.ratis.enabled` is explicitly set to `true`, 
otherwise Raft communication uses the `rpc_service` SSL configuration |
 
 
 <!--end-include-->
diff --git a/docs/security.md b/docs/security.md
index 8fa7b62fd1..588ee985e3 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -36,6 +36,8 @@ end="<!--end-include-->"
 
 When SSL is enabled for `rpc_service`, Raft communication between masters are 
secured **only when** `celeborn.master.ha.ratis.raft.rpc.type` is set to `grpc`.
 
+By default, Raft communication uses the `rpc_service` SSL configuration. To 
secure it with a separate certificate, set `celeborn.ssl.ratis.enabled` to 
`true` explicitly and configure the `ratis` module. The global 
`celeborn.ssl.enabled` default does not enable the `ratis` module. Setting 
`celeborn.ssl.ratis.enabled` to `false` does not disable TLS for Raft 
communication; it keeps using the `rpc_service` SSL configuration.
+
 Note that `celeborn.ssl`, **without any module**, can be used to set SSL 
default values which applies to all modules.
 
 Also note that `data` module at application side, maps to `push` and `fetch` 
at worker - hence, for SSL configuration, worker configuration for `push` and 
`fetch` should be compatible with each other and with `data` at application 
side.
diff --git 
a/master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java
 
b/master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java
index c36aa321de..86f090c4dd 100644
--- 
a/master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java
+++ 
b/master/src/main/java/org/apache/celeborn/service/deploy/master/clustermeta/ha/HARaftServer.java
@@ -62,7 +62,6 @@ import org.apache.celeborn.common.CelebornConf;
 import org.apache.celeborn.common.client.MasterClient;
 import org.apache.celeborn.common.exception.CelebornRuntimeException;
 import org.apache.celeborn.common.network.ssl.SSLFactory;
-import org.apache.celeborn.common.protocol.TransportModuleConstants;
 import org.apache.celeborn.common.util.ThreadUtils;
 import org.apache.celeborn.common.util.Utils;
 import org.apache.celeborn.service.deploy.master.clustermeta.ResourceProtos;
@@ -515,7 +514,7 @@ public class HARaftServer {
     // also why nThreads does not matter
     SSLFactory factory =
         SSLFactory.createSslFactory(
-            Utils.fromCelebornConf(conf, 
TransportModuleConstants.RPC_SERVICE_MODULE, 1));
+            Utils.fromCelebornConf(conf, 
MasterClusterInfo.ratisSslModule(conf), 1));
 
     assert (null != factory);
     assert (factory.hasKeyManagers());
diff --git 
a/master/src/main/scala/org/apache/celeborn/service/deploy/master/clustermeta/ha/MasterClusterInfo.scala
 
b/master/src/main/scala/org/apache/celeborn/service/deploy/master/clustermeta/ha/MasterClusterInfo.scala
index 83b6f92830..18ee5043c2 100644
--- 
a/master/src/main/scala/org/apache/celeborn/service/deploy/master/clustermeta/ha/MasterClusterInfo.scala
+++ 
b/master/src/main/scala/org/apache/celeborn/service/deploy/master/clustermeta/ha/MasterClusterInfo.scala
@@ -38,8 +38,7 @@ object MasterClusterInfo extends Logging {
   def loadHAConfig(conf: CelebornConf): MasterClusterInfo = {
     val localNodeIdOpt = conf.haMasterNodeId
     val clusterNodeIds = conf.haMasterNodeIds
-    // If ssl is enabled, we enable it for ratis as well
-    val sslEnabled = 
conf.sslEnabled(TransportModuleConstants.RPC_SERVICE_MODULE)
+    val sslEnabled = conf.sslEnabled(ratisSslModule(conf))
 
     val masterNodes = clusterNodeIds.map { nodeId =>
       val ratisHost = conf.haMasterRatisHost(nodeId)
@@ -72,6 +71,20 @@ object MasterClusterInfo extends Logging {
     MasterClusterInfo(localNodes.head, peerNodes.toList.asJava)
   }
 
+  /**
+   * SSL module for the inter-master Ratis channel. The dedicated `ratis` 
module is used only when
+   * `celeborn.ssl.ratis.enabled` is explicitly true; the global 
`celeborn.ssl.enabled` default
+   * does not select it, so deployments configuring only `rpc_service` keep 
its enablement and
+   * certificates.
+   */
+  def ratisSslModule(conf: CelebornConf): String = {
+    if (conf.sslEnabledExplicitly(TransportModuleConstants.RATIS_MODULE)) {
+      TransportModuleConstants.RATIS_MODULE
+    } else {
+      TransportModuleConstants.RPC_SERVICE_MODULE
+    }
+  }
+
   private def isLocalAddress(addr: InetAddress): Boolean = {
     if (addr.isAnyLocalAddress || addr.isLoopbackAddress) {
       return true
diff --git 
a/master/src/test/scala/org/apache/celeborn/service/deploy/master/clustermeta/ha/MasterClusterInfoSuite.scala
 
b/master/src/test/scala/org/apache/celeborn/service/deploy/master/clustermeta/ha/MasterClusterInfoSuite.scala
new file mode 100644
index 0000000000..6dfe9b82f1
--- /dev/null
+++ 
b/master/src/test/scala/org/apache/celeborn/service/deploy/master/clustermeta/ha/MasterClusterInfoSuite.scala
@@ -0,0 +1,140 @@
+/*
+ * 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.celeborn.service.deploy.master.clustermeta.ha
+
+import java.io.FileInputStream
+import java.security.KeyStore
+import java.security.cert.Certificate
+import javax.net.ssl.X509KeyManager
+
+import scala.collection.JavaConverters._
+
+import org.apache.celeborn.CelebornFunSuite
+import org.apache.celeborn.common.CelebornConf
+import org.apache.celeborn.common.network.ssl.{SSLFactory, SslSampleConfigs}
+import org.apache.celeborn.common.protocol.TransportModuleConstants
+import org.apache.celeborn.common.util.Utils
+
+class MasterClusterInfoSuite extends CelebornFunSuite {
+
+  private def sslEnabledKey(module: String): String = 
s"celeborn.ssl.$module.enabled"
+
+  private val globalSslKey = "celeborn.ssl.enabled"
+  private val ratisKey = sslEnabledKey(TransportModuleConstants.RATIS_MODULE)
+  private val rpcServiceKey = 
sslEnabledKey(TransportModuleConstants.RPC_SERVICE_MODULE)
+
+  private def setAll(conf: CelebornConf, entries: java.util.Map[String, 
String]): Unit = {
+    entries.asScala.foreach { case (k, v) => conf.set(k, v) }
+  }
+
+  private def ratisSslEnabled(conf: CelebornConf): Boolean = {
+    conf.sslEnabled(MasterClusterInfo.ratisSslModule(conf))
+  }
+
+  // Certificate presented by the key manager that Ratis TLS would be 
configured with.
+  private def selectedCertificate(conf: CelebornConf): Certificate = {
+    val module = MasterClusterInfo.ratisSslModule(conf)
+    val factory = SSLFactory.createSslFactory(Utils.fromCelebornConf(conf, 
module, 1))
+    try {
+      assert(factory.hasKeyManagers)
+      val keyManager = 
factory.getKeyManagers.get(0).asInstanceOf[X509KeyManager]
+      val alias = keyManager.getServerAliases("RSA", null).head
+      keyManager.getCertificateChain(alias).head
+    } finally {
+      factory.destroy()
+    }
+  }
+
+  private def keyStoreCertificate(path: String): Certificate = {
+    val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
+    val in = new FileInputStream(path)
+    try {
+      keyStore.load(in, "password".toCharArray)
+    } finally {
+      in.close()
+    }
+    keyStore.getCertificate(keyStore.aliases().nextElement())
+  }
+
+  test("ratis ssl is disabled when neither ratis nor rpc_service module is 
enabled") {
+    assert(!ratisSslEnabled(new CelebornConf()))
+  }
+
+  test("ratis ssl stays enabled when only rpc_service is enabled") {
+    // Legacy deployments configure only rpc_service; they must keep securing 
Ratis as before.
+    val conf = new CelebornConf()
+    conf.set(rpcServiceKey, "true")
+    assert(ratisSslEnabled(conf))
+    assert(MasterClusterInfo.ratisSslModule(conf) === 
TransportModuleConstants.RPC_SERVICE_MODULE)
+  }
+
+  test("ratis ssl is enabled by the dedicated ratis module alone") {
+    val conf = new CelebornConf()
+    conf.set(ratisKey, "true")
+    assert(ratisSslEnabled(conf))
+    assert(MasterClusterInfo.ratisSslModule(conf) === 
TransportModuleConstants.RATIS_MODULE)
+  }
+
+  test("ratis ssl is enabled by the ratis module even when rpc_service is 
explicitly disabled") {
+    val conf = new CelebornConf()
+    conf.set(ratisKey, "true")
+    conf.set(rpcServiceKey, "false")
+    assert(ratisSslEnabled(conf))
+  }
+
+  test("explicitly disabled ratis module falls back to rpc_service") {
+    val conf = new CelebornConf()
+    conf.set(ratisKey, "false")
+    conf.set(rpcServiceKey, "true")
+    assert(ratisSslEnabled(conf))
+    assert(MasterClusterInfo.ratisSslModule(conf) === 
TransportModuleConstants.RPC_SERVICE_MODULE)
+  }
+
+  test("global ssl default does not select the ratis module") {
+    val conf = new CelebornConf()
+    conf.set(globalSslKey, "true")
+    setAll(
+      conf,
+      
SslSampleConfigs.createDefaultConfigMapForModule(TransportModuleConstants.RPC_SERVICE_MODULE))
+    assert(MasterClusterInfo.ratisSslModule(conf) === 
TransportModuleConstants.RPC_SERVICE_MODULE)
+    assert(ratisSslEnabled(conf))
+    assert(
+      selectedCertificate(conf) === 
keyStoreCertificate(SslSampleConfigs.DEFAULT_KEY_STORE_PATH))
+  }
+
+  test("global ssl default does not enable ratis ssl when rpc_service is 
disabled") {
+    val conf = new CelebornConf()
+    conf.set(globalSslKey, "true")
+    conf.set(rpcServiceKey, "false")
+    assert(MasterClusterInfo.ratisSslModule(conf) === 
TransportModuleConstants.RPC_SERVICE_MODULE)
+    assert(!ratisSslEnabled(conf))
+  }
+
+  test("explicitly enabled ratis module uses its own certificate") {
+    val conf = new CelebornConf()
+    setAll(
+      conf,
+      
SslSampleConfigs.createDefaultConfigMapForModule(TransportModuleConstants.RPC_SERVICE_MODULE))
+    setAll(
+      conf,
+      
SslSampleConfigs.createAnotherConfigMapForModule(TransportModuleConstants.RATIS_MODULE))
+    assert(MasterClusterInfo.ratisSslModule(conf) === 
TransportModuleConstants.RATIS_MODULE)
+    assert(
+      selectedCertificate(conf) === 
keyStoreCertificate(SslSampleConfigs.SECOND_KEY_STORE_PATH))
+  }
+}

Reply via email to