Kubernetes v1.37:用 bind mount 选项与 emptyDir 权限加固容器存储

Kubernetes v1.37 加入两个存储安全特性:卷挂载上的 bind mount 选项(noexec / nosuid / nodev)与 emptyDir 的创建权限模式(含 sticky bit 01777)。前者补上了一个从 1.24 安全审计就被点名的缺口,两个都还在 Alpha 门控之后。

中文
复制

Kubernetes v1.37 带来了重要的存储安全特性:emptyDir 权限模式和 bind mount 选项。它们帮助应用开发者和安全从业者实施严格的安全策略,比如禁止跨容器删除文件、禁止从可写卷里执行任意二进制,而且可以直接在 Kubernetes 里做到,不需要任何复杂的绕行办法。

Linux 存储与权限基础

在深入这些新的 Kubernetes 特性之前,先简单回顾一下让它们成为可能的那些底层 Linux 安全机制。

Bind mount 标志

当 Linux 挂载或重新挂载一个目录时,虚拟文件系统(VFS)标志决定这个文件系统上允许哪些操作:

  • noexec:不允许在这个挂载的文件系统上直接执行任何二进制。
  • nosuid:不允许 set-user-identifier 或 set-group-identifier 位生效。
  • nodev:不解析这个文件系统上的字符设备或块设备。

目录权限与 sticky bit

标准的 Unix 权限在三个范围上管控访问:属主、属组、其他人(例如 07550777)。

除了标准的读、写、执行位之外,Linux 还支持 sticky bit(例如模式 01777)。作用在目录上时,sticky bit 保证目录里的文件只能被文件的属主或 root 删除或改名。对于 /tmp 这类共享的可写目录,这是必需的。

这些改进的动因

Kubernetes 为什么需要 bind mount 选项和 emptyDir 权限?

这些特性的首要目标是提高 Kubernetes 工作负载的安全性,办法是允许在卷挂载上使用与安全相关的 bind mount 选项。默认情况下,卷由容器运行时和 kubelet 以 bind mount 方式挂进容器,不带 noexecnosuidnodev 标志。这个默认值会削弱安全性。举例来说,缺少 noexec 时,一个被攻陷的进程可以利用任何可写卷(emptyDir、PersistentVolume 等等)下载任意二进制、chmod +x 再执行它,即使容器的根文件系统是只读的(readOnlyRootFilesystem: true)。支持 noexecnodevnosuid,让用户有了原生的办法来加固卷挂载,以符合安全基准和策略。

这个缺口在 emptyDir 卷上最明显,它是最常见的可写卷类型,也是多项安全发现的对象:

  • Issue #48912:被认定的安全缺口——无法在 emptyDir 上设置挂载选项这一点在审计中被指出,但一直悬而未决,直到现在。
  • Issue #119627:Kubernetes 1.24 安全审计(发现编号 NCC-E003660-7HM)——外部审计人员特别指出,无法以 noexec 挂载 emptyDir 构成一次安全失效。

不过,同样的缺口适用于所有卷类型。PersistentVolume 有一个 mountOptions 字段,但那些选项是 CSI 驱动在节点上应用的文件系统级标志,因此并不能可靠地转成容器内部的 bind mount 标志。此前没有任何机制可以为容器运行时为任何卷类型创建的 bind mount 设置 noexecnosuidnodev

此外,emptyDir 卷类型默认以硬编码的 0777 模式创建目录。这在此前意味着,任何能发现这个卷的进程都能读、写、删除卷里的任何东西,不管是谁创建的。

你可以——现在也仍然可以——用一个初始容器去设置不同的访问模式,但这更复杂,而且难以通过合规核验。

这会造成真实的问题:

  • 共享一个 emptyDir 的多容器 Pod,无法阻止一个容器删掉另一个容器的文件。sticky bit(01777)能解决这个问题,但此前没有原生的办法去设置它。
  • 一些应用和安全框架期望 /tmp 目录带 sticky bit(模式 01777)。在没有原生支持设置 emptyDir 模式的情况下,用户不得不借助 init 容器或其他卷类型来满足这个要求。
  • 想要更严格权限的平台工程师(比如只给属主和属组的 0750),只能使用跑 chmod 的 init 容器,这平添了不必要的复杂度。

emptyDir 卷类型是一个明显的缺口。作为 Kubernetes 里最常见的可写卷类型之一,它却没有任何办法控制自己创建时的权限。

现实中的用例

应用开发者与安全工程师紧密合作,负责维护应用的安全态势,并确保工作负载不会给更广泛的基础设施带来风险。这些特性让开发团队能够有信心地应对关键的安全场景:

防止可写挂载上的权限提升: 配置临时工作区卷(比如 emptyDir/tmp 挂载)的应用开发者,可以确保它们以 nosuidnoexec 挂载。这保证了即使应用被攻陷、恶意载荷被下载下来,工作负载也无法执行这个载荷,更无法用它去提升节点上的权限。

保护多容器 Pod 里的共享临时空间: 配置 CI/CD 流水线 Pod 的开发者经常需要多个容器(比如一个构建容器和一个 sidecar 日志容器)共享一个工作区。通过在 emptyDir 上设置 mode: 01777,开发者让这个共享工作区表现得像传统的 Unix /tmp 目录。每个容器都能独立写文件,但一个容器里被攻陷的进程无法删掉另一个容器产出的构建产物。

为应用数据落实最小权限原则: 部署数据库 Pod 的应用开发者可以锁死对数据库临时存储的访问。通过在 emptyDir 上设置 mode: 0750,开发者确保只有特定的数据库用户和组能读写这个卷,明确拒绝同一个 Pod 里任何其他进程或 sidecar 的访问。

注意: 这两个特性在 Kubernetes v1.37 里都处于 Alpha 特性门控之后。要使用它们,需要在 API server 和 kubelet 上启用 VolumeBindMountOptionsEmptyDirVolumeMode

示例 1:落实 bind mount 选项

这份完整的 Pod manifest 把一个 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: {}

示例 2:带 sticky bit 的 emptyDir 卷权限模式

这份完整的 Pod manifest 用 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

在 Linux 上验证这些特性

要验证这些特性确实在强制执行限制,你可以用 kubectl exec 进入容器。下面的示例模拟了一些尝试执行、但会被这些特性成功拦下的动作。

验证 noexec

尝试在一个以 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 这一层被强制执行。

验证 sticky bit

尝试在一个权限模式为 01777emptyDir 里删掉另一个用户的文件:

# 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

内核拦下了这次删除,因为 sticky bit(01777)把文件删除严格限制在文件属主身上。

需要知道的事

开始使用这些特性时,请记住下面这些关键细节。完整细节可以在 bind mount 选项、emptyDir 卷模式,以及 emptyDir 卷的官方文档里找到。

  • 默认行为不变: 如果你不写 bindMountOptions,或者不给 emptyDir 设置 mode,你会得到和以前完全一样的标准默认行为(比如 0777 权限)。
  • 卷类型支持很广: bindMountOptions 可用于 emptyDir、PersistentVolume、CSI 卷、projected 卷、ConfigMap、Secret 等等。唯一的例外是 image 卷,它被明确不支持。mode 字段适用于所有 emptyDir 介质类型:默认(磁盘支撑)、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: noexecnosuidnodev 这类标志和 Unix 权限模式都是 Linux 的概念。bindMountOptions 在 Windows 节点上没有效果。在 Windows 上,emptyDir 卷的 mode 字段也会被跳过,因为 Windows 不支持 Unix 风格的文件权限。
  • 版本偏斜是安全的: 这两个特性都是纯增量的。对于 emptyDirmode:如果 API server 启用了门控而 kubelet 没有,字段会被接受但忽略,kubelet 回落到 0777。对于 bindMountOptions:调度器用「节点声明的特性」来避免把 Pod 放到没有运行时支持的节点上;如果 Pod 到了这样的节点,kubelet 会拒绝它,而不是静默忽略这些选项。
  • 特性门控: 这两项能力在 Kubernetes v1.37 里都以 Alpha 特性提供:VolumeBindMountOptions:控制卷挂载上的 bind mount 标志。
  • EmptyDirVolumeMode:控制 emptyDir 卷创建时的权限模式。

我该怎么参与?

这些新特性由 SIG Node 和 SIG Storage 推动。你可以在这些增强的 KEP 里找到更多细节:KEP-5855(bind mount 选项)和 KEP-5502(emptyDir 权限模式)。

联系 SIG Node:

  • Slack: #sig-node
  • Mailing list

联系 SIG Storage:

  • Slack: #sig-storage
  • Mailing list

来源: Kubernetes Blog← 返回首页