ARTEMIS-3569 Validate users on AMQP remote open

This commit is contained in:
Domenico Francesco Bruscino 2021-11-23 11:18:23 +01:00 committed by Bruscino Domenico Francesco
parent b979189187
commit f8472fd736
12 changed files with 87 additions and 37 deletions

View File

@ -58,7 +58,6 @@ import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerReceiverContext; import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerReceiverContext;
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerSenderContext; import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerSenderContext;
import org.apache.activemq.artemis.protocol.amqp.proton.transaction.ProtonTransactionHandler; import org.apache.activemq.artemis.protocol.amqp.proton.transaction.ProtonTransactionHandler;
import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASLResult;
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult; import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
import org.apache.activemq.artemis.spi.core.protocol.SessionCallback; import org.apache.activemq.artemis.spi.core.protocol.SessionCallback;
import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.spi.core.remoting.Connection;
@ -194,30 +193,20 @@ public class AMQPSessionCallback implements SessionCallback {
String name = UUIDGenerator.getInstance().generateStringUUID(); String name = UUIDGenerator.getInstance().generateStringUUID();
String user = null;
String passcode = null;
if (saslResult != null) {
user = saslResult.getUser();
if (saslResult instanceof PlainSASLResult) {
passcode = ((PlainSASLResult) saslResult).getPassword();
}
}
if (connection.isBridgeConnection()) { if (connection.isBridgeConnection()) {
serverSession = manager.getServer().createInternalSession(name, ActiveMQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE, protonSPI.getProtonConnectionDelegate(), // RemotingConnection remotingConnection, serverSession = manager.getServer().createInternalSession(name, ActiveMQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE, protonSPI.getProtonConnectionDelegate(), // RemotingConnection remotingConnection,
false, // boolean autoCommitSends false, // boolean autoCommitSends
false, // boolean autoCommitAcks, false, // boolean autoCommitAcks,
false, // boolean preAcknowledge, false, // boolean preAcknowledge,
true, //boolean xa, true, //boolean xa,
(String) null, this, true, operationContext, manager.getPrefixes(), manager.getSecurityDomain()); null, this, true, operationContext, manager.getPrefixes(), manager.getSecurityDomain());
} else { } else {
final String validatedUser = manager.getServer().validateUser(user, passcode, protonSPI.getProtonConnectionDelegate(), manager.getSecurityDomain()); serverSession = manager.getServer().createSession(name, connection.getUser(), connection.getPassword(), ActiveMQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE, protonSPI.getProtonConnectionDelegate(), // RemotingConnection remotingConnection,
serverSession = manager.getServer().createSession(name, user, passcode, ActiveMQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE, protonSPI.getProtonConnectionDelegate(), // RemotingConnection remotingConnection,
false, // boolean autoCommitSends false, // boolean autoCommitSends
false, // boolean autoCommitAcks, false, // boolean autoCommitAcks,
false, // boolean preAcknowledge, false, // boolean preAcknowledge,
true, //boolean xa, true, //boolean xa,
(String) null, this, true, operationContext, manager.getPrefixes(), manager.getSecurityDomain(), validatedUser); null, this, true, operationContext, manager.getPrefixes(), manager.getSecurityDomain(), connection.getValidatedUser());
} }
} }

View File

@ -18,6 +18,7 @@ package org.apache.activemq.artemis.protocol.amqp.proton;
import java.net.URI; import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -61,6 +62,7 @@ import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Source; import org.apache.qpid.proton.amqp.messaging.Source;
import org.apache.qpid.proton.amqp.messaging.TerminusExpiryPolicy; import org.apache.qpid.proton.amqp.messaging.TerminusExpiryPolicy;
import org.apache.qpid.proton.amqp.transaction.Coordinator; import org.apache.qpid.proton.amqp.transaction.Coordinator;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.Connection; import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Delivery; import org.apache.qpid.proton.engine.Delivery;
@ -109,6 +111,10 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
private final ScheduleOperator scheduleOp = new ScheduleOperator(new ScheduleRunnable()); private final ScheduleOperator scheduleOp = new ScheduleOperator(new ScheduleRunnable());
private final AtomicReference<Future<?>> scheduledFutureRef = new AtomicReference(VOID_FUTURE); private final AtomicReference<Future<?>> scheduledFutureRef = new AtomicReference(VOID_FUTURE);
private String user;
private String password;
private String validatedUser;
public AMQPConnectionContext(ProtonProtocolManager protocolManager, public AMQPConnectionContext(ProtonProtocolManager protocolManager,
AMQPConnectionCallback connectionSP, AMQPConnectionCallback connectionSP,
String containerId, String containerId,
@ -238,6 +244,18 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
return handler; return handler;
} }
public String getUser() {
return user;
}
public String getPassword() {
return password;
}
public String getValidatedUser() {
return validatedUser;
}
public void destroy() { public void destroy() {
handler.runLater(() -> connectionCallback.close()); handler.runLater(() -> connectionCallback.close());
} }
@ -530,8 +548,8 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
log.error("Error init connection", e); log.error("Error init connection", e);
} }
if ((connectionCallback.getTransportConnection().getRedirectTo() != null && protocolManager.getRedirectHandler() if (!validateUser(connection) || (connectionCallback.getTransportConnection().getRedirectTo() != null
.redirect(this, connection)) || !validateConnection(connection)) { && protocolManager.getRedirectHandler().redirect(this, connection)) || !validateConnection(connection)) {
connection.close(); connection.close();
} else { } else {
connection.setContext(AMQPConnectionContext.this); connection.setContext(AMQPConnectionContext.this);
@ -557,6 +575,37 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
} }
} }
private boolean validateUser(Connection connection) throws Exception {
user = null;
password = null;
validatedUser = null;
SASLResult saslResult = getSASLResult();
if (saslResult != null) {
user = saslResult.getUser();
if (saslResult instanceof PlainSASLResult) {
password = ((PlainSASLResult) saslResult).getPassword();
}
}
if (isIncomingConnection() && saslClientFactory == null && !isBridgeConnection()) {
try {
validatedUser = protocolManager.getServer().validateUser(user, password, connectionCallback.getProtonConnectionDelegate(), protocolManager.getSecurityDomain());
} catch (ActiveMQSecurityException e) {
log.warn(e.getMessage(), e);
ErrorCondition error = new ErrorCondition();
error.setCondition(AmqpError.UNAUTHORIZED_ACCESS);
error.setDescription(e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage());
connection.setCondition(error);
connection.setProperties(Collections.singletonMap(AmqpSupport.CONNECTION_OPEN_FAILED, true));
return false;
}
}
return true;
}
class ScheduleOperator implements UnaryOperator<Future<?>> { class ScheduleOperator implements UnaryOperator<Future<?>> {
private long delay; private long delay;

View File

@ -44,7 +44,7 @@ public class AMQPRedirectHandler extends RedirectHandler<AMQPRedirectContext> {
protected void cannotRedirect(AMQPRedirectContext context) { protected void cannotRedirect(AMQPRedirectContext context) {
ErrorCondition error = new ErrorCondition(); ErrorCondition error = new ErrorCondition();
error.setCondition(ConnectionError.CONNECTION_FORCED); error.setCondition(ConnectionError.CONNECTION_FORCED);
switch (context.getResult().status) { switch (context.getResult().getStatus()) {
case REFUSED_USE_ANOTHER: case REFUSED_USE_ANOTHER:
error.setDescription(String.format("Broker balancer %s, rejected this connection", context.getConnection().getTransportConnection().getRedirectTo())); error.setDescription(String.format("Broker balancer %s, rejected this connection", context.getConnection().getTransportConnection().getRedirectTo()));
break; break;

View File

@ -37,7 +37,7 @@ public class MQTTRedirectHandler extends RedirectHandler<MQTTRedirectContext> {
@Override @Override
protected void cannotRedirect(MQTTRedirectContext context) { protected void cannotRedirect(MQTTRedirectContext context) {
switch (context.getResult().status) { switch (context.getResult().getStatus()) {
case REFUSED_USE_ANOTHER: case REFUSED_USE_ANOTHER:
context.getMQTTSession().getProtocolHandler().sendConnack(MqttConnectReturnCode.CONNECTION_REFUSED_USE_ANOTHER_SERVER); context.getMQTTSession().getProtocolHandler().sendConnack(MqttConnectReturnCode.CONNECTION_REFUSED_USE_ANOTHER_SERVER);
break; break;

View File

@ -61,9 +61,9 @@ public class BrokerBalancerControlImpl extends AbstractControl implements Broker
@Override @Override
public CompositeData getTarget(String key) throws Exception { public CompositeData getTarget(String key) throws Exception {
TargetResult result = balancer.getTarget(key); TargetResult result = balancer.getTarget(key);
if (TargetResult.Status.OK == result.status) { if (TargetResult.Status.OK == result.getStatus()) {
CompositeData connectorData = null; CompositeData connectorData = null;
TransportConfiguration connector = result.target.getConnector(); TransportConfiguration connector = result.getTarget().getConnector();
if (connector != null) { if (connector != null) {
TabularData paramsData = new TabularDataSupport(getParametersType()); TabularData paramsData = new TabularDataSupport(getParametersType());
@ -79,7 +79,7 @@ public class BrokerBalancerControlImpl extends AbstractControl implements Broker
return new CompositeDataSupport(getTargetCompositeType(), return new CompositeDataSupport(getTargetCompositeType(),
new String[]{"nodeID", "local", "connector"}, new String[]{"nodeID", "local", "connector"},
new Object[]{result.target.getNodeID(), result.target.isLocal(), connectorData}); new Object[]{result.getTarget().getNodeID(), result.getTarget().isLocal(), connectorData});
} }
return null; return null;
@ -88,12 +88,12 @@ public class BrokerBalancerControlImpl extends AbstractControl implements Broker
@Override @Override
public String getTargetAsJSON(String key) { public String getTargetAsJSON(String key) {
TargetResult result = balancer.getTarget(key); TargetResult result = balancer.getTarget(key);
if (TargetResult.Status.OK == result.status) { if (TargetResult.Status.OK == result.getStatus()) {
TransportConfiguration connector = result.target.getConnector(); TransportConfiguration connector = result.getTarget().getConnector();
JsonObjectBuilder targetDataBuilder = JsonLoader.createObjectBuilder() JsonObjectBuilder targetDataBuilder = JsonLoader.createObjectBuilder()
.add("nodeID", result.target.getNodeID()) .add("nodeID", result.getTarget().getNodeID())
.add("local", result.target.isLocal()); .add("local", result.getTarget().isLocal());
if (connector == null) { if (connector == null) {
targetDataBuilder.addNull("connector"); targetDataBuilder.addNull("connector");

View File

@ -40,7 +40,7 @@ public class ActiveMQRedirectHandler extends RedirectHandler<ActiveMQRedirectCon
@Override @Override
public void cannotRedirect(ActiveMQRedirectContext context) throws Exception { public void cannotRedirect(ActiveMQRedirectContext context) throws Exception {
switch (context.getResult().status) { switch (context.getResult().getStatus()) {
case REFUSED_UNAVAILABLE: case REFUSED_UNAVAILABLE:
throw ActiveMQMessageBundle.BUNDLE.cannotRedirect(); throw ActiveMQMessageBundle.BUNDLE.cannotRedirect();
case REFUSED_USE_ANOTHER: case REFUSED_USE_ANOTHER:

View File

@ -68,7 +68,7 @@ public class BrokerBalancer implements ActiveMQComponent {
} }
public Target getLocalTarget() { public Target getLocalTarget() {
return localTarget.target; return localTarget.getTarget();
} }
public String getLocalTargetFilter() { public String getLocalTargetFilter() {
@ -168,16 +168,16 @@ public class BrokerBalancer implements ActiveMQComponent {
} }
if (result != null) { if (result != null) {
if (pool.isTargetReady(result.target)) { if (pool.isTargetReady(result.getTarget())) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("The cache returns [" + result.target + "] ready for " + targetKey + "[" + key + "]"); logger.debug("The cache returns [" + result.getTarget() + "] ready for " + targetKey + "[" + key + "]");
} }
return result; return result;
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("The cache returns [" + result.target + "] not ready for " + targetKey + "[" + key + "]"); logger.debug("The cache returns [" + result.getTarget() + "] not ready for " + targetKey + "[" + key + "]");
} }
} }

View File

@ -43,7 +43,7 @@ public class RedirectContext {
} }
public Target getTarget() { public Target getTarget() {
return result.target; return result.getTarget();
} }
public TargetResult getResult() { public TargetResult getResult() {

View File

@ -54,7 +54,7 @@ public abstract class RedirectHandler<T extends RedirectContext> {
context.setResult(brokerBalancer.getTarget(transportConnection, context.getClientID(), context.getUsername())); context.setResult(brokerBalancer.getTarget(transportConnection, context.getClientID(), context.getUsername()));
if (TargetResult.Status.OK != context.getResult().status) { if (TargetResult.Status.OK != context.getResult().getStatus()) {
ActiveMQServerLogger.LOGGER.cannotRedirectClientConnection(transportConnection); ActiveMQServerLogger.LOGGER.cannotRedirectClientConnection(transportConnection);
cannotRedirect(context); cannotRedirect(context);

View File

@ -22,16 +22,25 @@ public class TargetResult {
public static final TargetResult REFUSED_UNAVAILABLE_RESULT = new TargetResult(Status.REFUSED_UNAVAILABLE); public static final TargetResult REFUSED_UNAVAILABLE_RESULT = new TargetResult(Status.REFUSED_UNAVAILABLE);
public static final TargetResult REFUSED_USE_ANOTHER_RESULT = new TargetResult(Status.REFUSED_USE_ANOTHER); public static final TargetResult REFUSED_USE_ANOTHER_RESULT = new TargetResult(Status.REFUSED_USE_ANOTHER);
public Status status; private final Status status;
public Target target; private final Target target;
public TargetResult(Target t) { public Status getStatus() {
this.target = t; return status;
}
public Target getTarget() {
return target;
}
public TargetResult(Target target) {
this.status = Status.OK; this.status = Status.OK;
this.target = target;
} }
private TargetResult(Status s) { private TargetResult(Status s) {
this.status = s; this.status = s;
this.target = null;
} }
public enum Status { public enum Status {

View File

@ -1676,6 +1676,9 @@ public class ActiveMQServerImpl implements ActiveMQServer {
final Map<SimpleString, RoutingType> prefixes, final Map<SimpleString, RoutingType> prefixes,
final String securityDomain, final String securityDomain,
String validatedUser) throws Exception { String validatedUser) throws Exception {
if (validatedUser == null) {
validatedUser = validateUser(username, password, connection, securityDomain);
}
checkSessionLimit(validatedUser); checkSessionLimit(validatedUser);

View File

@ -74,7 +74,7 @@ public class BrokerBalancerTest {
@Test @Test
public void getTarget() { public void getTarget() {
assertEquals( localTarget, underTest.getTarget("FOO_EE").target); assertEquals( localTarget, underTest.getTarget("FOO_EE").getTarget());
assertEquals(TargetResult.REFUSED_USE_ANOTHER_RESULT, underTest.getTarget("BAR_EE")); assertEquals(TargetResult.REFUSED_USE_ANOTHER_RESULT, underTest.getTarget("BAR_EE"));
} }