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();