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]

Reply via email to