Kubernetes v1.37 带来了重要的存储安全特性:emptyDir 权限模式和绑定挂载选项。它们帮助应用程序开发人员和安全专业人员直接在 Kubernetes 中实施严格的安全策略,例如禁止跨容器删除文件或从可写卷执行任意二进制文件,而无需任何复杂的规避手段。
在深入探讨新的 Kubernetes 特性之前,让我们简要回顾一下使这些特性成为可能的底层 Linux 安全机制。
当 Linux 挂载或重新挂载目录时,虚拟文件系统(VFS)标志控制着该文件系统上允许执行的操作:
noexec :不允许在挂载的文件系统上直接执行任何二进制文件。
nosuid :不允许 set-user-identifier 或 set-group-identifier 位生效。
nodev :不解释文件系统上的字符或块特殊设备。
标准 Unix 权限在三个范围内规范访问:所有者、组和其他(例如 0755 或 0777 )。
除了标准的读、写和执行位之外,Linux 还支持粘滞位(如模式 01777 )。当应用于目录时,粘滞位确保该目录内的文件只能由文件所有者或 root 删除或重命名。这对于像 /tmp 这样的共享可写目录至关重要。
为什么 Kubernetes 需要绑定挂载选项和 emptyDir 权限?
这些特性的主要目标是通过允许在卷挂载上使用与安全相关的绑定挂载选项,来提高 Kubernetes 工作负载的安全性。默认情况下,容器运行时和 kubelet 将卷绑定挂载到容器中时不会带有 noexec 、 nosuid 或 nodev 标志。这种默认设置可能会削弱安全性。例如,在缺少 noexec 的情况下,即使容器具有只读根文件系统( readOnlyRootFilesystem: true ),被攻陷的进程也可以使用任何可写卷( emptyDir 、 PersistentVolume 等)来下载、chmod +x 并执行任意二进制文件。支持 noexec 、 nodev 和 nosuid 为用户提供了一种原生方式来加固卷挂载,以符合安全基准和策略。
这一差距在 emptyDir 卷上最为明显,emptyDir 是最常见的可写卷类型,并且一直是多项安全发现的主题:
Issue #48912:已识别的安全差距——无法在 emptyDir 上设置挂载选项的问题在一次审计中被标记出来,但直到现在仍未解决。
Issue #119627:Kubernetes 1.24 安全审计(发现 NCC-E003660-7HM)——外部审计人员特别指出,无法以 noexec 挂载 emptyDir 代表了一种安全失败。
然而,同样的差距适用于所有卷类型。PersistentVolume 有一个 mountOptions 字段,但这些选项是由 CSI 驱动在节点上应用的文件系统级标志,因此它们不能可靠地转换为容器内部的 bind mount 标志。此前,没有任何机制可以在容器运行时为任何卷类型创建的 bind mount 上设置 noexec、nosuid 或 nodev。
此外,emptyDir 卷类型默认以硬编码的 0777 模式创建目录。这在以前意味着任何能够发现该卷的进程都可以读取、写入和删除卷中的任何内容,无论该卷是由谁创建的。
你可以——现在仍然可以——使用一个初始容器来设置不同的访问模式,但这更复杂,并且难以验证合规性。
这会导致实际问题:
共享 emptyDir 的多容器 Pod 无法阻止一个容器删除另一个容器的文件。粘滞位(01777)可以解决这个问题,但之前没有原生方式来设置它。
一些应用程序和安全框架期望 /tmp 目录设置粘滞位(模式 01777)。由于没有原生支持来设置 emptyDir 模式,用户不得不使用 init 容器或替代卷类型来满足这一要求。
希望设置更严格权限(例如,仅所有者与组可用的 0750)的平台工程师不得不使用运行 chmod 的 init 容器,这增加了不必要的复杂性。
emptyDir 卷类型是一个显著的差距。作为 Kubernetes 中最常见的可写卷类型之一,它无法控制其创建权限。
应用程序开发者与安全工程师紧密合作,负责维护其应用程序的安全态势,并确保工作负载不会对更广泛的基础设施构成风险。这些特性使开发团队能够自信地应对关键安全场景:
防止可写挂载上的权限提升:配置临时工作区卷(如 emptyDir 或 /tmp 挂载)的应用程序开发者可以确保它们以 nosuid 和 noexec 挂载。这保证了即使应用程序被攻破并下载了恶意负载,工作负载也无法执行该负载或利用它在节点上提升权限。
保护多容器 Pod 中的共享暂存空间:配置 CI/CD 流水线 Pod 的开发者通常需要多个容器(例如,一个构建容器和一个边车日志容器)共享一个工作区。通过在 emptyDir 上设置 mode: 01777,开发者可以确保共享工作区的行为类似于传统的 Unix /tmp 目录。每个容器可以独立写入文件,但一个容器中被攻破的进程无法删除另一个容器产生的构建产物。
为应用数据强制执行最小权限原则:部署数据库 Pod 的应用开发者可以锁定对数据库临时存储的访问。通过在 emptyDir 上设置 mode: 0750,开发者确保只有特定的数据库用户和组可以读写该卷,明确拒绝同一 Pod 中任何其他进程或边车容器的访问。
注意:这两个特性在 Kubernetes v1.37 中均处于 Alpha 特性门控之后。要使用它们,请在 API 服务器和 kubelet 上启用 VolumeBindMountOptions 和 EmptyDirVolumeMode。
此完整 Pod 清单将 emptyDir 卷挂载到 /tmp,并使用 bindMountOptions: [noexec, nosuid]。
apiVersion
:
v1
kind
:
Pod
metadata
:
name
:
hardened-bindmount-pod
namespace
:
default
spec
:
os
:
name
:
linux
containers
:
-
name
:
hardened-app
image
:
alpine:latest
command
:
[
"sleep"
,
"3600"
]
securityContext
:
readOnlyRootFilesystem
:
true
volumeMounts
:
-
name
:
temp-storage
mountPath
:
/tmp
bindMountOptions
:
-
noexec
-
nosuid
volumes
:
-
name
:
temp-storage
emptyDir
:
{}此完整 Pod 清单使用 mode: 01777 创建 emptyDir 卷,以跨容器强制执行标准 Unix /tmp sticky bit 保护。
apiVersion : v1 kind : Pod metadata : name : hardened-emptydir-pod namespace : default spec : os : name : linux containers : - name : app-container image : alpine:latest command : [ "sleep" , "3600" ] volumeMounts : - name : shared-tmp mountPath : /tmp volumes : - name : shared-tmp emptyDir : mode : 01777
要验证这些特性正在主动执行限制,你可以通过 kubectl exec 进入容器。以下示例模拟尝试执行被这些特性成功阻止的操作。
尝试在挂载了 noexec 的卷上写入并运行脚本:
# 1. Exec into the pod kubectl exec -it hardened-bindmount-pod -- sh # 2. Create an executable script on the mounted volume cd /tmp echo '#!/bin/sh' > test.sh echo 'echo "Executing untrusted code..."' >> test.sh chmod +x test.sh # 3. Attempt to run the script ./test.sh
sh: ./test.sh: Permission denied
即使创建了可执行文件,Linux 内核也会拒绝执行,因为 MS_NOEXEC 在 bind mount 级别被强制执行。
尝试在权限模式为 01777 的 emptyDir 中删除另一个用户的文件:
# 1. Exec into the pod kubectl exec -it hardened-emptydir-pod -- sh # 2. Verify directory permissions on /tmp ls -ld /tmp # Output: drwxrwxrwt 2 root root ... /tmp (Notice the 't' indicating sticky bit) # 3. Create a file as the guest user su -s /bin/sh -c "touch /tmp/guest_file" guest # 4. Attempt to delete that file as nobody su -s /bin/sh -c "rm /tmp/guest_file" nobody
rm: can't remove '/tmp/guest_file': Operation not permitted
内核会阻止删除,因为粘滞位(01777)将文件删除严格限制为仅文件所有者可执行。
在开始使用这些功能时,请牢记以下关键细节。完整详情请参阅 bind mount options、emptyDir volume mode 和 emptyDir volumes 的官方文档。
默认行为不变:如果省略 bindMountOptions 或未设置 emptyDir mode,你将获得与之前完全相同的标准默认行为(如 0777 权限)。
广泛的卷支持:bindMountOptions 适用于 emptyDir、PersistentVolumes、CSI 卷、projected 卷、ConfigMaps、Secrets 等。唯一的例外是 image 卷,明确不支持。mode 字段适用于所有 emptyDir 介质类型:default(磁盘支持)、Memory(tmpfs)和 HugePages。
运行时能力很重要(针对 bindMountOptions):容器运行时必须支持 CRI mount_options 字段,并通过 runtimeFeatures 声明该能力。调度器使用节点声明的特性来避免将 Pod 调度到不兼容的节点上。如果 Pod 仍然到达此类节点,kubelet 会拒绝它。不存在静默降级。但是,为 emptyDir 使用 mode 不需要运行时支持。
与 PV mountOptions 不同:PersistentVolume mountOptions 通过 CSI 驱动在存储层应用。新的 bindMountOptions 控制由运行时在容器内部应用的 bind mount 标志。它们在不同层操作,不会冲突。
fsGroup 交互:如果在 Pod 的 security context 中设置了 fsGroup,fsGroup 应用的组权限将覆盖为 emptyDir 卷指定的 mode。这与 Secret 和 ConfigMap 卷上 defaultMode 的现有行为相同。
仅限 Linux:noexec、nosuid、nodev 等标志以及 Unix 权限模式是 Linux 概念。bindMountOptions 对 Windows 节点无效。在 Windows 上,emptyDir 卷的 mode 字段也会被跳过,因为 Windows 不支持 Unix 风格的文件权限。
版本偏差安全性:这两个功能都是增量式的。对于 emptyDir mode:如果 API server 启用了该门控但 kubelet 没有,该字段会被接受但被忽略——kubelet 回退到 0777。对于 bindMountOptions:调度器使用 Node Declared Features 来防止将 Pod 调度到不支持运行时的节点上;如果 Pod 到达此类节点,kubelet 会拒绝它,而不是静默忽略这些选项。
特性门控:这两个能力在 Kubernetes v1.37 中作为 Alpha 特性提供:VolumeBindMountOptions:控制卷挂载上的 bind mount 标志。EmptyDirVolumeMode:控制 emptyDir 卷的创建权限模式。
转载声明:本文为转载整理,原文版权归原作者及原站点所有。
原文来源:Kubernetes Blog
原创地址:https://kubernetes.io/blog/2026/09/16/kubernetes-v1-37-hardening-container-storage/
原文作者:Kubernetes Blog;如有侵权,请联系管理员删除。