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 b88bc2fb8 fix(acl): report unrestricted account sources in risk
diagnostics (#7291)
b88bc2fb8 is described below
commit b88bc2fb8268b1b6c5a98b6d7eb746e82e9a5525
Author: coder999o <[email protected]>
AuthorDate: Mon Oct 26 20:09:00 2026 +0800
fix(acl): report unrestricted account sources in risk diagnostics (#7291)
* fix(acl): report unrestricted account sources in risk diagnostics
---
web/src/pages/instance/__tests__/AclPage.test.tsx | 48 ++++++++++++-----------
web/src/utils/aclRiskDiagnostics.test.ts | 31 +++++++++++++++
web/src/utils/aclRiskDiagnostics.ts | 7 +++-
3 files changed, 62 insertions(+), 24 deletions(-)
diff --git a/web/src/pages/instance/__tests__/AclPage.test.tsx
b/web/src/pages/instance/__tests__/AclPage.test.tsx
index 6be2f6fca..cfb3c8182 100644
--- a/web/src/pages/instance/__tests__/AclPage.test.tsx
+++ b/web/src/pages/instance/__tests__/AclPage.test.tsx
@@ -213,18 +213,20 @@ describe('ACL page', () => {
},
]);
vi.mocked(aclService.listAclRules).mockResolvedValue({
- items: [{
- id: 21,
- principal: 'directory-only-user',
- resource: 'orders',
- resourceType: 'Topic',
- resourcePattern: 'LITERAL',
- actions: ['PUB'],
- decision: 'ALLOW',
- scope: 'cluster',
- aclVersion: 2,
- gmtCreate: '2026-07-23T00:00:00Z',
- }],
+ items: [
+ {
+ id: 21,
+ principal: 'directory-only-user',
+ resource: 'orders',
+ resourceType: 'Topic',
+ resourcePattern: 'LITERAL',
+ actions: ['PUB'],
+ decision: 'ALLOW',
+ scope: 'cluster',
+ aclVersion: 2,
+ gmtCreate: '2026-07-23T00:00:00Z',
+ },
+ ],
total: 1,
page: 1,
size: 20,
@@ -1100,14 +1102,16 @@ describe('ACL page', () => {
aclEnabled: true,
aclVersion: 'ACL 2.0',
globalWhiteRemoteAddresses: [],
- accounts: [{
- accessKey: 'cluster-a-account',
- admin: false,
- defaultTopicPerm: 'DENY',
- defaultGroupPerm: 'DENY',
- topicPerms: [],
- groupPerms: [],
- }],
+ accounts: [
+ {
+ accessKey: 'cluster-a-account',
+ admin: false,
+ defaultTopicPerm: 'DENY',
+ defaultGroupPerm: 'DENY',
+ topicPerms: [],
+ groupPerms: [],
+ },
+ ],
accountCount: 1,
})
.mockRejectedValueOnce(new Error('cluster-b unavailable'));
@@ -1118,7 +1122,7 @@ describe('ACL page', () => {
const examineButton = screen.getByRole('button', { name: /检\s*查\s*配\s*置/
});
await user.type(clusterInput, 'cluster-a');
await user.click(examineButton);
- expect(await screen.findByText('cluster-a-account')).toBeInTheDocument();
+ expect(await
screen.findAllByText('cluster-a-account')).not.toHaveLength(0);
expect(screen.getByText('集群: cluster-a')).toBeInTheDocument();
await user.clear(clusterInput);
@@ -1127,7 +1131,7 @@ describe('ACL page', () => {
await waitFor(() =>
expect(aclService.examineBrokerClusterAclConfig).toHaveBeenCalledTimes(2));
await waitFor(() =>
expect(examineButton).not.toHaveClass('ant-btn-loading'));
- expect(screen.queryByText('cluster-a-account')).not.toBeInTheDocument();
+ expect(screen.queryAllByText('cluster-a-account')).toHaveLength(0);
expect(screen.queryByText('集群: cluster-a')).not.toBeInTheDocument();
});
diff --git a/web/src/utils/aclRiskDiagnostics.test.ts
b/web/src/utils/aclRiskDiagnostics.test.ts
index 210a7f4ab..7d53b5b9e 100644
--- a/web/src/utils/aclRiskDiagnostics.test.ts
+++ b/web/src/utils/aclRiskDiagnostics.test.ts
@@ -61,6 +61,37 @@ describe('ACL risk diagnostics', () => {
]);
});
+ it.each([undefined, null, '', ' \t '])(
+ 'reports an unrestricted account source for %jTest',
+ (whiteRemoteAddress) => {
+ const diagnostics = analyzeAclRisk(config({ accounts: [account({
whiteRemoteAddress })] }));
+
+ expect(diagnostics.status).toBe('critical');
+ expect(diagnostics.summary.broadWhitelistCount).toBe(1);
+ expect(diagnostics.issues).toEqual([
+ expect.objectContaining({
+ code: 'BROAD_ACCOUNT_WHITELIST',
+ severity: 'critical',
+ evidence: ['<empty>'],
+ }),
+ ]);
+ },
+ );
+
+ it('reports an unrestricted administrator despite deny-by-default
permissionsTest', () => {
+ const diagnostics = analyzeAclRisk(
+ config({ accounts: [account({ admin: true, whiteRemoteAddress: '' })] }),
+ );
+
+ expect(diagnostics.status).toBe('critical');
+ expect(diagnostics.score).toBeLessThan(100);
+ expect(diagnostics.summary.broadWhitelistCount).toBe(1);
+ expect(diagnostics.issues.map((item) => item.code)).toEqual(
+ expect.arrayContaining(['BROAD_ACCOUNT_WHITELIST',
'ADMIN_WITH_BROAD_ACCESS']),
+ );
+ expect(diagnostics.issues.some((item) => item.code ===
'BROAD_GLOBAL_WHITELIST')).toBe(false);
+ });
+
it('flags disabled ACL and missing accounts as critical risks', () => {
const diagnostics = analyzeAclRisk(
config({
diff --git a/web/src/utils/aclRiskDiagnostics.ts
b/web/src/utils/aclRiskDiagnostics.ts
index 17a47ab57..74efa0651 100644
--- a/web/src/utils/aclRiskDiagnostics.ts
+++ b/web/src/utils/aclRiskDiagnostics.ts
@@ -228,8 +228,11 @@ const collectDuplicateAccessKeys = (accounts:
PlainAccessConfig[]): Set<string>
return duplicates;
};
-const accountWhitelistClass = (account: PlainAccessConfig): WhitelistClass =>
- classifyWhitelist(splitWhitelist(account.whiteRemoteAddress));
+const accountWhitelistClass = (account: PlainAccessConfig): WhitelistClass => {
+ const addresses = splitWhitelist(account.whiteRemoteAddress);
+ // An empty account whitelist imposes no source restriction, unlike an empty
global trust list.
+ return addresses.length === 0 ? 'open' : classifyWhitelist(addresses);
+};
const addDefaultPermissionIssues = (
issues: AclRiskIssue[],