diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java index a6cad89a1b..e99a53a503 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java @@ -107,7 +107,7 @@ public class AMQPConnectionCallback implements FailureListener, CloseListener { if (isPermittedMechanism(mechanism)) { switch (mechanism) { case PlainSASL.NAME: - result = new PlainSASL(server.getSecurityStore()); + result = new PlainSASL(server.getSecurityStore(), manager.getSecurityDomain()); break; case AnonymousServerSASL.NAME: diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java index 33cf22e121..bef6b1a31e 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java @@ -206,7 +206,7 @@ public class AMQPSessionCallback implements SessionCallback { false, // boolean autoCommitAcks, false, // boolean preAcknowledge, true, //boolean xa, - (String) null, this, true, operationContext, manager.getPrefixes()); + (String) null, this, true, operationContext, manager.getPrefixes(), manager.getSecurityDomain()); } @Override diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java index 62f4e39836..b4bb3ec723 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerReceiverContext.java @@ -245,6 +245,11 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements public RemotingConnection getRemotingConnection() { return connection.connectionCallback.getProtonConnectionDelegate(); } + + @Override + public String getSecurityDomain() { + return connection.getProtocolManager().getSecurityDomain(); + } }); } catch (ActiveMQSecurityException e) { throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingProducer(e.getMessage()); diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainSASL.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainSASL.java index 20cd8add98..cdc49ff4ea 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainSASL.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainSASL.java @@ -21,16 +21,18 @@ import org.apache.activemq.artemis.core.security.SecurityStore; public class PlainSASL extends ServerSASLPlain { private final SecurityStore securityStore; + private final String securityDomain; - public PlainSASL(SecurityStore securityStore) { + public PlainSASL(SecurityStore securityStore, String securityDomain) { this.securityStore = securityStore; + this.securityDomain = securityDomain; } @Override protected boolean authenticate(String user, String password) { if (securityStore.isSecurityEnabled()) { try { - securityStore.authenticate(user, password, null); + securityStore.authenticate(user, password, null, securityDomain); return true; } catch (Exception e) { return false; diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTConnectionManager.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTConnectionManager.java index 544c5f6d1d..00629d998b 100644 --- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTConnectionManager.java +++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTConnectionManager.java @@ -134,7 +134,8 @@ public class MQTTConnectionManager { session.getSessionCallback(), MQTTUtil.SESSION_AUTO_CREATE_QUEUE, server.newOperationContext(), - session.getProtocolManager().getPrefixes()); + session.getProtocolManager().getPrefixes(), + session.getProtocolManager().getSecurityDomain()); return (ServerSessionImpl) serverSession; } diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java index 4e6601f91e..c8b3f883c7 100644 --- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java +++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireConnection.java @@ -235,6 +235,12 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se return this; } + // SecurityAuth implementation + @Override + public String getSecurityDomain() { + return protocolManager.getSecurityDomain(); + } + // SecurityAuth implementation @Override public String getPassword() { @@ -759,7 +765,7 @@ public class OpenWireConnection extends AbstractRemotingConnection implements Se } private void createInternalSession(ConnectionInfo info) throws Exception { - internalSession = server.createSession(UUIDGenerator.getInstance().generateStringUUID(), context.getUserName(), info.getPassword(), -1, this, true, false, false, false, null, null, true, operationContext, protocolManager.getPrefixes()); + internalSession = server.createSession(UUIDGenerator.getInstance().generateStringUUID(), context.getUserName(), info.getPassword(), -1, this, true, false, false, false, null, null, true, operationContext, protocolManager.getPrefixes(), protocolManager.getSecurityDomain()); } //raise the refCount of context diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java index efe7801168..415915c769 100644 --- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java +++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenWireProtocolManager.java @@ -114,6 +114,8 @@ public class OpenWireProtocolManager implements ProtocolManager, Cl private final ScheduledExecutorService scheduledPool; + private String securityDomain; + //bean properties //http://activemq.apache.org/failover-transport-reference.html private boolean rebalanceClusterClients = false; @@ -472,7 +474,7 @@ public class OpenWireProtocolManager implements ProtocolManager, Cl } public void validateUser(String login, String passcode, OpenWireConnection connection) throws Exception { - server.getSecurityStore().authenticate(login, passcode, connection); + server.getSecurityStore().authenticate(login, passcode, connection, getSecurityDomain()); } public void sendBrokerInfo(OpenWireConnection connection) throws Exception { @@ -589,6 +591,16 @@ public class OpenWireProtocolManager implements ProtocolManager, Cl return prefixes; } + @Override + public void setSecurityDomain(String securityDomain) { + this.securityDomain = securityDomain; + } + + @Override + public String getSecurityDomain() { + return securityDomain; + } + public List getTemporaryDestinations() { List total = new ArrayList<>(); for (OpenWireConnection connection : connections) { diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java index 8183449c4d..79b969eac5 100644 --- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java +++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/amq/AMQSession.java @@ -131,7 +131,7 @@ public class AMQSession implements SessionCallback { // now try { - coreSession = server.createSession(name, username, password, minLargeMessageSize, connection, true, false, false, false, null, this, true, connection.getOperationContext(), protocolManager.getPrefixes()); + coreSession = server.createSession(name, username, password, minLargeMessageSize, connection, true, false, false, false, null, this, true, connection.getOperationContext(), protocolManager.getPrefixes(), protocolManager.getSecurityDomain()); long sessionId = sessInfo.getSessionId().getValue(); if (sessionId == -1) { diff --git a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java index 2fe62248f9..a1d560815a 100644 --- a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java +++ b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/StompProtocolManager.java @@ -223,7 +223,7 @@ public class StompProtocolManager extends AbstractProtocolManager routingTypeMap = protocolManager.getPrefixes(); CoreSessionCallback sessionCallback = new CoreSessionCallback(request.getName(), protocolManager, channel, connection); - ServerSession session = server.createSession(request.getName(), activeMQPrincipal == null ? request.getUsername() : activeMQPrincipal.getUserName(), activeMQPrincipal == null ? request.getPassword() : activeMQPrincipal.getPassword(), request.getMinLargeMessageSize(), connection, request.isAutoCommitSends(), request.isAutoCommitAcks(), request.isPreAcknowledge(), request.isXA(), request.getDefaultAddress(), sessionCallback, true, sessionOperationContext, routingTypeMap); + ServerSession session = server.createSession(request.getName(), activeMQPrincipal == null ? request.getUsername() : activeMQPrincipal.getUserName(), activeMQPrincipal == null ? request.getPassword() : activeMQPrincipal.getPassword(), request.getMinLargeMessageSize(), connection, request.isAutoCommitSends(), request.isAutoCommitAcks(), request.isPreAcknowledge(), request.isXA(), request.getDefaultAddress(), sessionCallback, true, sessionOperationContext, routingTypeMap, protocolManager.getSecurityDomain()); ServerProducer serverProducer = new ServerProducerImpl(session.getName(), "CORE", request.getDefaultAddress()); session.addProducer(serverProducer); ServerSessionPacketHandler handler = new ServerSessionPacketHandler(server, protocolManager, session, server.getStorageManager(), channel); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java index 2e60d63ea6..445faff46f 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/protocol/core/impl/CoreProtocolManager.java @@ -88,6 +88,8 @@ public class CoreProtocolManager implements ProtocolManager { private final Map prefixes = new HashMap<>(); + private String securityDomain; + public CoreProtocolManager(final CoreProtocolManagerFactory factory, final ActiveMQServer server, final List incomingInterceptors, @@ -221,6 +223,16 @@ public class CoreProtocolManager implements ProtocolManager { return prefixes; } + @Override + public void setSecurityDomain(String securityDomain) { + this.securityDomain = securityDomain; + } + + @Override + public String getSecurityDomain() { + return securityDomain; + } + private boolean isArtemis(ActiveMQBuffer buffer) { return buffer.getByte(0) == 'A' && buffer.getByte(1) == 'R' && diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityAuth.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityAuth.java index 0cdb77d0ef..d2b0218bbe 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityAuth.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityAuth.java @@ -26,4 +26,6 @@ public interface SecurityAuth { String getPassword(); RemotingConnection getRemotingConnection(); + + String getSecurityDomain(); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java index b3ccc3444e..0732180fd1 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java @@ -23,6 +23,8 @@ public interface SecurityStore { String authenticate(String user, String password, RemotingConnection remotingConnection) throws Exception; + String authenticate(String user, String password, RemotingConnection remotingConnection, String securityDomain) throws Exception; + void check(SimpleString address, CheckType checkType, SecurityAuth session) throws Exception; void check(SimpleString address, SimpleString queue, CheckType checkType, SecurityAuth session) throws Exception; diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java index 54fac98e6f..06d702aa13 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java @@ -40,6 +40,7 @@ import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4; import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet; import org.apache.activemq.artemis.utils.collections.TypedProperties; import org.jboss.logging.Logger; @@ -112,6 +113,14 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC public String authenticate(final String user, final String password, RemotingConnection connection) throws Exception { + return authenticate(user, password, connection, null); + } + + @Override + public String authenticate(final String user, + final String password, + RemotingConnection connection, + String securityDomain) throws Exception { if (securityEnabled) { if (managementClusterUser.equals(user)) { @@ -133,7 +142,9 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC String validatedUser = null; boolean userIsValid = false; - if (securityManager instanceof ActiveMQSecurityManager3) { + if (securityManager instanceof ActiveMQSecurityManager4) { + validatedUser = ((ActiveMQSecurityManager4) securityManager).validateUser(user, password, connection, securityDomain); + } else if (securityManager instanceof ActiveMQSecurityManager3) { validatedUser = ((ActiveMQSecurityManager3) securityManager).validateUser(user, password, connection); } else if (securityManager instanceof ActiveMQSecurityManager2) { userIsValid = ((ActiveMQSecurityManager2) securityManager).validateUser(user, password, CertificateUtil.getCertsFromConnection(connection)); @@ -205,7 +216,10 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC } final boolean validated; - if (securityManager instanceof ActiveMQSecurityManager3) { + if (securityManager instanceof ActiveMQSecurityManager4) { + final ActiveMQSecurityManager4 securityManager4 = (ActiveMQSecurityManager4) securityManager; + validated = securityManager4.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection(), session.getSecurityDomain()) != null; + } else if (securityManager instanceof ActiveMQSecurityManager3) { final ActiveMQSecurityManager3 securityManager3 = (ActiveMQSecurityManager3) securityManager; validated = securityManager3.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection()) != null; } else if (securityManager instanceof ActiveMQSecurityManager2) { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java index 795fb92c5f..52d9a628cf 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java @@ -319,7 +319,8 @@ public interface ActiveMQServer extends ServiceComponent { SessionCallback callback, boolean autoCreateQueues, OperationContext context, - Map prefixes) throws Exception; + Map prefixes, + String securityDomain) throws Exception; SecurityStore getSecurityStore(); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java index 798d7d9a69..3a5c68b4c7 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java @@ -1519,7 +1519,8 @@ public class ActiveMQServerImpl implements ActiveMQServer { final SessionCallback callback, final boolean autoCreateQueues, final OperationContext context, - final Map prefixes) throws Exception { + final Map prefixes, + final String securityDomain) throws Exception { if (AuditLogger.isEnabled()) { AuditLogger.createCoreSession(this, name, username, "****", minLargeMessageSize, connection, autoCommitSends, @@ -1528,7 +1529,7 @@ public class ActiveMQServerImpl implements ActiveMQServer { String validatedUser = ""; if (securityStore != null) { - validatedUser = securityStore.authenticate(username, password, connection); + validatedUser = securityStore.authenticate(username, password, connection, securityDomain); } checkSessionLimit(validatedUser); @@ -1537,7 +1538,7 @@ public class ActiveMQServerImpl implements ActiveMQServer { callBrokerSessionPlugins(plugin -> plugin.beforeCreateSession(name, username, minLargeMessageSize, connection, autoCommitSends, autoCommitAcks, preAcknowledge, xa, defaultAddress, callback, autoCreateQueues, context, prefixes)); } - final ServerSessionImpl session = internalCreateSession(name, username, password, validatedUser, minLargeMessageSize, connection, autoCommitSends, autoCommitAcks, preAcknowledge, xa, defaultAddress, callback, context, autoCreateQueues, prefixes); + final ServerSessionImpl session = internalCreateSession(name, username, password, validatedUser, minLargeMessageSize, connection, autoCommitSends, autoCommitAcks, preAcknowledge, xa, defaultAddress, callback, context, autoCreateQueues, prefixes, securityDomain); sessions.put(name, session); @@ -1614,8 +1615,9 @@ public class ActiveMQServerImpl implements ActiveMQServer { SessionCallback callback, OperationContext context, boolean autoCreateJMSQueues, - Map prefixes) throws Exception { - return new ServerSessionImpl(name, username, password, validatedUser, minLargeMessageSize, autoCommitSends, autoCommitAcks, preAcknowledge, configuration.isPersistDeliveryCountBeforeDelivery(), xa, connection, storageManager, postOffice, resourceManager, securityStore, managementService, this, configuration.getManagementAddress(), defaultAddress == null ? null : new SimpleString(defaultAddress), callback, context, pagingManager, prefixes); + Map prefixes, + String securityDomain) throws Exception { + return new ServerSessionImpl(name, username, password, validatedUser, minLargeMessageSize, autoCommitSends, autoCommitAcks, preAcknowledge, configuration.isPersistDeliveryCountBeforeDelivery(), xa, connection, storageManager, postOffice, resourceManager, securityStore, managementService, this, configuration.getManagementAddress(), defaultAddress == null ? null : new SimpleString(defaultAddress), callback, context, pagingManager, prefixes, securityDomain); } @Override diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java index 6f7d9b91a5..b53dc8261e 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java @@ -118,6 +118,8 @@ public class ServerSessionImpl implements ServerSession, FailureListener { private boolean securityEnabled = true; + private final String securityDomain; + protected final String username; protected final String password; @@ -225,7 +227,8 @@ public class ServerSessionImpl implements ServerSession, FailureListener { final SessionCallback callback, final OperationContext context, final PagingManager pagingManager, - final Map prefixes) throws Exception { + final Map prefixes, + final String securityDomain) throws Exception { this.username = username; this.password = password; @@ -284,6 +287,8 @@ public class ServerSessionImpl implements ServerSession, FailureListener { } //When the ServerSessionImpl initialization is complete, need to create and send a SESSION_CREATED notification. sendSessionNotification(CoreNotificationType.SESSION_CREATED); + + this.securityDomain = securityDomain; } // ServerSession implementation --------------------------------------------------------------------------- @@ -1064,6 +1069,11 @@ public class ServerSessionImpl implements ServerSession, FailureListener { return remotingConnection; } + @Override + public String getSecurityDomain() { + return securityDomain; + } + public static class TempQueueCleanerUpper implements CloseListener, FailureListener { private final SimpleString bindingName; diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java index e27699b3ea..7a4d3b175a 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractProtocolManager.java @@ -32,6 +32,8 @@ public abstract class AbstractProtocolManager, C private final Map prefixes = new HashMap<>(); + private String securityDomain; + protected String invokeInterceptors(final List interceptors, final P message, final C connection) { if (interceptors != null && !interceptors.isEmpty()) { for (I interceptor : interceptors) { @@ -66,4 +68,14 @@ public abstract class AbstractProtocolManager, C public Map getPrefixes() { return prefixes; } + + @Override + public String getSecurityDomain() { + return securityDomain; + } + + @Override + public void setSecurityDomain(String securityDomain) { + this.securityDomain = securityDomain; + } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java index dc6ba99b62..770034de15 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/protocol/ProtocolManager.java @@ -74,4 +74,8 @@ public interface ProtocolManager

{ void setMulticastPrefix(String multicastPrefix); Map getPrefixes(); + + void setSecurityDomain(String securityDomain); + + String getSecurityDomain(); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java index bf8d97b3de..a26119b5c4 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java @@ -44,7 +44,7 @@ import static org.apache.activemq.artemis.core.remoting.CertificateUtil.getCerts * The {@link Subject} returned by the login context is expecting to have a set of {@link RolePrincipal} for each * role of the user. */ -public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { +public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager4 { private static final Logger logger = Logger.getLogger(ActiveMQJAASSecurityManager.class); @@ -90,13 +90,13 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { @Override public boolean validateUser(String user, String password) { - throw new UnsupportedOperationException("Invoke validateUser(String, String, X509Certificate[]) instead"); + throw new UnsupportedOperationException("Invoke validateUser(String, String, RemotingConnection, String) instead"); } @Override - public String validateUser(final String user, final String password, RemotingConnection remotingConnection) { + public String validateUser(final String user, final String password, RemotingConnection remotingConnection, final String securityDomain) { try { - return getUserFromSubject(getAuthenticatedSubject(user, password, remotingConnection)); + return getUserFromSubject(getAuthenticatedSubject(user, password, remotingConnection, securityDomain)); } catch (LoginException e) { if (logger.isDebugEnabled()) { logger.debug("Couldn't validate user", e); @@ -118,7 +118,7 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { @Override public boolean validateUserAndRole(String user, String password, Set roles, CheckType checkType) { - throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set, CheckType, String, RemotingConnection) instead"); + throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set, CheckType, String, RemotingConnection, String) instead"); } @Override @@ -127,10 +127,11 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { final Set roles, final CheckType checkType, final String address, - final RemotingConnection remotingConnection) { + final RemotingConnection remotingConnection, + final String securityDomain) { Subject localSubject; try { - localSubject = getAuthenticatedSubject(user, password, remotingConnection); + localSubject = getAuthenticatedSubject(user, password, remotingConnection, securityDomain); } catch (LoginException e) { if (logger.isDebugEnabled()) { logger.debug("Couldn't validate user", e); @@ -176,7 +177,8 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { private Subject getAuthenticatedSubject(final String user, final String password, - final RemotingConnection remotingConnection) throws LoginException { + final RemotingConnection remotingConnection, + final String securityDomain) throws LoginException { LoginContext lc; ClassLoader currentLoader = Thread.currentThread().getContextClassLoader(); ClassLoader thisLoader = this.getClass().getClassLoader(); @@ -184,7 +186,9 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { if (thisLoader != currentLoader) { Thread.currentThread().setContextClassLoader(thisLoader); } - if (certificateConfigurationName != null && certificateConfigurationName.length() > 0 && getCertsFromConnection(remotingConnection) != null) { + if (securityDomain != null) { + lc = new LoginContext(securityDomain, null, new JaasCallbackHandler(user, password, remotingConnection), null); + } else if (certificateConfigurationName != null && certificateConfigurationName.length() > 0 && getCertsFromConnection(remotingConnection) != null) { lc = new LoginContext(certificateConfigurationName, null, new JaasCallbackHandler(user, password, remotingConnection), certificateConfiguration); } else { lc = new LoginContext(configurationName, null, new JaasCallbackHandler(user, password, remotingConnection), configuration); @@ -307,5 +311,4 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { return instance; } - } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager4.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager4.java new file mode 100644 index 0000000000..dd68020477 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager4.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.spi.core.security; + +import java.util.Set; + +import org.apache.activemq.artemis.core.security.CheckType; +import org.apache.activemq.artemis.core.security.Role; +import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; + +/** + * Used to validate whether a user is authorized to connect to the + * server and perform certain functions on certain addresses + * + * This is an evolution of {@link ActiveMQSecurityManager3} + * that adds the ability to specify the JAAS domain per call. + */ +public interface ActiveMQSecurityManager4 extends ActiveMQSecurityManager { + + /** + * is this a valid user. + * + * This method is called instead of + * {@link ActiveMQSecurityManager#validateUser(String, String)}. + * + * @param user the user + * @param password the users password + * @param remotingConnection + * @param securityDomain the name of the JAAS security domain to use (can be null) + * @return the name of the validated user or null if the user isn't validated + */ + String validateUser(String user, String password, RemotingConnection remotingConnection, String securityDomain); + + /** + * Determine whether the given user is valid and whether they have + * the correct role for the given destination address. + * + * This method is called instead of + * {@link ActiveMQSecurityManager#validateUserAndRole(String, String, Set, CheckType)}. + * + * @param user the user + * @param password the user's password + * @param roles the user's roles + * @param checkType which permission to validate + * @param address the address for which to perform authorization + * @param remotingConnection the user's connection + * @param securityDomain the name of the JAAS security domain to use (can be null) + * @return the name of the validated user or null if the user isn't validated + */ + String validateUserAndRole(String user, + String password, + Set roles, + CheckType checkType, + String address, + RemotingConnection remotingConnection, + String securityDomain); +} diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java index 3b561742d3..d9e535b74e 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java @@ -80,7 +80,7 @@ public class JAASSecurityManagerTest { } ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("PropertiesLogin"); - String result = securityManager.validateUser("first", "secret", null); + String result = securityManager.validateUser("first", "secret", null, null); assertNotNull(result); assertEquals("first", result); @@ -88,7 +88,7 @@ public class JAASSecurityManagerTest { Role role = new Role("programmers", true, true, true, true, true, true, true, true, true, true); Set roles = new HashSet<>(); roles.add(role); - result = securityManager.validateUserAndRole("first", "secret", roles, CheckType.SEND, "someaddress", null); + result = securityManager.validateUserAndRole("first", "secret", roles, CheckType.SEND, "someaddress", null, null); assertNotNull(result); assertEquals("first", result); diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md index 77c69e6aa5..4436f5cc44 100644 --- a/docs/user-manual/en/security.md +++ b/docs/user-manual/en/security.md @@ -1253,3 +1253,16 @@ configure it in `bootstrap.xml` using the `security-manager` element, e.g.: ``` The `security-manager` example demonstrates how to do this is more detail. + +## Per-Acceptor Security Domains + +It's possible to override the broker's JAAS security domain by specifying a +security domain on an individual `acceptor`. Simply use the `securityDomain` +parameter and indicate which domain from your `login.config` to use, e.g.: + +```xml +tcp://127.0.0.1:61616?securityDomain=mySecurityDomain +``` + +Any client connecting to this acceptor will be have security enforced using +`mySecurityDomain`. \ No newline at end of file diff --git a/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java b/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java index c278217dbb..053fc02c27 100644 --- a/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java +++ b/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java @@ -25,15 +25,15 @@ import org.apache.activemq.artemis.core.security.Role; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; -import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4; -public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager3 { +public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager4 { ActiveMQJAASSecurityManager activeMQJAASSecurityManager; @Override - public String validateUser(String user, String password, RemotingConnection remotingConnection) { + public String validateUser(String user, String password, RemotingConnection remotingConnection, String securityDomain) { System.out.println("validateUser(" + user + ", " + password + ", " + remotingConnection.getRemoteAddress() + ")"); - return activeMQJAASSecurityManager.validateUser(user, password, remotingConnection); + return activeMQJAASSecurityManager.validateUser(user, password, remotingConnection, securityDomain); } @@ -43,9 +43,10 @@ public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager3 { Set roles, CheckType checkType, String address, - RemotingConnection remotingConnection) { + RemotingConnection remotingConnection, + String securityDomain) { System.out.println("validateUserAndRole(" + user + ", " + password + ", " + roles + ", " + checkType + ", " + address + ", " + remotingConnection.getRemoteAddress() + ")"); - return activeMQJAASSecurityManager.validateUserAndRole(user, password, roles, checkType, address, remotingConnection); + return activeMQJAASSecurityManager.validateUserAndRole(user, password, roles, checkType, address, remotingConnection, securityDomain); } @Override diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/HangConsumerTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/HangConsumerTest.java index 318dbf934a..c3fca7784a 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/HangConsumerTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/HangConsumerTest.java @@ -589,8 +589,9 @@ public class HangConsumerTest extends ActiveMQTestBase { SessionCallback callback, OperationContext context, boolean autoCreateQueue, - Map prefixes) throws Exception { - return new ServerSessionImpl(name, username, password, validatedUser, minLargeMessageSize, autoCommitSends, autoCommitAcks, preAcknowledge, getConfiguration().isPersistDeliveryCountBeforeDelivery(), xa, connection, getStorageManager(), getPostOffice(), getResourceManager(), getSecurityStore(), getManagementService(), this, getConfiguration().getManagementAddress(), defaultAddress == null ? null : new SimpleString(defaultAddress), new MyCallback(callback), context, getPagingManager(), prefixes); + Map prefixes, + String securityDomain) throws Exception { + return new ServerSessionImpl(name, username, password, validatedUser, minLargeMessageSize, autoCommitSends, autoCommitAcks, preAcknowledge, getConfiguration().isPersistDeliveryCountBeforeDelivery(), xa, connection, getStorageManager(), getPostOffice(), getResourceManager(), getSecurityStore(), getManagementService(), this, getConfiguration().getManagementAddress(), defaultAddress == null ? null : new SimpleString(defaultAddress), new MyCallback(callback), context, getPagingManager(), prefixes, securityDomain); } } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt/imported/MQTTSecurityPerAcceptorTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt/imported/MQTTSecurityPerAcceptorTest.java new file mode 100644 index 0000000000..d39cd013ab --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt/imported/MQTTSecurityPerAcceptorTest.java @@ -0,0 +1,91 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.tests.integration.mqtt.imported; + +import java.net.URL; +import java.util.Arrays; + +import org.apache.activemq.artemis.tests.util.Wait; +import org.fusesource.mqtt.client.BlockingConnection; +import org.fusesource.mqtt.client.MQTT; +import org.junit.Test; + +public class MQTTSecurityPerAcceptorTest extends MQTTTestSupport { + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = MQTTSecurityPerAcceptorTest.class.getClassLoader().getResource("login.config"); + if (resource != null) { + path = resource.getFile(); + System.setProperty("java.security.auth.login.config", path); + } + } + } + + @Override + public boolean isSecurityEnabled() { + return true; + } + + @Override + public void configureBroker() throws Exception { + server = createServer(true, createDefaultConfig(true).setSecurityEnabled(true)); + server.getConfiguration().addAcceptorConfiguration("MQTT", "tcp://localhost:" + port + "?securityDomain=PropertiesLogin"); + } + + @Test(timeout = 30000) + public void testConnectionPositive() throws Exception { + internalTestConnection("first", true); + } + + @Test(timeout = 30000) + public void testConnectionNegative() throws Exception { + internalTestConnection("fail", false); + } + + private void internalTestConnection(String username, boolean succeed) throws Exception { + for (String version : Arrays.asList("3.1", "3.1.1")) { + + BlockingConnection connection = null; + try { + MQTT mqtt = createMQTTConnection("test-" + version, true); + mqtt.setUserName(username); + mqtt.setPassword("secret"); + mqtt.setConnectAttemptsMax(1); + mqtt.setVersion(version); + connection = mqtt.blockingConnection(); + try { + connection.connect(); + if (!succeed) { + fail("Connection should have failed"); + } + } catch (Exception e) { + if (succeed) { + fail("Connection should have succeeded"); + } + } + BlockingConnection finalConnection = connection; + if (succeed) { + assertTrue("Should be connected", Wait.waitFor(() -> finalConnection.isConnected(), 2000, 100)); + } + } finally { + if (connection != null && connection.isConnected()) connection.disconnect(); + } + } + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityPerAcceptorJmsTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityPerAcceptorJmsTest.java new file mode 100644 index 0000000000..a3fe169c09 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityPerAcceptorJmsTest.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.tests.integration.security; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.QueueBrowser; +import javax.jms.Session; +import java.lang.management.ManagementFactory; +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.apache.activemq.artemis.api.core.QueueConfiguration; +import org.apache.activemq.artemis.api.core.RoutingType; +import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.core.security.Role; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.ActiveMQServers; +import org.apache.activemq.artemis.core.server.impl.AddressInfo; +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; +import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.apache.qpid.jms.JmsConnectionFactory; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class SecurityPerAcceptorJmsTest extends ActiveMQTestBase { + + private enum Protocol { + CORE, AMQP, OPENWIRE + } + + @Parameterized.Parameters(name = "protocol={0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] { + {Protocol.CORE}, + {Protocol.AMQP}, + {Protocol.OPENWIRE} + }); + } + + @Parameterized.Parameter(0) + public Protocol protocol; + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = SecurityPerAcceptorJmsTest.class.getClassLoader().getResource("login.config"); + if (resource != null) { + path = resource.getFile(); + System.setProperty("java.security.auth.login.config", path); + } + } + } + + private ConnectionFactory cf; + private final String URL = "tcp://127.0.0.1:61616"; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + switch (protocol) { + case CORE: + cf = new ActiveMQConnectionFactory(URL); + break; + case OPENWIRE: + cf = new org.apache.activemq.ActiveMQConnectionFactory(URL); + break; + case AMQP: + cf = new JmsConnectionFactory("amqp://localhost:61616"); + + } + } + + @Test + public void testJAASSecurityManagerAuthentication() throws Exception { + ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true).addAcceptorConfiguration("netty", URL + "?securityDomain=PropertiesLogin"), ManagementFactory.getPlatformMBeanServer(), new ActiveMQJAASSecurityManager(), false)); + server.start(); + try (Connection c = cf.createConnection("first", "secret")) { + Thread.sleep(200); + } catch (JMSException e) { + Assert.fail("should not throw exception"); + } + } + + @Test + public void testJAASSecurityManagerAuthorizationNegative() throws Exception { + final SimpleString ADDRESS = new SimpleString("address"); + + ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(); + ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().addAcceptorConfiguration("netty", "tcp://127.0.0.1:61616?securityDomain=PropertiesLogin").setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false)); + Set roles = new HashSet<>(); + roles.add(new Role("programmers", false, false, false, false, false, false, false, false, false, false)); + server.getConfiguration().putSecurityRoles("#", roles); + server.start(); + server.addAddressInfo(new AddressInfo(ADDRESS, RoutingType.ANYCAST)); + server.createQueue(new QueueConfiguration(ADDRESS).setAddress(ADDRESS).setRoutingType(RoutingType.ANYCAST)); + + Connection c = cf.createConnection("first", "secret"); + Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // PRODUCE + try { + MessageProducer producer = s.createProducer(s.createQueue(ADDRESS.toString())); + producer.send(s.createMessage()); + Assert.fail("should throw exception here"); + } catch (JMSException e) { + e.printStackTrace(); + assertTrue(e.getMessage().contains("User: first does not have permission='SEND' on address address")); + } + + // CONSUME + try { + MessageConsumer consumer = s.createConsumer(s.createQueue(ADDRESS.toString())); + Assert.fail("should throw exception here"); + } catch (JMSException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='CONSUME' for queue address on address address")); + } + + // BROWSE + try { + QueueBrowser browser = s.createBrowser(s.createQueue(ADDRESS.toString())); + browser.getEnumeration(); + Assert.fail("should throw exception here"); + } catch (JMSException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='BROWSE' for queue address on address address")); + } + c.close(); + } + + @Test + public void testJAASSecurityManagerAuthorizationPositive() throws Exception { + final String ADDRESS = "address"; + + ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true).addAcceptorConfiguration("netty", "tcp://127.0.0.1:61616?securityDomain=PropertiesLogin"), ManagementFactory.getPlatformMBeanServer(), new ActiveMQJAASSecurityManager(), false)); + Set roles = new HashSet<>(); + roles.add(new Role("programmers", true, true, true, true, true, true, true, true, true, true)); + server.getConfiguration().putSecurityRoles("#", roles); + server.start(); + + Connection c = cf.createConnection("first", "secret"); + Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // PRODUCE + try { + MessageProducer producer = s.createProducer(s.createQueue(ADDRESS)); + producer.send(s.createMessage()); + } catch (JMSException e) { + Assert.fail("should not throw exception here"); + } + + // CONSUME + try { + MessageConsumer consumer = s.createConsumer(s.createQueue(ADDRESS)); + } catch (JMSException e) { + Assert.fail("should not throw exception here"); + } + + // BROWSE + try { + QueueBrowser browser = s.createBrowser(s.createQueue(ADDRESS)); + browser.getEnumeration(); + } catch (JMSException e) { + Assert.fail("should not throw exception here"); + } + c.close(); + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityPerAcceptorTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityPerAcceptorTest.java new file mode 100644 index 0000000000..62646ac2f4 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityPerAcceptorTest.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.tests.integration.security; + +import java.lang.management.ManagementFactory; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +import org.apache.activemq.artemis.api.core.ActiveMQException; +import org.apache.activemq.artemis.api.core.QueueConfiguration; +import org.apache.activemq.artemis.api.core.RoutingType; +import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.api.core.client.ClientConsumer; +import org.apache.activemq.artemis.api.core.client.ClientProducer; +import org.apache.activemq.artemis.api.core.client.ClientSession; +import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; +import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.apache.activemq.artemis.core.security.Role; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.ActiveMQServers; +import org.apache.activemq.artemis.core.server.impl.AddressInfo; +import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SecurityPerAcceptorTest extends ActiveMQTestBase { + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = SecurityPerAcceptorTest.class.getClassLoader().getResource("login.config"); + if (resource != null) { + path = resource.getFile(); + System.setProperty("java.security.auth.login.config", path); + } + } + } + + private ServerLocator locator; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + locator = createNettyNonHALocator(); + } + + @Test + public void testJAASSecurityManagerAuthentication() throws Exception { + ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(); + ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true).addAcceptorConfiguration("netty", "tcp://127.0.0.1:61616?securityDomain=PropertiesLogin"), ManagementFactory.getPlatformMBeanServer(), securityManager, false)); + server.start(); + ClientSessionFactory cf = createSessionFactory(locator); + + try { + ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0); + session.close(); + } catch (ActiveMQException e) { + e.printStackTrace(); + Assert.fail("should not throw exception"); + } + } + + @Test + public void testJAASSecurityManagerAuthorizationNegative() throws Exception { + final SimpleString ADDRESS = new SimpleString("address"); + final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue"); + final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue"); + + ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(); + ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().addAcceptorConfiguration("netty", "tcp://127.0.0.1:61616?securityDomain=PropertiesLogin").setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false)); + Set roles = new HashSet<>(); + roles.add(new Role("programmers", false, false, false, false, false, false, false, false, false, false)); + server.getConfiguration().putSecurityRoles("#", roles); + server.start(); + server.addAddressInfo(new AddressInfo(ADDRESS, RoutingType.ANYCAST)); + server.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS).setRoutingType(RoutingType.ANYCAST)); + server.createQueue(new QueueConfiguration(NON_DURABLE_QUEUE).setAddress(ADDRESS).setRoutingType(RoutingType.ANYCAST).setDurable(false)); + + ClientSessionFactory cf = createSessionFactory(locator); + ClientSession session = addClientSession(cf.createSession("first", "secret", false, true, true, false, 0)); + + // CREATE_DURABLE_QUEUE + try { + session.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS)); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='CREATE_DURABLE_QUEUE' for queue durableQueue on address address")); + } + + // DELETE_DURABLE_QUEUE + try { + session.deleteQueue(DURABLE_QUEUE); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='DELETE_DURABLE_QUEUE' for queue durableQueue on address address")); + } + + // CREATE_NON_DURABLE_QUEUE + try { + session.createQueue(new QueueConfiguration(NON_DURABLE_QUEUE).setAddress(ADDRESS).setDurable(false)); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='CREATE_NON_DURABLE_QUEUE' for queue nonDurableQueue on address address")); + } + + // DELETE_NON_DURABLE_QUEUE + try { + session.deleteQueue(NON_DURABLE_QUEUE); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='DELETE_NON_DURABLE_QUEUE' for queue nonDurableQueue on address address")); + } + + // PRODUCE + try { + ClientProducer producer = session.createProducer(ADDRESS); + producer.send(session.createMessage(true)); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='SEND' on address address")); + } + + // CONSUME + try { + ClientConsumer consumer = session.createConsumer(DURABLE_QUEUE); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='CONSUME' for queue durableQueue on address address")); + } + + // MANAGE + try { + ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress()); + producer.send(session.createMessage(true)); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='MANAGE' on address activemq.management")); + } + + // BROWSE + try { + ClientConsumer browser = session.createConsumer(DURABLE_QUEUE, true); + Assert.fail("should throw exception here"); + } catch (ActiveMQException e) { + assertTrue(e.getMessage().contains("User: first does not have permission='BROWSE' for queue durableQueue on address address")); + } + } + + @Test + public void testJAASSecurityManagerAuthorizationPositive() throws Exception { + final SimpleString ADDRESS = new SimpleString("address"); + final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue"); + final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue"); + + ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(); + ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true).addAcceptorConfiguration("netty", "tcp://127.0.0.1:61616?securityDomain=PropertiesLogin"), ManagementFactory.getPlatformMBeanServer(), securityManager, false)); + Set roles = new HashSet<>(); + roles.add(new Role("programmers", true, true, true, true, true, true, true, true, true, true)); + server.getConfiguration().putSecurityRoles("#", roles); + server.start(); + + ClientSessionFactory cf = createSessionFactory(locator); + ClientSession session = addClientSession(cf.createSession("first", "secret", false, true, true, false, 0)); + + // CREATE_DURABLE_QUEUE + try { + session.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS)); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + + // DELETE_DURABLE_QUEUE + try { + session.deleteQueue(DURABLE_QUEUE); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + + // CREATE_NON_DURABLE_QUEUE + try { + session.createQueue(new QueueConfiguration(NON_DURABLE_QUEUE).setAddress(ADDRESS).setDurable(false)); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + + // DELETE_NON_DURABLE_QUEUE + try { + session.deleteQueue(NON_DURABLE_QUEUE); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + + session.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS)); + + // PRODUCE + try { + ClientProducer producer = session.createProducer(ADDRESS); + producer.send(session.createMessage(true)); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + + // CONSUME + try { + session.createConsumer(DURABLE_QUEUE); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + + // MANAGE + try { + ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress()); + producer.send(session.createMessage(true)); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + + // BROWSE + try { + session.createConsumer(DURABLE_QUEUE, true); + } catch (ActiveMQException e) { + Assert.fail("should not throw exception here"); + } + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java index fcd34ceb6d..2a2c1a377b 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java @@ -62,6 +62,7 @@ import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.CreateMessage; import org.apache.activemq.command.ActiveMQQueue; @@ -2202,6 +2203,142 @@ public class SecurityTest extends ActiveMQTestBase { } } + @Test + public void testCustomSecurityManager4() throws Exception { + final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); + final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager4() { + @Override + public boolean validateUser(final String username, final String password) { + fail("Unexpected call to overridden method"); + return false; + } + + @Override + public String validateUser(final String username, + final String password, + final RemotingConnection remotingConnection, + final String securityDomain) { + if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && password.equals("frobnicate")) { + return username; + } else { + return null; + } + } + + @Override + public boolean validateUserAndRole(final String username, + final String password, + final Set requiredRoles, + final CheckType checkType) { + + fail("Unexpected call to overridden method"); + return false; + } + + @Override + public String validateUserAndRole(final String username, + final String password, + final Set requiredRoles, + final CheckType checkType, + final String address, + final RemotingConnection connection, + final String securityDomain) { + + if (!(connection.getTransportConnection() instanceof InVMConnection)) { + return null; + } + + if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && password.equals("frobnicate")) { + + if (username.equals("all")) { + return username; + } else if (username.equals("foo")) { + if (address.equals("test.queue") && checkType == CheckType.CONSUME) + return username; + else + return null; + } else if (username.equals("bar")) { + if (address.equals("test.queue") && checkType == CheckType.SEND) + return username; + else + return null; + } else { + return null; + } + } else { + return null; + } + } + }; + final ActiveMQServer server = addServer(new ActiveMQServerImpl(configuration, customSecurityManager)); + server.start(); + + final ServerLocator locator = createInVMNonHALocator(); + locator.setBlockOnNonDurableSend(true).setBlockOnDurableSend(true); + final ClientSessionFactory factory = createSessionFactory(locator); + ClientSession adminSession = factory.createSession("all", "frobnicate", false, true, true, false, -1); + + final String queueName = "test.queue"; + adminSession.createQueue(new QueueConfiguration(queueName).setDurable(false)); + + final String otherQueueName = "other.queue"; + adminSession.createQueue(new QueueConfiguration(otherQueueName).setDurable(false)); + + // Wrong user name + try { + factory.createSession("baz", "frobnicate", false, true, true, false, -1); + Assert.fail("should throw exception"); + } catch (ActiveMQSecurityException se) { + //ok + } catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Wrong password + try { + factory.createSession("foo", "xxx", false, true, true, false, -1); + Assert.fail("should throw exception"); + } catch (ActiveMQSecurityException se) { + //ok + } catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Correct user and password, wrong queue for sending + try { + final ClientSession session = factory.createSession("foo", "frobnicate", false, true, true, false, -1); + checkUserReceiveNoSend(otherQueueName, session, adminSession); + Assert.fail("should throw exception"); + } catch (ActiveMQSecurityException se) { + //ok + } catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Correct user and password, wrong queue for receiving + try { + final ClientSession session = factory.createSession("foo", "frobnicate", false, true, true, false, -1); + checkUserReceiveNoSend(otherQueueName, session, adminSession); + Assert.fail("should throw exception"); + } catch (ActiveMQSecurityException se) { + //ok + } catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Correct user and password, allowed to send but not receive + { + final ClientSession session = factory.createSession("foo", "frobnicate", false, true, true, false, -1); + checkUserReceiveNoSend(queueName, session, adminSession); + } + + // Correct user and password, allowed to receive but not send + { + final ClientSession session = factory.createSession("bar", "frobnicate", false, true, true, false, -1); + checkUserSendNoReceive(queueName, session); + } + } + // Check the user connection has both send and receive permissions on the queue private void checkUserSendAndReceive(final String genericQueueName, final ClientSession connection) throws Exception { diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java index 8a5077de96..22f123ab72 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java @@ -50,9 +50,9 @@ public class StompWithClientIdValidationTest extends StompTestBase { ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(InVMLoginModule.class.getName(), new SecurityConfiguration()) { @Override - public String validateUser(String user, String password, RemotingConnection remotingConnection) { + public String validateUser(String user, String password, RemotingConnection remotingConnection, String securityDomain) { - String validatedUser = super.validateUser(user, password, remotingConnection); + String validatedUser = super.validateUser(user, password, remotingConnection, securityDomain); if (validatedUser == null) { return null; } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithSecurityPerAcceptorTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithSecurityPerAcceptorTest.java new file mode 100644 index 0000000000..49a0036e69 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithSecurityPerAcceptorTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.tests.integration.stomp; + +import java.lang.management.ManagementFactory; +import java.net.URI; +import java.net.URL; + +import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.protocol.stomp.Stomp; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.server.ActiveMQServers; +import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; +import org.apache.activemq.artemis.tests.integration.stomp.util.ClientStompFrame; +import org.apache.activemq.artemis.tests.integration.stomp.util.StompClientConnection; +import org.apache.activemq.artemis.tests.integration.stomp.util.StompClientConnectionFactory; +import org.junit.Before; +import org.junit.Test; + +public class StompWithSecurityPerAcceptorTest extends StompTestBase { + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = StompWithSecurityPerAcceptorTest.class.getClassLoader().getResource("login.config"); + if (resource != null) { + path = resource.getFile(); + System.setProperty("java.security.auth.login.config", path); + } + } + } + + @Override + public boolean isSecurityEnabled() { + return true; + } + + @Override + @Before + public void setUp() throws Exception { + uri = new URI(scheme + "://" + hostname + ":" + port); + + server = createServer(); + server.start(); + + waitForServerToStart(server); + } + + @Override + protected ActiveMQServer createServer() throws Exception { + Configuration config = createBasicConfig() + .setSecurityEnabled(isSecurityEnabled()) + .setPersistenceEnabled(isPersistenceEnabled()) + .addAcceptorConfiguration("stomp", "tcp://localhost:61613?securityDomain=PropertiesLogin"); + + server = addServer(ActiveMQServers.newActiveMQServer(config, ManagementFactory.getPlatformMBeanServer(), new ActiveMQJAASSecurityManager())); + return server; + } + + @Test + public void testSecurityPerAcceptorPositive() throws Exception { + StompClientConnection conn = StompClientConnectionFactory.createClientConnection(uri); + ClientStompFrame frame = conn.connect("first", "secret"); + assertTrue(frame.getCommand().equals(Stomp.Responses.CONNECTED)); + } + + @Test + public void testSecurityPerAcceptorNegative() throws Exception { + StompClientConnection conn = StompClientConnectionFactory.createClientConnection(uri); + ClientStompFrame frame = conn.connect("fail", "secret"); + assertTrue(frame.getCommand().equals(Stomp.Responses.ERROR)); + } +}