mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 13:16:28 +00:00
docs(operations): document rebalance impact assessment
This commit is contained in:
@@ -0,0 +1,263 @@
|
|||||||
|
# Rebalance Stored-Representation Impact Guide
|
||||||
|
|
||||||
|
This guide covers the historical data-movement read defect tracked by
|
||||||
|
[`rustfs/backlog#1850`](https://github.com/rustfs/backlog/issues/1850). It is an
|
||||||
|
impact-assessment and read-only triage guide. It does not repair, rewrite,
|
||||||
|
migrate, delete, or quarantine any object.
|
||||||
|
|
||||||
|
The defect affected data movement when the source reader returned logical
|
||||||
|
plaintext but the target writer preserved the source's stored-representation
|
||||||
|
metadata and sizes. Compressed objects could therefore be copied as plaintext
|
||||||
|
under compression metadata. Server-managed encrypted objects could be copied as
|
||||||
|
plaintext under encryption metadata. The forward rebalance fix reached `main`
|
||||||
|
in commit
|
||||||
|
[`e11fcfbd`](https://github.com/rustfs/rustfs/commit/e11fcfbd087f8a8dae2c0f2c62bc0f6e40e3f10a)
|
||||||
|
through [PR #6057](https://github.com/rustfs/rustfs/pull/6057).
|
||||||
|
|
||||||
|
Upgrading prevents this defect in later rebalance runs. It does not validate or
|
||||||
|
repair copies produced by an earlier run.
|
||||||
|
|
||||||
|
## Immediate Operator Decision
|
||||||
|
|
||||||
|
Treat a deployment as exposed when both conditions are true:
|
||||||
|
|
||||||
|
1. it ran rebalance in an affected build, or decommission in the narrower
|
||||||
|
historical window described below; and
|
||||||
|
2. the operation could have selected compressed, SSE-S3, or SSE-KMS objects.
|
||||||
|
|
||||||
|
For an exposed deployment:
|
||||||
|
|
||||||
|
- preserve old pool media, snapshots, replicas, and backups before any pool is
|
||||||
|
removed, reformatted, reused, or returned;
|
||||||
|
- stop destructive cleanup and do not use another rebalance or decommission run
|
||||||
|
as a repair mechanism;
|
||||||
|
- inventory and validate candidates with read-only operations;
|
||||||
|
- handle SSE-S3 and SSE-KMS candidates as a confidentiality incident as well as
|
||||||
|
a data-integrity incident;
|
||||||
|
- restore only from a separately verified source under an incident-specific
|
||||||
|
recovery plan.
|
||||||
|
|
||||||
|
## Affected Versions
|
||||||
|
|
||||||
|
The release boundaries below were verified by tag ancestry. Commit
|
||||||
|
[`a236b0d0`](https://github.com/rustfs/rustfs/commit/a236b0d01d40a152309446a553756ea991c9f901)
|
||||||
|
introduced the merged rebalance and decommission implementation. Commit
|
||||||
|
[`2f25cf60`](https://github.com/rustfs/rustfs/commit/2f25cf606e5ca814fe992be6327a91e31fe066b3)
|
||||||
|
introduced the raw stored-representation read mode and wired it into
|
||||||
|
decommission. Commit `e11fcfbd` wired the same mode into rebalance.
|
||||||
|
|
||||||
|
| Release or commit range | Rebalance | Decommission | Operator classification |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Through `1.0.0-alpha.90`, before `a236b0d0` | Path not present | Path not present | Not affected by this data-movement path |
|
||||||
|
| `1.0.0-alpha.91` through `1.0.0-beta.8`, from `a236b0d0` up to but excluding `2f25cf60` | Decoded read | Decoded read | Both operations require assessment |
|
||||||
|
| `1.0.0-beta.9` through `1.0.0-rc.1`, from `2f25cf60` up to but excluding `e11fcfbd` | Decoded read | Raw stored-representation read | Rebalance requires assessment; decommission is not affected by this defect |
|
||||||
|
| `1.0.0-rc.2` and later, at or after `e11fcfbd` | Raw stored-representation read | Raw stored-representation read | Forward-fixed; earlier copies still require assessment |
|
||||||
|
|
||||||
|
Preview tags follow the commit they reference. In particular, the `rc.1`
|
||||||
|
preview is affected and the `rc.2` preview contains the forward fix. For custom
|
||||||
|
or untagged builds, compare the deployed commit with the three commit boundaries
|
||||||
|
rather than inferring behavior from a version string.
|
||||||
|
|
||||||
|
The historical decommission result is narrower than the rebalance result but is
|
||||||
|
not empty. Before `2f25cf60`, decommission used the same ordinary decoded reader.
|
||||||
|
From `1.0.0-beta.9` onward it explicitly used `raw_data_movement_read: true`.
|
||||||
|
Any code change or automated remediation for the earlier decommission window is
|
||||||
|
outside this report and requires a separate issue.
|
||||||
|
|
||||||
|
## Why The Copy Could Be Accepted
|
||||||
|
|
||||||
|
The migration pipeline is a stored-representation copier. It preserves the
|
||||||
|
source ETag and internal metadata, uses stored `part.size` values to divide the
|
||||||
|
stream, and carries the decoded compression index. The affected rebalance read
|
||||||
|
options supplied only the version ID and lock setting, so the normal GET read
|
||||||
|
plan decompressed or decrypted the stream first. A target write could therefore
|
||||||
|
complete while its bytes no longer matched the metadata that described them.
|
||||||
|
|
||||||
|
Historical rebalance cleanup ran only after every version in an entry was
|
||||||
|
reported moved. It then deleted the source entry. A target write accepted as a
|
||||||
|
successful move could therefore be followed by source deletion even though a
|
||||||
|
later GET of the target would fail. Conversely, a source-read failure prevented
|
||||||
|
the version from being counted as moved and prevented normal source cleanup.
|
||||||
|
|
||||||
|
## Object Classification
|
||||||
|
|
||||||
|
| Stored object class | Affected read result | Risk | Triage priority |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Plain, uncompressed, unencrypted | Stored bytes and logical bytes are the same | No corruption expected from this defect alone | Low; sample to validate the scope assumption |
|
||||||
|
| Compressed | Decompressed bytes were divided using compressed part sizes while compression metadata and indexes were retained | Silent truncation or malformed compressed representation; GET can fail or return truncated data | High |
|
||||||
|
| SSE-S3 | Decrypted plaintext could be written while encryption metadata and ciphertext sizes were retained | Plaintext at rest on the target plus later decrypt failure | Critical |
|
||||||
|
| SSE-KMS | Decrypted plaintext could be written while KMS/encryption metadata and ciphertext sizes were retained | Plaintext at rest on the target plus later decrypt failure | Critical |
|
||||||
|
| SSE-C | The migration request did not have the customer key, so the normal read failed closed | Migration failure and possible incomplete progress; no successful corrupting copy is expected from this path | Medium; confirm the source was retained |
|
||||||
|
| Any compressed and encrypted combination | Multiple stored-representation assumptions were violated | Confidentiality exposure and data corruption | Critical |
|
||||||
|
|
||||||
|
The classification is specific to this defect. A low-risk classification does
|
||||||
|
not certify an object against unrelated corruption.
|
||||||
|
|
||||||
|
## Read-Only Assessment Workflow
|
||||||
|
|
||||||
|
### 1. Establish The Operation Window
|
||||||
|
|
||||||
|
Record the exact RustFS version and commit for every node that participated.
|
||||||
|
Collect the authenticated rebalance status response, decommission status when
|
||||||
|
applicable, service logs, deployment change records, and release history.
|
||||||
|
|
||||||
|
Persisted rebalance metadata records the run ID, participating pools, start and
|
||||||
|
end state, bucket lists, counters, and the last bucket/object progress value. It
|
||||||
|
does not persist a complete per-object movement ledger. Status metadata can
|
||||||
|
prove that a run occurred and narrow time, pool, and bucket scope, but it cannot
|
||||||
|
by itself enumerate every moved object.
|
||||||
|
|
||||||
|
If no reliable operation record remains, assume that every object version in a
|
||||||
|
bucket present during the affected deployment interval is a candidate until
|
||||||
|
other evidence narrows the set.
|
||||||
|
|
||||||
|
### 2. Build A Candidate Inventory
|
||||||
|
|
||||||
|
Use read-only S3 list and list-object-versions operations for the buckets in
|
||||||
|
scope. Preserve bucket, key, version ID, last-modified time, size, ETag, storage
|
||||||
|
class, and any client-side content digest. Join that list with:
|
||||||
|
|
||||||
|
- upload records that identify compression settings or SSE mode;
|
||||||
|
- KMS audit history and application catalogs;
|
||||||
|
- replication inventory and external backup manifests;
|
||||||
|
- rebalance/decommission timestamps and source/target pool records;
|
||||||
|
- server access logs showing successful or failed reads after movement.
|
||||||
|
|
||||||
|
Do not use ETag equality as proof of content integrity. The migration writer
|
||||||
|
preserved the source ETag, including for a malformed target copy, and multipart
|
||||||
|
or encrypted ETags are not general-purpose content hashes.
|
||||||
|
|
||||||
|
### 3. Classify Stored Metadata On Evidence Copies
|
||||||
|
|
||||||
|
When API and application records cannot classify a candidate, copy `xl.meta`
|
||||||
|
from each relevant shard disk to a restricted evidence location and inspect the
|
||||||
|
copy on an offline host. Do not edit or decode metadata in place on a live data
|
||||||
|
path. Keep the evidence copies under the same access controls as the object.
|
||||||
|
|
||||||
|
The existing `rustfs-filemeta` example can decode an evidence copy. It prints
|
||||||
|
metadata values, some of which are sensitive encryption material, so redact
|
||||||
|
metadata values before they reach a terminal or report:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --quiet -p rustfs-filemeta --example dump_fileinfo -- /evidence/object/xl.meta |
|
||||||
|
sed -E 's/^(meta\[[^]]+\])=.*/\1=<redacted>/'
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the output only as a screen:
|
||||||
|
|
||||||
|
- either the `x-rustfs-internal-compression` or
|
||||||
|
`x-minio-internal-compression` key marks a compressed representation;
|
||||||
|
- `actual-size`, per-part `size`/`actual_size`, and compression-index totals
|
||||||
|
should be arithmetically consistent;
|
||||||
|
- SSE-C customer-algorithm/MD5 markers identify SSE-C;
|
||||||
|
- KMS key-ID/context markers identify SSE-KMS;
|
||||||
|
- a managed encryption envelope without SSE-C or KMS markers identifies an
|
||||||
|
SSE-S3 candidate.
|
||||||
|
|
||||||
|
Never include encryption metadata values in tickets, logs, chat, or assessment
|
||||||
|
reports. Metadata consistency is necessary but not sufficient: the defect
|
||||||
|
preserved metadata, so plausible sizes and a decodable index do not prove that
|
||||||
|
the stored bytes match it.
|
||||||
|
|
||||||
|
### 4. Validate Logical Content Without Mutation
|
||||||
|
|
||||||
|
For each high- or critical-risk candidate, perform a complete authenticated GET
|
||||||
|
of the exact version into a restricted validation sink. Supply the customer key
|
||||||
|
only for an authorized SSE-C check. Record the status, byte count, and a
|
||||||
|
cryptographic digest calculated by the validation client. Compare it with a
|
||||||
|
digest from an independently trusted source, backup, replica, or application
|
||||||
|
record.
|
||||||
|
|
||||||
|
Interpret the result conservatively:
|
||||||
|
|
||||||
|
- a GET decode/decrypt error, unexpected EOF, or short byte count is a strong
|
||||||
|
affected-copy signal, but may also have another corruption cause;
|
||||||
|
- a matching independent cryptographic digest validates that logical version;
|
||||||
|
- a successful GET without an independent digest proves readability, not
|
||||||
|
identity;
|
||||||
|
- a matching ETag alone is inconclusive;
|
||||||
|
- an SSE-S3/KMS candidate moved in the affected window remains a confidentiality
|
||||||
|
incident until storage-level review excludes plaintext target copies and
|
||||||
|
derivative snapshots or backups.
|
||||||
|
|
||||||
|
Storage-level confirmation for managed-SSE candidates may expose plaintext and
|
||||||
|
sealed-key material. It must be performed only by the incident/security owner on
|
||||||
|
offline evidence copies. Do not print, upload, or serve raw shard bytes, and do
|
||||||
|
not bypass RustFS to return them to an application.
|
||||||
|
|
||||||
|
### 5. Record Confidence And Outcome
|
||||||
|
|
||||||
|
Record one result for every candidate version:
|
||||||
|
|
||||||
|
- `confirmed-good`: full logical bytes match an independent digest;
|
||||||
|
- `confirmed-affected`: target decode/decrypt/length evidence and a trusted
|
||||||
|
source establish the mismatch, or authorized storage review confirms
|
||||||
|
plaintext under managed-SSE metadata;
|
||||||
|
- `suspected`: the version and operation window match, but proof is incomplete;
|
||||||
|
- `not-applicable`: evidence proves the object was plain and uncompressed or was
|
||||||
|
never selected by an affected operation;
|
||||||
|
- `unrecoverable-pending-source`: affected or suspected, with no verified source
|
||||||
|
yet found.
|
||||||
|
|
||||||
|
Retain the evidence used for each decision. Do not collapse object versions with
|
||||||
|
the same key into one result.
|
||||||
|
|
||||||
|
## Source Retention And Recovery Limits
|
||||||
|
|
||||||
|
Successful historical migration could be followed by source-entry deletion.
|
||||||
|
Therefore, neither successful rebalance status nor absence from the old source
|
||||||
|
pool proves that the target bytes are sound. Recovery is possible only from a
|
||||||
|
separately verified source, such as:
|
||||||
|
|
||||||
|
- retained source-pool media or a snapshot taken before cleanup;
|
||||||
|
- an independently validated replica;
|
||||||
|
- an external backup;
|
||||||
|
- the original application or upstream source with a trusted digest.
|
||||||
|
|
||||||
|
SSE-C normally failed before the target copy was accepted because the migration
|
||||||
|
read had no customer key. That failure prevented normal source cleanup, but
|
||||||
|
operators must verify the exact version on retained source media rather than
|
||||||
|
assuming it is present.
|
||||||
|
|
||||||
|
If no verified source exists, mark the version unrecoverable for this incident.
|
||||||
|
Do not edit `xl.meta`, rewrite shard files, clear encryption/compression markers,
|
||||||
|
or overwrite the object in place. Those actions can destroy evidence, violate
|
||||||
|
retention/versioning policy, or turn a visible read failure into silent data
|
||||||
|
substitution. Any restoration or replacement procedure needs its own reviewed,
|
||||||
|
rollback-aware plan.
|
||||||
|
|
||||||
|
## Release Guidance
|
||||||
|
|
||||||
|
Release notes for `1.0.0-rc.2` and later should state:
|
||||||
|
|
||||||
|
> Rebalance now copies the stored object representation for compressed and
|
||||||
|
> encrypted objects. Deployments that ran rebalance on versions from
|
||||||
|
> `1.0.0-alpha.91` through `1.0.0-rc.1` should preserve old pool media and run
|
||||||
|
> the read-only assessment in this guide. Upgrading prevents new copies from
|
||||||
|
> this defect but does not repair historical copies. Deployments that ran
|
||||||
|
> decommission from `1.0.0-alpha.91` through `1.0.0-beta.8` require the same
|
||||||
|
> assessment. SSE-S3 and SSE-KMS candidates require security incident handling.
|
||||||
|
|
||||||
|
Do not recommend rerunning rebalance as remediation. Do not remove or repurpose
|
||||||
|
old pool media until high- and critical-risk candidates have a recorded outcome
|
||||||
|
and the incident owner has accepted the recovery limits.
|
||||||
|
|
||||||
|
## Evidence Audit
|
||||||
|
|
||||||
|
The conclusions above are grounded in these repository facts:
|
||||||
|
|
||||||
|
- `crates/ecstore/src/services/rebalance/migration.rs` now sets both
|
||||||
|
`data_movement` and `raw_data_movement_read` for rebalance source reads;
|
||||||
|
- `crates/ecstore/src/core/pools.rs` sets the same flags for decommission source
|
||||||
|
reads;
|
||||||
|
- `crates/ecstore/src/object_api/readers.rs` returns the stored byte range before
|
||||||
|
compression or encryption transforms when `raw_data_movement_read` is set;
|
||||||
|
- `crates/ecstore/src/data_movement/mod.rs` preserves stored part sizes, ETags,
|
||||||
|
indexes, and internal metadata during migration;
|
||||||
|
- the historical `a236b0d0` rebalance and decommission readers both used normal
|
||||||
|
read options, while `2f25cf60` changed only decommission to the raw mode;
|
||||||
|
- the historical rebalance entry deleted its source prefix only after all
|
||||||
|
versions were counted as moved;
|
||||||
|
- the tag ancestry boundaries are `1.0.0-alpha.91`, `1.0.0-beta.9`, and
|
||||||
|
`1.0.0-rc.2` for the implementation, decommission raw-read fix, and rebalance
|
||||||
|
raw-read fix respectively.
|
||||||
Reference in New Issue
Block a user