This is an automated email from the ASF dual-hosted git repository.
dongjoon-hyun 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 c46a91c42055 [SPARK-57493][K8S] Set `allowPrivilegeEscalation` to
false for driver/executor containers by default
c46a91c42055 is described below
commit c46a91c4205594d9bfc1c902175f83c5ec82b1ba
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Tue Jun 16 16:30:57 2026 -0700
[SPARK-57493][K8S] Set `allowPrivilegeEscalation` to false for
driver/executor containers by default
### What changes were proposed in this pull request?
This PR sets `securityContext.allowPrivilegeEscalation: false` on driver
and executor containers by default, with new configs to control it:
- `spark.kubernetes.securityContext.allowPrivilegeEscalation` (default
`false`)
- `spark.kubernetes.driver.securityContext.allowPrivilegeEscalation` (falls
back to the above)
- `spark.kubernetes.executor.securityContext.allowPrivilegeEscalation`
(falls back to the above)
### Why are the changes needed?
Kubernetes allows privilege escalation by default. Emitting
`allowPrivilegeEscalation: false` makes Spark pods secure-by-default and
compliant with the Pod Security Standards `restricted` profile, while the
configs allow opting out.
Note that `Pod Security Standards` `restricted` profile requires
`allowPrivilegeEscalation: false` for all containers.
### Does this PR introduce _any_ user-facing change?
Yes to improve the security. Since 4.3.0, driver/executor containers get
`allowPrivilegeEscalation: false` by default (previously unset, so Kubernetes
allowed it). To restore the legacy behavior, set
`spark.kubernetes.securityContext.allowPrivilegeEscalation=true`.
### How was this patch tested?
Pass the CIs with the newly added test cases.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (claude-opus-4-8)
Closes #56549 from dongjoon-hyun/SPARK-57493.
Authored-by: Dongjoon Hyun <[email protected]>
Signed-off-by: Dongjoon Hyun <[email protected]>
(cherry picked from commit f7c26bc628abb78e9ee047e368580a1118bff6c4)
Signed-off-by: Dongjoon Hyun <[email protected]>
---
docs/core-migration-guide.md | 2 ++
docs/running-on-kubernetes.md | 24 +++++++++++++++
.../scala/org/apache/spark/deploy/k8s/Config.scala | 34 +++++++++++++++++++++-
.../k8s/features/BasicDriverFeatureStep.scala | 5 ++++
.../k8s/features/BasicExecutorFeatureStep.scala | 5 ++++
.../k8s/features/BasicDriverFeatureStepSuite.scala | 25 ++++++++++++++++
.../features/BasicExecutorFeatureStepSuite.scala | 26 +++++++++++++++++
7 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/docs/core-migration-guide.md b/docs/core-migration-guide.md
index 9be60572387c..2d3df8a038f4 100644
--- a/docs/core-migration-guide.md
+++ b/docs/core-migration-guide.md
@@ -26,6 +26,8 @@ license: |
- Since Spark 4.3, Spark executor pods connect to the driver via the driver
pod IP directly instead of the driver's Kubernetes Service. To restore the
legacy behavior, you can set `spark.kubernetes.executor.useDriverPodIP` to
`false`.
+- Since Spark 4.3, Spark sets `allowPrivilegeEscalation` to `false` on the
driver and executor containers' security context by default. To restore the
legacy behavior, you can set
`spark.kubernetes.securityContext.allowPrivilegeEscalation` to `true`.
+
## Upgrading from Core 4.1 to 4.2
- Since Spark 4.2, Spark Master REST API uses Java 21 virtual threads by
default when running on Java 21 or later. To restore the legacy behavior, you
can set `spark.master.rest.virtualThread.enabled` to `false`.
diff --git a/docs/running-on-kubernetes.md b/docs/running-on-kubernetes.md
index 9be54d898486..390e054434ca 100644
--- a/docs/running-on-kubernetes.md
+++ b/docs/running-on-kubernetes.md
@@ -1583,6 +1583,30 @@ See the [configuration page](configuration.html) for
information on Spark config
</td>
<td>3.4.0</td>
</tr>
+<tr>
+
<td><code>spark.kubernetes.securityContext.allowPrivilegeEscalation</code></td>
+ <td><code>false</code></td>
+ <td>
+ Sets the <code>allowPrivilegeEscalation</code> field of the driver and
executor containers' security context. When <code>false</code> (default), a
container cannot gain more privileges than its parent process. Set to
<code>true</code> to opt out of this restriction. Driver and executor can be
configured individually via the container type-specific config below.
+ </td>
+ <td>4.3.0</td>
+</tr>
+<tr>
+
<td><code>spark.kubernetes.driver.securityContext.allowPrivilegeEscalation</code></td>
+ <td><code>(value of
spark.kubernetes.securityContext.allowPrivilegeEscalation)</code></td>
+ <td>
+ Sets the <code>allowPrivilegeEscalation</code> field of the driver
container's security context. Falls back to
<code>spark.kubernetes.securityContext.allowPrivilegeEscalation</code> if not
set.
+ </td>
+ <td>4.3.0</td>
+</tr>
+<tr>
+
<td><code>spark.kubernetes.executor.securityContext.allowPrivilegeEscalation</code></td>
+ <td><code>(value of
spark.kubernetes.securityContext.allowPrivilegeEscalation)</code></td>
+ <td>
+ Sets the <code>allowPrivilegeEscalation</code> field of the executor
container's security context. Falls back to
<code>spark.kubernetes.securityContext.allowPrivilegeEscalation</code> if not
set.
+ </td>
+ <td>4.3.0</td>
+</tr>
<tr>
<td><code>spark.kubernetes.executor.useDriverPodIP</code></td>
<td><code>true</code></td>
diff --git
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/Config.scala
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/Config.scala
index 120881469e3a..4ec27b6fab75 100644
---
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/Config.scala
+++
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/Config.scala
@@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit
import org.apache.spark.deploy.k8s.Constants._
import org.apache.spark.internal.Logging
-import org.apache.spark.internal.config.{ConfigBuilder, DYN_ALLOCATION_ENABLED}
+import org.apache.spark.internal.config.{ConfigBindingPolicy, ConfigBuilder,
DYN_ALLOCATION_ENABLED}
private[spark] object Config extends Logging {
@@ -120,6 +120,38 @@ private[spark] object Config extends Logging {
.booleanConf
.createWithDefault(false)
+ val KUBERNETES_ALLOW_PRIVILEGE_ESCALATION =
+ ConfigBuilder("spark.kubernetes.securityContext.allowPrivilegeEscalation")
+ .doc("Sets the allowPrivilegeEscalation field of the driver and executor
" +
+ "containers' security context. When false (default), a container
cannot gain " +
+ "more privileges than its parent process. Set to true to opt out of
this " +
+ "restriction. Driver and executor can be configured individually via
the " +
+ "container type-specific config.")
+ .version("4.3.0")
+ .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+ .booleanConf
+ .createWithDefault(false)
+
+ val KUBERNETES_DRIVER_ALLOW_PRIVILEGE_ESCALATION =
+
ConfigBuilder("spark.kubernetes.driver.securityContext.allowPrivilegeEscalation")
+ .doc("Sets the allowPrivilegeEscalation field of the driver container's
security " +
+ "context. When false (default), the container cannot gain more
privileges than " +
+ "its parent process. Set to true to opt out of this restriction. Falls
back to " +
+ s"${KUBERNETES_ALLOW_PRIVILEGE_ESCALATION.key} if not set.")
+ .version("4.3.0")
+ .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+ .fallbackConf(KUBERNETES_ALLOW_PRIVILEGE_ESCALATION)
+
+ val KUBERNETES_EXECUTOR_ALLOW_PRIVILEGE_ESCALATION =
+
ConfigBuilder("spark.kubernetes.executor.securityContext.allowPrivilegeEscalation")
+ .doc("Sets the allowPrivilegeEscalation field of the executor
container's security " +
+ "context. When false (default), the container cannot gain more
privileges than " +
+ "its parent process. Set to true to opt out of this restriction. Falls
back to " +
+ s"${KUBERNETES_ALLOW_PRIVILEGE_ESCALATION.key} if not set.")
+ .version("4.3.0")
+ .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+ .fallbackConf(KUBERNETES_ALLOW_PRIVILEGE_ESCALATION)
+
val KUBERNETES_EXECUTOR_USE_DRIVER_POD_IP =
ConfigBuilder("spark.kubernetes.executor.useDriverPodIP")
.doc("If true, executor pods use Driver pod IP directly instead of
Driver Service.")
diff --git
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStep.scala
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStep.scala
index eaf39914ea02..579e5baeffda 100644
---
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStep.scala
+++
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStep.scala
@@ -38,6 +38,8 @@ private[spark] class BasicDriverFeatureStep(conf:
KubernetesDriverConf)
private val driverContainerImage = conf.image
+ private val allowPrivilegeEscalation =
conf.get(KUBERNETES_DRIVER_ALLOW_PRIVILEGE_ESCALATION)
+
// CPU settings
private val driverCpuCores = conf.get(DRIVER_CORES)
private val driverCoresRequest = conf
@@ -140,6 +142,9 @@ private[spark] class BasicDriverFeatureStep(conf:
KubernetesDriverConf)
.addToLimits("memory", driverMemoryQuantity)
.addToLimits(driverResourceQuantities.asJava)
.endResources()
+ .editOrNewSecurityContext()
+ .withAllowPrivilegeEscalation(allowPrivilegeEscalation)
+ .endSecurityContext()
.build()
val driverPod = new PodBuilder(pod.pod)
diff --git
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStep.scala
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStep.scala
index 655df6c5acad..4209b2f513c2 100644
---
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStep.scala
+++
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStep.scala
@@ -42,6 +42,8 @@ private[spark] class BasicExecutorFeatureStep(
// Consider moving some of these fields to KubernetesConf or
KubernetesExecutorSpecificConf
private val executorContainerImage = kubernetesConf.image
+ private val allowPrivilegeEscalation =
+ kubernetesConf.get(KUBERNETES_EXECUTOR_ALLOW_PRIVILEGE_ESCALATION)
private val blockManagerPort = kubernetesConf
.sparkConf
.getInt(BLOCK_MANAGER_PORT.key, DEFAULT_BLOCKMANAGER_PORT)
@@ -238,6 +240,9 @@ private[spark] class BasicExecutorFeatureStep(
.addAllToEnv(executorEnv.asJava)
.addAllToPorts(requiredPorts.asJava)
.addToArgs("executor")
+ .editOrNewSecurityContext()
+ .withAllowPrivilegeEscalation(allowPrivilegeEscalation)
+ .endSecurityContext()
.build()
val executorContainerWithConfVolume = if (disableConfigMap) {
executorContainer
diff --git
a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStepSuite.scala
b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStepSuite.scala
index 55726ac40ce8..70e2239205ae 100644
---
a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStepSuite.scala
+++
b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicDriverFeatureStepSuite.scala
@@ -143,6 +143,31 @@ class BasicDriverFeatureStepSuite extends SparkFunSuite {
assert(featureStep.getAdditionalPodSystemProperties() ===
expectedSparkConf)
}
+ test("Set allowPrivilegeEscalation to false on the driver container by
default") {
+ val sparkConf = new SparkConf().set(CONTAINER_IMAGE, "spark-driver:latest")
+ val conf = KubernetesTestConf.createDriverConf(sparkConf = sparkConf)
+ val pod = new
BasicDriverFeatureStep(conf).configurePod(SparkPod.initialPod())
+ assert(pod.container.getSecurityContext.getAllowPrivilegeEscalation ===
false)
+ }
+
+ test("Support
spark.kubernetes.driver.securityContext.allowPrivilegeEscalation") {
+ val sparkConf = new SparkConf()
+ .set(CONTAINER_IMAGE, "spark-driver:latest")
+ .set(KUBERNETES_DRIVER_ALLOW_PRIVILEGE_ESCALATION, true)
+ val conf = KubernetesTestConf.createDriverConf(sparkConf = sparkConf)
+ val pod = new
BasicDriverFeatureStep(conf).configurePod(SparkPod.initialPod())
+ assert(pod.container.getSecurityContext.getAllowPrivilegeEscalation ===
true)
+ }
+
+ test("driver allowPrivilegeEscalation falls back to the shared config") {
+ val sparkConf = new SparkConf()
+ .set(CONTAINER_IMAGE, "spark-driver:latest")
+ .set(KUBERNETES_ALLOW_PRIVILEGE_ESCALATION, true)
+ val conf = KubernetesTestConf.createDriverConf(sparkConf = sparkConf)
+ val pod = new
BasicDriverFeatureStep(conf).configurePod(SparkPod.initialPod())
+ assert(pod.container.getSecurityContext.getAllowPrivilegeEscalation ===
true)
+ }
+
test("Check driver pod respects kubernetes driver request cores") {
val sparkConf = new SparkConf()
.set(KUBERNETES_DRIVER_POD_NAME, "spark-driver-pod")
diff --git
a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStepSuite.scala
b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStepSuite.scala
index 77dcb11ba43b..e1261cadf3ab 100644
---
a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStepSuite.scala
+++
b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStepSuite.scala
@@ -160,6 +160,32 @@ class BasicExecutorFeatureStepSuite extends SparkFunSuite
with BeforeAndAfter {
assert(executor.pod.getSpec.getTerminationGracePeriodSeconds === 0L)
}
+ test("Set allowPrivilegeEscalation to false on the executor container by
default") {
+ initDefaultProfile(baseConf)
+ val step = new BasicExecutorFeatureStep(newExecutorConf(), new
SecurityManager(baseConf),
+ defaultProfile)
+ val executor = step.configurePod(SparkPod.initialPod())
+ assert(executor.container.getSecurityContext.getAllowPrivilegeEscalation
=== false)
+ }
+
+ test("Support
spark.kubernetes.executor.securityContext.allowPrivilegeEscalation") {
+ baseConf.set(KUBERNETES_EXECUTOR_ALLOW_PRIVILEGE_ESCALATION, true)
+ initDefaultProfile(baseConf)
+ val step = new BasicExecutorFeatureStep(newExecutorConf(), new
SecurityManager(baseConf),
+ defaultProfile)
+ val executor = step.configurePod(SparkPod.initialPod())
+ assert(executor.container.getSecurityContext.getAllowPrivilegeEscalation
=== true)
+ }
+
+ test("executor allowPrivilegeEscalation falls back to the shared config") {
+ baseConf.set(KUBERNETES_ALLOW_PRIVILEGE_ESCALATION, true)
+ initDefaultProfile(baseConf)
+ val step = new BasicExecutorFeatureStep(newExecutorConf(), new
SecurityManager(baseConf),
+ defaultProfile)
+ val executor = step.configurePod(SparkPod.initialPod())
+ assert(executor.container.getSecurityContext.getAllowPrivilegeEscalation
=== true)
+ }
+
test("basic executor pod with resources") {
val fpgaResourceID = new ResourceID(SPARK_EXECUTOR_PREFIX, FPGA)
val gpuExecutorResourceID = new ResourceID(SPARK_EXECUTOR_PREFIX, GPU)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]