对称加密
在安全模式下,Ozone 会签发令牌来授权并验证每一次块和容器的访问。传统上,每个令牌由 Ozone Manager(OM)或 Storage Container Manager(SCM)使用 RSA 私钥签名,并由 Datanode 使用公钥和证书进行验证。然而,当 RSA 私钥长度为 2048 位时,签名操作的计算开销很大,可能占 Ozone Manager 读写操作延迟的 80% 以上。
由于 Ozone Manager 在设计上不支持水平扩展,最小化运行开销对于实现亚毫秒级延迟至关重要。非对称密钥签名无法满足这一要求。解决方案是使用对称密钥算法(例如配合 SHA256 的 HMAC)来签名令牌——这与 HDFS 的工作方式类似。该方法将签名生成的开销从毫秒级降低到微秒级。
相对于非对称加密的性能优势
| 方面 | 非对称(RSA-2048) | 对称(HMAC-SHA256) |
|---|---|---|
| 签名速度 | 毫秒 | 微秒 |
| CPU 开销 | 高 | 低 |
| 延迟影响 | >80% 的 OM 读写延迟 | 可忽略 |
| 可扩展性 | 受签名开销限制 | 高度可扩展 |
共享密钥模型
对称密钥算法要求签名方(OM)和验证方(Datanode)共享同一个 SecretKey。这就需要在各个 Ozone 组件之间管理 SecretKey 的分发和生命周期。
架构概览
各组件职责:
| 组件 | 职责 |
|---|---|
| SCM | 数据权威来源。负责生成、轮换、存储和分发 SecretKey。 |
| OM | 从 SCM 获取当前 SecretKey,进行缓存,并使用 HMAC 签名块令牌。 |
| Datanode | 通过心跳/注册接收 SecretKey,使用缓存的密钥验证令牌。 |
SecretKey 流转:
SecretKey 生命周期
密钥结构
每个 SecretKey 封装了以下内容:
- ID:SecretKey 的唯一标识符
- creationTime:密钥创建时间戳
- expiryTime:creationTime + X 天(可配置的过期时长)
- secretKey:实际的对称密钥材料
密钥生成与存储
- SCM 生成 SecretKey,并将其持久化存储在 SCM 文件系统中
- 每个 SCM 独立生成自己的 SecretKey
- SCM 同时维护当前活跃的 SecretKey 以及所有未过期的密钥
- 密钥存储在
<hdds.metadata.dir>/scm/<hdds.key.dir.name>路径下的 KeyStore 文件中 - 文件权限仅限 SCM 进程属主进行只读访问
密钥轮换
SCM 主动生成并分发下一个 SecretKey,以确保当前活动密钥在启用之前就已经存在于各数据节点上:
// When SCM first starts
currentKey = generateSecretKey();
nextKey = generateSecretKey();
allKeys.add(currentKey);
allKeys.add(nextKey);
// Key rotation (periodic)
currentKey = nextKey;
nextKey = generateSecretKey();
allKeys.add(nextKey);
filterExpiredSecretKeys(allKeys);在每个轮换周期中:
- 先前生成的
nextKey成为currentKey - 为下一个周期生成新的
nextKey - 过期的 SecretKey 从活动集合中移除
密钥分发
发送给 Ozone Manager
- OM 通过 RPC 从 SCM(leader)获取当前的 SecretKey
- 为提升性能,OM 会以可配置的 TTL 在内存中缓存该 SecretKey
- 签名的令牌包含 SecretKey ID,使 Datanode 能够识别应使用哪个密钥进行验证
发送给 Datanode
Datanode 通过两种机制接收 SecretKey:
- 注册:当 Datanode 加入或重新加入集群时,它会向所有 SCM 实例注册,并获取当前所有未过期的 SecretKey
- 心跳:在处理心跳的过程中,SCM 会检查是否需要分发新的 SecretKey,并将其包含在心跳响应中
Datanode 使用 HashMap 将 SecretKey 存储在内存中,以便通过 ID 快速查找。它们还会定期移除过期的密钥。
特殊事件处理
OM 重启
重启后,OM 会调用 SCM 获取并缓存当前的 SecretKey。
SCM 重启
重启后,SCM 会:
- 读取存储的文件,加载未过期的 SecretKey
- 移除任何已过期的密钥
- 根据已加载密钥的时间戳确定
currentKey - 如有必要,生成新的
nextKey
如果所有存储的密钥都已过期,SCM 将如同全新启动一样运行。
下表展示了在密钥过期周期为 7 天的情况下,SCM 的密钥恢复行为。在此示例中,kN 代表第 N 天生成的密钥。假设 SCM 一直运行到第 6 天,存储了密钥 k1-k7(其中 k6 为 currentKey,k7 为 nextKey),随后宕机。表格展示了 SCM 在不同日期重启时会发生什么:
| 存储的密钥 | 重启日期 | 密钥恢复结果 |
|---|---|---|
| k1-k7 | 第 6 天 | currentKey = k6,nextKey = k7,allKeys = [k1, k2, k3, k4, k5, k6, k7] |
| k1-k7 | 第 7 天 | currentKey = k7,nextKey = generateNewKey(),allKeys = [k1, k2, k3, k4, k5, k6, k7, nextKey] |
| k1-k7 | 第 8 天 | currentKey = k7,nextKey = generateNewKey(),allKeys = [k2, k3, k4, k5, k6, k7, nextKey] |
| k1-k7 | 第 13 天 | currentKey = k7,nextKey = generateNewKey(),allKeys = [k7, nextKey] |
| k1-k7 | 第 14 天 | currentKey = generateNewKey(),nextKey = generateNewKey(),allKeys = [currentKey, nextKey] |
说明:
- 第 6 天:与关机同一天,密钥按原样恢复
- 第 7 天:k7 被提升为 current,生成新的 nextKey
- 第 8 天:k1 过期(第 1 天生成 + 7 天 = 第 8 天),从 allKeys 中移除
- 第 13 天:仅 k7 仍然有效,k1-k6 全部过期
- 第 14 天:所有存储的密钥都已过期(k7:第 7 天 + 7 天 = 第 14 天),生成全新的密钥
SCM 故障切换
当 SCM 领导权转移到新实例时:
- 新 SCM 的 SecretKey 应当已经存在于各数据节点上(因为数据节点会向所有 SCM 实例注册)
- OM 可以继续使用其缓存的 SecretKey,直到缓存过期
- 数据节点缺少所需 SecretKey 的边缘情况,通过最终一致性机制加以处理
数据节点缺失 SecretKey
如果数据节点找不到所需的 SecretKey:
- 它会立即触发一次心跳,从所有 SCM 更新 SecretKey
- 向客户端返回
SecretKeyNotFound错误 - 客户端改用管线中的其他节点进行重试
- 如果所有节点都失败,客户端会向 OM 请求最新的块信息,并带上一个用于刷新 SecretKey 缓存的标志
- 系统会发出一个指标,以便在监控中暴露该情况
合规性与安全标准
算法选择
遵循 NIST SP 800-133 关于消息认证码的建议,Ozone 选用 HMAC,原因是:
- 性能极高
- 受 Java 安全核心(Java Security Core)支持
- 符合安全标准
默认配置使用 基于 SHA256 的 HMAC,根据 NIST SP 800-57,它可提供 128 位的安全强度。
密钥生成
SecretKey 使用 Java 的 SecureRandom 生成,符合 FIPS 140-2 对经认可的随机数生成器的要求。
密钥存储
- SecretKey 持久化存储在 KeyStore 文件中
- 文件权限仅限所有者读取
- 位置:
<hdds.metadata.dir>/scm/<hdds.key.dir.name>
密钥传输
SecretKey 通过受 TLS 保护的 RPC 连接在 SCM、OM 与数据节点之间传输,确保传输过程中的机密性。
- HDDS-7733 - 令牌签名的性能分析
- NIST SP 800-133 - 密码学密钥生成建议
- NIST SP 800-57 - 密钥管理建议
评论
登录后参与评论
KnowForge