在过去的十年中,Kubernetes 在镜像拉取策略和私有镜像仓库方面一直存在一个微妙却影响深远的安全漏洞。这个长期存在的问题(issues#18787),允许 Pod 重用节点上已存在的容器镜像,即使它们并没有被授权拉取这些镜像。
随着 Kubernetes v1.33 的发布,这个漏洞终于得到了修复。
问题所在:未授权重用私有镜像
在 Kubernetes 中,镜像拉取策略决定了 Kubelet 是否以及何时从镜像仓库拉取镜像。如果 Pod 设置了 imagePullPolicy: IfNotPresent,且目标镜像已存在于节点上,Kubelet 就会跳过拉取操作,直接使用本地缓存的镜像启动容器。这种机制对于公共镜像是合理的,但当涉及私有镜像时,就可能引发安全问题。
具体而言,可能出现如下场景:
-
命名空间 team-a 中的 Pod A 获授权使用拉取凭证从 private.registry/foo/bar:latest 拉取镜像,并将该镜像缓存在节点 X 上;
-
同一节点上,命名空间 team-b 中的 Pod B 尝试使用相同镜像,设置了 imagePullPolicy: IfNotPresent,但它并没有相应的拉取凭证;
-
由于该镜像已在节点上存在,Kubelet 会直接使用缓存镜像启动 Pod B,而不会检查其是否具备访问权限。
这破坏了命名空间间的隔离模型,可能导致非预期的权限提升。
修复方案:缓存镜像也要强制验证拉取凭证
从 Kubernetes v1.33 开始,Kubelet 引入了一个新的功能开关 KubeletEnsureSecretPulledImages,用以更改上述行为。启用该功能后,Kubelet 在运行已缓存的私有镜像前,会强制校验 Pod 是否拥有合法的凭证。
核心原则是:镜像存在不再代表可以使用。无论镜像拉取策略是 IfNotPresent 还是 Never,都必须进行授权验证。
不同拉取策略下的新行为
-
Always:行为不变。Kubelet 始终重新拉取镜像,并强制进行凭证校验;
-
IfNotPresent:新增校验机制,检查当前凭证是否与历史成功拉取该镜像时使用的凭证匹配;
-
Never:虽然不进行镜像拉取,但仍会校验凭证是否与原始拉取操作中使用的一致。
内部实现原理
该特性通过一个基于文件的本地缓存机制来运作,记录以下信息:
-
成功拉取镜像时所使用凭证的哈希值;
-
提供这些凭证的 Secret 名称。
当 Pod 尝试使用已缓存镜像时:
-
如果其提供的凭证与缓存记录(哈希或 Secret 名称)匹配,则允许启动;
-
如果不匹配,Kubelet 会使用新凭证尝试拉取镜像,以此验证访问权限。
这种机制确保了凭证复用的安全性,同时避免了不必要的镜像仓库请求。
支持投射式服务账户令牌(PSAT)
与此同时,KEP-4412 的引入使得 Kubelet 可以使用投射式服务账户令牌(Projected Service Account Tokens)来拉取镜像,从而实现更强的工作负载隔离和更细粒度的访问控制。
这些改进共同提升了镜像拉取过程中的认证与授权机制,尤其是在多租户场景下的节点级别。
如何启用
如需测试该功能,只需在 Kubelet 启动参数中添加:
# Set this flag on the kubelet
--feature-gates=KubeletEnsureSecretPulledImages=true
该功能的演进路径可参考 KEP-2535,预计将在 v1.34 中进入 Beta 阶段。
后续计划
社区已规划了多项未来改进,包括:
-
支持基于 PSAT 的镜像拉取凭证提供器(参考 KEP-4412);
-
为凭证缓存设置 TTL,以支持过期与撤销;
-
使用内存缓存以降低 I/O 延迟;
-
在高负载下进行性能基准测试。
结语
一个存在了十年的问题,能够以如此全面且具前瞻性的方式被解决,实属罕见。Kubernetes v1.33 中对镜像拉取安全性的增强,终于为多租户使用私有镜像仓库的场景补上了这块缺失已久的安全拼图。
如果你在运行共享集群,启用该功能几乎是必选项。