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]