中文内容
Kubernetes v1.35:向 CSI 驱动传递服务账号令牌的更好方式
如果你维护使用服务账号令牌的 CSI 驱动,Kubernetes v1.35 带来了一项你会想了解的改进。自 TokenRequests 功能引入以来,CSI 驱动请求的服务账号令牌一直通过 volume_context 字段传递给它们。虽然这种方式可用,但它并不是存放敏感信息的理想位置,而且我们已经看到过令牌被 CSI 驱动意外记录到日志中的情况。
Kubernetes v1.35 引入了一个测试版解决方案来处理这一问题:CSI Driver Opt-in for Service Account Tokens via Secrets Field。这允许 CSI 驱动通过 NodePublishVolumeRequest 中的 secrets 字段接收服务账号令牌;在 CSI 规范中,这是用于敏感数据的合适位置。
理解现有方式
当 CSI 驱动使用 TokenRequests 功能时,它们可以通过在 CSIDriver 规范中配置 TokenRequests 字段,为工作负载身份请求服务账号令牌。这些令牌会作为卷属性映射的一部分传递给驱动,使用的键为 csi.storage.k8s.io/serviceAccount.tokens。
volume_context 字段可以工作,但它并非为敏感数据而设计。因此,会带来一些挑战:
首先,CSI 驱动使用的 protosanitizer 工具不会将卷上下文视为敏感信息,因此在记录 gRPC 请求日志时,服务账号令牌可能会进入日志。Secrets Store CSI Driver 中的 CVE-2023-2878 以及 Azure File CSI Driver 中的 CVE-2024-3744 就发生过这种情况。
其次,每个想要避免这一问题的 CSI 驱动都需要实现自己的脱敏逻辑,这会导致各驱动之间不一致。
CSI 规范在 NodePublishVolumeRequest 中已经有一个 secrets 字段,正是为这类敏感信息设计的。挑战在于,我们不能直接改变令牌放置的位置,否则会破坏那些期望在卷上下文中获取令牌的现有 CSI 驱动。
选择加入机制如何工作
Kubernetes v1.35 引入了一种选择加入机制,让 CSI 驱动可以选择如何接收服务账号令牌。这样,现有驱动会继续按当前方式工作,而驱动也可以在准备好时迁移到更合适的 secrets 字段。
CSI 驱动可以在其 CSIDriver 规范中设置一个新字段:
#
# CAUTION: this is an example configuration.
# Do not use this for your own cluster!
#
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: example-csi-driver
spec:
# ... existing fields ...
tokenRequests:
- audience: "example.com"
expirationSeconds: 3600
# New field for opting into secrets delivery
serviceAccountTokenInSecrets: true # defaults to false
行为取决于 serviceAccountTokenInSecrets 字段:
当设置为 false(默认值)时,令牌会像今天一样,以键 csi.storage.k8s.io/serviceAccount.tokens 放入 VolumeContext。当设置为 true 时,令牌只会以相同的键放入 Secrets 字段。
关于测试版发布
CSIServiceAccountTokenSecrets 功能门控在 kubelet 和 kube-apiserver 上均默认启用。由于 serviceAccountTokenInSecrets 字段默认值为 false,启用该功能门控不会改变任何现有行为。除非驱动显式选择加入,否则所有驱动仍会通过卷上下文接收令牌。这就是我们认为可以从 beta 而不是 alpha 开始的原因。
CSI 驱动作者指南
如果你维护使用服务账号令牌的 CSI 驱动,下面介绍如何采用此功能。
添加回退逻辑
首先,更新你的驱动代码以检查两个位置是否存在令牌。这样可以让你的驱动同时兼容旧方式和新方式:
const serviceAccountTokenKey = "csi.storage.k8s.io/serviceAccount.tokens"
func getServiceAccountTokens(req *csi.NodePublishVolumeRequest) (string, error) {
// Check secrets field first (new behavior when driver opts in)
if tokens, ok := req.Secrets[serviceAccountTokenKey]; ok {
return tokens, nil
}
// Fall back to volume context (existing behavior)
if tokens, ok := req.VolumeContext[serviceAccountTokenKey]; ok {
return tokens, nil
}
return "", fmt.Errorf("service account tokens not found")
}
这种回退逻辑向后兼容,且可以安全地发布到任何驱动版本中,即使集群尚未升级到 v1.35。
发布顺序
CSI 驱动作者在采用此功能时需要遵循特定顺序,以避免破坏现有卷。
驱动准备(可随时进行)
你可以立即开始准备驱动,添加用于同时检查 secrets 字段和卷上下文中令牌的回退逻辑。这项代码变更向后兼容,且可以安全地发布到任何驱动版本中,即使集群尚未升级到 v1.35。我们鼓励你尽早添加此回退逻辑、发布版本,并在可行时回移植到维护分支。
集群升级和功能启用
在你的驱动已部署回退逻辑后,以下是在集群中启用该功能的安全发布顺序:
- 完成 kube-apiserver 升级到 1.35 或更高版本
- 在所有节点上完成 kubelet 升级到 1.35 或更高版本
- 确保已部署带有回退逻辑的 CSI 驱动版本(如果尚未在准备阶段完成)
- 在所有节点上完全完成 CSI 驱动 DaemonSet 发布
- 更新你的 CSIDriver 清单,将 serviceAccountTokenInSecrets 设置为 true
重要约束
最重要的是记住时机。如果你的 CSI 驱动 DaemonSet 和 CSIDriver 对象位于同一个清单或 Helm chart 中,你需要进行两次单独更新。先部署带有回退逻辑的新驱动版本,等待 DaemonSet 发布完成,然后更新 CSIDriver 规范,将 serviceAccountTokenInSecrets 设置为 true。
此外,在所有驱动 Pod 都完成发布之前,不要更新 CSIDriver。否则,仍在运行旧驱动版本的节点上的卷挂载将会失败,因为这些 Pod 只会检查卷上下文。
为什么这很重要
采用此功能在几个方面有所帮助:
- 它消除了在 gRPC 请求中将服务账号令牌作为卷上下文的一部分意外记录到日志的风险
- 它使用 CSI 规范为敏感数据指定的字段,这更为合适
- protosanitizer 工具会自动正确处理 secrets 字段,因此你不需要特定于驱动的变通方案
- 它是选择加入式的,因此你可以按自己的节奏迁移,而不会破坏现有部署
行动号召
我们(Kubernetes SIG Storage)鼓励 CSI 驱动作者采用此功能,并就迁移体验提供反馈。如果你对 API 设计有想法,或在采用过程中遇到任何问题,请通过 Kubernetes Slack 上的 #csi 频道联系我们(如需邀请,请访问 https://slack.k8s.io/ )。
你可以关注 KEP-5538,以跟踪未来 Kubernetes 版本中的进展。
- ← 上一篇
- 下一篇 →