From 6bc54b56c0d66f48d5a30b990bca59dcc0cb1ad1 Mon Sep 17 00:00:00 2001 From: Giuseppe Baccini Date: Wed, 22 Nov 2023 16:28:46 +0100 Subject: [PATCH] rgw/sfs: set default obj-lock retention mode for multipart objects When an object is uploaded with multipart, for object-lock enabled buckets, the default rentention mode must be set in the object's attributes (when a retention mode is not explicitely set by the user for the object). Therefore, RGW_ATTR_OBJECT_RETENTION must be set in the attrs of each part being uploaded in the SFSMultipartUploadV2::complete() function. Fixes: https://github.com/aquarist-labs/s3gw/issues/761 Signed-off-by: Giuseppe Baccini --- .../sfs/tests/test-sfs-object-locking.py | 50 +++++++++++++++++++ src/rgw/driver/sfs/multipart.cc | 16 ++++++ 2 files changed, 66 insertions(+) diff --git a/qa/rgw/store/sfs/tests/test-sfs-object-locking.py b/qa/rgw/store/sfs/tests/test-sfs-object-locking.py index 7b2b6b0049845..970734db2b663 100644 --- a/qa/rgw/store/sfs/tests/test-sfs-object-locking.py +++ b/qa/rgw/store/sfs/tests/test-sfs-object-locking.py @@ -33,6 +33,7 @@ class ObjectLockingTests(unittest.TestCase): BUCKET_NAME_3 = "bobjlockenabled3" BUCKET_NAME_4 = "bobjlockenabled4" BUCKET_NAME_5 = "bobjlockenabled5" + BUCKET_NAME_6 = "bobjlockenabled6" ObjVersions = {} @@ -462,6 +463,55 @@ def test_object_locking_legal_hold(self): self.assertTrue(response["ResponseMetadata"]["HTTPStatusCode"] == 204) + def test_multipart_upload_has_default_retention(self): + self.ensure_bucket(ObjectLockingTests.BUCKET_NAME_6, True) + + self.s3_client.put_object_lock_configuration( + Bucket=ObjectLockingTests.BUCKET_NAME_6, + ObjectLockConfiguration={ + "ObjectLockEnabled": "Enabled", + "Rule": {"DefaultRetention": {"Mode": "COMPLIANCE", "Years": 7}}, + }, + ) + + res = self.s3_client.create_multipart_upload( + Bucket=ObjectLockingTests.BUCKET_NAME_6, Key="key.1" + ) + + upload_id = res["UploadId"] + parts_lst = [] + res = self.s3_client.upload_part( + Body="data", + Bucket=ObjectLockingTests.BUCKET_NAME_6, + Key="key.1", + UploadId=upload_id, + PartNumber=1, + ) + parts_lst.append({"ETag": res["ETag"], "PartNumber": 1}) + self.s3_client.complete_multipart_upload( + Bucket=ObjectLockingTests.BUCKET_NAME_6, + Key="key.1", + UploadId=upload_id, + MultipartUpload={"Parts": parts_lst}, + ) + + response = self.s3_client.list_object_versions( + Bucket=ObjectLockingTests.BUCKET_NAME_6, Prefix="key.1" + ) + + for version in response["Versions"]: + if version["Key"] == "key.1" and version["IsLatest"] == True: + self.ObjVersions["key.1.6"] = version["VersionId"] + print(self.ObjVersions["key.1.6"]) + + response = self.s3_client.get_object_retention( + Bucket=ObjectLockingTests.BUCKET_NAME_6, + Key="key.1", + VersionId=self.ObjVersions["key.1.6"], + ) + + self.check_object_retention(response, "COMPLIANCE", 7, "Years") + if __name__ == "__main__": if len(sys.argv) == 2: diff --git a/src/rgw/driver/sfs/multipart.cc b/src/rgw/driver/sfs/multipart.cc index c6a9f396943ce..f706d5b83c086 100644 --- a/src/rgw/driver/sfs/multipart.cc +++ b/src/rgw/driver/sfs/multipart.cc @@ -470,6 +470,22 @@ int SFSMultipartUploadV2::complete( return -ERR_INTERNAL_ERROR; } + // for object-locking enabled buckets, set the bucket's object-locking + // profile when not defined on the MP part + if (bucketref->get_info().obj_lock_enabled() && + bucketref->get_info().obj_lock.has_rule()) { + auto iter = mp->attrs.find(RGW_ATTR_OBJECT_RETENTION); + if (iter == mp->attrs.end()) { + ceph::real_time lock_until_date = + bucketref->get_info().obj_lock.get_lock_until_date( + ceph::real_clock::now() + ); + std::string mode = bucketref->get_info().obj_lock.get_mode(); + RGWObjectRetention obj_retention(mode, lock_until_date); + encode(obj_retention, mp->attrs[RGW_ATTR_OBJECT_RETENTION]); + } + } + // Server-side encryption: The decryptor needs a manifest to // identify encrypted chunks. Each MP part corresponds to a chunk. if (mp->attrs.find(RGW_ATTR_CRYPT_MODE) != mp->attrs.end()) {