如果你维护使用服务账号令牌的 CSI 驱动程序,
Kubernetes v1.35 带来了一项你希望了解的改进。
自从引入 TokenRequests 特性以来,
CSI 驱动程序请求的服务账号令牌一直通过 volume_context 字段传递给他们。
虽然这可以工作,但它不是存储敏感信息的理想位置,
我们已经看到过在 CSI 驱动程序中意外记录令牌的情况。
Kubernetes v1.35 引入了一个 Beta 解决方案来解决这个问题:
通过 Secret 字段实现服务账号令牌的 CSI 驱动程序选择加入。
这允许 CSI 驱动程序通过 NodePublishVolumeRequest 中的 secrets 字段接收服务账号令牌,
这是 CSI 规范中存储敏感数据的适当位置。
当 CSI 驱动程序使用 TokenRequests 特性时,
它们可以通过在 CSIDriver spec 中配置 TokenRequests 字段来请求工作负载身份的服务账号令牌。
这些令牌作为卷属性映射的一部分传递给驱动程序,
使用密钥 csi.storage.k8s.io/serviceAccount.tokens。
volume_context 字段可以工作,但它不是为敏感数据设计的。
正因为如此,存在一些挑战:
首先,CSI 驱动程序使用的
protosanitizer
工具不会将卷上下文视为敏感数据,
因此服务账号令牌可能会在记录 gRPC 请求时出现在日志中。
这在 Secret Store CSI 驱动程序的
CVE-2023-2878
和 Azure File CSI 驱动程序的
CCVE-2024-3744 中都发生过。
其次,每个想避免此问题的 CSI 驱动程序都需要实现自己的清理逻辑, 这导致了驱动程序之间的不一致。
CSI 规范在 NodePublishVolumeRequest 中已经有一个 secrets 字段,
这正是为这类敏感信息设计的。
挑战在于,我们不能仅仅改变令牌放置的位置,
而不破坏期望在卷上下文中获取令牌的现有 CSI 驱动程序。
Kubernetes v1.35 引入了一个选择加入机制,让 CSI 驱动程序选择如何接收服务账号令牌。 这样,现有的驱动程序继续按当前方式工作,而驱动程序可以在准备好时迁移到更合适的 Secret 字段。
CSI 驱动程序可以在其 CSIDriver spec 中设置一个新字段:
#
# 注意:这只是一个配置示例。
# 请勿将此配置用于你自己的集群!
#
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: example-csi-driver
spec:
# ... 现有字段...
tokenRequests:
- audience: "example.com"
expirationSeconds: 3600
# 新增 Secret 传递选项
serviceAccountTokenInSecrets: true # 默认为 false
行为取决于 serviceAccountTokenInSecrets 字段:
当设置为 false(默认)时,令牌会被放置在 VolumeContext 中,
密钥为 csi.storage.k8s.io/serviceAccount.tokens,与今天相同。
当设置为 true 时,令牌仅被放置在 Secrets 字段中,使用相同的密钥。
CSIServiceAccountTokenSecrets 特性门控在 kubelet 和 kube-apiserver 上默认启用。
由于 serviceAccountTokenInSecrets 字段默认为 false,
启用特性门控不会改变任何现有行为。
所有驱动程序继续通过卷上下文接收令牌,除非它们明确选择加入。
这就是为什么我们觉得从 Beta 开始而不是 Alpha 会更安心。
如果你维护使用服务账号令牌的 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 驱动程序作者在采用此特性时需要遵循特定顺序,以避免破坏现有卷。
驱动程序准备(可以随时进行)
你可以立即通过添加检查 Secret 字段和卷上下文中令牌的回退逻辑来准备驱动程序。 此代码更改向后兼容,可以安全地包含在任何驱动程序版本中,即使在集群升级到 v1.35 之前也是如此。 我们鼓励你尽早添加此回退逻辑、发布版本,并在可行的情况下甚至 backport 到维护分支。
集群升级和特性启用
一旦你的驱动程序部署了回退逻辑,以下是在集群中启用该特性的安全推出顺序:
serviceAccountTokenInSecrets: true最重要需要记住的是时机。
如果你的 CSI 驱动程序 DaemonSet 和 CSIDriver 对象在同一个清单或 Helm Chart 中,
你需要两次单独的更新。
首先部署带有回退逻辑的新驱动程序版本,等待 DaemonSet 推出完成,
然后更新 CSIDriver 以设置 serviceAccountTokenInSecrets: true。
此外,在所有驱动程序 Pod 推出完成之前不要更新 CSIDriver。 如果你这样做了,在仍在运行旧驱动程序版本的节点上,卷挂载将失败, 因为那些 Pod 只检查卷上下文。
采用此特性有助于以下几方面:
protosanitizer 工具自动正确处理 Secret 字段,因此你不需要特定于驱动程序的变通方法我们(Kubernetes SIG Storage)鼓励 CSI 驱动程序作者采用此特性并提供关于迁移体验的反馈。 如果你对 API 设计有想法或在采用过程中遇到任何问题, 请在 Kubernetes Slack 的 #csi 频道联系我们 (获取邀请,请访问 https://slack.k8s.io/)。
你可以在 KEP-5538 上跟踪后续 Kubernetes 版本的进度。