This is an automated email from the ASF dual-hosted git repository.
smengcl pushed a commit to branch HDDS-13323-sts
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-13323-sts by this push:
new ef10673605c HDDS-15771. [STS] Add s3Action for expected bucket owner
condition checks (#10688)
ef10673605c is described below
commit ef10673605cb2598ea7e9b592b1703f84bf374c3
Author: fmorg-git <[email protected]>
AuthorDate: Wed Jul 8 16:28:30 2026 -0700
HDDS-15771. [STS] Add s3Action for expected bucket owner condition checks
(#10688)
Co-authored-by: Fabian Morgan <[email protected]>
---
.../hadoop/ozone/s3/endpoint/ObjectEndpoint.java | 6 +-
.../TestS3ActionOverrideForOwnerVerification.java | 149 +++++++++++++++++++++
2 files changed, 153 insertions(+), 2 deletions(-)
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
index 9ab04c8502e..e1d03ef3a0d 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
@@ -848,7 +848,8 @@ private Response createMultipartKey(OzoneVolume volume,
OzoneBucket ozoneBucket,
String sourceBucket = result.getLeft();
String sourceKey = result.getRight();
if (S3Owner.hasBucketOwnershipVerificationConditions(getHeaders())) {
- String sourceBucketOwner = volume.getBucket(sourceBucket).getOwner();
+ final String sourceBucketOwner = runWithS3ActionString(
+ "GetObject", () -> volume.getBucket(sourceBucket).getOwner());
S3Owner.verifyBucketOwnerConditionOnCopyOperation(getHeaders(),
sourceBucket, sourceBucketOwner, bucketName,
ozoneBucket.getOwner());
}
@@ -1061,7 +1062,8 @@ private CopyObjectResponse copyObject(OzoneVolume volume,
final MessageDigest md5Digest = getMD5DigestInstance();
if (S3Owner.hasBucketOwnershipVerificationConditions(getHeaders())) {
- String sourceBucketOwner = volume.getBucket(sourceBucket).getOwner();
+ final String sourceBucketOwner = runWithS3ActionString(
+ "GetObject", () -> volume.getBucket(sourceBucket).getOwner());
// The destBucket owner has already been checked in the caller method
S3Owner.verifyBucketOwnerConditionOnCopyOperation(getHeaders(),
sourceBucket, sourceBucketOwner, null, null);
}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3ActionOverrideForOwnerVerification.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3ActionOverrideForOwnerVerification.java
new file mode 100644
index 00000000000..0cb57755670
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3ActionOverrideForOwnerVerification.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.s3.endpoint;
+
+import static org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.put;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.COPY_SOURCE_HEADER;
+import static
org.apache.hadoop.ozone.s3.util.S3Consts.EXPECTED_BUCKET_OWNER_HEADER;
+import static
org.apache.hadoop.ozone.s3.util.S3Consts.EXPECTED_SOURCE_BUCKET_OWNER_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.UNSIGNED_PAYLOAD;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.core.HttpHeaders;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
+import org.apache.hadoop.ozone.om.protocol.S3Auth;
+import org.apache.hadoop.ozone.s3.signature.SignatureInfo;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies bucket-owner-condition verification (source bucket owner lookup)
runs under the correct IAM S3 action
+ * string for copy-style operations.
+ */
+public class TestS3ActionOverrideForOwnerVerification {
+
+ private static final String DEST_BUCKET = "dest-bucket";
+ private static final String DEST_KEY = "dest-key";
+ private static final String SOURCE_BUCKET = "source-bucket";
+ private static final String SOURCE_KEY = "source-key";
+ private static final String SOURCE_OWNER = "source-owner";
+ private static final String DEST_OWNER = "dest-owner";
+
+ @Test
+ public void
testUploadPartCopyUsesGetObjectActionForSourceBucketOwnerLookup() throws
Exception {
+ final AtomicReference<String> actionAtSourceBucketOwnerLookup = new
AtomicReference<>();
+ final ObjectEndpoint endpoint =
newEndpoint(actionAtSourceBucketOwnerLookup);
+
+ // Trigger UploadPartCopy (MPU part upload with copy header).
+ final String uploadId = "upload-id";
+ assertThrows(Exception.class, () -> put(endpoint, DEST_BUCKET, DEST_KEY,
1, uploadId, ""));
+
+ assertEquals("GetObject", actionAtSourceBucketOwnerLookup.get());
+ }
+
+ @Test
+ public void testCopyObjectUsesGetObjectActionForSourceBucketOwnerLookup()
throws Exception {
+ final AtomicReference<String> actionAtSourceBucketOwnerLookup = new
AtomicReference<>();
+ final ObjectEndpoint endpoint =
newEndpoint(actionAtSourceBucketOwnerLookup);
+
+ // Trigger CopyObject (PUT with copy header, no upload ID).
+ assertThrows(Exception.class, () -> put(endpoint, DEST_BUCKET, DEST_KEY,
""));
+
+ assertEquals("GetObject", actionAtSourceBucketOwnerLookup.get());
+ }
+
+ private static ObjectEndpoint newEndpoint(AtomicReference<String>
actionAtSourceBucketOwnerLookup) throws Exception {
+ final HttpHeaders headers = mock(HttpHeaders.class);
+
when(headers.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn(UNSIGNED_PAYLOAD);
+ when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn("STANDARD");
+ when(headers.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(SOURCE_BUCKET
+ "/" + SOURCE_KEY);
+
when(headers.getHeaderString(EXPECTED_SOURCE_BUCKET_OWNER_HEADER)).thenReturn(SOURCE_OWNER);
+
when(headers.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(DEST_OWNER);
+
+ final SignatureInfo signatureInfo = mock(SignatureInfo.class);
+ when(signatureInfo.isSignPayload()).thenReturn(true);
+ when(signatureInfo.getStringToSign()).thenReturn("string-to-sign");
+ when(signatureInfo.getSignature()).thenReturn("signature");
+ when(signatureInfo.getAwsAccessId()).thenReturn("access-id");
+ when(signatureInfo.getSessionToken()).thenReturn(null);
+
+ final OzoneClient client = mock(OzoneClient.class);
+ final ObjectStore objectStore = mock(ObjectStore.class);
+ final ClientProtocol clientProtocol = mock(ClientProtocol.class);
+ final OzoneVolume volume = mock(OzoneVolume.class);
+ final OzoneBucket destBucket = mock(OzoneBucket.class);
+ final OzoneBucket sourceBucket = mock(OzoneBucket.class);
+
+ final AtomicReference<S3Auth> s3AuthRef = new AtomicReference<>();
+ doAnswer(invocationOnMock -> {
+ s3AuthRef.set(invocationOnMock.getArgument(0));
+ return null;
+ }).when(clientProtocol).setThreadLocalS3Auth(any(S3Auth.class));
+ doNothing().when(clientProtocol).setIsS3Request(true);
+
+ when(client.getObjectStore()).thenReturn(objectStore);
+ when(client.getProxy()).thenReturn(clientProtocol);
+ when(objectStore.getClientProxy()).thenReturn(clientProtocol);
+ when(objectStore.getS3Volume()).thenReturn(volume);
+
+ when(volume.getName()).thenReturn("s3Volume");
+
+ when(destBucket.getName()).thenReturn(DEST_BUCKET);
+ when(destBucket.getOwner()).thenReturn(DEST_OWNER);
+ when(volume.getBucket(DEST_BUCKET)).thenReturn(destBucket);
+
+ when(sourceBucket.getOwner()).thenAnswer(invocationOnMock -> {
+ final S3Auth s3Auth = s3AuthRef.get();
+ assertNotNull(s3Auth, "S3Auth must be initialized before owner lookup");
+ actionAtSourceBucketOwnerLookup.set(s3Auth.getS3Action());
+ return SOURCE_OWNER;
+ });
+ when(volume.getBucket(SOURCE_BUCKET)).thenReturn(sourceBucket);
+
+ // Stop the request after the source-bucket owner check, without needing
to set up full copy behavior.
+ when(clientProtocol.getKeyDetails(anyString(), eq(SOURCE_BUCKET),
eq(SOURCE_KEY)))
+ .thenThrow(new OMException("stop-after-owner-check",
ResultCodes.KEY_NOT_FOUND));
+
+ final OzoneConfiguration conf = new OzoneConfiguration();
+ return EndpointBuilder.newObjectEndpointBuilder()
+ .setClient(client)
+ .setConfig(conf)
+ .setHeaders(headers)
+ .setSignatureInfo(signatureInfo)
+ .build();
+ }
+}
+
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]