From dd073df1e24a997620af798a12dd01bb1637f293 Mon Sep 17 00:00:00 2001 From: echonesis Date: Fri, 9 Jan 2026 13:46:52 +0800 Subject: [PATCH 1/3] HDDS-14380. The user who starts Recon process will have administrator privilege --- .../ozone/recon/ReconControllerModule.java | 7 +++ .../hadoop/ozone/recon/ReconServer.java | 48 ++++++++++++++- .../recon/api/filters/ReconAdminFilter.java | 27 +++------ .../recon/api/filters/TestAdminFilter.java | 60 ++++++++++++++++++- 4 files changed, 120 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 3f7e99056e44..9a9dfb48e74b 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 c9fc0ab3470b..6960a581631e 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 { private ReconStorageConfig reconStorage; private CertificateClient certClient; private ReconTaskStatusMetrics reconTaskStatusMetrics; + private OzoneAdmins reconAdmins; private volatile boolean isStarted = false; @@ -104,9 +108,23 @@ public Void call() throws Exception { ReconServer.class, originalArgs, LOG, configuration); ConfigurationProvider.setConfiguration(configuration); + String reconStarterUser = UserGroupInformation.getCurrentUser().getShortUserName(); + Collection adminUsers = + OzoneAdmins.getOzoneAdminsFromConfig(configuration, reconStarterUser); + adminUsers.addAll( + configuration.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS)); + + Collection adminGroups = + OzoneAdmins.getOzoneAdminsGroupsFromConfig(configuration); + adminGroups.addAll( + configuration.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS_GROUPS)); + + reconAdmins = new OzoneAdmins(adminUsers, adminGroups); + 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 +445,32 @@ public ReconTaskController getReconTaskController() { ReconHttpServer getHttpServer() { return httpServer; } + + /** + * Get the collection of Recon admin usernames. + * + * @return Collection of admin usernames + */ + public Collection getReconAdminUsernames() { + return reconAdmins.getAdminUsernames(); + } + + /** + * Get the collection of Recon admin groups. + * + * @return Collection of admin groups + */ + public Collection getReconAdminGroups() { + return reconAdmins.getAdminGroups(); + } + + /** + * 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 546a0b1949b2..f4ae82b7d613 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 admins = - conf.getStringCollection(OzoneConfigKeys.OZONE_ADMINISTRATORS); - admins.addAll( - conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS)); - Collection 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 b6aa522d4e70..c8c8797b1986 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 adminUsers = + OzoneAdmins.getOzoneAdminsFromConfig(conf, reconStarterUser); + adminUsers.addAll( + conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS)); + + Collection 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; + } } From 19cbd269f634c440e25a59dfa8314783c52c382a Mon Sep 17 00:00:00 2001 From: echonesis Date: Sun, 18 Jan 2026 11:59:34 +0800 Subject: [PATCH 2/3] fix: CR update --- .../apache/hadoop/ozone/recon/ReconServer.java | 18 ------------------ 1 file changed, 18 deletions(-) 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 6960a581631e..9b5f78c083a2 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 @@ -446,24 +446,6 @@ ReconHttpServer getHttpServer() { return httpServer; } - /** - * Get the collection of Recon admin usernames. - * - * @return Collection of admin usernames - */ - public Collection getReconAdminUsernames() { - return reconAdmins.getAdminUsernames(); - } - - /** - * Get the collection of Recon admin groups. - * - * @return Collection of admin groups - */ - public Collection getReconAdminGroups() { - return reconAdmins.getAdminGroups(); - } - /** * Check if a user is a Recon administrator. * From 75dca980cb39c1789c49f91700d7feda393c4341 Mon Sep 17 00:00:00 2001 From: echonesis Date: Wed, 21 Jan 2026 23:12:56 +0800 Subject: [PATCH 3/3] fix: CR update --- .../hadoop/ozone/recon/ReconServer.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) 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 9b5f78c083a2..78a4938166a3 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 @@ -108,18 +108,13 @@ public Void call() throws Exception { ReconServer.class, originalArgs, LOG, configuration); ConfigurationProvider.setConfiguration(configuration); - String reconStarterUser = UserGroupInformation.getCurrentUser().getShortUserName(); - Collection adminUsers = - OzoneAdmins.getOzoneAdminsFromConfig(configuration, reconStarterUser); - adminUsers.addAll( - configuration.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS)); - - Collection adminGroups = - OzoneAdmins.getOzoneAdminsGroupsFromConfig(configuration); - adminGroups.addAll( - configuration.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS_GROUPS)); - - reconAdmins = new OzoneAdmins(adminUsers, adminGroups); + 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..."); @@ -446,6 +441,28 @@ 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 adminUsers = + OzoneAdmins.getOzoneAdminsFromConfig(conf, starterUser); + adminUsers.addAll( + conf.getStringCollection(ReconConfigKeys.OZONE_RECON_ADMINISTRATORS)); + + Collection 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. *