From f6d5a224d96e7b640833ee48966f082cc2505c55 Mon Sep 17 00:00:00 2001 From: Anupam Yadav Date: Sat, 11 Apr 2026 03:50:01 +0000 Subject: [PATCH 1/4] [SPARK-54293][SQL] Disable SNI host check in ThriftHttpCLIService Disable SNI host check in ThriftHttpCLIService's Jetty SSL connector, consistent with the fix applied to JettyUtils.scala in SPARK-45522. Since Jetty 10, SniHostCheck defaults to true. This was fixed in the Spark UI server but not in ThriftHttpCLIService, which also creates a Jetty server with SSL support. Note: RestSubmissionServer (also mentioned in the JIRA) does not have SSL support, so it does not need this fix. --- .../cli/thrift/ThriftHttpCLIService.java | 10 ++- .../ThriftHttpCLIServiceSuite.scala | 81 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftHttpCLIServiceSuite.scala 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 1ada2bdb0bca2..82400ab11e88b 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.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; @@ -91,8 +93,14 @@ protected void initializeServer() { Arrays.toString(sslContextFactoryServer.getExcludeProtocols())); sslContextFactoryServer.setKeyStorePath(keyStorePath); sslContextFactoryServer.setKeyStorePassword(keyStorePassword); + // SPARK-54293: Disable SNI host check, which defaults to true since Jetty 10. + // This is consistent with the fix in JettyUtils.scala (SPARK-45522). + HttpConfiguration httpConfig = new HttpConfiguration(); + SecureRequestCustomizer src = new SecureRequestCustomizer(); + src.setSniHostCheck(false); + 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/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 0000000000000..b18bbc3691f29 --- /dev/null +++ b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftHttpCLIServiceSuite.scala @@ -0,0 +1,81 @@ +/* + * 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 { + + test("SPARK-54293: SSL connection factories should disable SNI host check") { + // Reproduce the connection factory chain created by ThriftHttpCLIService + // when SSL is enabled. Without the fix, no SecureRequestCustomizer is added, + // and Jetty 10+ defaults sniHostCheck to true, causing SSL failures when + // the certificate CN doesn't match the hostname. + val sslContextFactory = new SslContextFactory.Server() + val httpConfig = new HttpConfiguration() + val src = new SecureRequestCustomizer() + src.setSniHostCheck(false) + httpConfig.addCustomizer(src) + val connectionFactories = AbstractConnectionFactory.getFactories( + sslContextFactory, new HttpConnectionFactory(httpConfig)) + + // Find the HttpConnectionFactory and verify its SecureRequestCustomizer + val httpFactory = connectionFactories + .find(_.isInstanceOf[HttpConnectionFactory]) + .map(_.asInstanceOf[HttpConnectionFactory]) + .getOrElse(fail("HttpConnectionFactory not found in SSL connection factories")) + + val customizers = httpFactory.getHttpConfiguration.getCustomizers + val secureCustomizer = customizers.toArray + .find(_.isInstanceOf[SecureRequestCustomizer]) + .map(_.asInstanceOf[SecureRequestCustomizer]) + .getOrElse(fail("SecureRequestCustomizer not found in HttpConfiguration")) + + assert(!secureCustomizer.isSniHostCheck, + "SNI host check should be disabled for ThriftHttpCLIService SSL connections") + } + + 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) + } + } +} From 49892b4e54d6d2889fe076b866827f5c44a42d6a Mon Sep 17 00:00:00 2001 From: Anupam Yadav Date: Wed, 6 May 2026 18:23:21 +0000 Subject: [PATCH 2/4] [SPARK-54293][SQL] Make ThriftHttpCLIService SNI host check configurable Address review feedback from @pan3793 (PR #55308): follow the pattern established in SPARK-56528 and make the SNI host check configurable rather than unconditionally disabled. Introduces spark.sql.hive.thriftServer.http.sniHostCheckEnabled (default: false, preserving the behavior from the first commit on this branch). Operators who want stricter host checking for security can opt in by setting the flag to true. --- .../spark/sql/internal/StaticSQLConf.scala | 11 +++++++ .../cli/thrift/ThriftHttpCLIService.java | 11 ++++--- .../hive/service/server/HiveServer2.java | 2 +- .../hive/thriftserver/HiveThriftServer2.scala | 5 ++- .../ThriftHttpCLIServiceSuite.scala | 32 ++++++++++++------- .../configs-without-binding-policy-exceptions | 1 + 6 files changed, 45 insertions(+), 17 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 6a7d13fb5421b..27d63fbed9e2b 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,17 @@ 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. Spark has disabled SNI host check to preserve backward compatibility. " + + "Set to true to enforce SNI host checking for " + + "stricter security.") + .version("4.3.0") + .booleanConf + .createWithDefault(false) + 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 82400ab11e88b..da3c3629556bb 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 @@ -52,9 +52,11 @@ 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 @@ -93,11 +95,12 @@ protected void initializeServer() { Arrays.toString(sslContextFactoryServer.getExcludeProtocols())); sslContextFactoryServer.setKeyStorePath(keyStorePath); sslContextFactoryServer.setKeyStorePassword(keyStorePassword); - // SPARK-54293: Disable SNI host check, which defaults to true since Jetty 10. - // This is consistent with the fix in JettyUtils.scala (SPARK-45522). + // SPARK-54293: Configure SNI host check, which defaults to true since Jetty 10. + // Controlled by spark.sql.hive.thriftServer.http.sniHostCheckEnabled (default: false), + // consistent with the fix in JettyUtils.scala (SPARK-45522). HttpConfiguration httpConfig = new HttpConfiguration(); SecureRequestCustomizer src = new SecureRequestCustomizer(); - src.setSniHostCheck(false); + src.setSniHostCheck(sniHostCheckEnabled); httpConfig.addCustomizer(src); connectionFactories = AbstractConnectionFactory.getFactories( sslContextFactoryServer, new HttpConnectionFactory(httpConfig)); 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 9e3ec3fc61ce8..e01a3cfcf48c7 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 synchronized void init(HiveConf hiveConf) { 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 f0ccd5320c1a1..3825c6ed750f4 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 index b18bbc3691f29..61982c8ca739b 100644 --- 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 @@ -24,33 +24,43 @@ import org.apache.spark.SparkFunSuite class ThriftHttpCLIServiceSuite extends SparkFunSuite { - test("SPARK-54293: SSL connection factories should disable SNI host check") { - // Reproduce the connection factory chain created by ThriftHttpCLIService - // when SSL is enabled. Without the fix, no SecureRequestCustomizer is added, - // and Jetty 10+ defaults sniHostCheck to true, causing SSL failures when - // the certificate CN doesn't match the hostname. + /** + * 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(false) + src.setSniHostCheck(sniHostCheckEnabled) httpConfig.addCustomizer(src) val connectionFactories = AbstractConnectionFactory.getFactories( sslContextFactory, new HttpConnectionFactory(httpConfig)) - // Find the HttpConnectionFactory and verify its SecureRequestCustomizer val httpFactory = connectionFactories .find(_.isInstanceOf[HttpConnectionFactory]) .map(_.asInstanceOf[HttpConnectionFactory]) .getOrElse(fail("HttpConnectionFactory not found in SSL connection factories")) - val customizers = httpFactory.getHttpConfiguration.getCustomizers - val secureCustomizer = customizers.toArray + httpFactory.getHttpConfiguration.getCustomizers.toArray .find(_.isInstanceOf[SecureRequestCustomizer]) .map(_.asInstanceOf[SecureRequestCustomizer]) .getOrElse(fail("SecureRequestCustomizer not found in HttpConfiguration")) + } + + test("SPARK-54293: SNI host check disabled by default") { + // Default behavior: sniHostCheckEnabled = false + val customizer = buildSslFactoriesAndGetCustomizer(sniHostCheckEnabled = false) + assert(!customizer.isSniHostCheck, + "SNI host check should be disabled when sniHostCheckEnabled is false") + } - assert(!secureCustomizer.isSniHostCheck, - "SNI host check should be disabled for ThriftHttpCLIService SSL connections") + test("SPARK-54293: SNI host check enabled when configured") { + // Opt-in behavior: sniHostCheckEnabled = true + val customizer = buildSslFactoriesAndGetCustomizer(sniHostCheckEnabled = true) + assert(customizer.isSniHostCheck, + "SNI host check should be enabled when sniHostCheckEnabled is true") } test("SPARK-54293: SSL connection factories without fix have SNI host check enabled") { 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 2aa6cb885ca31..b7df765be47a0 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 From 16d0837094951b6a8cc47ae6a25b74b1d053057b Mon Sep 17 00:00:00 2001 From: Anupam Yadav Date: Mon, 18 May 2026 13:40:39 -0700 Subject: [PATCH 3/4] Apply suggestion from @pan3793 Co-authored-by: Cheng Pan --- .../scala/org/apache/spark/sql/internal/StaticSQLConf.scala | 6 ++---- 1 file changed, 2 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 27d63fbed9e2b..25114a93f04cc 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 @@ -126,12 +126,10 @@ object StaticSQLConf { buildStaticConf("spark.sql.hive.thriftServer.http.sniHostCheckEnabled") .internal() .doc("Whether to enable Jetty's SNI host check on the ThriftHttpCLIService HTTPS " + - "connector. Spark has disabled SNI host check to preserve backward compatibility. " + - "Set to true to enforce SNI host checking for " + - "stricter security.") + "connector. Set to false to restore the behavior prior to SPARK-45522 (Jetty 10+).") .version("4.3.0") .booleanConf - .createWithDefault(false) + .createWithDefault(true) val SPARK_SESSION_EXTENSIONS = buildStaticConf("spark.sql.extensions") .doc("A comma-separated list of classes that implement " + From c3378db2c46a6d5305ad14b7dc9cfe0d380d0743 Mon Sep 17 00:00:00 2001 From: Anupam Yadav Date: Mon, 18 May 2026 20:43:17 +0000 Subject: [PATCH 4/4] Update comments and test names to reflect default=true --- .../cli/thrift/ThriftHttpCLIService.java | 4 ++-- .../ThriftHttpCLIServiceSuite.scala | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) 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 da3c3629556bb..5cc3794f6da4d 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 @@ -96,8 +96,8 @@ protected void initializeServer() { 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: false), - // consistent with the fix in JettyUtils.scala (SPARK-45522). + // 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); 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 index 61982c8ca739b..8fec2fc510665 100644 --- 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 @@ -49,20 +49,20 @@ class ThriftHttpCLIServiceSuite extends SparkFunSuite { .getOrElse(fail("SecureRequestCustomizer not found in HttpConfiguration")) } - test("SPARK-54293: SNI host check disabled by default") { - // Default behavior: sniHostCheckEnabled = false - val customizer = buildSslFactoriesAndGetCustomizer(sniHostCheckEnabled = false) - assert(!customizer.isSniHostCheck, - "SNI host check should be disabled when sniHostCheckEnabled is false") - } - - test("SPARK-54293: SNI host check enabled when configured") { - // Opt-in behavior: sniHostCheckEnabled = true + 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.