mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-29 08:27:06 +00:00
perf(ecstore): enable encrypted range part-seek by default (#6598)
Range GETs on encrypted objects read the whole ciphertext from offset 0 and discarded the decrypted prefix, because the part-boundary seek shipped behind RUSTFS_ENCRYPTED_RANGE_SEEK defaulted to false (backlog#1316 Phase A). Flip the default to true. Safety rests on the marker chain: MPUs created without a candidate layout marker never become seek-eligible, CompleteMultipartUpload promotes the candidate to the quorum marker only after revalidating it against the object's data_dir under the uploadId write lock, and reads seek only when the quorum marker matches the current data_dir. Single-part, compressed and markerless objects keep the full-read path; RUSTFS_ENCRYPTED_RANGE_SEEK=false remains the kill switch. The stale default-off regression test becomes test_legacy_range_seek_defaults_enabled: the unset-env default must match the explicit opt-in plan, seek past the leading parts, and not span the whole ciphertext.
This commit is contained in:
@@ -53,7 +53,7 @@ pub const BLOCK_SIZE_V2: usize = 1024 * 1024; // 1M
|
||||
pub(crate) const ENCRYPTED_PART_LAYOUT_CANDIDATE_SUFFIX: &str = "encrypted-part-layout-quorum-candidate-v1";
|
||||
pub(crate) const ENCRYPTED_PART_LAYOUT_QUORUM_SUFFIX: &str = "encrypted-part-layout-quorum-v1";
|
||||
pub(crate) const ENV_RUSTFS_ENCRYPTED_RANGE_SEEK: &str = "RUSTFS_ENCRYPTED_RANGE_SEEK";
|
||||
pub(crate) const DEFAULT_RUSTFS_ENCRYPTED_RANGE_SEEK: bool = false;
|
||||
pub(crate) const DEFAULT_RUSTFS_ENCRYPTED_RANGE_SEEK: bool = true;
|
||||
|
||||
pub(crate) fn has_encrypted_part_layout_marker(metadata: &HashMap<String, String>, suffix: &str, expected: &str) -> bool {
|
||||
let mut value = None;
|
||||
@@ -70,7 +70,11 @@ pub(crate) fn has_encrypted_part_layout_marker(metadata: &HashMap<String, String
|
||||
}
|
||||
|
||||
pub(crate) fn legacy_encrypted_range_seek_enabled() -> bool {
|
||||
// RUSTFS_COMPAT_TODO(backlog-1316): mixed-version MPUs need opt-in. Remove after all servers use candidate markers and uploadId locks.
|
||||
// On by default (backlog-1316 Phase A): every misfit direction falls back to the
|
||||
// conservative full read — MPUs created without a candidate marker, completions
|
||||
// that cannot revalidate the candidate against the data_dir under the uploadId
|
||||
// lock, and reads whose quorum marker disagrees with the current data_dir all
|
||||
// serve the full-object path. RUSTFS_ENCRYPTED_RANGE_SEEK=false is the kill switch.
|
||||
#[cfg(test)]
|
||||
{
|
||||
rustfs_utils::get_env_bool(ENV_RUSTFS_ENCRYPTED_RANGE_SEEK, DEFAULT_RUSTFS_ENCRYPTED_RANGE_SEEK)
|
||||
|
||||
@@ -3329,11 +3329,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_legacy_range_seek_defaults_disabled() {
|
||||
async_with_vars([(ENV_RUSTFS_ENCRYPTED_RANGE_SEEK, None::<&str>)], async {
|
||||
let key_bytes = [0x7D; 32];
|
||||
let fixture = build_legacy_ssec_multipart_fixture(key_bytes, &[20_000, 9_000, 5_000]).await;
|
||||
async fn test_legacy_range_seek_defaults_enabled() {
|
||||
let key_bytes = [0x7D; 32];
|
||||
|
||||
// The unset-env default must behave exactly like the explicit opt-in:
|
||||
// an eligible multipart object seeks to the covering part boundary
|
||||
// instead of reading the whole ciphertext.
|
||||
let default_plan = async_with_vars([(ENV_RUSTFS_ENCRYPTED_RANGE_SEEK, None::<&str>)], async {
|
||||
let fixture = build_legacy_ssec_multipart_fixture(key_bytes, &[20_000, 9_000, 5_000]).await;
|
||||
let plan = ReadPlan::build(
|
||||
Some(range(33_900, 33_999)),
|
||||
&fixture.object_info,
|
||||
@@ -3341,12 +3344,31 @@ mod tests {
|
||||
&ssec_headers_from_key(key_bytes),
|
||||
)
|
||||
.await
|
||||
.expect("default-disabled read plan should build");
|
||||
|
||||
assert_eq!(plan.storage_offset, 0);
|
||||
assert_eq!(plan.storage_length, fixture.ciphertext.len() as i64);
|
||||
.expect("default read plan should build");
|
||||
(plan.storage_offset, plan.storage_length, fixture.ciphertext.len())
|
||||
})
|
||||
.await;
|
||||
|
||||
let opt_in_plan = async_with_vars([(ENV_RUSTFS_ENCRYPTED_RANGE_SEEK, Some("true"))], async {
|
||||
let fixture = build_legacy_ssec_multipart_fixture(key_bytes, &[20_000, 9_000, 5_000]).await;
|
||||
let plan = ReadPlan::build(
|
||||
Some(range(33_900, 33_999)),
|
||||
&fixture.object_info,
|
||||
&ObjectOptions::default(),
|
||||
&ssec_headers_from_key(key_bytes),
|
||||
)
|
||||
.await
|
||||
.expect("opt-in read plan should build");
|
||||
(plan.storage_offset, plan.storage_length)
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!((default_plan.0, default_plan.1), opt_in_plan, "unset env must match explicit opt-in");
|
||||
assert_ne!(default_plan.0, 0, "an eligible range read must seek past the leading parts");
|
||||
assert!(
|
||||
(default_plan.1 as usize) < default_plan.2,
|
||||
"an eligible range read must not span the whole ciphertext"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user