news 2026/9/23 4:04:51

Rook OSD 密钥加密密钥(KEK)轮换机制:设计与实现深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rook OSD 密钥加密密钥(KEK)轮换机制:设计与实现深度解析

Rook OSD 密钥加密密钥(KEK)轮换机制:设计与实现深度解析

【免费下载链接】rookStorage Orchestration for Kubernetes项目地址: https://gitcode.com/gh_mirrors/roo/rook

本指南以 Rook 设计文档 design/ceph/key-encryption-key-rotation.md 为核心骨架,结合仓库内 Operator 与 Daemon 层的完整源码实现,系统讲解 Rook 如何在无停机的前提下,通过 Kubernetes CronJob 周期性轮换加密 OSD 底盘的密钥加密密钥(Key Encryption Key,KEK),并同步更新 KMS 中的密钥版本。读完本文,你将掌握security.keyRotation配置的完整语义、五步安全轮换算法的工作原理,以及底层luksAddKey/luksChangeKey/luksKillSlot命令的封装细节与失败边界。

背景:为什么需要 KEK 轮换

Rook 使用 dm-crypt 并借助 cryptsetup 的 LUKS 扩展(cryptsetup(8))来加密承载 OSD 的 PVC。加密设备本身使用数据加密密钥(DEK),而 DEK 则由一把"密钥加密密钥"(KEK)保护,KEK 通常存放在 Rook 支持的各类密钥管理服务(KMS)中,包括:

  • Kubernetes Secrets(默认)
  • HashiCorp Vault
  • IBM Key Protect
  • KMIP(Key Management Interoperability Protocol)
  • Azure Key Vault(源码 pkg/daemon/ceph/osd/kms/kms.go 中同样支持,用于 OSD 加密场景)

从安全角度看,长期不更换 KEK 会扩大密钥泄露带来的影响面。Rook 需要能够周期性地轮换 KEK,并同时在两个位置原子化地更新:一是承载 OSD 的加密设备(LUKS 密钥槽),二是 KMS 中保存的密钥本身。该功能的目标版本为 release-1.11.1(见设计文档 front-matter 中的target-version)。

目标(Goals)

  • Rook 能够按计划周期性轮换 KEK,同步更新承载 OSD 的加密设备与 KMS,全程无停机

非目标(Non-Goals)

  • 暂不支持按需(on-demand)触发KEK 轮换。当前实现仅基于 CronJob 定时调度执行。

整体设计概览

整个 KEK 轮换特性由以下四个部分组成,它们共同构成了一个从"声明式配置"到"底层设备操作"的完整闭环:

  1. CRD 配置入口CephCluster.spec.security.keyRotation字段(enabled+schedule),对应 API 类型KeyRotationSpec,定义于 pkg/apis/ceph.rook.io/v1/types.go。
  2. Operator 侧调度器:为每个加密的 PVC 后端 OSD 创建命名规律、带亲和性约束的 CronJob,实现位于 pkg/operator/ceph/cluster/osd/key_rotation.go。
  3. CLI 命令入口:CronJob 容器内执行rook key-management rotate-key命令,注册于 cmd/rook/secret.go。
  4. Daemon 侧轮换逻辑:实际执行"取旧钥→写槽→生成新钥→更新 KMS→验证→清理旧槽"的算法,实现在 pkg/daemon/ceph/osd/key_rotation.go,底层 LUKS 命令封装在 pkg/daemon/ceph/osd/encryption.go。

下面逐一深入每个环节。

KEK 轮换 CronJob:调度器的设计与实现

设计文档明确了 CronJob 的三条关键约束,源码 pkg/operator/ceph/cluster/osd/key_rotation.go 对它们逐一落实:

1. 一个加密 OSD 对应一个 CronJob

命名规则为rook-ceph-osd-key-rotation-<osdID>(源码常量keyRotationCronJobAppNameFmt = "rook-ceph-osd-key-rotation-%d")。makeKeyRotationCronJob在 key_rotation.go#L204-L234 中构造 CronJob,其中:

  • 调度表达式schedule取自spec.Security.KeyRotation.Schedule
  • 若未配置 schedule,默认使用@weekly(源码注释说明默认值写在代码中而非 CRD 默认值,是为了避免 CRD 默认值机制带来的兼容性问题);
  • ConcurrencyPolicy设置为ForbidConcurrent,保证同一 OSD 的轮换任务不会并发执行,避免密钥槽竞争。

2. 使用 OSD Pod 亲和性调度到同一节点

applyKeyRotationPlacement在 key_rotation.go#L48-L65 中清空TopologySpreadConstraintsPodAntiAffinity,并设置RequiredDuringSchedulingIgnoredDuringExecution级别的PodAffinity:以 OSD 的标签(app=rook-ceph-osdosd-id=<ID>等)为LabelSelector,以主机名kubernetes.io/hostnameTopologyKey。这样 CronJob 的 Pod 会被强制调度到目标 OSD 所在的宿主机——因为轮换必须操作宿主机上的加密设备。对应的单元测试 pkg/operator/ceph/cluster/osd/key_rotation_test.go 验证了该亲和性在 Affinity 为 nil 或已有反亲和性时的覆盖行为。

3. 与 OSD 共享宿主机设备访问能力

CronJob Pod 挂载了三类卷(见getKeyRotationPodTemplateSpec,key_rotation.go#L112-L201):

  • devices:宿主机的/dev目录,用于访问加密块设备;
  • udev:宿主机的/run/udev目录,用于与宿主机的 udev 守护进程交互——源码注释明确指出"cryptsetup synchronizes with udev on host through semaphore",因此同时设置了HostIPC: true
  • bridge:宿主机上的 bridge 目录(<dataDirHostPath>/<namespace>/<pvcName>/ceph-<osdID>),内部包含 Rook 为加密设备在/var/lib/ceph/osd/下建立的映射入口(<blockType>-tmp)。

轮换任务处理的设备列表依据 OSD 拓扑动态生成:block 设备(bluestore_block)必然包含;若配置了 metadata PVC 与 wal PVC,则追加bluestore_metadatabluestore_wal设备(key_rotation.go#L139-L145)。此外,如果 KMS 为 Vault,还会额外挂载 Vault 的 TLS 证书卷。

CronJob 的容器以特权模式(privileged: true)、root 用户运行,使用 Rook Operator 镜像,并携带 KMS 配置环境变量(kms.ConfigToEnvVar)、ROOK_CEPH_VERSION以及集群配置环境变量;命令行参数为key-management rotate-key <pvcClaimName> <device1> [device2] [device3]

协调(Reconcile)流程

reconcileKeyRotationCronJob(key_rotation.go#L237-L303)是调度器的心脏:

  • 未启用:通过标签选择器app=rook-ceph-osd-key-rotation执行DeleteCollection,清理所有已存在的轮换 CronJob(忽略 NotFound 错误);
  • 已启用:列出带app=rook-ceph-osdosd-over-pvc标签的 OSD Deployment,逐个解析 OSD 信息与 PVC 属性,跳过未开启加密(!osdProps.encrypted)的 OSD;随后通过SetOwnerReference将 CronJob 归属于对应 OSD Deployment(保证 OSD 删除时 CronJob 被级联回收),最后以CreateOrUpdateCronJob幂等地创建或更新 CronJob。

KMS 侧更新能力:UpdateSecret

设计文档要求为每种 KMS 类型补充KMS.UpdateSecret(),用于将新的 KEK 写入 KMS。其统一接口实现在 pkg/daemon/ceph/osd/kms/kms.go#L233-L266:

  • Kubernetes Secrets:直接更新同名 Secret 的值(updateSecretInKubernetes);
  • HashiCorp Vault:按GenerateOSDEncryptionSecretName规范化的名称,通过 Vault 的PutSecret覆盖写入(Vault 原生保留版本历史);
  • IBM Key Protect / KMIP / Azure Key Vault:当前返回错误"update secret is not supported for the %q KMS"。也就是说,截至当前仓库实现,KEK 轮换的 KMS 更新能力仅对 Kubernetes Secrets 与 Vault 可用;IBM Key Protect 与 KMIP 在创建(PutSecret)和查询(GetSecret)上已有实现(kms.go#L82-L231),但轮换场景下的原地更新尚未支持。这是使用该功能前必须确认的约束。

KMS 提供方由KMS_PROVIDER连接参数决定(kms.go#L56-L80),为空时默认使用 Kubernetes Secrets。

五步安全轮换算法:KMS 与 LUKS 槽位的状态机

设计文档给出了 KEK 轮换的核心状态机,以 K1(KMS 中的当前 KEK)与 K2(待加入的新 KEK)为两个密钥版本,通过 LUKS 的槽位 0 与槽位 1 做缓冲,保证任何一步中断都不会让设备"打不开":

Step操作LUKS Slot 0LUKS Slot 1KMS 中的 Key
1获取 K1K1K1
2将 K1 加入 slot 1K1K1K1
3生成 K2 并加入 slot 0K2K1K1
4将 K2 更新到 KMSK2K1K2
5从 slot 1 移除 K1K2K2

设计文档特别强调:上述步骤保证了即使操作在任意一步被打断,KMS 中的 KEK 也始终能够打开加密设备,所有中断引发的边界情况都被覆盖。

源码层面的对应实现:Daemon 侧入口RotateKeyEncryptionKey(pkg/daemon/ceph/osd/key_rotation.go#L31-L102)与状态机逐条对应:

  1. 获取 K1kms.GetSecret(secretName),若 Secret 为空直接报错拒绝轮换;
  2. K1 写入 slot 1:对每个设备执行addEncryptionKey(device, currentKey, currentKey, slot1)——用 K1 作为口令、K1 作为新口令写入槽位 1;
  3. 生成 K2 并写入 slot 0:先调用oposd.GenerateDmCryptKey()生成新密钥(pkg/operator/ceph/cluster/osd/config.go#L61-L68,基于随机字节的 base64 编码);随后对每个设备先removeEncryptionKeySlot(device, currentKey, slot0)清理可能残留的槽位 0,再用addEncryptionKey(device, currentKey, newKey, slot0)将 K2 写入槽位 0;
  4. KMS 更新与校验kms.UpdateSecret(secretName, newKey)后将新值写回 KMS,随后kms.GetSecret回读并严格比对——不一致则整体报错("failed to verify the new key in the KMS"),在验证通过之前绝不进入清理旧密钥的阶段
  5. 移除旧钥:最后对每个设备执行removeEncryptionKeySlot(device, newKey, slot1),用新钥 K2 作为口令清除槽位 1 中的旧钥 K1,完成轮换。

注意第 5 步使用 K2 作为luksKillSlot的认证口令——因为此时设备已被 K2 接管,只有持有 K2 才能合法清理旧槽位。

底层 LUKS 命令封装:幂等性与失败容忍

轮换逻辑依赖的三个 cryptsetup 命令封装位于 pkg/daemon/ceph/osd/encryption.go,它们体现了极强的幂等设计:

luksAddKey(addEncryptionKey,encryption.go#L234-L283)

  • 通过临时口令文件传递--key-file--key-slot
  • 若槽位为空则直接添加成功;
  • 若报错"Key slot <N> is full",则调用ensureEncryptionKey(内部使用luksChangeKey)探测目标槽位中的口令是否恰好等于待写入的新口令:
    • 相同 → 幂等返回,无需任何改动;
    • 不同 → 先用旧口令luksKillSlot清空该槽位,再递归调用luksAddKey写入新口令。

luksChangeKey(ensureEncryptionKey,encryption.go#L198-L227)

  • 用于探测"目标槽位中是否已存在指定口令";若错误信息包含"No key available with this passphrase",返回false表示不匹配,而视为致命错误。这正是重试/续跑场景下的关键容错:CronJob 重跑时,如果槽位状态已经符合预期,不会重复报错。

luksKillSlot(removeEncryptionKeySlot,encryption.go#L170-L193)

  • 删除指定槽位;如果错误信息包含"Keyslot <N> is not active"(槽位本就不存在),同样忽略错误——保证轮换流程可安全重入。

这三层幂等封装与"每步均可中断恢复"的设计目标互为印证:即便某个步骤失败导致 CronJob 在下个周期重试,addEncryptionKey/removeEncryptionKeySlot也能基于当前真实槽位状态继续推进,而不会破坏设备可用性。对应测试见 pkg/daemon/ceph/osd/encryption_test.go。

CephCluster CR 配置:开启 KEK 轮换

设计文档给出了新增的security.keyRotation配置段,对应源码中的KeyRotationSpec(pkg/apis/ceph.rook.io/v1/types.go#L530-L539),包含两个字段:

字段类型默认值说明
enabledboolfalse+kubebuilder:default=false是否启用 KEK 轮换
schedulestring@weekly(代码内默认,见 key_rotation.go#L210-L214)轮换调度的 cron 表达式

CephCluster中与既有 KMS 配置组合使用的示例:

apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: rook-ceph namespace: rook-ceph spec: security: kms: connectionDetails: KMS_PROVIDER: vault # 或 ibm-kp / kmip / 空(默认 Kubernetes Secrets) # ... 其他 KMS 连接参数,见 # Documentation/Storage-Configuration/Advanced/key-management-system.md tokenSecretName: vault-token keyRotation: enabled: "true" # 设计文档示例中的字符串形式,实际字段为布尔值 schedule: "@weekly"

几点说明:

  • schedule使用标准 cron 格式,也支持 Kubernetes CronJob 的快捷别名(如@weekly@daily);
  • 轮换只对加密且由 PVC 承载的 OSD 生效(reconcileKeyRotationCronJob中通过osdProps.encrypted过滤);非 PVC 或未启用加密的 OSD 不会创建 CronJob;
  • KMS 的完整配置方式(Vault token、IBM Key Protect 凭据、KMIP 连接等)可参考 key-management-system.md 与 ceph-cluster-crd.md。

如何验证轮换是否生效

  1. 确认 CronJob 已创建kubectl -n rook-ceph get cronjob,应看到形如rook-ceph-osd-key-rotation-0rook-ceph-osd-key-rotation-1的任务(每个加密 OSD 一个);
  2. 确认调度节点正确kubectl -n rook-ceph get pod -l app=rook-ceph-osd-key-rotation -o wide,Pod 所在节点应与对应 OSD 一致;
  3. 查看轮换日志kubectl -n rook-ceph logs job/<cronjob 触发的 job>,应依次出现fetching the current keyadding the current key to slot "1"generating new keyupdating the new key in the KMSSuccessfully rotated the key等日志;
  4. 关闭轮换:将security.keyRotation.enabled置为false后,协调器会删除全部轮换 CronJob。

边界情况、限制与安全说明

基于设计文档与源码实现,使用该功能时需要明确以下几点:

  • 中断安全性:五步状态机保证任意步骤中断后,KMS 中的 KEK 仍能打开设备(每一步设备内至少保留一个可用密钥);CronJob 的ForbidConcurrent策略与底层命令的幂等设计共同防止并发与重复执行造成破坏。
  • KMS 更新能力差异:当前UpdateSecret仅对 Kubernetes Secrets 与 Vault 实现;IBM Key Protect、KMIP、Azure Key Vault 的轮换更新会直接报错。设计文档中的方案表述为"需要为每种 KMS 类型增加支持",即该能力仍在演进中,启用前务必确认你的 KMS 类型受支持
  • 不支持按需轮换:非目标明确排除了按需触发;临时需要轮换只能调整 schedule 或等待下个周期(CronJob 也可手动触发,但这不是受支持的正式接口)。
  • 特权容器:轮换 Pod 以 privileged + root 运行并挂载宿主机/dev,这是操作 dm-crypt 设备所必需的,安全边界与 OSD provision 容器一致。
  • 默认关闭enabled默认false,不会对未显式开启的集群产生额外负载。

总结

KEK 轮换是 Rook 加密存储安全能力的重要一环:通过security.keyRotation声明式配置、按 OSD 粒度生成的 CronJob 调度、LUKS 双槽位缓冲的五步状态机,以及 KMS 的密钥版本更新,Rook 在无需重启 OSD、无停机的前提下实现了加密密钥的周期性更换。设计文档(key-encryption-key-rotation.md)奠定了方案骨架,而 Operator 与 Daemon 层的源码则把"任意步骤可中断、任意状态可重入"的工程细节落到了实处。若要进一步深入,可依次阅读 pkg/operator/ceph/cluster/osd/key_rotation.go、pkg/daemon/ceph/osd/key_rotation.go、pkg/daemon/ceph/osd/kms/kms.go 及 pkg/daemon/ceph/osd/encryption.go 四份核心文件。

【免费下载链接】rookStorage Orchestration for Kubernetes项目地址: https://gitcode.com/gh_mirrors/roo/rook

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 4:04:51

3个真实案例教你吉林大学校园网登录报错新手避坑指南

3个真实案例教你吉林大学校园网登录报错新手避坑指南 满屏红色的 StackTrace 直接糊脸, java.net.ConnectException: Connection timed out 后面跟着一长串你看不懂的类名和方法调用栈。是不是瞬间懵了?这种 报错一堆看不懂 StackTrace…

作者头像 李华
网站建设 2026/9/23 4:04:51

3步搞定崖边报告面试必问,保姆级教程助应届生拿offer

3步搞定崖边报告面试必问,保姆级教程助应届生拿offer 复制来的代码跑不通,对着报错日志发呆两小时,这是多少应届生的噩梦?别急,这篇保姆级教程不教你写八股文,而是带你拆解【崖边报告】背后的底层逻辑。…

作者头像 李华
网站建设 2026/9/23 4:04:40

告别盲目:Synapse与Synopsis选型速查手册

告别盲目:Synapse与Synopsis选型速查手册 别再对着教程发呆了。很多人看了一堆视频,敲了无数行代码,真到写项目时还是卡壳。问题不在手速,而在选型混乱。今天这份速查手册,专治“不知道选哪个”的纠结症。我们直接拆解两个极易混淆但底层逻辑截然不同的概念: Synapse 与 Synopsis…

作者头像 李华
网站建设 2026/9/23 4:04:34

图解原理拆解360更新机制:3个核心差异帮你避开90%的坑

图解原理拆解360更新机制:3个核心差异帮你避开90%的坑 官方文档里那些密密麻麻的参数说明和晦涩的术语,真的能把人逼疯。刚接手项目时,我盯着那几百页的 API 文档,眼睛都花了却抓不住重点,根本不知道哪里才是坑。其实,只要看懂背后的 图解原理…

作者头像 李华
网站建设 2026/9/23 4:04:30

面试被问点弹性公式答不上? 手写实现从入门到精通

面试被问点弹性公式答不上? 手写实现从入门到精通 上周陪一个做后端的朋友面大厂,面试官甩出一句:“给我讲讲点弹性公式,手写一个。”他愣了五秒,脑子里全是“弹性系数”、“微积分”这些词,结果卡壳。那种尴尬,懂技术的都懂。很多技术博客把“点弹性公式”讲得天花乱坠,全是数学推导,没人告诉你怎么在代码里落地…

作者头像 李华
网站建设 2026/9/23 4:04:19

Subscibe订阅机制面试避坑指南:3个高频考点助你拿Offer

Subscibe订阅机制面试避坑指南:3个高频考点助你拿Offer 复制来的代码跑不通,是不是觉得哪里不对劲却找不到原因?别急,这在面试中太常见了。很多候选人把 Subscibe 当黑盒用,结果一到追问环节就露馅。掌握其 最佳实践 ,不仅能解决线上 Bug,更是大厂面试的敲门砖。 考点梳理:别把…

作者头像 李华