From a9115f729e330abc51e9c1cdd929283b2d5df9cf Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Mon, 6 Jul 2026 14:05:39 +0800 Subject: [PATCH] fix: block force delete on object lock buckets (#4298) --- crates/ecstore/src/bucket/metadata.rs | 20 +++++++++++++++++++- rustfs/src/app/object_usecase.rs | 13 ++++++++++--- rustfs/src/app/storage_api.rs | 2 -- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/ecstore/src/bucket/metadata.rs b/crates/ecstore/src/bucket/metadata.rs index 3fe4ecec6..effc04dc3 100644 --- a/crates/ecstore/src/bucket/metadata.rs +++ b/crates/ecstore/src/bucket/metadata.rs @@ -423,7 +423,7 @@ impl BucketMetadata { } pub fn object_locking(&self) -> bool { - self.lock_enabled || (self.versioning_config.as_ref().is_some_and(|v| v.enabled())) + self.lock_enabled || self.object_lock_config.as_ref().is_some_and(|v| v.enabled()) } pub fn table_bucket_enabled(&self) -> bool { @@ -1113,6 +1113,24 @@ mod test { assert_eq!(bm.name, new.name); } + #[test] + fn object_locking_requires_lock_metadata_not_plain_versioning() { + use s3s::dto::ObjectLockEnabled; + + let mut bm = BucketMetadata::new("test-bucket"); + bm.versioning_config = Some(VersioningConfiguration { + status: Some(s3s::dto::BucketVersioningStatus::from_static("Enabled")), + ..Default::default() + }); + assert!(!bm.object_locking()); + + bm.object_lock_config = Some(ObjectLockConfiguration { + object_lock_enabled: Some(ObjectLockEnabled::from_static(ObjectLockEnabled::ENABLED)), + ..Default::default() + }); + assert!(bm.object_locking()); + } + #[test] fn parse_all_configs_parses_stored_configs_without_store_dependency() { let mut bm = BucketMetadata::new("test-bucket"); diff --git a/rustfs/src/app/object_usecase.rs b/rustfs/src/app/object_usecase.rs index f3f30ac45..d92b990e9 100644 --- a/rustfs/src/app/object_usecase.rs +++ b/rustfs/src/app/object_usecase.rs @@ -17,6 +17,8 @@ // Performance metrics recording (with zero-copy-metrics integration) use rustfs_io_metrics::buffered_write; +use crate::storage_api::table::get_bucket_metadata; + use super::storage_api::object_usecase::ECStore; use super::storage_api::object_usecase::access::{ PostObjectRequestMarker, authorize_request, has_bypass_governance_header, req_info_mut, @@ -35,7 +37,7 @@ use super::storage_api::object_usecase::bucket::{ metadata_sys, object_lock::{ objectlock::{get_object_legalhold_meta, get_object_retention_meta}, - objectlock_sys::{BucketObjectLockSys, check_object_lock_for_deletion, is_retention_active}, + objectlock_sys::{check_object_lock_for_deletion, is_retention_active}, }, predict_lifecycle_expiration, quota::QuotaOperation, @@ -5053,8 +5055,7 @@ impl DefaultObjectUsecase { }); let force_delete = opts.delete_prefix; - let lock_cfg = BucketObjectLockSys::get(&bucket).await; - if lock_cfg.is_some() && opts.delete_prefix { + if opts.delete_prefix && bucket_object_locking_enabled(&bucket).await { return Err(S3Error::with_message( S3ErrorCode::Custom("force-delete is forbidden on Object Locking enabled buckets".into()), "force-delete is forbidden on Object Locking enabled buckets", @@ -6265,6 +6266,12 @@ fn object_attributes_requested(object_attributes: &[ObjectAttributes], name: &'s }) } +async fn bucket_object_locking_enabled(bucket: &str) -> bool { + get_bucket_metadata(bucket) + .await + .is_ok_and(|metadata| metadata.object_locking()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/rustfs/src/app/storage_api.rs b/rustfs/src/app/storage_api.rs index da00419e0..3e1f7e583 100644 --- a/rustfs/src/app/storage_api.rs +++ b/rustfs/src/app/storage_api.rs @@ -567,8 +567,6 @@ pub(crate) mod bucket { } pub(crate) mod objectlock_sys { - pub(crate) type BucketObjectLockSys = - crate::storage::storage_api::ecstore_bucket::object_lock::objectlock_sys::BucketObjectLockSys; pub(crate) type ObjectLockBlockReason = crate::storage::storage_api::ecstore_bucket::object_lock::objectlock_sys::ObjectLockBlockReason;