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

weichiu 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 51deb3c9004 HDDS-13826. Move ACL check in OMKeySetTimesRequest (#9192)
51deb3c9004 is described below

commit 51deb3c9004c0c6d3f46a0efd7fadbc992f25bb1
Author: Sergey Soldatov <[email protected]>
AuthorDate: Wed Nov 5 12:29:37 2025 -0800

    HDDS-13826. Move ACL check in OMKeySetTimesRequest (#9192)
---
 .../om/TestOMHALeaderSpecificACLEnforcement.java   | 115 ++++++++++++++++++++-
 .../ozone/om/request/key/OMKeySetTimesRequest.java |  27 +++--
 .../request/key/OMKeySetTimesRequestWithFSO.java   |   9 +-
 3 files changed, 134 insertions(+), 17 deletions(-)

diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMHALeaderSpecificACLEnforcement.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMHALeaderSpecificACLEnforcement.java
index 4e49a92a9c9..2c50aa9ddce 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMHALeaderSpecificACLEnforcement.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMHALeaderSpecificACLEnforcement.java
@@ -17,11 +17,13 @@
 
 package org.apache.hadoop.ozone.om;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS;
 import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED;
 import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PERMISSION_DENIED;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -31,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.TimeoutException;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.utils.IOUtils;
@@ -41,8 +44,10 @@
 import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.client.OzoneClientFactory;
+import org.apache.hadoop.ozone.client.OzoneKey;
 import org.apache.hadoop.ozone.client.OzoneVolume;
 import org.apache.hadoop.ozone.client.VolumeArgs;
+import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
 import org.apache.hadoop.ozone.security.acl.OzoneNativeAuthorizer;
@@ -50,6 +55,7 @@
 import org.apache.ozone.test.GenericTestUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
 
@@ -76,16 +82,20 @@ public class TestOMHALeaderSpecificACLEnforcement {
   private MiniOzoneHAClusterImpl cluster;
   private OzoneClient client;
   private UserGroupInformation testUserUgi;
+  private UserGroupInformation adminUserUgi;
+  private OzoneManager theLeaderOM;
 
   @BeforeAll
   public void init() throws Exception {
     // Create test user
     testUserUgi = UserGroupInformation.createUserForTesting(TEST_USER, new 
String[]{"testgroup"});
+    adminUserUgi = UserGroupInformation.getCurrentUser();
     
     // Set up and start the cluster
     setupCluster();
     
     // Create admin volume that will be used for bucket permission testing
+    theLeaderOM = cluster.getOMLeader();
     createAdminVolume();
   }
 
@@ -97,6 +107,22 @@ public void shutdown() {
     }
   }
 
+  @BeforeEach
+  public void restoreLeadership() throws IOException, InterruptedException, 
TimeoutException {
+    OzoneManager currentLeader = cluster.getOMLeader();
+    if (!currentLeader.getOMNodeId().equals(theLeaderOM.getOMNodeId())) {
+      currentLeader.transferLeadership(theLeaderOM.getOMNodeId());
+      GenericTestUtils.waitFor(() -> {
+        try {
+          OzoneManager currentLeaderCheck = cluster.getOMLeader();
+          return 
!currentLeaderCheck.getOMNodeId().equals(currentLeader.getOMNodeId());
+        } catch (Exception e) {
+          return false;
+        }
+      }, 1000, 30000);
+    }
+  }
+
   /**
    * Main test method that validates leader-specific ACL enforcement in OM HA.
    * 1. Creates a mini cluster with OM HA
@@ -165,7 +191,7 @@ private OzoneConfiguration createBaseConfiguration() throws 
IOException {
     conf.setBoolean(OZONE_ACL_ENABLED, true);
     
     // Set current user as initial admin (needed for cluster setup)
-    String currentUser = 
UserGroupInformation.getCurrentUser().getShortUserName();
+    String currentUser = adminUserUgi.getShortUserName();
     conf.set(OZONE_ADMINISTRATORS, currentUser);
     
     return conf;
@@ -181,7 +207,7 @@ private void createAdminVolume() throws Exception {
     
     // Create volume as admin user
     VolumeArgs volumeArgs = VolumeArgs.newBuilder()
-        .setOwner(UserGroupInformation.getCurrentUser().getShortUserName())
+        .setOwner(adminUserUgi.getShortUserName())
         .build();
     
     adminObjectStore.createVolume(ADMIN_VOLUME, volumeArgs);
@@ -260,7 +286,90 @@ private void testVolumeAndBucketCreationAsUser(boolean 
shouldSucceed) throws Exc
       }
     } finally {
       // Reset to original user
-      UserGroupInformation.setLoginUser(UserGroupInformation.getCurrentUser());
+      UserGroupInformation.setLoginUser(adminUserUgi);
+    }
+  }
+
+  /**
+   * Tests that setTimes ACL check is enforced in preExecute and is 
leader-specific.
+   * 1. Creates a key with admin user
+   * 2. Adds test user as admin on the current leader
+   * 3. Verifies that test user (as admin) can setTimes on key owned by 
someone else
+   * 4. Transfers leadership to another node
+   * 5. Verifies that setTimes fails with PERMISSION_DENIED when test user is 
no longer admin
+   */
+  @Test
+  public void testKeySetTimesAclEnforcementAfterLeadershipChange() throws 
Exception {
+    // Step 1: Create a volume, bucket, and key as the admin user
+    ObjectStore adminObjectStore = client.getObjectStore();
+    String keyTestVolume = "keyvol-" +
+        RandomStringUtils.secure().nextAlphabetic(5).toLowerCase(Locale.ROOT);
+    String keyTestBucket = "keybucket-" +
+        RandomStringUtils.secure().nextAlphabetic(5).toLowerCase(Locale.ROOT);
+    String keyName = "testkey-" +
+        RandomStringUtils.secure().nextAlphabetic(5).toLowerCase(Locale.ROOT);
+
+    String adminUser = adminUserUgi.getShortUserName();
+    VolumeArgs volumeArgs = VolumeArgs.newBuilder()
+        .setOwner(adminUser)
+        .build();
+    adminObjectStore.createVolume(keyTestVolume, volumeArgs);
+    OzoneVolume adminVolume = adminObjectStore.getVolume(keyTestVolume);
+
+    BucketArgs bucketArgs = BucketArgs.newBuilder().build();
+    adminVolume.createBucket(keyTestBucket, bucketArgs);
+    OzoneBucket adminBucket = adminVolume.getBucket(keyTestBucket);
+
+    // Create a key as admin (so test user is NOT the owner)
+    try (OzoneOutputStream out = adminBucket.createKey(keyName, 0)) {
+      out.write("test data".getBytes(UTF_8));
+    }
+
+    OzoneKey key = adminBucket.getKey(keyName);
+    assertNotNull(key, "Key should be created successfully");
+    long originalMtime = key.getModificationTime().toEpochMilli();
+
+    // Step 2: Get the current leader and add test user as admin
+    OzoneManager currentLeader = cluster.getOMLeader();
+    String leaderNodeId = currentLeader.getOMNodeId();
+    addAdminToSpecificOM(currentLeader, TEST_USER);
+
+    // Verify admin was added
+    assertTrue(currentLeader.getOmAdminUsernames().contains(TEST_USER),
+        "Test user should be admin on leader OM");
+
+    // Switch to test user and try setTimes as admin (should succeed)
+    UserGroupInformation.setLoginUser(testUserUgi);
+    try (OzoneClient userClient = 
OzoneClientFactory.getRpcClient(OM_SERVICE_ID, cluster.getConf())) {
+      ObjectStore userObjectStore = userClient.getObjectStore();
+      OzoneVolume userVolume = userObjectStore.getVolume(keyTestVolume);
+      OzoneBucket userBucket = userVolume.getBucket(keyTestBucket);
+
+      long newMtime = System.currentTimeMillis();
+      userBucket.setTimes(keyName, newMtime, -1);
+
+      // Verify the modification time was updated
+      key = userBucket.getKey(keyName);
+      assertEquals(newMtime, key.getModificationTime().toEpochMilli(),
+          "Modification time should be updated by admin user");
+      assertNotEquals(originalMtime, key.getModificationTime().toEpochMilli(),
+          "Modification time should have changed");
+
+      OzoneManager newLeader = transferLeadershipToAnotherNode(currentLeader);
+      assertNotEquals(leaderNodeId, newLeader.getOMNodeId(),
+          "Leadership should have transferred to a different node");
+      assertFalse(newLeader.getOmAdminUsernames().contains(TEST_USER),
+          "Test user should NOT be admin on new leader OM");
+
+      long anotherMtime = System.currentTimeMillis() + 10000;
+      OMException exception = assertThrows(OMException.class, () -> {
+        userBucket.setTimes(keyName, anotherMtime, -1);
+      }, "setTimes should fail for non-admin user on new leader");
+      assertEquals(PERMISSION_DENIED, exception.getResult(),
+          "Should get PERMISSION_DENIED when ACL check fails in preExecute");
+    } finally {
+      // Reset to original user
+      UserGroupInformation.setLoginUser(adminUserUgi);
     }
   }
 
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequest.java
index 353a1775702..e137847eb39 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequest.java
@@ -78,6 +78,27 @@ public OMRequest preExecute(OzoneManager ozoneManager) 
throws IOException {
 
     OzoneManagerProtocolProtos.KeyArgs newKeyArgs = 
resolveBucketLink(ozoneManager, keyArgs);
 
+    // ACL check during preExecute
+    if (ozoneManager.getAclsEnabled()) {
+      try {
+        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
+            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE_ACL,
+            newKeyArgs.getVolumeName(), newKeyArgs.getBucketName(), 
newKeyArgs.getKeyName());
+      } catch (IOException ex) {
+        // Ensure audit log captures preExecute failures
+        Map<String, String> auditMap = new LinkedHashMap<>();
+        auditMap.put(OzoneConsts.VOLUME, newKeyArgs.getVolumeName());
+        auditMap.put(OzoneConsts.BUCKET, newKeyArgs.getBucketName());
+        auditMap.put(OzoneConsts.KEY, newKeyArgs.getKeyName());
+        auditMap.put(OzoneConsts.MODIFICATION_TIME,
+            String.valueOf(getModificationTime()));
+        markForAudit(ozoneManager.getAuditLogger(),
+            buildAuditMessage(OMAction.SET_TIMES, auditMap, ex,
+                getOmRequest().getUserInfo()));
+        throw ex;
+      }
+    }
+
     return request.toBuilder()
         .setSetTimesRequest(
             setTimesRequest.toBuilder()
@@ -194,12 +215,6 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
       bucket = getBucketName();
       key = getKeyName();
 
-      // check Acl
-      if (ozoneManager.getAclsEnabled()) {
-        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
-            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE_ACL,
-            volume, bucket, key);
-      }
       mergeOmLockDetails(
           omMetadataManager.getLock().acquireWriteLock(BUCKET_LOCK, volume,
               bucket));
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequestWithFSO.java
index 009bcd1662c..6cc68b6f718 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeySetTimesRequestWithFSO.java
@@ -42,8 +42,6 @@
 import org.apache.hadoop.ozone.om.response.key.OMKeySetTimesResponseWithFSO;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
-import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
-import org.apache.hadoop.ozone.security.acl.OzoneObj;
 
 /**
  * Handle set times request for bucket for prefix layout.
@@ -53,6 +51,7 @@ public class OMKeySetTimesRequestWithFSO extends 
OMKeySetTimesRequest {
   @Override
   public OzoneManagerProtocolProtos.OMRequest preExecute(
       OzoneManager ozoneManager) throws IOException {
+    // The parent class handles ACL checks in preExecute, so just call super
     return super.preExecute(ozoneManager);
   }
 
@@ -82,12 +81,6 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       bucket = getBucketName();
       key = getKeyName();
 
-      // check Acl
-      if (ozoneManager.getAclsEnabled()) {
-        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
-            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE_ACL,
-            volume, bucket, key);
-      }
       mergeOmLockDetails(omMetadataManager.getLock()
           .acquireWriteLock(BUCKET_LOCK, volume, bucket));
       lockAcquired = getOmLockDetails().isLockAcquired();


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

Reply via email to