Kubernetes v1.37:使用绑定挂载选项与 emptyDir 权限加固容器存储

Kubernetes v1.37 带来了两项重要的存储安全功能:emptyDir 权限模式和绑定挂载选项。它们让应用开发者与安全人员可以直接在 Kubernetes 中实现严格的安全策略,例如禁止容器之间删除其他用户文件,或禁止从可写卷直接执行任意二进制文件,而无需复杂的变通方案。

Linux 存储与权限基础

介绍 Kubernetes 新功能前,先简要回顾支持它们的底层 Linux 安全机制。

绑定挂载标志

Linux 挂载或重新挂载目录时,虚拟文件系统(VFS)标志控制该文件系统上允许的操作:

  • noexec:不允许直接执行挂载文件系统上的二进制文件。
  • nosuid:不允许设置用户 ID 或设置组 ID 位生效。
  • nodev:不将文件系统上的字符或块特殊文件解释为设备。

目录权限与粘滞位

标准 Unix 权限分别控制所有者、组和其他用户的访问,例如0755或0777。

除了标准读、写、执行位,Linux 还支持粘滞位(sticky bit),例如权限模式01777。应用到目录时,它保证目录中的文件只能由文件所有者或 root 删除、重命名。这对于 /tmp 等共享可写目录非常重要。

改进动机

Kubernetes 为什么需要绑定挂载选项与 emptyDir 权限?

这些功能的主要目标,是允许在卷挂载上设置安全相关的绑定挂载选项,从而提高工作负载安全性。默认情况下,容器运行时与 kubelet 将卷绑定挂载到容器中时,不设置 noexec、nosuid 或 nodev,这可能削弱安全性。例如,缺少 noexec 时,即使容器根文件系统只读(readOnlyRootFilesystem: true),受侵害的进程仍能在任意可写卷(emptyDir、PersistentVolume 等)上下载文件、执行 chmod +x,再运行任意二进制文件。原生支持这些标志后,用户可以加固卷挂载,满足安全基准与策略要求。

这一缺口在 emptyDir 卷上尤为明显。它是最常见的可写卷类型,也曾出现在多项安全发现中:

  • 问题 #48912:已确认的安全缺口。审计曾指出无法为 emptyDir 设置挂载选项,直到现在才得到解决。
  • 问题 #119627:Kubernetes 1.24 安全审计(发现 NCC-E003660-7HM)。外部审计人员明确指出,无法使用 noexec 挂载 emptyDir 构成安全缺陷。

同样的缺口适用于所有卷类型。PersistentVolume 有 mountOptions 字段,但这些选项是 CSI 驱动在节点应用的文件系统层标志,不能可靠地转化为容器内部的绑定挂载标志。此前没有机制能在容器运行时为任何卷类型创建的绑定挂载上设置 noexec、nosuid 或 nodev。

此外,emptyDir 默认使用写死的0777模式创建目录。此前,这意味着任何能发现该卷的进程,都可以读、写、删除其中的内容,无论内容由谁创建。

你过去可以、现在也仍可使用初始化容器设置不同的访问模式,但这更复杂,也更难验证合规性。

这造成了实际问题:

  • 共享 emptyDir 的多容器 Pod,无法阻止一个容器删除另一个容器的文件。粘滞位01777可以解决这一问题,但此前没有原生设置方式。
  • 一些应用和安全框架要求 /tmp 目录设置粘滞位01777。没有原生 emptyDir 模式支持时,用户只能使用初始化容器或其他卷类型。
  • 平台工程师若希望更严格的权限,例如仅允许所有者和组访问的0750,就必须使用运行 chmod 的初始化容器,增加不必要的复杂性。

emptyDir 是 Kubernetes 最常见的可写卷类型之一,却没有办法控制创建时的权限,因此是一个显著缺口。

实际使用场景

应用开发者与安全工程师紧密合作,负责维持应用的安全状态,并确保工作负载不会危及更广泛的基础设施。这些功能让开发团队能更有把握地处理关键安全场景:

防止可写挂载上的提权:配置 emptyDir 或 /tmp 等临时工作卷时,开发者可以确保使用 nosuid 和 noexec 挂载。原文认为,这能阻止应用遭到侵害、恶意载荷被下载后直接执行该载荷,或利用文件的特权位在节点上提权。

保护多容器 Pod 的共享临时空间:CI/CD 流水线 Pod 常需要构建容器与日志 sidecar 等多个容器共享工作区。设置 emptyDir 的 mode: 01777 后,工作区类似传统 Unix /tmp。各容器可以独立写文件,但一个容器中的受侵害进程无法删除另一个用户创建的构建产物。

对应用数据落实最小权限:部署数据库 Pod 时,可以把 emptyDir 设为 mode: 0750,使只有特定数据库用户和组可以访问其临时存储,明确拒绝同一 Pod 内其他不属于该用户或组的进程与 sidecar。

注意:在 Kubernetes v1.37 中,这两项功能都处于 Alpha 阶段。使用时必须在 API server 和 kubelet 上启用 VolumeBindMountOptions 与 EmptyDirVolumeMode 特性门控。

示例1:强制绑定挂载选项

以下完整 Pod 清单在 /tmp 挂载 emptyDir,并设置 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:带粘滞位的 emptyDir 权限模式

以下完整 Pod 清单使用 mode: 01777 创建 emptyDir,在容器之间落实标准 Unix /tmp 粘滞位保护。

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 而拒绝直接执行。

验证粘滞位

尝试在权限模式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将文件删除限制为文件所有者;root 仍拥有相应特权。

需要了解的细节

开始使用时,请牢记以下细节。完整说明见绑定挂载选项、emptyDir 卷模式与emptyDir 卷官方文档。

  • 默认行为不变:省略 bindMountOptions,或不设置 emptyDir mode,就保持以前的标准默认行为,例如0777权限。
  • 广泛的卷支持:bindMountOptions 适用于 emptyDir、PersistentVolume、CSI 卷、投射卷、ConfigMap、Secret 等,唯独明确不支持 image 卷。mode 字段适用于所有 emptyDir 介质:默认磁盘、Memory(tmpfs)与 HugePages。
  • 运行时能力:绑定挂载选项要求容器运行时支持 CRI 的 mount_options 字段,并通过 runtimeFeatures 公布。调度器利用节点声明的特性,避免将 Pod 调度到不兼容节点。如果 Pod 仍到达这类节点,kubelet 会拒绝它,而不会静默降级。emptyDir 的 mode 不需要运行时支持。
  • 不同于 PV mountOptions:PersistentVolume 的 mountOptions 经 CSI 驱动作用于存储层,新 bindMountOptions 控制运行时在容器内应用的绑定挂载标志。两者位于不同层,不会冲突。
  • 与 fsGroup 交互:若 Pod 安全上下文设置 fsGroup,它应用的组权限会覆盖 emptyDir 指定的 mode。这与 Secret、ConfigMap 的 defaultMode 现有行为一致。
  • 仅 Linux:noexec、nosuid、nodev 和 Unix 权限模式属于 Linux 概念。bindMountOptions 对 Windows 节点无效;Windows 不支持 Unix 权限,因此 emptyDir 的 mode 也被跳过。
  • 版本差异安全性:两项功能均为增量添加。emptyDir mode 在 API server 开启门控、kubelet 未开启时,会被接受但忽略,kubelet 回退到0777。绑定挂载选项由调度器使用节点声明特性避免调度到不支持的节点;若 Pod 仍到达该节点,kubelet 会拒绝,而不会静默忽略。
  • 特性门控:两项能力均为 v1.37 Alpha 功能。VolumeBindMountOptions 控制卷挂载标志;EmptyDirVolumeMode 控制 emptyDir 创建时的权限模式。

如何参与

这些功能由 SIG Node 与 SIG Storage 推动。增强提案的详情见 KEP-5855(绑定挂载选项)与 KEP-5502(emptyDir 权限模式)。

联系 SIG Node:

联系 SIG Storage:

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容