Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,17 @@ public <R, W> ServerCall.Listener<R> interceptCall(ServerCall<R, W> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte[], byte[]> call = serverCall("SomeMethodAddedInAFutureRelease");
ServerCallHandler<byte[], byte[]> next = mock(ServerCallHandler.class);

interceptor.interceptCall(call, new Metadata(), next);
verify(next, never()).startCall(any(), any());
org.mockito.ArgumentCaptor<Status> 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
// ---------------------------------------------------------------------
Expand Down