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

ArafatKhan2198 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 fd90ad1b7a5 HDDS-16530. Allow Recon service principal to call OM 
getDBUpdates (#11382).
fd90ad1b7a5 is described below

commit fd90ad1b7a5da8c2e34871d1009e8dc14c41a537
Author: Gargi Jaiswal <[email protected]>
AuthorDate: Fri Oct 2 14:32:22 2026 +0530

    HDDS-16530. Allow Recon service principal to call OM getDBUpdates (#11382).
---
 .../ozone/om/TestGetDBUpdatesAuthorization.java    | 73 +++++++++++++++++-----
 .../org/apache/hadoop/ozone/om/OzoneManager.java   | 53 +++++++++++++---
 2 files changed, 99 insertions(+), 27 deletions(-)

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
index 906597647f2..bf0e875f2cf 100644
--- 
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
@@ -17,6 +17,7 @@
 
 package org.apache.hadoop.ozone.om;
 
+import static 
org.apache.hadoop.hdds.recon.ReconConfig.ConfigStrings.OZONE_RECON_KERBEROS_PRINCIPAL_KEY;
 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;
@@ -25,8 +26,8 @@
 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.assertNotNull;
 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;
@@ -50,8 +51,9 @@
 /**
  * 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).
+ * OM->Recon replication, so it is restricted to admins, read-only admins, and 
the configured
+ * Recon service principal ({@code ozone.recon.kerberos.principal}), matching
+ * {@link OMDBCheckpointServlet} checkpoint download access.
  */
 public class TestGetDBUpdatesAuthorization {
 
@@ -61,17 +63,12 @@ public class TestGetDBUpdatesAuthorization {
   private static final String VOL = "vol1";
   private static final String BUCKET = "bucket1";
   private static final String KEY = "key1";
+  private static final String READONLY_ADMIN = "roadmin";
   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);
+    conf = authTestConfiguration();
     cluster = MiniOzoneCluster.newBuilder(conf).setNumDatanodes(3).build();
     cluster.waitForClusterToBeReady();
 
@@ -98,11 +95,25 @@ static void shutdown() {
     }
   }
 
-  private static DBUpdates getDBUpdates(UserGroupInformation user) throws 
Exception {
+  private static OzoneConfiguration authTestConfiguration() {
+    OzoneConfiguration testConf = new OzoneConfiguration();
+    testConf.setBoolean(OZONE_ACL_ENABLED, true);
+    testConf.set(OZONE_ACL_AUTHORIZER_CLASS, 
OZONE_ACL_AUTHORIZER_CLASS_NATIVE);
+    testConf.set(OZONE_ADMINISTRATORS, "admin");
+    testConf.set(OZONE_READONLY_ADMINISTRATORS, READONLY_ADMIN);
+    // Recon is allowed by its service principal alone, without being listed 
as an admin.
+    testConf.set(OZONE_RECON_KERBEROS_PRINCIPAL_KEY, RECON_PRINCIPAL);
+    // Make admin authorization effective without a KDC so the gate actually 
runs.
+    testConf.setBoolean(OZONE_TEST_AUTHORIZATION_ENABLED, true);
+    return testConf;
+  }
+
+  private static DBUpdates getDBUpdates(OzoneConfiguration ozoneConf, 
UserGroupInformation user)
+      throws Exception {
     return user.doAs((PrivilegedExceptionAction<DBUpdates>) () -> {
       OzoneManagerProtocolClientSideTranslatorPB omClient =
           new OzoneManagerProtocolClientSideTranslatorPB(
-              OmTransportFactory.create(conf, user, null),
+              OmTransportFactory.create(ozoneConf, user, null),
               ClientId.randomId().toString());
       DBUpdatesRequest req = DBUpdatesRequest.newBuilder()
           .setSequenceNumber(0)
@@ -111,11 +122,16 @@ private static DBUpdates 
getDBUpdates(UserGroupInformation user) throws Exceptio
     });
   }
 
+  private static void assertDbUpdatesNonEmpty(DBUpdates updates) {
+    assertNotNull(updates.getData());
+    assertFalse(updates.getData().isEmpty());
+  }
+
   @Test
   void nonAdminIsDenied() {
     UserGroupInformation nonAdmin =
         UserGroupInformation.createUserForTesting("nonadmin", new String[] 
{"users"});
-    OMException ex = assertThrows(OMException.class, () -> 
getDBUpdates(nonAdmin));
+    OMException ex = assertThrows(OMException.class, () -> getDBUpdates(conf, 
nonAdmin));
     assertEquals(OMException.ResultCodes.PERMISSION_DENIED, ex.getResult());
   }
 
@@ -123,15 +139,38 @@ void nonAdminIsDenied() {
   void adminIsAllowed() throws Exception {
     UserGroupInformation admin =
         UserGroupInformation.createUserForTesting("admin", new String[] 
{"admins"});
-    DBUpdates updates = getDBUpdates(admin);
-    assertFalse(updates.getData().isEmpty());
+    assertDbUpdatesNonEmpty(getDBUpdates(conf, admin));
   }
 
   @Test
   void readOnlyAdminIsAllowed() throws Exception {
+    UserGroupInformation readOnlyAdmin =
+        UserGroupInformation.createUserForTesting(READONLY_ADMIN, new String[] 
{"users"});
+    assertDbUpdatesNonEmpty(getDBUpdates(conf, readOnlyAdmin));
+  }
+
+  @Test
+  void reconPrincipalIsAllowed() throws Exception {
     UserGroupInformation recon =
         UserGroupInformation.createUserForTesting(RECON_PRINCIPAL, new 
String[] {"recon"});
-    DBUpdates updates = getDBUpdates(recon);
-    assertTrue(updates.getData() != null && !updates.getData().isEmpty());
+    assertDbUpdatesNonEmpty(getDBUpdates(conf, recon));
+  }
+
+  @Test
+  void accessDeniedWhenReconPrincipalNotConfigured() throws Exception {
+    OzoneConfiguration noReconConf = authTestConfiguration();
+    noReconConf.unset(OZONE_RECON_KERBEROS_PRINCIPAL_KEY);
+    MiniOzoneCluster denyCluster =
+        MiniOzoneCluster.newBuilder(noReconConf).setNumDatanodes(1).build();
+    try {
+      denyCluster.waitForClusterToBeReady();
+      UserGroupInformation reconLikeUser =
+          UserGroupInformation.createUserForTesting(RECON_PRINCIPAL, new 
String[] {"recon"});
+      OMException ex = assertThrows(OMException.class,
+          () -> getDBUpdates(noReconConf, reconLikeUser));
+      assertEquals(OMException.ResultCodes.PERMISSION_DENIED, ex.getResult());
+    } finally {
+      denyCluster.shutdown();
+    }
   }
 }
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 497ef7065cf..07d70312c91 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
@@ -195,6 +195,7 @@
 import 
org.apache.hadoop.hdds.protocolPB.ReconfigureProtocolServerSideTranslatorPB;
 import 
org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB;
 import org.apache.hadoop.hdds.ratis.RatisHelper;
+import org.apache.hadoop.hdds.recon.ReconConfig;
 import org.apache.hadoop.hdds.scm.ScmConfigKeys;
 import org.apache.hadoop.hdds.scm.ScmInfo;
 import org.apache.hadoop.hdds.scm.client.HddsClientUtils;
@@ -449,6 +450,8 @@ public final class OzoneManager extends 
ServiceRuntimeInfoImpl
   private final OzoneAdmins s3OzoneAdmins;
   private final OzoneBlacklist omBlacklist;
   private final OzoneBlacklist readBlacklist;
+  /** Short name of the Recon service principal, null when Recon is not 
configured. */
+  private final String reconUser;
 
   private final OMMetrics metrics;
   private final OmSnapshotInternalMetrics omSnapshotIntMetrics;
@@ -752,6 +755,8 @@ private OzoneManager(OzoneConfiguration conf, StartupOption 
startupOption)
     readOnlyAdmins = OzoneAdmins.getReadonlyAdmins(conf);
     readBlacklist = OzoneBlacklist.getReadonlyBlacklist(conf);
 
+    reconUser = resolveReconUserShortName(conf);
+
     s3OzoneAdmins = OzoneAdmins.getS3Admins(conf);
 
     serviceManager = new OMServiceManager();
@@ -5220,10 +5225,9 @@ public DBUpdates getDBUpdates(
       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.
+    // OM->Recon replication and is not a per-object client read, so restrict 
it to admins,
+    // read-only admins and the configured Recon service principal, matching
+    // {@link OMDBCheckpointServlet} checkpoint download access.
     checkGetDBUpdatesPrivilege();
     long limitCount = Long.MAX_VALUE;
     if (dbUpdatesRequest.hasLimitCount()) {
@@ -5332,10 +5336,10 @@ 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)}.
+   * Allowed for full OM admins, read-only admins and the configured Recon 
service principal,
+   * which consumes this feed to replicate the OM DB. Only enforced when admin 
authorization is
+   * enabled. Recon access follows the same {@code 
ozone.recon.kerberos.principal} config as
+   * {@link OMDBCheckpointServlet}.
    */
   private void checkGetDBUpdatesPrivilege() throws IOException {
     // Skip check if authorization is disabled
@@ -5344,12 +5348,41 @@ private void checkGetDBUpdatesPrivilege() throws 
IOException {
     }
 
     final UserGroupInformation ugi = getRemoteUser();
-    if (!isAdmin(ugi) && !isReadOnlyAdmin(ugi)) {
-      throw new OMException("Only Ozone admins and read-only admins are 
allowed to "
+    if (!isAdmin(ugi) && !isReadOnlyAdmin(ugi) && !isReconUser(ugi)) {
+      throw new OMException("Only Ozone admins, read-only admins and Recon are 
allowed to "
           + "access the OM metadata database via getDBUpdates.", 
PERMISSION_DENIED);
     }
   }
 
+  /**
+   * Resolve the short name of the Recon service principal from {@code 
ozone.recon.kerberos.principal},
+   * using the same mapping as {@link OMDBCheckpointServlet}.
+   *
+   * @return the Recon short name, or null when Recon is unconfigured or the 
principal cannot be mapped
+   */
+  private static String resolveReconUserShortName(OzoneConfiguration conf) {
+    String reconPrincipal = 
conf.getObject(ReconConfig.class).getKerberosPrincipal();
+    if (reconPrincipal.isEmpty()) {
+      return null;
+    }
+    try {
+      return 
UserGroupInformation.createRemoteUser(reconPrincipal).getShortUserName();
+    } catch (IllegalArgumentException e) {
+      LOG.warn("Unable to map Recon principal {} to a short name. Recon will 
be denied access to"
+          + " getDBUpdates unless it is listed in {} or {}.", reconPrincipal, 
OZONE_ADMINISTRATORS,
+          OZONE_READONLY_ADMINISTRATORS, e);
+      return null;
+    }
+  }
+
+  /**
+   * @param callerUgi Caller UserGroupInformation
+   * @return true if {@code callerUgi} is the configured Recon service 
principal
+   */
+  private boolean isReconUser(UserGroupInformation callerUgi) {
+    return reconUser != null && callerUgi != null && 
reconUser.equals(callerUgi.getShortUserName());
+  }
+
   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