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

pan3793 pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 006bfa65706e [SPARK-54293][SQL] Make ThriftHttpCLIService SNI host 
check configurable
006bfa65706e is described below

commit 006bfa65706eb6009782d552adbe160d1a1ee9d5
Author: Anupam Yadav <[email protected]>
AuthorDate: Thu May 21 21:27:40 2026 +0800

    [SPARK-54293][SQL] Make ThriftHttpCLIService SNI host check configurable
    
    ### What changes were proposed in this pull request?
    
    Make the SNI host check in `ThriftHttpCLIService` configurable via 
`spark.sql.hive.thriftServer.http.sniHostCheckEnabled`, following the same 
pattern as SPARK-56528 which made it configurable for the Spark UI.
    
    ### Why are the changes needed?
    
    SPARK-45522 (Jetty 10+ upgrade) silently enabled SNI host checking, which 
causes HTTPS connection failures when the certificate CN does not match the 
hostname. This was made configurable in the Spark UI server (SPARK-56528) but 
`ThriftHttpCLIService` had no way to disable it.
    
    This PR adds the same configurability: default `true` (preserving the 
current SPARK-45522 behavior), with the option to set `false` to restore 
pre-SPARK-45522 behavior.
    
    ### Does this PR introduce _any_ user-facing change?
    
    A new internal configuration 
`spark.sql.hive.thriftServer.http.sniHostCheckEnabled` (default: `true`) is 
added. Users who need to disable SNI host checking can set it to `false`.
    
    ### How was this patch tested?
    
    Added `ThriftHttpCLIServiceSuite` with 3 tests:
    - SNI host check enabled by default
    - SNI host check disabled when configured
    - Jetty 10+ default behavior verification
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Yes
    
    Closes #55308 from yadavay-amzn/fix/SPARK-54293-sni-host-check.
    
    Authored-by: Anupam Yadav <[email protected]>
    Signed-off-by: Cheng Pan <[email protected]>
    (cherry picked from commit fa1373efa35a93c32cd377443e9bdc8fc0d83335)
    Signed-off-by: Cheng Pan <[email protected]>
---
 .../apache/spark/sql/internal/StaticSQLConf.scala  |  9 +++
 .../service/cli/thrift/ThriftHttpCLIService.java   | 15 +++-
 .../apache/hive/service/server/HiveServer2.java    |  2 +-
 .../sql/hive/thriftserver/HiveThriftServer2.scala  |  5 +-
 .../thriftserver/ThriftHttpCLIServiceSuite.scala   | 91 ++++++++++++++++++++++
 .../configs-without-binding-policy-exceptions      |  1 +
 6 files changed, 119 insertions(+), 4 deletions(-)

diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/StaticSQLConf.scala 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/StaticSQLConf.scala
index 6a7d13fb5421..25114a93f04c 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/StaticSQLConf.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/StaticSQLConf.scala
@@ -122,6 +122,15 @@ object StaticSQLConf {
       .booleanConf
       .createWithDefault(false)
 
+  val HIVE_THRIFT_SERVER_HTTP_SNI_HOST_CHECK_ENABLED =
+    buildStaticConf("spark.sql.hive.thriftServer.http.sniHostCheckEnabled")
+      .internal()
+      .doc("Whether to enable Jetty's SNI host check on the 
ThriftHttpCLIService HTTPS " +
+        "connector. Set to false to restore the behavior prior to SPARK-45522 
(Jetty 10+).")
+      .version("4.3.0")
+      .booleanConf
+      .createWithDefault(true)
+
   val SPARK_SESSION_EXTENSIONS = buildStaticConf("spark.sql.extensions")
     .doc("A comma-separated list of classes that implement " +
       "Function1[SparkSessionExtensions, Unit] used to configure Spark Session 
extensions. The " +
diff --git 
a/sql/hive-thriftserver/src/main/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java
 
b/sql/hive-thriftserver/src/main/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java
index 1ada2bdb0bca..5cc3794f6da4 100644
--- 
a/sql/hive-thriftserver/src/main/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java
+++ 
b/sql/hive-thriftserver/src/main/java/org/apache/hive/service/cli/thrift/ThriftHttpCLIService.java
@@ -40,6 +40,8 @@ import org.apache.thrift.protocol.TProtocolFactory;
 import org.eclipse.jetty.server.AbstractConnectionFactory;
 import org.eclipse.jetty.server.ConnectionFactory;
 import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
 import org.eclipse.jetty.ee10.servlet.ServletHolder;
@@ -50,9 +52,11 @@ import 
org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
 public class ThriftHttpCLIService extends ThriftCLIService {
 
   protected org.eclipse.jetty.server.Server httpServer;
+  private final boolean sniHostCheckEnabled;
 
-  public ThriftHttpCLIService(CLIService cliService) {
+  public ThriftHttpCLIService(CLIService cliService, boolean 
sniHostCheckEnabled) {
     super(cliService, ThriftHttpCLIService.class.getSimpleName());
+    this.sniHostCheckEnabled = sniHostCheckEnabled;
   }
 
   @Override
@@ -91,8 +95,15 @@ public class ThriftHttpCLIService extends ThriftCLIService {
           Arrays.toString(sslContextFactoryServer.getExcludeProtocols()));
         sslContextFactoryServer.setKeyStorePath(keyStorePath);
         sslContextFactoryServer.setKeyStorePassword(keyStorePassword);
+        // SPARK-54293: Configure SNI host check, which defaults to true since 
Jetty 10.
+        // Controlled by spark.sql.hive.thriftServer.http.sniHostCheckEnabled 
(default: true),
+        // consistent with the behavior since SPARK-45522 (Jetty 10+).
+        HttpConfiguration httpConfig = new HttpConfiguration();
+        SecureRequestCustomizer src = new SecureRequestCustomizer();
+        src.setSniHostCheck(sniHostCheckEnabled);
+        httpConfig.addCustomizer(src);
         connectionFactories = AbstractConnectionFactory.getFactories(
-            sslContextFactoryServer, new HttpConnectionFactory());
+            sslContextFactoryServer, new HttpConnectionFactory(httpConfig));
       } else {
         connectionFactories = new ConnectionFactory[] { new 
HttpConnectionFactory() };
       }
diff --git 
a/sql/hive-thriftserver/src/main/java/org/apache/hive/service/server/HiveServer2.java
 
b/sql/hive-thriftserver/src/main/java/org/apache/hive/service/server/HiveServer2.java
index 9e3ec3fc61ce..e01a3cfcf48c 100644
--- 
a/sql/hive-thriftserver/src/main/java/org/apache/hive/service/server/HiveServer2.java
+++ 
b/sql/hive-thriftserver/src/main/java/org/apache/hive/service/server/HiveServer2.java
@@ -64,7 +64,7 @@ public class HiveServer2 extends CompositeService {
     cliService = new CLIService(this);
     addService(cliService);
     if (isHTTPTransportMode(hiveConf)) {
-      thriftCLIService = new ThriftHttpCLIService(cliService);
+      thriftCLIService = new ThriftHttpCLIService(cliService, false);
     } else {
       thriftCLIService = new ThriftBinaryCLIService(cliService);
     }
diff --git 
a/sql/hive-thriftserver/src/main/scala/org/apache/spark/sql/hive/thriftserver/HiveThriftServer2.scala
 
b/sql/hive-thriftserver/src/main/scala/org/apache/spark/sql/hive/thriftserver/HiveThriftServer2.scala
index f0ccd5320c1a..3825c6ed750f 100644
--- 
a/sql/hive-thriftserver/src/main/scala/org/apache/spark/sql/hive/thriftserver/HiveThriftServer2.scala
+++ 
b/sql/hive-thriftserver/src/main/scala/org/apache/spark/sql/hive/thriftserver/HiveThriftServer2.scala
@@ -34,6 +34,7 @@ import org.apache.spark.sql.{SparkSession, SQLContext}
 import org.apache.spark.sql.hive.HiveUtils
 import org.apache.spark.sql.hive.thriftserver.ReflectionUtils._
 import org.apache.spark.sql.hive.thriftserver.ui._
+import org.apache.spark.sql.internal.StaticSQLConf
 import org.apache.spark.status.ElementTrackingStore
 import org.apache.spark.util.{ShutdownHookManager, Utils}
 
@@ -158,7 +159,9 @@ private[hive] class HiveThriftServer2(sparkSession: 
SparkSession)
     addService(sparkSqlCliService)
 
     val thriftCliService = if (isHTTPTransportMode(hiveConf)) {
-      new ThriftHttpCLIService(sparkSqlCliService)
+      val sniHostCheckEnabled = sparkSession.conf.get(
+        StaticSQLConf.HIVE_THRIFT_SERVER_HTTP_SNI_HOST_CHECK_ENABLED)
+      new ThriftHttpCLIService(sparkSqlCliService, sniHostCheckEnabled)
     } else {
       new ThriftBinaryCLIService(sparkSqlCliService)
     }
diff --git 
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftHttpCLIServiceSuite.scala
 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftHttpCLIServiceSuite.scala
new file mode 100644
index 000000000000..8fec2fc51066
--- /dev/null
+++ 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftHttpCLIServiceSuite.scala
@@ -0,0 +1,91 @@
+/*
+ * 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.spark.sql.hive.thriftserver
+
+import org.eclipse.jetty.server.{AbstractConnectionFactory, HttpConfiguration, 
HttpConnectionFactory, SecureRequestCustomizer}
+import org.eclipse.jetty.util.ssl.SslContextFactory
+
+import org.apache.spark.SparkFunSuite
+
+class ThriftHttpCLIServiceSuite extends SparkFunSuite {
+
+  /**
+   * Helper that builds the SSL connection factory chain the same way
+   * ThriftHttpCLIService.initializeServer() does, using the given 
sniHostCheck value.
+   */
+  private def buildSslFactoriesAndGetCustomizer(
+      sniHostCheckEnabled: Boolean): SecureRequestCustomizer = {
+    val sslContextFactory = new SslContextFactory.Server()
+    val httpConfig = new HttpConfiguration()
+    val src = new SecureRequestCustomizer()
+    src.setSniHostCheck(sniHostCheckEnabled)
+    httpConfig.addCustomizer(src)
+    val connectionFactories = AbstractConnectionFactory.getFactories(
+      sslContextFactory, new HttpConnectionFactory(httpConfig))
+
+    val httpFactory = connectionFactories
+      .find(_.isInstanceOf[HttpConnectionFactory])
+      .map(_.asInstanceOf[HttpConnectionFactory])
+      .getOrElse(fail("HttpConnectionFactory not found in SSL connection 
factories"))
+
+    httpFactory.getHttpConfiguration.getCustomizers.toArray
+      .find(_.isInstanceOf[SecureRequestCustomizer])
+      .map(_.asInstanceOf[SecureRequestCustomizer])
+      .getOrElse(fail("SecureRequestCustomizer not found in 
HttpConfiguration"))
+  }
+
+  test("SPARK-54293: SNI host check enabled by default") {
+    // Default behavior: sniHostCheckEnabled = true
+    val customizer = buildSslFactoriesAndGetCustomizer(sniHostCheckEnabled = 
true)
+    assert(customizer.isSniHostCheck,
+      "SNI host check should be enabled when sniHostCheckEnabled is true")
+  }
+
+  test("SPARK-54293: SNI host check disabled when configured") {
+    // Opt-out behavior: sniHostCheckEnabled = false
+    val customizer = buildSslFactoriesAndGetCustomizer(sniHostCheckEnabled = 
false)
+    assert(!customizer.isSniHostCheck,
+      "SNI host check should be disabled when sniHostCheckEnabled is false")
+  }
+
+  test("SPARK-54293: SSL connection factories without fix have SNI host check 
enabled") {
+    // Demonstrate that without the fix (no SecureRequestCustomizer), Jetty 10+
+    // defaults to sniHostCheck=true, which causes the bug.
+    val sslContextFactory = new SslContextFactory.Server()
+    val connectionFactories = AbstractConnectionFactory.getFactories(
+      sslContextFactory, new HttpConnectionFactory())
+
+    val httpFactory = connectionFactories
+      .find(_.isInstanceOf[HttpConnectionFactory])
+      .map(_.asInstanceOf[HttpConnectionFactory])
+      .getOrElse(fail("HttpConnectionFactory not found"))
+
+    val customizers = httpFactory.getHttpConfiguration.getCustomizers
+    val secureCustomizer = customizers.toArray
+      .find(_.isInstanceOf[SecureRequestCustomizer])
+      .map(_.asInstanceOf[SecureRequestCustomizer])
+
+    // Without the fix, either there's no SecureRequestCustomizer at all,
+    // or it has sniHostCheck=true (the Jetty 10+ default)
+    secureCustomizer match {
+      case Some(src) => assert(src.isSniHostCheck,
+        "Default Jetty 10+ behavior should have SNI host check enabled")
+      case None => // No customizer means Jetty adds one with defaults 
(sniHostCheck=true)
+    }
+  }
+}
diff --git 
a/sql/hive/src/test/resources/conf/binding-policy-exceptions/configs-without-binding-policy-exceptions
 
b/sql/hive/src/test/resources/conf/binding-policy-exceptions/configs-without-binding-policy-exceptions
index 36fda2b50688..c8dc75ae9182 100644
--- 
a/sql/hive/src/test/resources/conf/binding-policy-exceptions/configs-without-binding-policy-exceptions
+++ 
b/sql/hive/src/test/resources/conf/binding-policy-exceptions/configs-without-binding-policy-exceptions
@@ -648,6 +648,7 @@ spark.sql.hive.metastorePartitionPruningFastFallback
 spark.sql.hive.metastorePartitionPruningInSetThreshold
 spark.sql.hive.tablePropertyLengthThreshold
 spark.sql.hive.thriftServer.async
+spark.sql.hive.thriftServer.http.sniHostCheckEnabled
 spark.sql.hive.thriftServer.singleSession
 spark.sql.hive.useDelegateForSymlinkTextInputFormat
 spark.sql.hive.version


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

Reply via email to