提交 afaa63fd 编写于 作者: R Rajan 提交者: Matteo Merli

Async authorization check while creation of producer/consumer (#98)

上级 15683fd0
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
package com.yahoo.pulsar.broker.authorization; package com.yahoo.pulsar.broker.authorization;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -26,7 +26,6 @@ import com.yahoo.pulsar.broker.ServiceConfiguration; ...@@ -26,7 +26,6 @@ import com.yahoo.pulsar.broker.ServiceConfiguration;
import com.yahoo.pulsar.broker.cache.ConfigurationCacheService; import com.yahoo.pulsar.broker.cache.ConfigurationCacheService;
import com.yahoo.pulsar.common.naming.DestinationName; import com.yahoo.pulsar.common.naming.DestinationName;
import com.yahoo.pulsar.common.policies.data.AuthAction; import com.yahoo.pulsar.common.policies.data.AuthAction;
import com.yahoo.pulsar.common.policies.data.Policies;
/** /**
*/ */
...@@ -50,9 +49,19 @@ public class AuthorizationManager { ...@@ -50,9 +49,19 @@ public class AuthorizationManager {
* @param role * @param role
* the app id used to send messages to the destination. * the app id used to send messages to the destination.
*/ */
public boolean canProduce(DestinationName destination, String role) { public CompletableFuture<Boolean> canProduceAsync(DestinationName destination, String role) {
return checkAuthorization(destination, role, AuthAction.produce); return checkAuthorization(destination, role, AuthAction.produce);
} }
public boolean canProduce(DestinationName destination, String role) {
try {
return canProduceAsync(destination, role).get();
} catch (Exception e) {
log.warn("Producer-client with Role - {} failed to get permissions for destination - {}", role,
destination, e);
return false;
}
}
/** /**
* Check if the specified role has permission to receive messages from the specified fully qualified destination * Check if the specified role has permission to receive messages from the specified fully qualified destination
...@@ -63,9 +72,19 @@ public class AuthorizationManager { ...@@ -63,9 +72,19 @@ public class AuthorizationManager {
* @param role * @param role
* the app id used to receive messages from the destination. * the app id used to receive messages from the destination.
*/ */
public boolean canConsume(DestinationName destination, String role) { public CompletableFuture<Boolean> canConsumeAsync(DestinationName destination, String role) {
return checkAuthorization(destination, role, AuthAction.consume); return checkAuthorization(destination, role, AuthAction.consume);
} }
public boolean canConsume(DestinationName destination, String role) {
try {
return canConsumeAsync(destination, role).get();
} catch (Exception e) {
log.warn("Consumer-client with Role - {} failed to get permissions for destination - {}", role,
destination, e);
return false;
}
}
/** /**
* Check whether the specified role can perform a lookup for the specified destination. * Check whether the specified role can perform a lookup for the specified destination.
...@@ -80,10 +99,14 @@ public class AuthorizationManager { ...@@ -80,10 +99,14 @@ public class AuthorizationManager {
return canProduce(destination, role) || canConsume(destination, role); return canProduce(destination, role) || canConsume(destination, role);
} }
private boolean checkAuthorization(DestinationName destination, String role, AuthAction action) { private CompletableFuture<Boolean> checkAuthorization(DestinationName destination, String role,
if (isSuperUser(role)) AuthAction action) {
return true; if (isSuperUser(role)) {
return checkPermission(destination, role, action) && checkCluster(destination); return CompletableFuture.completedFuture(true);
} else {
return checkPermission(destination, role, action)
.thenApply(isPermission -> isPermission && checkCluster(destination));
}
} }
private boolean checkCluster(DestinationName destination) { private boolean checkCluster(DestinationName destination) {
...@@ -98,38 +121,49 @@ public class AuthorizationManager { ...@@ -98,38 +121,49 @@ public class AuthorizationManager {
} }
} }
public boolean checkPermission(DestinationName destination, String role, AuthAction action) { public CompletableFuture<Boolean> checkPermission(DestinationName destination, String role,
AuthAction action) {
CompletableFuture<Boolean> permissionFuture = new CompletableFuture<>();
try { try {
Optional<Policies> policies = configCache.policiesCache().get(POLICY_ROOT + destination.getNamespace()); configCache.policiesCache().getAsync(POLICY_ROOT + destination.getNamespace()).thenAccept(policies -> {
if (!policies.isPresent()) { if (!policies.isPresent()) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Policies node couldn't be found for destination : {}", destination); log.debug("Policies node couldn't be found for destination : {}", destination);
}
permissionFuture.complete(false);
} else {
Set<AuthAction> namespaceActions = policies.get().auth_policies.namespace_auth.get(role);
if (namespaceActions != null && namespaceActions.contains(action)) {
// The role has namespace level permission
permissionFuture.complete(true);
} else {
Map<String, Set<AuthAction>> roles = policies.get().auth_policies.destination_auth
.get(destination.toString());
if (roles == null) {
// Destination has no custom policy
permissionFuture.complete(false);
} else {
Set<AuthAction> resourceActions = roles.get(role);
if (resourceActions != null && resourceActions.contains(action)) {
// The role has destination level permission
permissionFuture.complete(true);
} else {
permissionFuture.complete(false);
}
}
}
} }
return false; }).exceptionally(ex -> {
} log.warn("Client with Role - {} failed to get permissions for destination - {}", role, destination,
ex);
Set<AuthAction> namespaceActions = policies.get().auth_policies.namespace_auth.get(role); permissionFuture.complete(false);
if (namespaceActions != null && namespaceActions.contains(action)) { return null;
// The role has namespace level permission });
return true;
}
Map<String, Set<AuthAction>> roles = policies.get().auth_policies.destination_auth.get(destination.toString());
if (roles == null) {
// Destination has no custom policy
return false;
}
Set<AuthAction> resourceActions = roles.get(role);
if (resourceActions != null && resourceActions.contains(action)) {
// The role has destination level permission
return true;
}
return false;
} catch (Exception e) { } catch (Exception e) {
log.warn("Client with Role - {} failed to get permissions for destination - {}", role, destination, e); log.warn("Client with Role - {} failed to get permissions for destination - {}", role, destination, e);
return false; permissionFuture.complete(false);
} }
return permissionFuture;
} }
/** /**
......
...@@ -381,7 +381,7 @@ public class ServerCnxTest { ...@@ -381,7 +381,7 @@ public class ServerCnxTest {
@Test(timeOut = 30000) @Test(timeOut = 30000)
public void testProducerCommandWithAuthorizationPositive() throws Exception { public void testProducerCommandWithAuthorizationPositive() throws Exception {
AuthorizationManager authorizationManager = mock(AuthorizationManager.class); AuthorizationManager authorizationManager = mock(AuthorizationManager.class);
doReturn(true).when(authorizationManager).canProduce(Mockito.any(), Mockito.any()); doReturn(CompletableFuture.completedFuture(true)).when(authorizationManager).canProduceAsync(Mockito.any(), Mockito.any());
doReturn(authorizationManager).when(brokerService).getAuthorizationManager(); doReturn(authorizationManager).when(brokerService).getAuthorizationManager();
doReturn(true).when(brokerService).isAuthenticationEnabled(); doReturn(true).when(brokerService).isAuthenticationEnabled();
resetChannel(); resetChannel();
...@@ -409,7 +409,7 @@ public class ServerCnxTest { ...@@ -409,7 +409,7 @@ public class ServerCnxTest {
ConfigurationCacheService configCacheService = mock(ConfigurationCacheService.class); ConfigurationCacheService configCacheService = mock(ConfigurationCacheService.class);
doReturn(configCacheService).when(pulsar).getConfigurationCache(); doReturn(configCacheService).when(pulsar).getConfigurationCache();
doReturn(zkDataCache).when(configCacheService).policiesCache(); doReturn(zkDataCache).when(configCacheService).policiesCache();
doThrow(new NoNodeException()).when(zkDataCache).get(matches(".*nonexistent.*")); doReturn(CompletableFuture.completedFuture(Optional.empty())).when(zkDataCache).getAsync(matches(".*nonexistent.*"));
AuthorizationManager authorizationManager = spy(new AuthorizationManager(svcConfig, configCacheService)); AuthorizationManager authorizationManager = spy(new AuthorizationManager(svcConfig, configCacheService));
doReturn(authorizationManager).when(brokerService).getAuthorizationManager(); doReturn(authorizationManager).when(brokerService).getAuthorizationManager();
...@@ -440,7 +440,7 @@ public class ServerCnxTest { ...@@ -440,7 +440,7 @@ public class ServerCnxTest {
doReturn(authorizationManager).when(brokerService).getAuthorizationManager(); doReturn(authorizationManager).when(brokerService).getAuthorizationManager();
doReturn(true).when(brokerService).isAuthorizationEnabled(); doReturn(true).when(brokerService).isAuthorizationEnabled();
doReturn(false).when(authorizationManager).isSuperUser(Mockito.anyString()); doReturn(false).when(authorizationManager).isSuperUser(Mockito.anyString());
doReturn(true).when(authorizationManager).checkPermission(any(DestinationName.class), Mockito.anyString(), doReturn(CompletableFuture.completedFuture(true)).when(authorizationManager).checkPermission(any(DestinationName.class), Mockito.anyString(),
any(AuthAction.class)); any(AuthAction.class));
resetChannel(); resetChannel();
...@@ -493,7 +493,7 @@ public class ServerCnxTest { ...@@ -493,7 +493,7 @@ public class ServerCnxTest {
public void testProducerCommandWithAuthorizationNegative() throws Exception { public void testProducerCommandWithAuthorizationNegative() throws Exception {
AuthorizationManager authorizationManager = mock(AuthorizationManager.class); AuthorizationManager authorizationManager = mock(AuthorizationManager.class);
doReturn(false).when(authorizationManager).canProduce(Mockito.any(), Mockito.any()); doReturn(CompletableFuture.completedFuture(false)).when(authorizationManager).canProduceAsync(Mockito.any(), Mockito.any());
doReturn(authorizationManager).when(brokerService).getAuthorizationManager(); doReturn(authorizationManager).when(brokerService).getAuthorizationManager();
doReturn(true).when(brokerService).isAuthenticationEnabled(); doReturn(true).when(brokerService).isAuthenticationEnabled();
doReturn(true).when(brokerService).isAuthorizationEnabled(); doReturn(true).when(brokerService).isAuthorizationEnabled();
...@@ -1022,7 +1022,7 @@ public class ServerCnxTest { ...@@ -1022,7 +1022,7 @@ public class ServerCnxTest {
@Test(timeOut = 30000) @Test(timeOut = 30000)
public void testSubscribeCommandWithAuthorizationPositive() throws Exception { public void testSubscribeCommandWithAuthorizationPositive() throws Exception {
AuthorizationManager authorizationManager = mock(AuthorizationManager.class); AuthorizationManager authorizationManager = mock(AuthorizationManager.class);
doReturn(true).when(authorizationManager).canConsume(Mockito.any(), Mockito.any()); doReturn(CompletableFuture.completedFuture(true)).when(authorizationManager).canConsumeAsync(Mockito.any(), Mockito.any());
doReturn(authorizationManager).when(brokerService).getAuthorizationManager(); doReturn(authorizationManager).when(brokerService).getAuthorizationManager();
doReturn(true).when(brokerService).isAuthenticationEnabled(); doReturn(true).when(brokerService).isAuthenticationEnabled();
doReturn(true).when(brokerService).isAuthorizationEnabled(); doReturn(true).when(brokerService).isAuthorizationEnabled();
...@@ -1042,7 +1042,7 @@ public class ServerCnxTest { ...@@ -1042,7 +1042,7 @@ public class ServerCnxTest {
@Test(timeOut = 30000) @Test(timeOut = 30000)
public void testSubscribeCommandWithAuthorizationNegative() throws Exception { public void testSubscribeCommandWithAuthorizationNegative() throws Exception {
AuthorizationManager authorizationManager = mock(AuthorizationManager.class); AuthorizationManager authorizationManager = mock(AuthorizationManager.class);
doReturn(false).when(authorizationManager).canConsume(Mockito.any(), Mockito.any()); doReturn(CompletableFuture.completedFuture(false)).when(authorizationManager).canConsumeAsync(Mockito.any(), Mockito.any());
doReturn(authorizationManager).when(brokerService).getAuthorizationManager(); doReturn(authorizationManager).when(brokerService).getAuthorizationManager();
doReturn(true).when(brokerService).isAuthenticationEnabled(); doReturn(true).when(brokerService).isAuthenticationEnabled();
doReturn(true).when(brokerService).isAuthorizationEnabled(); doReturn(true).when(brokerService).isAuthorizationEnabled();
......
...@@ -22,6 +22,7 @@ import java.io.IOException; ...@@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import javax.naming.AuthenticationException; import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
...@@ -73,11 +74,16 @@ public abstract class AbstractWebSocketHandler extends WebSocketAdapter implemen ...@@ -73,11 +74,16 @@ public abstract class AbstractWebSocketHandler extends WebSocketAdapter implemen
} }
} }
if (service.isAuthorizationEnabled() && !isAuthorized(authRole)) { if (service.isAuthorizationEnabled()) {
log.warn("[{}] WebSocket Client [{}] is not authorized on topic {}", session.getRemoteAddress(), authRole, final String role = authRole;
topic); isAuthorized(authRole).thenApply(isAuthorized -> {
close(WebSocketError.NotAuthorizedError); if(!isAuthorized) {
return; log.warn("[{}] WebSocket Client [{}] is not authorized on topic {}", session.getRemoteAddress(), role,
topic);
close(WebSocketError.NotAuthorizedError);
}
return null;
});
} }
} }
...@@ -120,7 +126,7 @@ public abstract class AbstractWebSocketHandler extends WebSocketAdapter implemen ...@@ -120,7 +126,7 @@ public abstract class AbstractWebSocketHandler extends WebSocketAdapter implemen
return null; return null;
} }
protected abstract boolean isAuthorized(String authRole); protected abstract CompletableFuture<Boolean> isAuthorized(String authRole);
private String extractTopicName(HttpServletRequest request) { private String extractTopicName(HttpServletRequest request) {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
......
...@@ -24,6 +24,7 @@ import java.time.ZoneId; ...@@ -24,6 +24,7 @@ import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
...@@ -177,8 +178,8 @@ public class ConsumerHandler extends AbstractWebSocketHandler { ...@@ -177,8 +178,8 @@ public class ConsumerHandler extends AbstractWebSocketHandler {
} }
@Override @Override
protected boolean isAuthorized(String authRole) { protected CompletableFuture<Boolean> isAuthorized(String authRole) {
return service.getAuthorizationManager().canConsume(DestinationName.get(topic), authRole); return service.getAuthorizationManager().canConsumeAsync(DestinationName.get(topic), authRole);
} }
private static String extractSubscription(HttpServletRequest request) { private static String extractSubscription(HttpServletRequest request) {
......
...@@ -17,6 +17,7 @@ package com.yahoo.pulsar.websocket; ...@@ -17,6 +17,7 @@ package com.yahoo.pulsar.websocket;
import java.io.IOException; import java.io.IOException;
import java.util.Base64; import java.util.Base64;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
...@@ -119,8 +120,8 @@ public class ProducerHandler extends AbstractWebSocketHandler { ...@@ -119,8 +120,8 @@ public class ProducerHandler extends AbstractWebSocketHandler {
}); });
} }
protected boolean isAuthorized(String authRole) { protected CompletableFuture<Boolean> isAuthorized(String authRole) {
return service.getAuthorizationManager().canProduce(DestinationName.get(topic), authRole); return service.getAuthorizationManager().canProduceAsync(DestinationName.get(topic), authRole);
} }
private void sendAckResponse(ProducerAck response) { private void sendAckResponse(ProducerAck response) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册