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]

Reply via email to