unbridled-41 opened a new pull request, #5448:
URL: https://github.com/apache/rocketmq-dashboard/pull/5448
## Problem / Evidence
A DLQ resend performs a group read and a topic write without the
resource-ownership guard every sibling write path enforces, so any Apache
instance registered against the same cluster can replay another instance's dead
letters.
- `instance/dlq/DLQService.java` — `resendMessages(...)` (`:56-66`) and
`resendSelectedMessages(...)` (`:87-103`) call only
`requireApacheInstance(instanceId)` plus name/time validation, then delegate to
the provider.
- Full path: `DLQController.resendMessages` → `DLQService` →
`RocketMQDLQProvider.resendMessages` → `resendAll` (`:559-583`) →
`runtimeAdminClientResolver.executeProducer(instanceId, …)` →
`producer.send(...)` into `targetTopic` or the dead letter's origin topic.
Nothing in the provider or the service consults `ResourceOwnershipGuard`
(`grep` for it in `RocketMQDLQProvider.java`: 0 hits).
- The siblings all do: `MetadataService.sendMessage` (`:293-299`),
`redeliverMessage` (`:319-342`), `resetOffset`
(`RocketMQAdminClientImpl:1147-1157`), `consumeMessageDirectly` (`:563-571`).
- Trigger: instances A and B both registered as `APACHE` against one
cluster, A owning group `G` and topic `T`. `POST /api/dlq/resend
{"instanceId":"B","groupName":"G","targetTopic":"T"}` reads A's `%DLQ%G` and
publishes into A's `T` (the same request through the AI `redelivery_dlq` tool).
Expected: 409/404 from the ownership guard.
`ResourceOwnershipGuard.topicResource` already maps `%DLQ%<group>` onto the
group's ownership, so the guard expresses this resource exactly.
## Root cause / Fix
The ownership-isolation feature (`b42a6ed1`) covered the topic/group/message
providers but never `DLQService`/`RocketMQDLQProvider`. Wrap both resend entry
points in `withOwned` for the GROUP resource — plus the TOPIC resource when an
explicit target is named — so a foreign resource is refused with 409 before
anything is read or published:
```java
InstanceVO instance = ownershipGuard.requireInstance(instanceId);
List<Resource> resources = new ArrayList<>();
resources.add(new Resource(Kind.GROUP, groupName));
if (StringUtils.hasText(targetTopic)) {
resources.add(ownershipGuard.topicResource(targetTopic));
}
return ownershipGuard.withOwned(instance, resources, action);
```
The implicit destination (a dead letter's own origin topic) is not known
until the provider resolves it per message; the group check above covers the
DLQ read and the explicit-target case, and extending the guard to per-message
destinations belongs to the provider.
## Priority & scoring
PRIORITY 73 = 影响 28 (cross-instance authorization bypass: another instance's
dead letters are read and republished, audited as success) + 波及 12 (both resend
endpoints, every Apache instance sharing a cluster, and the AI tool that
forwards a caller-supplied target) + 可复现 18 (deterministic with two instances
on one cluster) + 维护价值 15 (brings the last unguarded topic/group write in line).
FIX_CONFIDENCE 80: the guard and the resource mapping already exist; the
wrapper mirrors `redeliverMessage`, and the added tests pin both the check and
the refusal.
## Tests
```
cd server && mvn -o test
-Dtest='DLQServiceTest,DLQControllerTest,RocketMQDLQProviderTest'
```
- Red before the fix:
`resendMessagesShouldCheckOwnershipOfTheGroupAndTheTargetTopicTest`,
`resendSelectedMessagesShouldCheckOwnershipOfTheGroupOnlyWhenNoTargetIsNamedTest`
and `resendMessagesShouldNotReachTheProviderWhenOwnershipRefusesTest` all fail
(the guard is never consulted and a refusal does not stop the provider).
- Green after: `Tests run: 91, Failures: 0, Errors: 0, Skipped: 0`.
- The fixtures stub the guard to pass through by default (lenient, because
validation-failure tests never reach it) and the refusal test asserts the
provider is never called.
- `mvn -o checkstyle:check` passes.
## Risk
Low. Ownership is checked only on the resend paths; listing/exporting a DLQ
is untouched. An instance that owns the group and the target (the normal case)
sees no change.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]