iloveeyjafjalla opened a new issue, #11406: URL: https://github.com/apache/rocketmq/issues/11406
### Before Creating the Bug Report - [x] I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions). - [x] I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate. - [x] I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ. ### Runtime platform environment OS: any (broker with ACL 2.0 authorization enabled: `authorizationEnabled=true`, `authorizationMetadataProvider=...LocalAuthorizationMetadataProvider`) ### RocketMQ version branch: develop Git commit id: a7edeecfb (the file touched below is unchanged by that commit) ### JDK Version Compiler: Temurin 1.8.0_504 (target 1.8, same as CI) ### Describe the Bug Retry topics are authorized through the consumer group they belong to: the authorization context builder maps `%RETRY%<group>` to the `Group:<group>` resource in every request path that reads a caller-supplied topic, and PR #11009 ("Map retry topics to consumer-group resources for `VIEW_MESSAGE_BY_ID` authorization") extended that rule to `VIEW_MESSAGE_BY_ID` on purpose. `QUERY_MESSAGE` is the remaining read path that does not do it. `DefaultAuthorizationContextBuilder` builds the context as a **topic** resource for that request code: ```java case RequestCode.QUERY_MESSAGE: topic = Resource.ofTopic(requireResource(fields.get(TOPIC), "topic")); result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.GET), sourceIp)); break; ``` while the sibling paths that read a topic with the very same `SUB, GET` actions all map retry topics to the group, for example: ```java case RequestCode.VIEW_MESSAGE_BY_ID: String viewTopic = requireResource(fields.get(TOPIC), "topic"); Resource viewResource = NamespaceUtil.isRetryTopic(viewTopic) ? Resource.ofGroup(viewTopic) : Resource.ofTopic(viewTopic); result.add(DefaultAuthorizationContext.of(subject, viewResource, Action.GET, sourceIp)); break; ``` (`GET_MAX_OFFSET`, `SEARCH_OFFSET_BY_TIMESTAMP`, `GET_ROUTEINFO_BY_TOPIC`, `PULL_MESSAGE`, `LITE_PULL_MESSAGE`, `QUERY_CONSUMER_OFFSET`, `UPDATE_CONSUMER_OFFSET`, `LOCK_BATCH_MQ`, `UNLOCK_BATCH_MQ` and `HEART_BEAT` behave the same way.) Because `Resource.isMatch` refuses to match two different resource types (`auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Resource.java:111-113`), a role that grants `Group:<group>` with `SUB`/`GET` can read, pull and view the retry messages of that group but can never query them by key: the built resource `Topic:%RETRY%<group>` only matches a policy that also grants the internal retry topic as a topic, which is exactly the kind of grant the group convention avoids. ### Steps to Reproduce 1. Enable ACL 2.0 authorization on a broker and create a role/user that is granted `Group:myGroup` (actions `SUB`, `GET`), i.e. the permission that lets the same user pull and view that group's retry messages. 2. Run `mqadmin queryMsgByKey -t %RETRY%myGroup -k <someKey> -n <namesrv>` as that user (client path: `QueryMsgByKeySubCommand` → `MQClientAPIImpl.queryMessage` → `RequestCode.QUERY_MESSAGE`). 3. At the unit level the same step is reproduced deterministically by building the request: ```java QueryMessageRequestHeader header = new QueryMessageRequestHeader(); header.setTopic("%RETRY%myGroup"); List<DefaultAuthorizationContext> result = builder.build(channelHandlerContext, remotingRequest(RequestCode.QUERY_MESSAGE, header, null)); // observed: Topic:%RETRY%myGroup // expected: Group:myGroup (the same resource the sibling read paths produce) ``` ### What Did You Expect to See? The query is authorized by the `Group:myGroup` grant, like every other read of a retry topic: the context is `Group:myGroup` with actions `SUB, GET`, so the request reaches the broker and returns the matching messages. ### What Did You See Instead? The context is `Topic:%RETRY%myGroup`, which no group-level policy can match, so the request is rejected with `NO_PERMISSION` (`CODE: 1` / `DESC: Authentication failed`/authorization denial) even though the caller is authorized for the retry topic's consumer group. The only way to make the command work is to grant the internal retry topic as a topic resource, which contradicts the convention the other paths follow. ### Additional Context Minimal fix: give the `QUERY_MESSAGE` case the same retry-topic mapping its sibling cases already use, i.e. ```java case RequestCode.QUERY_MESSAGE: String queryMessageTopic = requireResource(fields.get(TOPIC), "topic"); Resource queryMessageResource = NamespaceUtil.isRetryTopic(queryMessageTopic) ? Resource.ofGroup(queryMessageTopic) : Resource.ofTopic(queryMessageTopic); result.add(DefaultAuthorizationContext.of(subject, queryMessageResource, Arrays.asList(Action.SUB, Action.GET), sourceIp)); break; ``` Non-retry topics are unaffected, and the request codes that also read a topic but cannot legitimately carry a retry topic (`SEND_REPLY_MESSAGE`, `SEND_REPLY_MESSAGE_V2`, `RECALL_MESSAGE`, `END_TRANSACTION`, `QUERY_ASSIGNMENT`, `SET_MESSAGE_REQUEST_MODE`, `UPDATE_AND_CREATE_STATIC_TOPIC`) are left as they are. `BATCH_ACK_MESSAGE` and `CHECK_CLIENT_CONFIG` additionally authorize the consumer group of the same request and are out of scope here. I will open a PR with this change plus a unit test in `DefaultAuthorizationContextBuilderTest` (red before the change: `expected: Group:myGroup but was: Topic:%RETRY%myGroup`). -- 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]
