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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 6024b7bfe fix(acl): skip broker push rules without a resource type 
instead of widening them (#6727)
6024b7bfe is described below

commit 6024b7bfed0a9b647f1a4061cd71cd2787320055
Author: messere <[email protected]>
AuthorDate: Mon Oct 26 20:32:39 2026 +0800

    fix(acl): skip broker push rules without a resource type instead of 
widening them (#6727)
    
    * fix(acl): skip broker push rules without a resource type instead of 
widening them
---
 .../rocketmq/studio/instance/acl/AclService.java   | 19 ++++++++-
 .../studio/instance/acl/AclServiceTest.java        | 47 ++++++++++++++++++++++
 2 files changed, 64 insertions(+), 2 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
index f3e93bbe7..7c7536659 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
@@ -437,6 +437,16 @@ public class AclService {
                 continue;
             }
             String resource = brokerResource(rule);
+            if (resource == null) {
+                // A rule without a resource type does not say whether its 
resource names a topic
+                // or a group. Pushing it as the ANY resource would widen a 
named grant to the
+                // whole cluster, so it is skipped with a reason like every 
other rule the broker
+                // model cannot express.
+                skipped.add(rule.getResource() + ": resource type "
+                        + (rule.getResourceType() == null ? "" : 
rule.getResourceType())
+                        + " has no broker ACL equivalent");
+                continue;
+            }
             entries.add(AclInfo.PolicyEntryInfo.of(resource, actions,
                     splitWhiteList(user.getWhiteRemoteAddress()),
                     (rule.getDecision() == null ? "" : 
rule.getDecision()).toUpperCase(Locale.ROOT)));
@@ -507,7 +517,9 @@ public class AclService {
     /**
      * Broker ACL 2.0 resources are typed strings ("Topic:orders", 
"Group:cg"); a bare name parses
      * to a null resource and the broker rejects the entry with CODE 16. 
Cluster-scoped rules map to
-     * the ANY resource, which the broker stores as "*".
+     * the ANY resource, which the broker stores as "*". A rule with no 
resource type (or one the
+     * broker model has no equivalent for) returns {@code null}: its resource 
name cannot be typed,
+     * and pushing it as ANY would widen a named grant to the whole cluster.
      */
     private String brokerResource(AclRuleVO rule) {
         String type = rule.getResourceType() == null ? "" : 
rule.getResourceType();
@@ -517,7 +529,10 @@ public class AclService {
         if ("Group".equalsIgnoreCase(type)) {
             return "Group:" + rule.getResource();
         }
-        return "*";
+        if ("Cluster".equalsIgnoreCase(type)) {
+            return "*";
+        }
+        return null;
     }
 
     /** ALL expands to both message directions; anything else the broker 
cannot act on is dropped. */
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
index 9618d89d3..c91836d45 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
@@ -1430,6 +1430,53 @@ class AclServiceTest {
         assertThat(entry.getSourceIps()).containsExactly("10.0.0.0/8", 
"192.168.1.5");
     }
 
+    @Test
+    void 
pushAclToBrokerSkipsRulesWithoutAResourceTypeInsteadOfWideningThemTest() throws 
Exception {
+        // A rule whose resourceType is absent does not say whether its 
resource names a topic or a
+        // group. The push used to map every such rule to the ANY resource 
("*"), widening a named
+        // grant (e.g. "Allow SUB on topic-a") to the whole cluster on the 
broker, while rules with
+        // an unrepresentable pattern were already skipped with a reason.
+        AclUserVO stored = AclUserVO.builder()
+                .id(11L)
+                .username("orders")
+                .secretKey("secret-4")
+                .build();
+        when(aclRepository.findUsers()).thenReturn(List.of(stored));
+        when(aclRepository.findUserById(11L)).thenReturn(Optional.of(stored));
+        when(aclRepository.findRulePage("user:orders", null, null, null, null, 
1, 200))
+                .thenReturn(PageResult.of(List.of(
+                        
AclRuleVO.builder().principal("user:orders").resource("topic-a")
+                                
.resourceType("Topic").resourcePattern("LITERAL")
+                                
.actions(List.of("PUB")).decision("ALLOW").build(),
+                        
AclRuleVO.builder().principal("user:orders").resource("topic-a")
+                                .resourcePattern("LITERAL")
+                                
.actions(List.of("SUB")).decision("ALLOW").build(),
+                        
AclRuleVO.builder().principal("user:orders").resource("*")
+                                
.resourceType("Cluster").resourcePattern("LITERAL")
+                                
.actions(List.of("GET")).decision("ALLOW").build()),
+                        3, 1, 200));
+        when(clusterProvider.discoverBrokers("instance-a", 
null)).thenReturn(List.of());
+
+        DefaultMQAdminExt admin = mock(DefaultMQAdminExt.class);
+        
when(admin.examineBrokerClusterInfo()).thenReturn(clusterInfoWithMaster("10.0.0.1:10911"));
+        when(admin.getUser("10.0.0.1:10911", "orders"))
+                .thenThrow(new MQBrokerException(207, "no such user"));
+        when(runtimeAdminClientResolver.execute(eq("instance-a"), 
any(MqAdminExtFactory.AdminAction.class)))
+                .thenAnswer(call -> call.getArgument(1, 
MqAdminExtFactory.AdminAction.class).apply(admin));
+
+        PushAclToBrokerResultVO result = 
aclService.pushAclToBroker("instance-a");
+
+        PushAclToBrokerResultVO.AccountOutcome account = 
result.getAccounts().get(0);
+        assertThat(account.getPolicyCount()).isEqualTo(2);
+        assertThat(account.getSkipped()).singleElement()
+                .asString().contains("topic-a").contains("no broker ACL 
equivalent");
+        ArgumentCaptor<AclInfo> aclCaptor = 
ArgumentCaptor.forClass(AclInfo.class);
+        verify(admin).updateAcl(eq("10.0.0.1:10911"), aclCaptor.capture());
+        assertThat(aclCaptor.getValue().getPolicies().get(0).getEntries())
+                .extracting(AclInfo.PolicyEntryInfo::getResource)
+                .containsExactly("Topic:topic-a", "*");
+    }
+
     @Test
     void pushAclToBrokerReportsBrokerFailuresWithoutHidingOtherAccountsTest() 
throws Exception {
         AclUserVO stored = 
AclUserVO.builder().id(8L).username("reader").secretKey("secret-2").build();

Reply via email to