You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by si...@apache.org on 2026/10/17 18:35:01 UTC

(ozone) 03/13: Improve request handling in SCMBlockProtocolServer.

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

smengcl pushed a commit to branch ozone-2.2
in repository https://gitbox.apache.org/repos/asf/ozone.git

commit c85d09937e2cfb361a78a197e9e5206957bf75bb
Author: Siyao Meng <50...@users.noreply.github.com>
AuthorDate: Tue Oct 13 20:07:42 2026 -0700

    Improve request handling in SCMBlockProtocolServer.
    
    (cherry picked from commit fc8896c126d06068967843f2415dac9b8725f8ae)
    
    Conflicts:
            hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMBlockProtocolServer.java
            hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMBlockProtocolServer.java
    
    Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
 .../common/src/main/resources/ozone-default.xml    |  5 ++-
 .../apache/hadoop/hdds/utils/HddsServerUtil.java   | 18 ++++++++++
 .../hdds/scm/server/SCMBlockProtocolServer.java    | 25 +++++++++++++
 .../scm/server/TestSCMBlockProtocolServer.java     | 42 ++++++++++++++++++++++
 4 files changed, 89 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 f93b14e9c23..eef89592300 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -2437,7 +2437,10 @@
     <name>ozone.om.kerberos.principal</name>
     <value>OM/_HOST@REALM</value>
     <tag>OZONE, SECURITY, KERBEROS</tag>
-    <description>The OzoneManager service principal. Ex om/_HOST@REALM.COM</description>
+    <description>The OzoneManager service principal. Ex om/_HOST@REALM.COM
+      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 760bdfbd04b..f224a65de6c 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
@@ -79,6 +79,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;
@@ -139,6 +141,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";
 
@@ -765,6 +769,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 e563c9f08ff..1ccaf47af6d 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;
@@ -78,6 +79,8 @@
 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;
 import org.slf4j.LoggerFactory;
@@ -94,12 +97,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.
@@ -108,6 +115,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);
@@ -183,6 +191,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,
@@ -190,6 +213,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));
@@ -264,6 +288,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 895baef27d6..bde75796f2c 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;
@@ -30,6 +32,7 @@
 import com.google.common.collect.ImmutableMap;
 import java.io.File;
 import java.io.IOException;
+import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -65,7 +68,10 @@
 import org.apache.hadoop.net.StaticMapping;
 import org.apache.hadoop.ozone.ClientVersion;
 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.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -159,6 +165,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/_HOST@EXAMPLE.COM");
     config.set(NET_TOPOLOGY_NODE_SWITCH_MAPPING_IMPL_KEY,
         StaticMapping.class.getName());
     List<DatanodeDetails> datanodes = new ArrayList<>(NODE_COUNT);
@@ -297,6 +304,41 @@ public void testSortDatanodes() throws Exception {
         node -> System.out.println(node.getNetworkName()));
   }
 
+  @Test
+  void allocateAndDeleteBlocksAreDeniedForOtherUsers() {
+    for (String user : new String[] {"alice", "alice/om@EXAMPLE.COM", "dn/host1@EXAMPLE.COM", "om@EXAMPLE.COM",
+        "om/host1@OTHER.COM"}) {
+      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/host1@EXAMPLE.COM");
+    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/host1@EXAMPLE.COM",
+        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: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org