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]