diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptor.java index 9f4d7052839..b831deba568 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptor.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptor.java @@ -193,7 +193,17 @@ public ServerCall.Listener interceptCall(ServerCall call, Metada // After the fail-closed guards above, requireAuth implies authzEnabled, so the ACL is // enforced exactly when the cluster authorization switch is on; there is no path where // requireAuth alone would ask the (globally gated) evaluator to enforce and be ignored. - if (resourceAction != null && authzEnabled) { + if (authzEnabled) { + // A served method that is not in the map cannot be authorized: skipping the check + // would run the call with authentication but no ACL while the engine is on. The + // Admin surface grows through rocketmq-apis, so deny and point at the map instead + // of silently serving the next RPC that misses an entry here. + if (resourceAction == null) { + call.close(Status.PERMISSION_DENIED.withDescription( + "admin method " + method + " is not mapped to a proxy.admin.* resource; " + + "refusing to serve it without an ACL check"), new Metadata()); + return noopListener(); + } if (StringUtils.isBlank(username)) { call.close(Status.UNAUTHENTICATED.withDescription("missing credentials for proxy admin"), new Metadata()); diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptorTest.java index c44038a7272..8643d827630 100644 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptorTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/admin/ProxyAdminAuthInterceptorTest.java @@ -127,6 +127,43 @@ private static ProxyAdminAuthInterceptor.ResourceAction ra(String resource, Acti return new ProxyAdminAuthInterceptor.ResourceAction(resource, action); } + @Test + public void everyServedAdminMethodHasAnAclMapping() { + // the served surface comes from rocketmq-apis (admin.proto) while the ACL map is + // maintained by hand: fail this test the moment a new RPC lands without an entry, + // instead of discovering the gap on a production call + for (io.grpc.MethodDescriptor served : + apache.rocketmq.v2.AdminGrpc.getServiceDescriptor().getMethods()) { + String bare = served.getBareMethodName(); + assertNotNull("no proxy.admin.* ACL mapping for served admin method " + bare, + ProxyAdminAuthInterceptor.resolveResourceAction(bare)); + } + } + + @Test + @SuppressWarnings("unchecked") + public void unmappedMethodIsDeniedWhenAuthorizationEnabled() { + // authorization on, no credentials wired: a MAPPED method demands credentials (see + // authorizationEnabledRejectsMissingCredentials), but an unmapped one used to skip the + // ACL branch entirely and run — fail closed instead + AuthConfig authConfig = new AuthConfig(); + authConfig.setConfigName("proxy-admin-unmapped-test-" + System.nanoTime()); + authConfig.setAuthenticationEnabled(false); + authConfig.setAuthorizationEnabled(true); + ConfigurationManager.getProxyConfig().setGrpcAdminServerAuthEnable(false); + + ProxyAdminAuthInterceptor interceptor = new ProxyAdminAuthInterceptor(authConfig, messagingProcessor); + ServerCall call = serverCall("SomeMethodAddedInAFutureRelease"); + ServerCallHandler next = mock(ServerCallHandler.class); + + interceptor.interceptCall(call, new Metadata(), next); + verify(next, never()).startCall(any(), any()); + org.mockito.ArgumentCaptor statusCaptor = org.mockito.ArgumentCaptor.forClass(Status.class); + verify(call).close(statusCaptor.capture(), any(Metadata.class)); + assertEquals(Status.Code.PERMISSION_DENIED, statusCaptor.getValue().getCode()); + assertTrue(statusCaptor.getValue().getDescription().contains("not mapped")); + } + // --------------------------------------------------------------------- // behavior modes // ---------------------------------------------------------------------