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