iloveeyjafjalla opened a new issue, #11362:
URL: https://github.com/apache/rocketmq/issues/11362

   ### 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 (proxy module; the affected path needs a PROXY-protocol ingress — 
HAProxy / NLB / any proxy-protocol load balancer — in front of the admin port)
   
   ### RocketMQ version
   
   branch: develop
   Git commit id: 9ea2ccdc2b41445aee5e177e14e501d606f5a1ed (2026-10-08)
   
   ### JDK Version
   
   Compiler: Temurin 1.8.0_504 (target 1.8, same as CI)
   
   ### Describe the Bug
   
   The Proxy Admin ACL evaluates the source IP against the transport peer 
instead of the client address.
   
   `ProxyAdminAuthInterceptor#resolveSourceIp` reads the raw transport address:
   
   ```java
   InetSocketAddress remoteAddress = (InetSocketAddress) call.getAttributes()
       .get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
   ```
   
   The admin server shares the data plane's negotiator 
(`ProxyAndTlsProtocolNegotiator`), so it accepts PROXY protocol connections, 
and its interceptor chain contains `HeaderInterceptor`, which resolves the real 
client address — the PROXY protocol address when present, the transport peer 
otherwise — and puts it into the metadata:
   
   ```java
   String remoteAddress = getProxyProtocolAddress(call.getAttributes());
   if (StringUtils.isBlank(remoteAddress)) {
       remoteAddress = 
parseSocketAddress(call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
   }
   GrpcUtils.putHeader(headers, GrpcConstants.REMOTE_ADDRESS, remoteAddress);
   ```
   
   The data plane authorizes against exactly that value 
(`DefaultAuthorizationContextBuilder` derives the source IP with
   `StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), 
":")`), while the admin surface bypasses it.
   Behind a proxy-protocol load balancer the transport peer is the balancer, so:
   
   - an ACL entry that whitelists the operator range (`-i <client-range>`) is 
evaluated against the balancer's address: operators are denied if the balancer 
is outside the range, and every client that can reach the balancer is accepted 
if it is inside the range (the `-i` restriction silently stops restricting);
   - the `sourceIp` field of every `[PROXY-ADMIN-AUDIT]` audit line records the 
balancer instead of the caller;
   - data plane and admin plane disagree about the source IP of the same client.
   
   ### Steps to Reproduce
   
   1. Run a proxy with `grpcAdminServerEnable=true` behind a load balancer that 
sends PROXY protocol to the admin port.
   2. Enable cluster authentication + authorization and add a policy for a 
`proxy.admin.*` resource with an `-i <client-range>` restriction.
   3. Call any admin RPC from a client inside the range: the decision is made 
against the load balancer's address, not the client's (the `RemoteAddress` seen 
by the ACL evaluator is the balancer).
   
   Unit level (see the pull request): build a `ServerCall` whose attributes 
carry the PROXY protocol client address while the
   transport peer is the balancer, and resolve the source IP — the peer address 
is used.
   
   ### Expected behavior
   
   The admin ACL subject uses the client address `HeaderInterceptor` resolved 
(the same value the data plane authorizes
   against), so `-i` restrictions and the audit log refer to the real caller.
   
   ### Actual behavior
   
   The transport peer (the load balancer) is used as the ACL source IP and in 
the audit log.
   
   ### Additional context
   
   Minimal fix: prefer the normalized `GrpcConstants.REMOTE_ADDRESS` header, 
keep the transport peer as the fallback for the
   case where the interceptor runs without the standard pipeline.
   


-- 
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