RustFS Rebalance 存储表示复制缺陷(backlog1850)影响评估与只读排查指南
2026/9/11 21:17:37 网站建设 项目流程

RustFS Rebalance 存储表示复制缺陷(backlog#1850)影响评估与只读排查指南

【免费下载链接】rustfs🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs

本文针对 RustFS 历史版本中 pool rebalance(以及在更窄时间窗口内的 decommission)将压缩对象、SSE-S3/SSE-KMS 加密对象以明文形式复制到目标池、却保留其原始压缩/加密元数据的缺陷,给出完整的暴露判定、受影响版本边界、对象风险分级与五步只读评估工作流。读完本文,你将掌握如何在不做任何修复性写入的前提下,识别受影响的候选对象版本、在离线证据副本上核实存储元数据、验证逻辑内容并记录每个版本的最终置信度结论,从而为后续独立的恢复计划提供事实基础。本文是只读分诊指南,不包含任何修复动作。

缺陷机制:迁移管线如何让字节与元数据失配

RustFS 的 rebalance 迁移管线本质上是存储表示(stored-representation)复制器,而不是逻辑内容复制器。依据 migration.rs 与 data_movement/mod.rs 的实现,迁移过程会执行以下动作:

  • 保留源 ETag 与内部元数据:目标写入层通过preserve_etag: object_info.etag.clone()直接继承源对象 ETag(见 data_movement/mod.rs 及第 466、482 行的同类逻辑);
  • 按存储的part.size划分数据流:目标侧以part.size(以及part.actual_size非零时对应的实际大小)重新划分分片,见 data_movement/mod.rs;
  • 携带解码后的压缩索引:迁移读取路径同时携带压缩索引,使目标侧得以按原始表示继续组织数据。

缺陷的根源在于受影响版本的 rebalance 源端读取选项(read options)只提供了版本 ID 与锁设置,而未告知读取计划"我需要原始存储字节"。由于正常的 GET 读取计划会先执行解压缩或解密变换,迁移读取到的流是解压/解密后的逻辑字节,而写入侧仍按压缩/加密元数据组织目标:压缩对象变成"带着压缩元数据的明文",SSE-S3 / SSE-KMS 对象变成"带着加密元数据的明文"。目标写入可以成功完成,但其字节与描述它们的元数据已经不再匹配。

历史清理逻辑放大了后果

历史版本的 rebalance 清理在源条目下每一个版本都被报告为已迁移之后才会删除源条目。因此:

  • 一个被当作"成功迁移"接受的目标写入,随后可能触发源删除——即使之后对目标的 GET 会失败;
  • 反之,源读取失败会使该版本不被计入已迁移,从而阻塞正常的源清理。

两者叠加意味着:rebalance 成功状态本身既不证明目标字节完好,源池中没有条目也不证明目标内容可靠。

正向修复与它的边界

正向修复(commite11fcfbd,PR #6057)为 rebalance 源读取设置了raw_data_movement_read: true。在 migration.rs 中可以看到当前实现的rebalance_object_migration_read_opts已包含该标志(连同no_lockdata_movementskip_decommissionedskip_rebalancing):

pub(super) fn rebalance_object_migration_read_opts(version_id: Option<String>) -> ObjectOptions { ObjectOptions { version_id, no_lock: true, data_movement: true, raw_data_movement_read: true, skip_decommissioned: true, skip_rebalancing: true, ..Default::default() } }

该标志定义于 object_api/types.rs(pub raw_data_movement_read: bool)。在读取计划构建处 object_api/readers.rs,一旦该标志为真,读取计划直接返回ReadTransform::Plain的存储区间,跳过压缩与加密变换;同文件第 2530、2561、2597 行的单元测试分别验证了该路径绕过压缩变换、绕过加密变换、绕过 SSE-C 头解析。rebalance 单元测试 rebalance_unit_tests.rs 也断言迁移读取选项携带raw_data_movement_read。decommission 侧同样在 core/pools.rs 的decommission_object_migration_read_opts中设置了该标志。

需要特别强调:升级可以阻止缺陷在后续运行中再次出现,但不会校验或修复更早一次运行所产生的副本。这正是本文只读分诊流程存在的原因。

即时操作决策:判定暴露并冻结破坏性动作

当以下两个条件同时成立时,将部署视为已暴露:

  1. 部署在受影响构建上运行过 rebalance,或在下方更窄的历史时间窗口内运行过 decommission;
  2. 操作可能选中过压缩、SSE-S3 或 SSE-KMS 对象。

对已暴露部署,请立即执行:

  • 保留旧池介质、快照、副本与备份:在任何池被移除、重新格式化、复用或归还之前完成保留;
  • 停止破坏性清理不要将另一次 rebalance 或 decommission 运行当作修复机制;
  • 用只读操作盘点并验证候选对象(见下文工作流);
  • 将 SSE-S3 与 SSE-KMS 候选对象同时按机密性事件与数据完整性事件处理(目标池可能存在明文落盘);
  • 仅从独立验证过的源恢复,且须遵循事件专属的恢复计划。

受影响版本边界

版本边界由 tag 血缘关系验证得出,涉及三个关键提交:

  • commita236b0d0:引入合并后的 rebalance 与 decommission 实现;
  • commit2f25cf60:引入原始存储表示读取模式,并将其接入 decommission;
  • commite11fcfbd:将同一读取模式接入 rebalance(正向修复)。
Release 或 commit 范围RebalanceDecommission运维分类
1.0.0-alpha.90a236b0d0之前路径不存在路径不存在不受此数据移动路径影响
1.0.0-alpha.911.0.0-beta.8、自a236b0d0起且不含2f25cf60解码读取解码读取两个操作均需评估
1.0.0-beta.91.0.0-rc.1、自2f25cf60起且不含e11fcfbd解码读取原始存储表示读取仅 rebalance 需评估;decommission 不受此缺陷影响
1.0.0-rc.2及之后、e11fcfbd及之后原始存储表示读取原始存储表示读取已正向修复;更早副本仍需评估

预览 tag 跟随其引用的 commit:rc.1预览受影响,rc.2预览包含正向修复。对于自定义或未打 tag 的构建,应对照三个 commit 边界比较实际部署的 commit,而不是根据版本字符串推断行为。

两点补充说明:

  • decommission 的暴露窗口比 rebalance 窄,但并非为空:在2f25cf60之前,decommission 使用与普通读取相同的解码读取器。针对该更早 decommission 窗口的代码修复或自动化补救不在本指南范围内,需要单独建 issue;
  • 本缺陷追踪于rustfs/backlog#1850

对象分类与风险分级

存储对象类别受影响的读取结果风险分诊优先级
普通、未压缩、未加密存储字节与逻辑字节相同单就此缺陷预计不会造成损坏低;抽样以验证范围假设
压缩解压后的字节按压缩后的 part size 划分,同时保留压缩元数据与索引静默截断或压缩表示损坏;GET 可能失败或返回截断数据
SSE-S3解密明文可能被写入,同时保留加密元数据与密文大小目标池明文落盘 + 后续解密失败严重
SSE-KMS解密明文可能被写入,同时保留 KMS/加密元数据与密文大小目标池明文落盘 + 后续解密失败严重
SSE-C迁移请求没有客户密钥,普通读取按失败关闭迁移失败与可能的不完整进度;预计该路径不会产生成功的损坏副本中;确认源已保留
任意压缩+加密组合多项存储表示假设均被违背机密性泄露与数据损坏严重

该分类仅针对此缺陷。低风险分类并不等于对无关损坏的免检证书。

只读评估工作流

评估全程只读:不编辑任何存储元数据、不重写分片文件、不执行修复性迁移。

第 1 步:建立操作窗口

为每个参与节点记录确切的 RustFS 版本与 commit。收集:经过鉴权的 rebalance 状态响应、适用时的 decommission 状态、服务日志、部署变更记录与发布历史。

持久化的 rebalance 元数据记录了运行 ID、参与的池、起止状态、bucket 列表、计数器以及最后的 bucket/object 进度值。它不持久化逐对象迁移台账:状态元数据能证明一次运行发生过并缩小时间、池与 bucket 范围,但无法枚举每一个被迁移的对象。

如果已无可靠的操作记录,请假定受影响部署区间内存在于某 bucket 的每个对象版本都是候选对象,直到其他证据缩小范围。

第 2 步:构建候选清单

使用只读的 S3 list 与 list-object-versions 操作列出范围内 bucket,保留 bucket、key、版本 ID、最后修改时间、size、ETag、存储类别以及任何客户端内容摘要。将该清单与以下记录关联:

  • 记录压缩设置或 SSE 模式的上传记录
  • KMS 审计历史与应用目录
  • 复制清单与外部备份清单
  • rebalance/decommission 时间戳与源/目标池记录
  • 显示移动后读取成功或失败的服务器访问日志

不要将 ETag 相等当作内容完整性的证明:迁移写入侧保留了源 ETag(包括损坏的目标副本),且 multipart 或加密对象的 ETag 并非通用内容哈希。

第 3 步:在证据副本上分类存储元数据

当 API 与应用记录无法对候选对象分类时,将每个相关分片磁盘上的xl.meta复制到受限的证据位置,并在离线主机上检查副本。不要在活跃数据路径上原地编辑或解码元数据。证据副本须与对象本体保持相同级别的访问控制。

rustfs-filemeta示例可以解码证据副本,它输出包括 partsizeactual_sizeetag以及压缩索引在内的元数据值(见 dump_fileinfo.rs)。部分输出是敏感加密材料,送达终端或报告前必须先脱敏:

cargo run --quiet -p rustfs-filemeta --example dump_fileinfo -- /evidence/object/xl.meta | sed -E 's/^(meta\[[^]]+\])=.*/\1=<redacted>/'

将输出仅用作筛选依据:

  • 出现x-rustfs-internal-compressionx-minio-internal-compression任一键即标记压缩表示;
  • actual-size、每个 part 的size/actual_size与压缩索引合计应算术一致;
  • SSE-C 的 customer-algorithm/MD5 标记用于识别 SSE-C;
  • KMS 的 key-ID/context 标记用于识别 SSE-KMS;
  • 无 SSE-C 或 KMS 标记的托管加密信封则指向 SSE-S3 候选。

绝不在 ticket、日志、聊天或评估报告中包含加密元数据值。此外,元数据一致是必要但非充分条件:本缺陷保留了元数据,因此看似合理的大小与可解码的索引并不能证明存储字节与它匹配。

第 4 步:无变更验证逻辑内容

对每个高或严重风险候选对象,对确切版本执行一次完整鉴权 GET,写入受限的验证汇聚端。仅在授权的 SSE-C 检查场景下提供客户密钥。记录状态、字节数与验证客户端计算的密码学摘要,并与来自独立可信来源(备份、副本或应用记录)的摘要对比。

保守解读结果:

  • GET 解码/解密错误、意外 EOF 或字节数偏短是强受影响副本信号,但也可能是其他损坏原因;
  • 与独立密码学摘要匹配,则验证了该逻辑版本;
  • 成功的 GET 而无独立摘要,只证明可读,不证明同一性;
  • 仅 ETag 匹配不足以定论;
  • 在受影响窗口内移动的 SSE-S3/KMS 候选对象,在存储级审查排除目标池明文副本及衍生的快照/备份之前,始终是机密性事件

托管 SSE 候选对象的存储级确认可能暴露明文与 sealed-key 材料,只能由事件/安全负责人对离线证据副本执行。不要打印、上传或对外提供原始分片字节,也不要以绕过 RustFS 的方式把它们返回给应用。

第 5 步:记录置信度与结果

为每一个候选版本记录一个结果:

结果含义
confirmed-good完整逻辑字节与独立摘要匹配。
confirmed-affected目标解码/解密/长度证据与可信源确立了失配,或经授权的存储审查确认托管 SSE 元数据下存在明文。
suspected版本与操作窗口吻合,但证据不完整。
not-applicable证据证明对象是普通且未压缩的,或从未被受影响操作选中。
unrecoverable-pending-source已受影响或疑似受影响,且尚未找到已核实源。

保留每次决策所用的证据。不要将相同 key 下的多个对象版本合并为一个结果。

源保留与恢复边界

历史迁移成功后可能伴随源条目删除,因此成功的 rebalance 状态与旧源池中条目缺失都不能证明目标字节完好。恢复只能来自独立验证过的源:

  • 清理前保留的源池介质或快照;
  • 经独立验证的副本;
  • 外部备份;
  • 携带可信摘要的原始应用或上游源。

SSE-C 通常会在目标副本被接受前失败(迁移读取没有客户密钥),该失败阻止了正常的源清理;但运维人员仍应核实保留源介质上的确切版本,而非假设它存在。

如果不存在已核实源,请将该版本标记为本次事件的不可恢复不要编辑xl.meta、重写分片文件、清除加密/压缩标记或在原地覆盖对象——这些动作可能销毁证据、违反保留/版本策略,或把可见的读取失败变成静默的数据替换。任何恢复或替换流程都需要独立的、经过评审的、可回滚的计划。

总结

backlog#1850 的本质是:迁移管线忠实复制"存储表示相关元数据",而受影响构建的读取计划却先解码了内容,二者失配导致压缩/加密对象以明文落盘且元数据自洽。评估的关键纪律是全程只读、证据先行、ETag 不可信、独立摘要才可信;修复(e11fcfbd/ PR #6057)只对未来运行有效,存量副本必须按本文五步工作流逐个版本判定为confirmed-goodconfirmed-affectedsuspectednot-applicableunrecoverable-pending-source,并仅从独立验证过的源执行恢复。

【免费下载链接】rustfs🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询