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

smengcl 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 fc8896c126d Improve request handling in SCMBlockProtocolServer.
fc8896c126d is described below

commit fc8896c126d06068967843f2415dac9b8725f8ae
Author: Siyao Meng <[email protected]>
AuthorDate: Tue Oct 13 20:07:42 2026 -0700

    Improve request handling in SCMBlockProtocolServer.
---
 .../common/src/main/resources/ozone-default.xml    |  5 ++-
 .../apache/hadoop/hdds/utils/HddsServerUtil.java   | 18 ++++++++++
 .../hdds/scm/server/SCMBlockProtocolServer.java    | 24 +++++++++++++
 .../scm/server/TestSCMBlockProtocolServer.java     | 42 ++++++++++++++++++++++
 4 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml 
b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index a12aa411bcc..83d2a76135c 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -2568,7 +2568,10 @@
     <name>ozone.om.kerberos.principal</name>
     <value>OM/_HOST@REALM</value>
     <tag>OZONE, SECURITY, KERBEROS</tag>
-    <description>The OzoneManager service principal. Ex 
om/[email protected]</description>
+    <description>The OzoneManager service principal. Ex om/[email protected]
+      SCM allocates and deletes blocks only for this service user or an SCM 
admin, so the property must be set in the
+      configuration of SCM as well.
+    </description>
   </property>
   <property>
     <name>ozone.om.kerberos.principal.pattern</name>
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
index 1d51c8b7365..d763753c54b 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
@@ -82,6 +82,8 @@
 import java.util.Set;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.compress.archivers.ArchiveOutputStream;
@@ -141,6 +143,8 @@ public final class HddsServerUtil {
 
   private static final int SHUTDOWN_HOOK_PRIORITY = 0;
 
+  private static final Pattern KERBEROS_PRINCIPAL = 
Pattern.compile("([^/@]+)(/[^/@]+)?(@[^/@]+)?");
+
   public static final String OZONE_RATIS_SNAPSHOT_COMPLETE_FLAG_NAME =
       "OZONE_RATIS_SNAPSHOT_COMPLETE";
 
@@ -775,6 +779,20 @@ public static UserGroupInformation getRemoteUser() throws 
IOException {
     return (ugi != null) ? ugi : UserGroupInformation.getCurrentUser();
   }
 
+  /**
+   * @return true if {@code user} is a service user of the Kerberos {@code 
principal}: not a proxy user, with the same
+   * service name, with the same realm if the principal has one, and with a 
host if the principal has one. The host is
+   * not compared, as _HOST in the principal stands for the host of any 
instance of the service.
+   */
+  public static boolean isServiceUser(UserGroupInformation user, String 
principal) {
+    final Matcher expected = KERBEROS_PRINCIPAL.matcher(principal);
+    final Matcher actual = KERBEROS_PRINCIPAL.matcher(user.getUserName());
+    return user.getRealUser() == null && expected.matches() && actual.matches()
+        && expected.group(1).equals(actual.group(1))
+        && (expected.group(2) == null) == (actual.group(2) == null)
+        && (expected.group(3) == null || 
expected.group(3).equals(actual.group(3)));
+  }
+
   /**
    * Add exception classes which server won't log at all.
    * @param server
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
index 3678925a8e4..f8c154d63f4 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
@@ -29,6 +29,7 @@
 import static org.apache.hadoop.hdds.server.ServerUtils.getRemoteUserName;
 import static org.apache.hadoop.hdds.server.ServerUtils.updateRPCListenAddress;
 import static org.apache.hadoop.hdds.utils.HddsServerUtil.getRemoteUser;
+import static org.apache.hadoop.hdds.utils.HddsServerUtil.isServiceUser;
 
 import com.google.common.collect.Maps;
 import com.google.protobuf.BlockingService;
@@ -85,6 +86,7 @@
 import org.apache.hadoop.ozone.common.BlockGroup;
 import org.apache.hadoop.ozone.common.DeleteBlockGroupResult;
 import org.apache.hadoop.ozone.common.DeletedBlock;
+import org.apache.hadoop.security.AccessControlException;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.util.Time;
 import org.slf4j.Logger;
@@ -102,12 +104,16 @@ public class SCMBlockProtocolServer implements
   private static final AuditLogger AUDIT =
       new AuditLogger(AuditLoggerType.SCMLOGGER);
 
+  // TODO: move OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY to hdds-common.
+  static final String OM_KERBEROS_PRINCIPAL_KEY = 
"ozone.om.kerberos.principal";
+
   private final StorageContainerManager scm;
   private final RPC.Server blockRpcServer;
   private final InetSocketAddress blockRpcAddress;
   private final ProtocolMessageMetrics<ScmBlockLocationProtocolProtos.Type>
       protocolMessageMetrics;
   private final SCMPerformanceMetrics perfMetrics;
+  private final String omPrincipal;
 
   /**
    * The RPC server that listens to requests from block service clients.
@@ -116,6 +122,7 @@ public SCMBlockProtocolServer(OzoneConfiguration conf,
       StorageContainerManager scm) throws IOException {
     this.scm = scm;
     this.perfMetrics = scm.getPerfMetrics();
+    this.omPrincipal = conf.get(OM_KERBEROS_PRINCIPAL_KEY, "");
     final int handlerCount = conf.getInt(OZONE_SCM_BLOCK_HANDLER_COUNT_KEY,
         OZONE_SCM_HANDLER_COUNT_KEY, OZONE_SCM_HANDLER_COUNT_DEFAULT,
             LOG::info);
@@ -191,6 +198,21 @@ public void join() throws InterruptedException {
     getBlockRpcServer().join();
   }
 
+  /**
+   * Blocks are allocated and deleted on behalf of OM only.
+   */
+  private void checkOmOrAdminAccess() throws IOException {
+    final UserGroupInformation user = getRemoteUser();
+    if (scm.isAdminAuthorizationEnabled() && !isServiceUser(user, 
omPrincipal)) {
+      try {
+        scm.checkAdminAccess(user, false);
+      } catch (AccessControlException e) {
+        throw new AccessControlException(e.getMessage() + " Only SCM admins 
and the OM service user ("
+            + OM_KERBEROS_PRINCIPAL_KEY + " in the configuration of SCM) can 
allocate or delete blocks.");
+      }
+    }
+  }
+
   @Override
   public List<AllocatedBlock> allocateBlock(
       long size, int num,
@@ -198,6 +220,7 @@ public List<AllocatedBlock> allocateBlock(
       String owner, ExcludeList excludeList,
       String clientMachine
   ) throws IOException {
+    checkOmOrAdminAccess();
     long startNanos = Time.monotonicNowNanos();
     Map<String, String> auditMap = Maps.newHashMap();
     auditMap.put("size", String.valueOf(size));
@@ -272,6 +295,7 @@ public List<AllocatedBlock> allocateBlock(
   @Override
   public List<DeleteBlockGroupResult> deleteKeyBlocks(
       List<BlockGroup> keyBlocksInfoList) throws IOException {
+    checkOmOrAdminAccess();
     long totalBlocks = 0;
     for (BlockGroup bg : keyBlocksInfoList) {
       totalBlocks += bg.getDeletedBlocks().size();
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMBlockProtocolServer.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMBlockProtocolServer.java
index 816462a366f..3b490088f81 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMBlockProtocolServer.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMBlockProtocolServer.java
@@ -21,6 +21,8 @@
 import static 
org.apache.hadoop.hdds.protocol.MockDatanodeDetails.randomDatanodeDetails;
 import static org.apache.hadoop.hdds.scm.net.NetConstants.ROOT_LEVEL;
 import static org.apache.hadoop.ozone.OzoneConsts.MB;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -39,6 +41,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -87,7 +90,10 @@
 import org.apache.hadoop.ozone.ClientVersion;
 import org.apache.hadoop.ozone.audit.SCMAction;
 import org.apache.hadoop.ozone.common.BlockGroup;
+import org.apache.hadoop.ozone.common.DeleteBlockGroupResult;
 import org.apache.hadoop.ozone.container.common.SCMTestUtils;
+import org.apache.hadoop.security.AccessControlException;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.ozone.test.GenericTestUtils.LogCapturer;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -183,6 +189,7 @@ public void close() throws IOException {
   @BeforeEach
   void setUp(@TempDir File dir) throws Exception {
     config = SCMTestUtils.getConf(dir);
+    config.set(SCMBlockProtocolServer.OM_KERBEROS_PRINCIPAL_KEY, 
"om/[email protected]");
     config.set(NET_TOPOLOGY_NODE_SWITCH_MAPPING_IMPL_KEY,
         StaticMapping.class.getName());
     List<DatanodeDetails> datanodes = new ArrayList<>(NODE_COUNT);
@@ -321,6 +328,41 @@ public void testSortDatanodes() throws Exception {
         node -> System.out.println(node.getNetworkName()));
   }
 
+  @Test
+  void allocateAndDeleteBlocksAreDeniedForOtherUsers() {
+    for (String user : new String[] {"alice", "alice/[email protected]", 
"dn/[email protected]", "[email protected]",
+        "om/[email protected]"}) {
+      UserGroupInformation ugi = UserGroupInformation.createRemoteUser(user);
+      assertThatThrownBy(() -> ugi.doAs((PrivilegedExceptionAction<?>) 
this::allocateBlock))
+          .isInstanceOf(AccessControlException.class)
+          
.hasMessageContaining(SCMBlockProtocolServer.OM_KERBEROS_PRINCIPAL_KEY);
+      assertThatThrownBy(() -> ugi.doAs((PrivilegedExceptionAction<?>) () ->
+          server.deleteKeyBlocks(Collections.emptyList())))
+          .isInstanceOf(AccessControlException.class);
+    }
+  }
+
+  @Test
+  void allocateAndDeleteBlocksAreAllowedForOm() throws Exception {
+    UserGroupInformation om = 
UserGroupInformation.createRemoteUser("om/[email protected]");
+    assertThat(om.doAs((PrivilegedExceptionAction<List<AllocatedBlock>>) 
this::allocateBlock)).hasSize(1);
+    
assertThat(om.doAs((PrivilegedExceptionAction<List<DeleteBlockGroupResult>>) () 
->
+        server.deleteKeyBlocks(Collections.emptyList()))).isEmpty();
+  }
+
+  @Test
+  void allocateBlockIsDeniedForProxyUserOfOm() {
+    UserGroupInformation proxy = 
UserGroupInformation.createProxyUser("om/[email protected]",
+        UserGroupInformation.createRemoteUser("alice"));
+    assertThatThrownBy(() -> proxy.doAs((PrivilegedExceptionAction<?>) 
this::allocateBlock))
+        .isInstanceOf(AccessControlException.class);
+  }
+
+  private List<AllocatedBlock> allocateBlock() throws IOException {
+    return server.allocateBlock(128 * MB, 1, 
RatisReplicationConfig.getInstance(ReplicationFactor.THREE), "o",
+        new ExcludeList(), null);
+  }
+
   @Test
   void testAllocateBlockWithClientMachine() throws IOException {
     final DatanodeDetails clientDatanode = nodeManager.getAllNodes().get(0);


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

Reply via email to