This is an automated email from the ASF dual-hosted git repository.
dongjoon-hyun pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new e081f47b5848 [SPARK-57509][CORE] Return `FORBIDDEN` instead of
`METHOD_NOT_ALLOWED` for disallowed `/workers/kill` requests
e081f47b5848 is described below
commit e081f47b5848cfb72a8a901639f2e273af1ccffc
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Wed Jun 17 14:27:08 2026 -0700
[SPARK-57509][CORE] Return `FORBIDDEN` instead of `METHOD_NOT_ALLOWED` for
disallowed `/workers/kill` requests
### What changes were proposed in this pull request?
When the Master Web UI `/workers/kill` endpoint rejects a decommission
request, return `403 FORBIDDEN` instead of `405 METHOD_NOT_ALLOWED`.
```scala
if (!isDecommissioningRequestAllowed(req)) {
- resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED)
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN)
}
```
This is a subtask of SPARK-55556 `Improve Web Security`.
### Why are the changes needed?
The original feature was introduced at Apache Spark 3.1.0.
- https://github.com/apache/spark/pull/29015
The request is rejected by policy/origin
(`spark.master.ui.decommission.allow.mode` is `DENY`, or `LOCAL` with a
non-local caller), not because of the HTTP method. `405` means the method is
unsupported and must carry an `Allow` header; `403` ("understood the request
but refuses to authorize it") is the correct code, consistent with
`HttpSecurityFilter` and `JWSFilter` in the same module.
### Does this PR introduce _any_ user-facing change?
Yes but only a different HTTP ErrorCode is returned because a disallowed
`/workers/kill` request now returns `403` instead of `405`. The functional
behavior is the same rejection. So, no functional behavior change.
### 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 #56573 from dongjoon-hyun/SPARK-57509.
Authored-by: Dongjoon Hyun <[email protected]>
Signed-off-by: Dongjoon Hyun <[email protected]>
(cherry picked from commit ec83fe65458b072d14cc6ef9be1c22e9e2e3d5cf)
Signed-off-by: Dongjoon Hyun <[email protected]>
---
.../spark/deploy/master/ui/MasterWebUI.scala | 2 +-
.../spark/deploy/master/ui/MasterWebUISuite.scala | 22 ++++++++++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git
a/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterWebUI.scala
b/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterWebUI.scala
index 66036e7a5e5c..3e047c98f872 100644
--- a/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterWebUI.scala
+++ b/core/src/main/scala/org/apache/spark/deploy/master/ui/MasterWebUI.scala
@@ -75,7 +75,7 @@ class MasterWebUI(
val hostnames: Seq[String] = Option(req.getParameterValues("host"))
.getOrElse(Array[String]()).toImmutableArraySeq
if (!isDecommissioningRequestAllowed(req)) {
- resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED)
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN)
} else {
val removedWorkers = masterEndpointRef.askSync[Integer](
DecommissionWorkersOnHosts(hostnames))
diff --git
a/core/src/test/scala/org/apache/spark/deploy/master/ui/MasterWebUISuite.scala
b/core/src/test/scala/org/apache/spark/deploy/master/ui/MasterWebUISuite.scala
index 5e75d1c424ea..37d0c5ec692d 100644
---
a/core/src/test/scala/org/apache/spark/deploy/master/ui/MasterWebUISuite.scala
+++
b/core/src/test/scala/org/apache/spark/deploy/master/ui/MasterWebUISuite.scala
@@ -24,6 +24,7 @@ import java.util.Date
import scala.collection.mutable.HashMap
+import jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN
import org.mockito.Mockito.{mock, times, verify, when}
import org.apache.spark.{SecurityManager, SparkConf, SparkFunSuite}
@@ -31,6 +32,7 @@ import
org.apache.spark.deploy.DeployMessages.{DecommissionWorkersOnHosts, KillD
import org.apache.spark.deploy.DeployTestUtils._
import org.apache.spark.deploy.master._
import org.apache.spark.internal.config.DECOMMISSION_ENABLED
+import org.apache.spark.internal.config.UI.MASTER_UI_DECOMMISSION_ALLOW_MODE
import org.apache.spark.rpc.{RpcEndpointRef, RpcEnv}
import org.apache.spark.util.Utils
@@ -106,6 +108,26 @@ class MasterWebUISuite extends SparkFunSuite {
test("Kill multiple hosts") {
testKillWorkers(Seq("noSuchHost", "LocalHost"))
}
+
+ test("SPARK-57509: /workers/kill responds with 403 Forbidden when the
request is not allowed") {
+ val denyConf = new SparkConf()
+ .set(DECOMMISSION_ENABLED, true)
+ .set(MASTER_UI_DECOMMISSION_ALLOW_MODE.key, "DENY")
+ val denyMaster = mock(classOf[Master])
+ when(denyMaster.securityMgr).thenReturn(new SecurityManager(denyConf))
+ when(denyMaster.conf).thenReturn(denyConf)
+ when(denyMaster.rpcEnv).thenReturn(rpcEnv)
+ when(denyMaster.self).thenReturn(masterEndpointRef)
+ val denyWebUI = new MasterWebUI(denyMaster, 0)
+ try {
+ denyWebUI.bind()
+ val url =
s"http://${Utils.localHostNameForURI()}:${denyWebUI.boundPort}/workers/kill/"
+ val body = convPostDataToString(Seq(("host",
Utils.localHostNameForURI())))
+ assert(sendHttpRequest(url, "POST", body).getResponseCode ===
SC_FORBIDDEN)
+ } finally {
+ denyWebUI.stop()
+ }
+ }
}
object MasterWebUISuite {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]