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 dff6876d6 fix(client): constrain diagnostics to registered nameservers
(#4534)
dff6876d6 is described below
commit dff6876d60d896d9be5507061294299395b54f12
Author: zmuxuny <[email protected]>
AuthorDate: Mon Sep 21 21:01:16 2026 +0800
fix(client): constrain diagnostics to registered nameservers (#4534)
`ClientService.listConnectionsAt` handed the caller-supplied `namesrvAddr`
straight to the provider after a `trim()`, so anyone with read access to the
Clients API could make Studio open an outbound RocketMQ connection to an
arbitrary endpoint that was never registered — an SSRF surface that the
registry boundary was supposed to close.
The address now goes through
`NameserverRegistryService.requireRegisteredAddress`, which normalizes it with
`NamesrvAddrParser.normalize` and requires an exact `selectCount` match on
`rmq_nameserver.namesrv_addr`, answering `BusinessException(404)` when there is
none; a malformed address is rejected by the parser before the database is
consulted. `listConnectionsAt` passes the normalized value on to
`findConnectionsAt`. The method has a single caller in `ClientController`, and
the web [...]
Fixes #4502
---
.../studio/cluster/client/ClientService.java | 5 +++-
.../nameserver/NameserverRegistryService.java | 10 ++++++++
.../studio/cluster/client/ClientServiceTest.java | 28 ++++++++++++++++++---
.../nameserver/NameserverRegistryServiceTest.java | 29 ++++++++++++++++++++++
4 files changed, 67 insertions(+), 5 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
index 76c734d19..88594c124 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.cluster.client;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.cluster.nameserver.NameserverRegistryService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@@ -30,6 +31,7 @@ import java.util.List;
public class ClientService {
private final ClientProvider clientProvider;
+ private final NameserverRegistryService nameserverRegistryService;
public List<ClientConnectionVO> listConnections(String instanceId, String
clusterId, String type) {
log.info("Listing client connections, instanceId={}, clusterId={},
type={}", instanceId, clusterId, type);
@@ -41,7 +43,8 @@ public class ClientService {
if (!StringUtils.hasText(namesrvAddr)) {
throw new BusinessException(400, "namesrvAddr is required");
}
- return clientProvider.findConnectionsAt(namesrvAddr.trim(),
normalizeFilter(clusterId), normalizeFilter(type));
+ String registeredAddress =
nameserverRegistryService.requireRegisteredAddress(namesrvAddr);
+ return clientProvider.findConnectionsAt(registeredAddress,
normalizeFilter(clusterId), normalizeFilter(type));
}
private String requireInstanceId(String instanceId) {
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
index 908cb4095..516f6bac0 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
@@ -39,6 +39,16 @@ public class NameserverRegistryService {
.toList();
}
+ public String requireRegisteredAddress(String rawAddress) {
+ String normalized = NamesrvAddrParser.normalize(rawAddress);
+ Long matches = nameserverMapper.selectCount(new
QueryWrapper<RmqNameserver>()
+ .eq("namesrv_addr", normalized));
+ if (matches == null || matches == 0L) {
+ throw new BusinessException(404, "NameServer endpoint is not
registered: " + normalized);
+ }
+ return normalized;
+ }
+
public NameserverRegistryVO create(CreateNameserverRegistryDTO command) {
String name = normalizeName(command.getName());
Long existing = nameserverMapper.selectCount(new
QueryWrapper<RmqNameserver>()
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
index 7ea32724d..78fef0525 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.cluster.client;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.cluster.nameserver.NameserverRegistryService;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -37,6 +38,9 @@ class ClientServiceTest {
@Mock
private ClientProvider clientProvider;
+ @Mock
+ private NameserverRegistryService nameserverRegistryService;
+
@InjectMocks
private ClientService clientService;
@@ -74,15 +78,31 @@ class ClientServiceTest {
}
@Test
- void listConnectionsAtShouldTrimAndDelegateToProvider() {
- when(clientProvider.findConnectionsAt("10.0.1.31:9876",
"DefaultCluster", null))
+ void listConnectionsAtShouldUseRegisteredNormalizedAddressTest() {
+ when(nameserverRegistryService.requireRegisteredAddress(" NS1:9876 ;
ns2:9876 "))
+ .thenReturn("ns1:9876,ns2:9876");
+ when(clientProvider.findConnectionsAt("ns1:9876,ns2:9876",
"DefaultCluster", null))
.thenReturn(List.of());
List<ClientConnectionVO> result =
- clientService.listConnectionsAt(" 10.0.1.31:9876 ", "
DefaultCluster ", " ");
+ clientService.listConnectionsAt(" NS1:9876 ; ns2:9876 ", "
DefaultCluster ", " ");
assertThat(result).isEmpty();
- verify(clientProvider).findConnectionsAt("10.0.1.31:9876",
"DefaultCluster", null);
+ verify(nameserverRegistryService).requireRegisteredAddress(" NS1:9876
; ns2:9876 ");
+ verify(clientProvider).findConnectionsAt("ns1:9876,ns2:9876",
"DefaultCluster", null);
+ }
+
+ @Test
+ void listConnectionsAtShouldRejectUnregisteredAddressBeforeProviderTest() {
+
when(nameserverRegistryService.requireRegisteredAddress("10.0.9.9:9876"))
+ .thenThrow(new BusinessException(404,
+ "NameServer endpoint is not registered:
10.0.9.9:9876"));
+
+ assertThatThrownBy(() ->
clientService.listConnectionsAt("10.0.9.9:9876", null, null))
+ .isInstanceOf(BusinessException.class)
+ .satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(404));
+
+ verifyNoInteractions(clientProvider);
}
@Test
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
index 29a50f398..c0020903e 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
@@ -80,6 +80,35 @@ class NameserverRegistryServiceTest {
verify(nameserverMapper).selectList(any());
}
+ @Test
+ void requireRegisteredAddressShouldNormalizeBeforeLookupTest() {
+ when(nameserverMapper.selectCount(any())).thenReturn(1L);
+
+ String result = service.requireRegisteredAddress(" NS1:9876 ; ns2:9876
");
+
+ assertThat(result).isEqualTo("ns1:9876,ns2:9876");
+ verify(nameserverMapper).selectCount(any());
+ }
+
+ @Test
+ void requireRegisteredAddressShouldRejectUnregisteredEndpointTest() {
+ when(nameserverMapper.selectCount(any())).thenReturn(0L);
+
+ assertThatThrownBy(() -> service.requireRegisteredAddress(" NS1:9876 ;
ns2:9876 "))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("NameServer endpoint is not registered:
ns1:9876,ns2:9876")
+ .satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(404));
+ }
+
+ @Test
+ void
requireRegisteredAddressShouldRejectMalformedEndpointBeforeDatabaseLookupTest()
{
+ assertThatThrownBy(() -> service.requireRegisteredAddress("ns1"))
+ .isInstanceOf(BusinessException.class)
+ .satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(400));
+
+ verify(nameserverMapper, never()).selectCount(any());
+ }
+
@Test
void createShouldPersistAndReturnStoredEntryTest() {
when(nameserverMapper.selectCount(any())).thenReturn(0L);