This is an automated email from the ASF dual-hosted git repository.

ChenSammi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new f71c648e710 HDDS-14898. Fix Latent S3 API Issue having No Acl Check 
for ListParts (#9976)
f71c648e710 is described below

commit f71c648e7107526e8b22d653515d7ba4c2fed6c4
Author: fmorg-git <[email protected]>
AuthorDate: Wed May 6 22:39:49 2026 -0700

    HDDS-14898. Fix Latent S3 API Issue having No Acl Check for ListParts 
(#9976)
---
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |  34 ++--
 .../ozone/om/TestOzoneManagerListPartsAcls.java    | 196 +++++++++++++++++++++
 2 files changed, 217 insertions(+), 13 deletions(-)

diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index f408d4a3be7..d258324b3bb 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -3941,24 +3941,32 @@ public OmMultipartUploadListParts listParts(final 
String volumeName,
       final String bucketName, String keyName, String uploadID,
       int partNumberMarker, int maxParts)  throws IOException {
 
-    ResolvedBucket bucket = resolveBucketLink(Pair.of(volumeName, bucketName));
-
-    Map<String, String> auditMap = bucket.audit();
-    auditMap.put(OzoneConsts.KEY, keyName);
-    auditMap.put(OzoneConsts.UPLOAD_ID, uploadID);
-    auditMap.put(OzoneConsts.PART_NUMBER_MARKER,
-        Integer.toString(partNumberMarker));
-    auditMap.put(OzoneConsts.MAX_PARTS, Integer.toString(maxParts));
+    final ResolvedBucket bucket = resolveBucketLink(Pair.of(volumeName, 
bucketName));
+    final String realVolumeName = bucket.realVolume();
+    final String realBucketName = bucket.realBucket();
 
-    metrics.incNumListMultipartUploadParts();
+    final Map<String, String> auditMap = bucket.audit();
     try {
-      OmMultipartUploadListParts omMultipartUploadListParts =
-          keyManager.listParts(bucket.realVolume(), bucket.realBucket(),
-              keyName, uploadID, partNumberMarker, maxParts);
+      auditMap.put(OzoneConsts.KEY, keyName);
+      auditMap.put(OzoneConsts.UPLOAD_ID, uploadID);
+      auditMap.put(OzoneConsts.PART_NUMBER_MARKER,
+          Integer.toString(partNumberMarker));
+      auditMap.put(OzoneConsts.MAX_PARTS, Integer.toString(maxParts));
+
+      if (getAclsEnabled()) {
+        omMetadataReader.checkAcls(
+            ResourceType.BUCKET, StoreType.OZONE, ACLType.READ, 
realVolumeName, realBucketName, null);
+        omMetadataReader.checkAcls(
+            ResourceType.KEY, StoreType.OZONE, ACLType.READ, realVolumeName, 
realBucketName, keyName);
+      }
+
+      metrics.incNumListMultipartUploadParts();
+      final OmMultipartUploadListParts omMultipartUploadListParts = 
keyManager.listParts(
+          realVolumeName, realBucketName, keyName, uploadID, partNumberMarker, 
maxParts);
       AUDIT.logReadSuccess(buildAuditMessageForSuccess(OMAction
           .LIST_MULTIPART_UPLOAD_PARTS, auditMap));
       return omMultipartUploadListParts;
-    } catch (IOException ex) {
+    } catch (Exception ex) {
       metrics.incNumListMultipartUploadPartFails();
       AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction
           .LIST_MULTIPART_UPLOAD_PARTS, auditMap, ex));
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerListPartsAcls.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerListPartsAcls.java
new file mode 100644
index 00000000000..537133eae1e
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerListPartsAcls.java
@@ -0,0 +1,196 @@
+/*
+ * 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.om;
+
+import static 
org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ;
+import static 
org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.BUCKET;
+import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.KEY;
+import static org.apache.hadoop.ozone.security.acl.OzoneObj.StoreType.OZONE;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.scm.HddsWhiteboxTestUtils;
+import org.apache.hadoop.hdds.server.ServerUtils;
+import org.apache.hadoop.ozone.audit.AuditMessage;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadListParts;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Authentication;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.InOrder;
+
+
+/**
+ * Unit tests for ACL checks on {@link OzoneManager#listParts}.
+ */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class TestOzoneManagerListPartsAcls {
+
+  private OmTestManagers omTestManagers;
+  private OzoneManager om;
+
+  private OzoneManager omSpy;
+  private OmMetadataReader omMetadataReader;
+  private KeyManager keyManager;
+  private OMMetrics metrics;
+
+  private static final String REQUESTED_VOLUME = "requestedVolume";
+  private static final String REQUESTED_BUCKET = "requestedBucket";
+  private static final String REAL_VOLUME = "realVolume";
+  private static final String REAL_BUCKET = "realBucket";
+  private static final String KEY_NAME = "object/key";
+  private static final String UPLOAD_ID = "uploadId";
+
+  @BeforeAll
+  void setup(@TempDir File folder) throws Exception {
+    final OzoneConfiguration conf = new OzoneConfiguration();
+    ServerUtils.setOzoneMetaDirPath(conf, folder.toString());
+    omTestManagers = new OmTestManagers(conf);
+    om = omTestManagers.getOzoneManager();
+  }
+
+  @AfterAll
+  void cleanup() {
+    if (omTestManagers != null) {
+      omTestManagers.stop();
+    }
+  }
+
+  @BeforeEach
+  void init() throws Exception {
+    omSpy = spy(om);
+    omMetadataReader = mock(OmMetadataReader.class);
+    keyManager = mock(KeyManager.class);
+    metrics = mock(OMMetrics.class);
+
+    HddsWhiteboxTestUtils.setInternalState(omSpy, "omMetadataReader", 
omMetadataReader);
+    HddsWhiteboxTestUtils.setInternalState(omSpy, "keyManager", keyManager);
+    HddsWhiteboxTestUtils.setInternalState(omSpy, "metrics", metrics);
+
+    doReturn(new ResolvedBucket(REQUESTED_VOLUME, REQUESTED_BUCKET, 
REAL_VOLUME, REAL_BUCKET, "owner", null))
+        .when(omSpy).resolveBucketLink(Pair.of(REQUESTED_VOLUME, 
REQUESTED_BUCKET));
+
+    final AuditMessage mockAuditMessage = mock(AuditMessage.class);
+    when(mockAuditMessage.getOp()).thenReturn("LIST_MULTIPART_UPLOAD_PARTS");
+    doReturn(mockAuditMessage).when(omSpy).buildAuditMessageForSuccess(any(), 
anyMap());
+    doReturn(mockAuditMessage).when(omSpy).buildAuditMessageForFailure(any(), 
anyMap(), any(Throwable.class));
+
+    when(keyManager.listParts(anyString(), anyString(), anyString(), 
anyString(), anyInt(), anyInt()))
+        .thenReturn(mock(OmMultipartUploadListParts.class));
+  }
+
+  @AfterEach
+  void tearDown() {
+    OzoneManager.setS3Auth(null);
+  }
+
+  @Test
+  void testSkipsAclChecksWhenAclsAreDisabled() throws Exception {
+    setupS3Request();
+    when(omSpy.getAclsEnabled()).thenReturn(false);
+
+    omSpy.listParts(REQUESTED_VOLUME, REQUESTED_BUCKET, KEY_NAME, UPLOAD_ID, 
0, 10);
+
+    verify(omMetadataReader, never()).checkAcls(any(), any(), any(), any(), 
any(), any());
+    verify(keyManager).listParts(
+        eq(REAL_VOLUME), eq(REAL_BUCKET), eq(KEY_NAME), eq(UPLOAD_ID), eq(0), 
eq(10));
+    verify(metrics).incNumListMultipartUploadParts();
+    verify(metrics, never()).incNumListMultipartUploadPartFails();
+  }
+
+  @Test
+  void testAclsEnabledChecksBucketReadThenKeyReadUsingResolvedNames() throws 
Exception {
+    setupS3Request();
+    when(omSpy.getAclsEnabled()).thenReturn(true);
+
+    omSpy.listParts(REQUESTED_VOLUME, REQUESTED_BUCKET, KEY_NAME, UPLOAD_ID, 
0, 10);
+
+    final InOrder inOrder = inOrder(omMetadataReader);
+    inOrder.verify(omMetadataReader).checkAcls(BUCKET, OZONE, READ, 
REAL_VOLUME, REAL_BUCKET, null);
+    inOrder.verify(omMetadataReader).checkAcls(KEY, OZONE, READ, REAL_VOLUME, 
REAL_BUCKET, KEY_NAME);
+    verify(keyManager).listParts(
+        eq(REAL_VOLUME), eq(REAL_BUCKET), eq(KEY_NAME), eq(UPLOAD_ID), eq(0), 
eq(10));
+    verify(metrics).incNumListMultipartUploadParts();
+    verify(metrics, never()).incNumListMultipartUploadPartFails();
+  }
+
+  @Test
+  void 
testReadAclAccessDeniedSkipsKeyManagerAndKeyReadAclChecksAndRecordsFailure() 
throws Exception {
+    setupS3Request();
+    when(omSpy.getAclsEnabled()).thenReturn(true);
+
+    doThrow(new OMException("denied", 
OMException.ResultCodes.PERMISSION_DENIED))
+        .when(omMetadataReader).checkAcls(BUCKET, OZONE, READ, REAL_VOLUME, 
REAL_BUCKET, null);
+
+    assertThrows(
+        OMException.class, () -> omSpy.listParts(
+            REQUESTED_VOLUME, REQUESTED_BUCKET, KEY_NAME, UPLOAD_ID, 0, 10));
+
+    verify(keyManager, never()).listParts(
+        anyString(), anyString(), anyString(), anyString(), anyInt(), 
anyInt());
+    verify(metrics, never()).incNumListMultipartUploadParts();
+    verify(metrics).incNumListMultipartUploadPartFails();
+    verify(omSpy).buildAuditMessageForFailure(any(), anyMap(), 
any(Throwable.class));
+    verify(omMetadataReader, never()).checkAcls(KEY, OZONE, READ, REAL_VOLUME, 
REAL_BUCKET, KEY_NAME);
+  }
+
+  @Test
+  void testKeyReadAclAccessDeniedSkipsKeyManagerAndRecordsFailure() throws 
Exception {
+    setupS3Request();
+    when(omSpy.getAclsEnabled()).thenReturn(true);
+
+    doNothing().when(omMetadataReader).checkAcls(BUCKET, OZONE, READ, 
REAL_VOLUME, REAL_BUCKET, null);
+    doThrow(new OMException("denied", 
OMException.ResultCodes.PERMISSION_DENIED))
+        .when(omMetadataReader).checkAcls(KEY, OZONE, READ, REAL_VOLUME, 
REAL_BUCKET, KEY_NAME);
+
+    assertThrows(
+        OMException.class, () -> omSpy.listParts(
+            REQUESTED_VOLUME, REQUESTED_BUCKET, KEY_NAME, UPLOAD_ID, 0, 10));
+
+    verify(keyManager, never()).listParts(
+        anyString(), anyString(), anyString(), anyString(), anyInt(), 
anyInt());
+    verify(metrics, never()).incNumListMultipartUploadParts();
+    verify(metrics).incNumListMultipartUploadPartFails();
+    verify(omSpy).buildAuditMessageForFailure(any(), anyMap(), 
any(Throwable.class));
+  }
+
+  private void setupS3Request() {
+    
OzoneManager.setS3Auth(S3Authentication.newBuilder().setAccessId("AKIAJWFJK62WUTKNFJJA").build());
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to