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]