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

dongjoon-hyun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 8cd1be4fb380 [SPARK-57453][CORE] Support 
`spark.master.rest.allowedAppResourcePatterns`
8cd1be4fb380 is described below

commit 8cd1be4fb380429dec536da2629ba88b6ebc39d3
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Mon Jun 15 04:17:34 2026 -0700

    [SPARK-57453][CORE] Support `spark.master.rest.allowedAppResourcePatterns`
    
    ### What changes were proposed in this pull request?
    
    This PR adds a new configuration 
`spark.master.rest.allowedAppResourcePatterns`, a comma-separated list of 
regular expressions (default: empty), for Apache Spark 4.3.0.
    - When empty, all application resources are allowed (existing behavior).
    - When non-empty, the Standalone Master REST API rejects a submission whose 
`appResource` does not fully match any pattern.
    
    ### Why are the changes needed?
    
    To let operators restrict the Master REST API to application jars from 
trusted locations only.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, the default behavior is the same.
    
    ### How was this patch tested?
    
    Pass the CIs with the newly added test case.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 4.8)
    
    Closes #56512 from dongjoon-hyun/SPARK-57453.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 .../spark/deploy/rest/StandaloneRestServer.scala   |  7 +++++++
 .../org/apache/spark/internal/config/package.scala | 13 ++++++++++++
 .../deploy/rest/StandaloneRestSubmitSuite.scala    | 24 +++++++++++++++++++++-
 3 files changed, 43 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/scala/org/apache/spark/deploy/rest/StandaloneRestServer.scala 
b/core/src/main/scala/org/apache/spark/deploy/rest/StandaloneRestServer.scala
index 5e29199a352f..bb598b042958 100644
--- 
a/core/src/main/scala/org/apache/spark/deploy/rest/StandaloneRestServer.scala
+++ 
b/core/src/main/scala/org/apache/spark/deploy/rest/StandaloneRestServer.scala
@@ -195,6 +195,13 @@ private[rest] class StandaloneSubmitRequestServlet(
     val appResource = Option(request.appResource).getOrElse {
       throw new SubmitRestMissingFieldException("Application jar is missing.")
     }
+    Option(this.conf).foreach { c =>
+      val patterns = 
c.get(config.MASTER_REST_SERVER_ALLOWED_APP_RESOURCE_PATTERNS).map(_.r)
+      if (patterns.nonEmpty &&
+          !patterns.exists(_.pattern.matcher(appResource).matches())) {
+        throw new SubmitRestMissingFieldException("Valid application jar path 
is required")
+      }
+    }
     val mainClass = Option(request.mainClass).getOrElse {
       throw new SubmitRestMissingFieldException("Main class is missing.")
     }
diff --git a/core/src/main/scala/org/apache/spark/internal/config/package.scala 
b/core/src/main/scala/org/apache/spark/internal/config/package.scala
index 141e033ea992..640af74ac70b 100644
--- a/core/src/main/scala/org/apache/spark/internal/config/package.scala
+++ b/core/src/main/scala/org/apache/spark/internal/config/package.scala
@@ -2078,6 +2078,19 @@ package object config {
       .booleanConf
       .createWithDefault(true)
 
+  private[spark] val MASTER_REST_SERVER_ALLOWED_APP_RESOURCE_PATTERNS =
+    ConfigBuilder("spark.master.rest.allowedAppResourcePatterns")
+      .doc("Comma-separated list of regular expressions matched against the 
application " +
+        "resource (application jar path) in the Spark Master REST API. When 
non-empty, a " +
+        "driver submission is rejected unless its application resource fully 
matches at " +
+        "least one of the patterns. When empty (the default), all application 
resources " +
+        "are allowed. Example: \"file:.*,hdfs://.*\".")
+      .version("4.3.0")
+      .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+      .stringConf
+      .toSequence
+      .createWithDefault(Nil)
+
   private[spark] val MASTER_UI_PORT = ConfigBuilder("spark.master.ui.port")
     .version("1.1.0")
     .intConf
diff --git 
a/core/src/test/scala/org/apache/spark/deploy/rest/StandaloneRestSubmitSuite.scala
 
b/core/src/test/scala/org/apache/spark/deploy/rest/StandaloneRestSubmitSuite.scala
index a155e4cc3ac9..bf4a7c4f4d9d 100644
--- 
a/core/src/test/scala/org/apache/spark/deploy/rest/StandaloneRestSubmitSuite.scala
+++ 
b/core/src/test/scala/org/apache/spark/deploy/rest/StandaloneRestSubmitSuite.scala
@@ -35,7 +35,7 @@ import org.apache.spark.deploy.{SparkSubmit, 
SparkSubmitArguments}
 import org.apache.spark.deploy.DeployMessages._
 import org.apache.spark.deploy.master.DriverState._
 import org.apache.spark.deploy.master.RecoveryState
-import org.apache.spark.internal.config.{MASTER_REST_SERVER_FILTERS, 
MASTER_REST_SERVER_MAX_THREADS, MASTER_REST_SERVER_VIRTUAL_THREADS}
+import 
org.apache.spark.internal.config.{MASTER_REST_SERVER_ALLOWED_APP_RESOURCE_PATTERNS,
 MASTER_REST_SERVER_FILTERS, MASTER_REST_SERVER_MAX_THREADS, 
MASTER_REST_SERVER_VIRTUAL_THREADS}
 import org.apache.spark.rpc._
 import org.apache.spark.util.ArrayImplicits._
 import org.apache.spark.util.Utils
@@ -547,6 +547,28 @@ class StandaloneRestSubmitSuite extends SparkFunSuite {
     }
   }
 
+  test("SPARK-57453: Support allowedAppResourcePatterns") {
+    val conf = new SparkConf()
+      .set(MASTER_REST_SERVER_ALLOWED_APP_RESOURCE_PATTERNS.key, 
"file:/opt/spark/jars/.*\\.jar")
+    val servlet = new StandaloneSubmitRequestServlet(null, null, conf)
+    val request = new CreateSubmissionRequest
+    request.mainClass = ""
+    request.appArgs = Array.empty[String]
+    request.sparkProperties = Map.empty[String, String]
+    request.environmentVariables = Map.empty[String, String]
+
+    // An application resource matching the allowed pattern is accepted.
+    request.appResource = "file:/opt/spark/jars/app.jar"
+    servlet.buildDriverDescription(request, "spark://master:7077", 6066)
+
+    // An application resource not matching the allowed pattern is rejected.
+    request.appResource = "file:/tmp/evil.jar"
+    val e = intercept[SubmitRestMissingFieldException] {
+      servlet.buildDriverDescription(request, "spark://master:7077", 6066)
+    }
+    assert(e.getMessage === "Valid application jar path is required")
+  }
+
   test("SPARK-50381: Support spark.master.rest.maxThreads") {
     val conf = new SparkConf()
     val localhost = Utils.localHostName()


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

Reply via email to