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 05b184ae645 HDDS-14380. The user who starts Recon process will have 
administrator privilege (#9627)
05b184ae645 is described below

commit 05b184ae6457abc575018d4061a3186a93518a1f
Author: Eric C. Ho <[email protected]>
AuthorDate: Fri Feb 27 07:41:21 2026 +0800

    HDDS-14380. The user who starts Recon process will have administrator 
privilege (#9627)
    
    Co-authored-by: Doroszlai, Attila <[email protected]>
---
 .../hadoop/ozone/recon/ReconControllerModule.java  |  7 +++
 .../org/apache/hadoop/ozone/recon/ReconServer.java | 47 ++++++++++++++++-
 .../ozone/recon/api/filters/ReconAdminFilter.java  | 27 +++-------
 .../ozone/recon/api/filters/TestAdminFilter.java   | 60 +++++++++++++++++++++-
 4 files changed, 119 insertions(+), 22 deletions(-)

diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconControllerModule.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconControllerModule.java
index 3f7e99056e4..9a9dfb48e74 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconControllerModule.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconControllerModule.java
@@ -92,8 +92,15 @@ public class ReconControllerModule extends AbstractModule {
   private static final Logger LOG =
       LoggerFactory.getLogger(ReconControllerModule.class);
 
+  private final ReconServer reconServer;
+
+  public ReconControllerModule(ReconServer reconServer) {
+    this.reconServer = reconServer;
+  }
+
   @Override
   protected void configure() {
+    bind(ReconServer.class).toInstance(reconServer);
     bind(OzoneConfiguration.class).toProvider(ConfigurationProvider.class);
     bind(ReconHttpServer.class).in(Singleton.class);
     bind(ReconStorageConfig.class).in(Singleton.class);
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
index c9fc0ab3470..78a4938166a 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java
@@ -31,6 +31,7 @@
 import com.google.inject.Injector;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.util.Collection;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.sql.DataSource;
@@ -38,9 +39,11 @@
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import 
org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB;
 import org.apache.hadoop.hdds.recon.ReconConfig;
+import org.apache.hadoop.hdds.recon.ReconConfigKeys;
 import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
 import org.apache.hadoop.hdds.security.SecurityConfig;
 import 
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
+import org.apache.hadoop.hdds.server.OzoneAdmins;
 import org.apache.hadoop.hdds.utils.HddsServerUtil;
 import org.apache.hadoop.ozone.OzoneSecurityUtil;
 import org.apache.hadoop.ozone.recon.api.types.FeatureProvider;
@@ -85,6 +88,7 @@ public class ReconServer extends GenericCli implements 
Callable<Void> {
   private ReconStorageConfig reconStorage;
   private CertificateClient certClient;
   private ReconTaskStatusMetrics reconTaskStatusMetrics;
+  private OzoneAdmins reconAdmins;
 
   private volatile boolean isStarted = false;
 
@@ -104,9 +108,18 @@ public Void call() throws Exception {
             ReconServer.class, originalArgs, LOG, configuration);
     ConfigurationProvider.setConfiguration(configuration);
 
+    try {
+      reconAdmins = createReconAdmins(configuration);
+    } catch (IOException e) {
+      LOG.error("Failed to identify current user for Recon admin 
initialization. " +
+          "Please check Kerberos configuration or system user settings.", e);
+      throw e;
+    }
+    LOG.info("Recon start with adminUsers: {}", 
reconAdmins.getAdminUsernames());
+
     LOG.info("Initializing Recon server...");
     try {
-      injector = Guice.createInjector(new ReconControllerModule(),
+      injector = Guice.createInjector(new ReconControllerModule(this),
           new ReconRestServletModule(configuration),
           new ReconSchemaGenerationModule());
 
@@ -427,4 +440,36 @@ public ReconTaskController getReconTaskController() {
   ReconHttpServer getHttpServer() {
     return httpServer;
   }
+
+  /**
+   * Creates OzoneAdmins for Recon with the starter user and configured admins.
+   *
+   * @param conf OzoneConfiguration
+   * @return OzoneAdmins instance
+   * @throws IOException if unable to get current user
+   */
+  private OzoneAdmins createReconAdmins(OzoneConfiguration conf) throws 
IOException {
+    String starterUser = 
UserGroupInformation.getCurrentUser().getShortUserName();
+    Collection<String> adminUsers =
+        OzoneAdmins.getOzoneAdminsFromConfig(conf, starterUser);
+    adminUsers.addAll(
+        conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS));
+
+    Collection<String> adminGroups =
+        OzoneAdmins.getOzoneAdminsGroupsFromConfig(conf);
+    adminGroups.addAll(
+        
conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS_GROUPS));
+
+    return new OzoneAdmins(adminUsers, adminGroups);
+  }
+
+  /**
+   * Check if a user is a Recon administrator.
+   *
+   * @param user UserGroupInformation
+   * @return true if the user is an admin, false otherwise
+   */
+  public boolean isAdmin(UserGroupInformation user) {
+    return reconAdmins.isAdmin(user);
+  }
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/filters/ReconAdminFilter.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/filters/ReconAdminFilter.java
index 546a0b1949b..f4ae82b7d61 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/filters/ReconAdminFilter.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/filters/ReconAdminFilter.java
@@ -21,7 +21,6 @@
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.security.Principal;
-import java.util.Collection;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -30,10 +29,7 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.hdds.recon.ReconConfigKeys;
-import org.apache.hadoop.hdds.server.OzoneAdmins;
-import org.apache.hadoop.ozone.OzoneConfigKeys;
+import org.apache.hadoop.ozone.recon.ReconServer;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -48,15 +44,17 @@ public class ReconAdminFilter implements Filter {
   private static final Logger LOG =
       LoggerFactory.getLogger(ReconAdminFilter.class);
 
-  private final OzoneConfiguration conf;
+  private final ReconServer reconServer;
 
   @Inject
-  ReconAdminFilter(OzoneConfiguration conf) {
-    this.conf = conf;
+  ReconAdminFilter(ReconServer reconServer) {
+    this.reconServer = reconServer;
   }
 
   @Override
-  public void init(FilterConfig filterConfig) throws ServletException { }
+  public void init(FilterConfig filterConfig) throws ServletException {
+    LOG.info("ReconAdminFilter initialized");
+  }
 
   @Override
   public void doFilter(ServletRequest servletRequest,
@@ -100,15 +98,6 @@ public void doFilter(ServletRequest servletRequest,
   public void destroy() { }
 
   private boolean hasPermission(UserGroupInformation user) {
-    Collection<String> admins =
-        conf.getStringCollection(OzoneConfigKeys.OZONE_ADMINISTRATORS);
-    admins.addAll(
-        conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS));
-    Collection<String> adminGroups =
-        conf.getStringCollection(OzoneConfigKeys.OZONE_ADMINISTRATORS_GROUPS);
-    adminGroups.addAll(
-        conf.getStringCollection(
-            ReconConfigKeys.OZONE_RECON_ADMINISTRATORS_GROUPS));
-    return new OzoneAdmins(admins, adminGroups).isAdmin(user);
+    return reconServer.isAdmin(user);
   }
 }
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
index b6aa522d4e7..c8c8797b198 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/filters/TestAdminFilter.java
@@ -20,12 +20,15 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.Sets;
+import java.io.IOException;
 import java.security.Principal;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
 import javax.servlet.FilterChain;
@@ -34,7 +37,9 @@
 import javax.ws.rs.Path;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.recon.ReconConfigKeys;
+import org.apache.hadoop.hdds.server.OzoneAdmins;
 import org.apache.hadoop.ozone.OzoneConfigKeys;
+import org.apache.hadoop.ozone.recon.ReconServer;
 import org.apache.hadoop.ozone.recon.api.AdminOnly;
 import org.apache.hadoop.ozone.recon.api.ClusterStateEndpoint;
 import org.apache.hadoop.ozone.recon.api.MetricsProxyEndpoint;
@@ -170,8 +175,31 @@ public void testAdminFilterNoAdmins() throws Exception {
     testAdminFilterWithPrincipal(new OzoneConfiguration(), "reject", false);
   }
 
+  @Test
+  public void testAdminFilterStarterUserAutoAdmin() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    String currentUser = 
UserGroupInformation.getCurrentUser().getShortUserName();
+
+    testAdminFilterWithPrincipal(conf, currentUser, true);
+    testAdminFilterWithPrincipal(conf, "otheruser", false);
+  }
+
+  @Test
+  public void testAdminFilterStarterUserPlusConfiguredAdmins() throws 
Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.setStrings(OzoneConfigKeys.OZONE_ADMINISTRATORS, "configadmin");
+
+    String currentUser = 
UserGroupInformation.getCurrentUser().getShortUserName();
+
+    testAdminFilterWithPrincipal(conf, currentUser, true);
+    testAdminFilterWithPrincipal(conf, "configadmin", true);
+    testAdminFilterWithPrincipal(conf, "reject", false);
+  }
+
   private void testAdminFilterWithPrincipal(OzoneConfiguration conf,
       String principalToUse, boolean shouldPass) throws Exception {
+    ReconServer mockReconServer = createMockReconServer(conf);
+
     Principal mockPrincipal = mock(Principal.class);
     when(mockPrincipal.getName()).thenReturn(principalToUse);
     HttpServletRequest mockRequest = mock(HttpServletRequest.class);
@@ -180,8 +208,9 @@ private void 
testAdminFilterWithPrincipal(OzoneConfiguration conf,
     HttpServletResponse mockResponse = mock(HttpServletResponse.class);
     FilterChain mockFilterChain = mock(FilterChain.class);
 
-    new ReconAdminFilter(conf).doFilter(mockRequest, mockResponse,
-        mockFilterChain);
+    ReconAdminFilter filter = new ReconAdminFilter(mockReconServer);
+    filter.init(null);
+    filter.doFilter(mockRequest, mockResponse, mockFilterChain);
 
     if (shouldPass) {
       verify(mockFilterChain).doFilter(mockRequest, mockResponse);
@@ -189,4 +218,31 @@ private void 
testAdminFilterWithPrincipal(OzoneConfiguration conf,
       verify(mockResponse).setStatus(HttpServletResponse.SC_FORBIDDEN);
     }
   }
+
+  /**
+   * Creates a mock ReconServer that mimics the actual admin initialization 
logic.
+   */
+  private ReconServer createMockReconServer(OzoneConfiguration conf) throws 
IOException {
+    String reconStarterUser = 
UserGroupInformation.getCurrentUser().getShortUserName();
+    Collection<String> adminUsers =
+        OzoneAdmins.getOzoneAdminsFromConfig(conf, reconStarterUser);
+    adminUsers.addAll(
+        conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS));
+
+    Collection<String> adminGroups =
+        OzoneAdmins.getOzoneAdminsGroupsFromConfig(conf);
+    adminGroups.addAll(
+        
conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS_GROUPS));
+
+    OzoneAdmins reconAdmins = new OzoneAdmins(adminUsers, adminGroups);
+
+    ReconServer mockReconServer = mock(ReconServer.class);
+    when(mockReconServer.isAdmin(any(UserGroupInformation.class)))
+        .thenAnswer(invocation -> {
+          UserGroupInformation user = invocation.getArgument(0);
+          return reconAdmins.isAdmin(user);
+        });
+
+    return mockReconServer;
+  }
 }


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

Reply via email to