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

jojochuang pushed a commit to branch ozone-2.2
in repository https://gitbox.apache.org/repos/asf/ozone.git

commit a42bea1e9cd00f19d99b5f13a81aa023062a7eb4
Author: Wei-Chiu Chuang <[email protected]>
AuthorDate: Fri Aug 14 09:05:55 2026 -0700

    Adding OM tests.
    
    Change-Id: Ie5e6ceef61bd6ffa3e2eed30c7ccb3e9d718592a
    (cherry picked from commit b58885e7bafd71ed837ce757cff447f93c7147cc)
---
 .../ozone/om/TestGetDBUpdatesAuthorization.java    | 137 +++++++++++++++++++++
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |  27 ++++
 2 files changed, 164 insertions(+)

diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestGetDBUpdatesAuthorization.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestGetDBUpdatesAuthorization.java
new file mode 100644
index 00000000000..906597647f2
--- /dev/null
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestGetDBUpdatesAuthorization.java
@@ -0,0 +1,137 @@
+/*
+ * 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.hdds.security.SecurityConfig.OZONE_TEST_AUTHORIZATION_ENABLED;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS_NATIVE;
+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.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.security.PrivilegedExceptionAction;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+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.OzoneVolume;
+import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.DBUpdates;
+import org.apache.hadoop.ozone.om.protocolPB.OmTransportFactory;
+import 
org.apache.hadoop.ozone.om.protocolPB.OzoneManagerProtocolClientSideTranslatorPB;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DBUpdatesRequest;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.ratis.protocol.ClientId;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests authorization of the OzoneManager getDBUpdates RPC (OmClientProtocol 
DBUpdates).
+ * getDBUpdates streams the raw RocksDB delta of the whole OM metadata DB and 
backs
+ * OM->Recon replication, so it is restricted to admins and read-only admins, 
like the
+ * other whole-system reads (listOpenFiles, getQuotaRepairStatus).
+ */
+public class TestGetDBUpdatesAuthorization {
+
+  private static MiniOzoneCluster cluster;
+  private static OzoneConfiguration conf;
+
+  private static final String VOL = "vol1";
+  private static final String BUCKET = "bucket1";
+  private static final String KEY = "key1";
+  private static final String RECON_PRINCIPAL = "reconsvc";
+
+  @BeforeAll
+  static void init() throws Exception {
+    conf = new OzoneConfiguration();
+    conf.setBoolean(OZONE_ACL_ENABLED, true);
+    conf.set(OZONE_ACL_AUTHORIZER_CLASS, OZONE_ACL_AUTHORIZER_CLASS_NATIVE);
+    conf.set(OZONE_ADMINISTRATORS, "admin");
+    conf.set(OZONE_READONLY_ADMINISTRATORS, RECON_PRINCIPAL);
+    // Make admin authorization effective without a KDC so the gate actually 
runs.
+    conf.setBoolean(OZONE_TEST_AUTHORIZATION_ENABLED, true);
+    cluster = MiniOzoneCluster.newBuilder(conf).setNumDatanodes(3).build();
+    cluster.waitForClusterToBeReady();
+
+    UserGroupInformation.createUserForTesting("admin", new String[] {"admins"})
+        .doAs((PrivilegedExceptionAction<Void>) () -> {
+          try (OzoneClient c = OzoneClientFactory.getRpcClient(conf)) {
+            c.getObjectStore().createVolume(VOL);
+            OzoneVolume vol = c.getObjectStore().getVolume(VOL);
+            vol.createBucket(BUCKET);
+            OzoneBucket b = vol.getBucket(BUCKET);
+            byte[] data = 
"hello".getBytes(java.nio.charset.StandardCharsets.UTF_8);
+            try (OzoneOutputStream os = b.createKey(KEY, data.length)) {
+              os.write(data);
+            }
+          }
+          return null;
+        });
+  }
+
+  @AfterAll
+  static void shutdown() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  private static DBUpdates getDBUpdates(UserGroupInformation user) throws 
Exception {
+    return user.doAs((PrivilegedExceptionAction<DBUpdates>) () -> {
+      OzoneManagerProtocolClientSideTranslatorPB omClient =
+          new OzoneManagerProtocolClientSideTranslatorPB(
+              OmTransportFactory.create(conf, user, null),
+              ClientId.randomId().toString());
+      DBUpdatesRequest req = DBUpdatesRequest.newBuilder()
+          .setSequenceNumber(0)
+          .build();
+      return omClient.getDBUpdates(req);
+    });
+  }
+
+  @Test
+  void nonAdminIsDenied() {
+    UserGroupInformation nonAdmin =
+        UserGroupInformation.createUserForTesting("nonadmin", new String[] 
{"users"});
+    OMException ex = assertThrows(OMException.class, () -> 
getDBUpdates(nonAdmin));
+    assertEquals(OMException.ResultCodes.PERMISSION_DENIED, ex.getResult());
+  }
+
+  @Test
+  void adminIsAllowed() throws Exception {
+    UserGroupInformation admin =
+        UserGroupInformation.createUserForTesting("admin", new String[] 
{"admins"});
+    DBUpdates updates = getDBUpdates(admin);
+    assertFalse(updates.getData().isEmpty());
+  }
+
+  @Test
+  void readOnlyAdminIsAllowed() throws Exception {
+    UserGroupInformation recon =
+        UserGroupInformation.createUserForTesting(RECON_PRINCIPAL, new 
String[] {"recon"});
+    DBUpdates updates = getDBUpdates(recon);
+    assertTrue(updates.getData() != null && !updates.getData().isEmpty());
+  }
+}
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 6dcfb7663ed..3555c31667d 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
@@ -4653,6 +4653,13 @@ public boolean isFilesystemSnapshotEnabled() {
   public DBUpdates getDBUpdates(
       DBUpdatesRequest dbUpdatesRequest)
       throws IOException {
+    // getDBUpdates returns the raw RocksDB delta of the entire OM metadata DB 
(all
+    // volume/bucket/key names, ACLs, block locations, tenant/S3-secret 
state). It backs
+    // OM->Recon replication and is not a per-object client read, so restrict 
it to admins
+    // and read-only admins (the Recon service principal is expected to be 
one), consistent
+    // with the gating already applied to the other whole-system reads such as 
listOpenFiles
+    // and getQuotaRepairStatus.
+    checkGetDBUpdatesPrivilege();
     long limitCount = Long.MAX_VALUE;
     if (dbUpdatesRequest.hasLimitCount()) {
       limitCount = dbUpdatesRequest.getLimitCount();
@@ -4754,6 +4761,26 @@ private void checkAdminUserPrivilege(String operation) 
throws IOException {
     }
   }
 
+  /**
+   * Authorize a getDBUpdates call, which returns the raw whole-DB metadata 
delta.
+   * Allowed for full OM admins and read-only admins (the read-only-admin 
allow-list is the
+   * mechanism intended to grant the Recon service principal read access to 
the metadata feed
+   * without full admin rights). Only enforced when admin authorization is 
enabled, matching
+   * {@link #checkAdminUserPrivilege(String)}.
+   */
+  private void checkGetDBUpdatesPrivilege() throws IOException {
+    // Skip check if authorization is disabled
+    if (!isAdminAuthorizationEnabled()) {
+      return;
+    }
+
+    final UserGroupInformation ugi = getRemoteUser();
+    if (!isAdmin(ugi) && !isReadOnlyAdmin(ugi)) {
+      throw new OMException("Only Ozone admins and read-only admins are 
allowed to "
+          + "access the OM metadata database via getDBUpdates.", 
PERMISSION_DENIED);
+    }
+  }
+
   public boolean isS3Admin(UserGroupInformation callerUgi) {
     return OzoneAdmins.isS3Admin(callerUgi, s3OzoneAdmins);
   }


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

Reply via email to