From 765b2259243b1855ca0e0f2d4d3e481453951ebe Mon Sep 17 00:00:00 2001 From: jbertram Date: Tue, 28 Jun 2016 21:33:44 -0500 Subject: [PATCH] ARTEMIS-584 add validated user to msg Implements a new feature to aid in security auditing by adding the name of the validated user to the messages it sends. --- .../config/ActiveMQDefaultConfiguration.java | 10 + .../activemq/artemis/api/core/Message.java | 5 + .../activemq/artemis/reader/MessageUtil.java | 5 +- .../artemis/jms/client/ActiveMQMessage.java | 20 +- .../impl/openmbean/JMSOpenTypeSupport.java | 2 +- .../openwire/OpenWireProtocolManager.java | 8 +- .../artemis/core/protocol/stomp/Stomp.java | 2 + .../protocol/stomp/StompProtocolManager.java | 8 +- .../core/protocol/stomp/StompUtils.java | 29 +- .../artemis/core/config/Configuration.java | 4 + .../core/config/impl/ConfigurationImpl.java | 16 + .../impl/FileConfigurationParser.java | 2 + .../artemis/core/security/SecurityStore.java | 2 +- .../core/security/impl/SecurityStoreImpl.java | 23 +- .../core/server/impl/ActiveMQServerImpl.java | 10 +- .../core/server/impl/ServerSessionImpl.java | 9 + .../security/ActiveMQJAASSecurityManager.java | 34 ++- .../security/ActiveMQSecurityManager3.java | 64 ++++ .../schema/artemis-configuration.xsd | 8 + .../config/impl/FileConfigurationTest.java | 1 + .../ConfigurationTest-full-config.xml | 1 + docs/user-manual/en/configuration-index.md | 1 + docs/user-manual/en/security.md | 7 + .../integration/client/HangConsumerTest.java | 3 +- .../management/JMSQueueControlTest.java | 14 +- .../integration/security/SecurityTest.java | 287 +++++++++++++----- .../integration/stomp/StompTestBase.java | 32 +- .../stomp/StompTestWithSecurity.java | 61 ++++ 28 files changed, 545 insertions(+), 123 deletions(-) create mode 100644 artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager3.java create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestWithSecurity.java diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java index 1239b0b460..e8fe1c51ae 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java @@ -390,6 +390,9 @@ public final class ActiveMQDefaultConfiguration { // Will this backup server come live on a normal server shutdown private static boolean DEFAULT_FAILOVER_ON_SERVER_SHUTDOWN = false; + // Will the broker populate the message with the name of the validated user + private static boolean DEFAULT_POPULATE_VALIDATED_USER = false; + // its possible that you only want a server to partake in scale down as a receiver, via a group. In this case set scale-down to false private static boolean DEFAULT_SCALE_DOWN_ENABLED = true; @@ -1060,6 +1063,13 @@ public final class ActiveMQDefaultConfiguration { return DEFAULT_FAILOVER_ON_SERVER_SHUTDOWN; } + /** + * Will the broker populate the message with the name of the validated user + */ + public static boolean isDefaultPopulateValidatedUser() { + return DEFAULT_POPULATE_VALIDATED_USER; + } + /** * its possible that you only want a server to partake in scale down as a receiver, via a group. In this case set scale-down to false */ diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java index 5bb6f42d69..01db007939 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java @@ -106,6 +106,11 @@ public interface Message { */ SimpleString HDR_CONTENT_TYPE = new SimpleString("_AMQ_CONTENT_TYPE"); + /** + * The name of the validated user who sent the message. Useful for auditing. + */ + SimpleString HDR_VALIDATED_USER = new SimpleString("_AMQ_VALIDATED_USER"); + byte DEFAULT_TYPE = 0; byte OBJECT_TYPE = 2; diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/reader/MessageUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/reader/MessageUtil.java index f61384df7a..be6568cf5f 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/reader/MessageUtil.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/reader/MessageUtil.java @@ -49,6 +49,8 @@ public class MessageUtil { public static final String JMSXGROUPID = "JMSXGroupID"; + public static final String JMSXUSERID = "JMSXUserID"; + public static final SimpleString CONNECTION_ID_PROPERTY_NAME = new SimpleString("__AMQ_CID"); // public static ActiveMQBuffer getBodyBuffer(Message message) { @@ -155,6 +157,7 @@ public class MessageUtil { public static boolean propertyExists(Message message, String name) { return message.containsProperty(new SimpleString(name)) || name.equals(MessageUtil.JMSXDELIVERYCOUNT) || - MessageUtil.JMSXGROUPID.equals(name) && message.containsProperty(Message.HDR_GROUP_ID); + (MessageUtil.JMSXGROUPID.equals(name) && message.containsProperty(Message.HDR_GROUP_ID)) || + (MessageUtil.JMSXUSERID.equals(name) && message.containsProperty(Message.HDR_VALIDATED_USER)); } } diff --git a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java index 7b2ee5bb7b..2dd0df1f68 100644 --- a/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java +++ b/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessage.java @@ -581,6 +581,9 @@ public class ActiveMQMessage implements javax.jms.Message { if (MessageUtil.JMSXGROUPID.equals(name)) { return message.getStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_GROUP_ID); } + else if (MessageUtil.JMSXUSERID.equals(name)) { + return message.getStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER); + } else { return message.getStringProperty(new SimpleString(name)); } @@ -656,7 +659,10 @@ public class ActiveMQMessage implements javax.jms.Message { public void setStringProperty(final String name, final String value) throws JMSException { checkProperty(name); - if (handleGroupID(name, value)) { + if (handleCoreProperty(name, value, MessageUtil.JMSXGROUPID, org.apache.activemq.artemis.api.core.Message.HDR_GROUP_ID)) { + return; + } + else if (handleCoreProperty(name, value, MessageUtil.JMSXUSERID, org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER)) { return; } else { @@ -666,7 +672,11 @@ public class ActiveMQMessage implements javax.jms.Message { @Override public void setObjectProperty(final String name, final Object value) throws JMSException { - if (handleGroupID(name, value)) { + if (handleCoreProperty(name, value, MessageUtil.JMSXGROUPID, org.apache.activemq.artemis.api.core.Message.HDR_GROUP_ID)) { + return; + } + + if (handleCoreProperty(name, value, MessageUtil.JMSXUSERID, org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER)) { return; } @@ -954,11 +964,11 @@ public class ActiveMQMessage implements javax.jms.Message { } } - private boolean handleGroupID(final String name, final Object value) { + private boolean handleCoreProperty(final String name, final Object value, String jmsPropertyName, SimpleString corePropertyName) { boolean result = false; - if (MessageUtil.JMSXGROUPID.equals(name)) { - message.putStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_GROUP_ID, SimpleString.toSimpleString(value.toString())); + if (jmsPropertyName.equals(name)) { + message.putStringProperty(corePropertyName, SimpleString.toSimpleString(value.toString())); result = true; } diff --git a/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/management/impl/openmbean/JMSOpenTypeSupport.java b/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/management/impl/openmbean/JMSOpenTypeSupport.java index 513d1f2ff3..06ba26ccae 100644 --- a/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/management/impl/openmbean/JMSOpenTypeSupport.java +++ b/artemis-jms-server/src/main/java/org/apache/activemq/artemis/jms/management/impl/openmbean/JMSOpenTypeSupport.java @@ -166,7 +166,7 @@ public final class JMSOpenTypeSupport { rc.put(JMSCompositeDataConstants.JMS_REDELIVERED, data.get(CompositeDataConstants.REDELIVERED)); putStringProperty(rc, data, JMSCompositeDataConstants.JMSXGROUP_ID, Message.HDR_GROUP_ID.toString()); putIntProperty(rc, data, JMSCompositeDataConstants.JMSXGROUP_SEQ, JMSCompositeDataConstants.JMSXGROUP_SEQ); - putStringProperty(rc, data, JMSCompositeDataConstants.JMSXUSER_ID, JMSCompositeDataConstants.JMSXUSER_ID); + putStringProperty(rc, data, JMSCompositeDataConstants.JMSXUSER_ID, Message.HDR_VALIDATED_USER.toString()); putStringProperty(rc, data, JMSCompositeDataConstants.ORIGINAL_DESTINATION, Message.HDR_ORIGINAL_ADDRESS.toString()); rc.put(CompositeDataConstants.PROPERTIES, "" + data.get(CompositeDataConstants.PROPERTIES)); 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 b91a4e4589..d20b66162d 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 @@ -50,6 +50,7 @@ import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Acceptor; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3; import org.apache.activemq.artemis.utils.DataConstants; import org.apache.activemq.command.ActiveMQMessage; import org.apache.activemq.command.ActiveMQTopic; @@ -442,7 +443,12 @@ public class OpenWireProtocolManager implements ProtocolManager, Cl ActiveMQSecurityManager sm = server.getSecurityManager(); if (sm != null && server.getConfiguration().isSecurityEnabled()) { - validated = sm.validateUser(login, passcode); + if (sm instanceof ActiveMQSecurityManager3) { + validated = ((ActiveMQSecurityManager3) sm).validateUser(login, passcode, null) != null; + } + else { + validated = sm.validateUser(login, passcode); + } } return validated; diff --git a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/Stomp.java b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/Stomp.java index 160766689b..8e8acb3054 100644 --- a/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/Stomp.java +++ b/artemis-protocols/artemis-stomp-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/stomp/Stomp.java @@ -125,6 +125,8 @@ public interface Stomp { String ACK = "ack"; String PERSISTENT = "persistent"; + + String VALIDATED_USER = "JMSXUserID"; } public interface Subscribe { 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 601d8339a9..d572cd017d 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 @@ -45,6 +45,7 @@ import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Acceptor; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3; import org.apache.activemq.artemis.utils.UUIDGenerator; import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProtocolMessageBundle.BUNDLE; @@ -331,7 +332,12 @@ class StompProtocolManager extends AbstractProtocolManager 1)); @@ -98,22 +98,25 @@ public class StompUtils { } command.addHeader(Stomp.Headers.Message.TIMESTAMP, "" + message.getTimestamp()); - if (message.getObjectProperty("JMSType") != null) { - command.addHeader(Stomp.Headers.Message.TYPE, message.getObjectProperty("JMSType").toString()); + if (message.getObjectProperty(MessageUtil.TYPE_HEADER_NAME) != null) { + command.addHeader(Stomp.Headers.Message.TYPE, message.getObjectProperty(MessageUtil.TYPE_HEADER_NAME).toString()); } if (message.getStringProperty(Message.HDR_CONTENT_TYPE.toString()) != null) { command.addHeader(Stomp.Headers.CONTENT_TYPE, message.getStringProperty(Message.HDR_CONTENT_TYPE.toString())); } + if (message.getStringProperty(Message.HDR_VALIDATED_USER.toString()) != null) { + command.addHeader(Stomp.Headers.Message.VALIDATED_USER, message.getStringProperty(Message.HDR_VALIDATED_USER.toString())); + } - // now let's add all the message headers + // now let's add all the rest of the message headers Set names = message.getPropertyNames(); for (SimpleString name : names) { - String value = name.toString(); if (name.equals(ClientMessageImpl.REPLYTO_HEADER_NAME) || name.equals(Message.HDR_CONTENT_TYPE) || - value.equals("JMSType") || - value.equals("JMSCorrelationID") || - value.equals(Stomp.Headers.Message.DESTINATION)) { + name.equals(Message.HDR_VALIDATED_USER) || + name.equals(MessageUtil.TYPE_HEADER_NAME) || + name.equals(MessageUtil.CORRELATIONID_HEADER_NAME) || + name.toString().equals(Stomp.Headers.Message.DESTINATION)) { continue; } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java index 4a24b57c70..8eb7f10689 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java @@ -948,6 +948,10 @@ public interface Configuration { Configuration setStoreConfiguration(StoreConfiguration storeConfiguration); + boolean isPopulateValidatedUser(); + + Configuration setPopulateValidatedUser(boolean populateValidatedUser); + /** It will return all the connectors in a toString manner for debug purposes. */ String debugConnectors(); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java index 19ef7da96e..6ecdc77e9c 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java @@ -237,6 +237,8 @@ public class ConfigurationImpl implements Configuration, Serializable { private StoreConfiguration storeConfiguration; + protected boolean populateValidatedUser = ActiveMQDefaultConfiguration.isDefaultPopulateValidatedUser(); + /** * Parent folder for all data folders. */ @@ -1352,6 +1354,17 @@ public class ConfigurationImpl implements Configuration, Serializable { return this; } + @Override + public boolean isPopulateValidatedUser() { + return populateValidatedUser; + } + + @Override + public ConfigurationImpl setPopulateValidatedUser(boolean populateValidatedUser) { + this.populateValidatedUser = populateValidatedUser; + return this; + } + @Override public int hashCode() { final int prime = 31; @@ -1417,6 +1430,7 @@ public class ConfigurationImpl implements Configuration, Serializable { result = prime * result + (runSyncSpeedTest ? 1231 : 1237); result = prime * result + scheduledThreadPoolMaxSize; result = prime * result + (securityEnabled ? 1231 : 1237); + result = prime * result + (populateValidatedUser ? 1231 : 1237); result = prime * result + (int) (securityInvalidationInterval ^ (securityInvalidationInterval >>> 32)); result = prime * result + ((securitySettings == null) ? 0 : securitySettings.hashCode()); result = prime * result + (int) (serverDumpInterval ^ (serverDumpInterval >>> 32)); @@ -1654,6 +1668,8 @@ public class ConfigurationImpl implements Configuration, Serializable { return false; if (securityEnabled != other.securityEnabled) return false; + if (populateValidatedUser != other.populateValidatedUser) + return false; if (securityInvalidationInterval != other.securityInvalidationInterval) return false; if (securitySettings == null) { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java index ca4bb52efe..0a47f9f1ed 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java @@ -274,6 +274,8 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { config.setPasswordCodec(getString(e, "password-codec", DefaultSensitiveStringCodec.class.getName(), Validators.NOT_NULL_OR_EMPTY)); + config.setPopulateValidatedUser(getBoolean(e, "populate-validated-user", config.isPopulateValidatedUser())); + // parsing cluster password String passwordText = getString(e, "cluster-password", null, Validators.NO_CHECK); 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 88fab08be0..87ba380367 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 @@ -22,7 +22,7 @@ import org.apache.activemq.artemis.api.core.SimpleString; public interface SecurityStore { - void authenticate(String user, String password, X509Certificate[] certificates) throws Exception; + String authenticate(String user, String password, X509Certificate[] certificates) throws Exception; void check(SimpleString address, 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 dcda59f33e..208d0cc19f 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 @@ -35,6 +35,7 @@ import org.apache.activemq.artemis.core.settings.HierarchicalRepository; import org.apache.activemq.artemis.core.settings.HierarchicalRepositoryChangeListener; 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.utils.ConcurrentHashSet; import org.apache.activemq.artemis.utils.TypedProperties; import org.jboss.logging.Logger; @@ -99,7 +100,7 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC } @Override - public void authenticate(final String user, final String password, X509Certificate[] certificates) throws Exception { + public String authenticate(final String user, final String password, X509Certificate[] certificates) throws Exception { if (securityEnabled) { if (managementClusterUser.equals(user)) { @@ -115,20 +116,24 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC throw ActiveMQMessageBundle.BUNDLE.unableToValidateClusterUser(user); } else { - return; + return managementClusterUser; } } + String validatedUser = null; boolean userIsValid = false; - if (securityManager instanceof ActiveMQSecurityManager2) { + if (securityManager instanceof ActiveMQSecurityManager3) { + validatedUser = ((ActiveMQSecurityManager3)securityManager).validateUser(user, password, certificates); + } + else if (securityManager instanceof ActiveMQSecurityManager2) { userIsValid = ((ActiveMQSecurityManager2)securityManager).validateUser(user, password, certificates); } else { userIsValid = securityManager.validateUser(user, password); } - if (!userIsValid) { + if (!userIsValid && validatedUser == null) { if (notificationService != null) { TypedProperties props = new TypedProperties(); @@ -139,7 +144,11 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC throw ActiveMQMessageBundle.BUNDLE.unableToValidateUser(); } + + return validatedUser; } + + return null; } @Override @@ -167,7 +176,11 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC } final boolean validated; - if (securityManager instanceof ActiveMQSecurityManager2) { + 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) { final ActiveMQSecurityManager2 securityManager2 = (ActiveMQSecurityManager2) securityManager; validated = securityManager2.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection()); } 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 8acdc118f5..e67cd06f9c 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 @@ -1161,19 +1161,20 @@ public class ActiveMQServerImpl implements ActiveMQServer { final String defaultAddress, final SessionCallback callback, final boolean autoCreateQueues) throws Exception { + String validatedUser = ""; if (securityStore != null) { X509Certificate[] certificates = null; if (connection.getTransportConnection() instanceof NettyConnection) { certificates = CertificateUtil.getCertsFromChannel(((NettyConnection) connection.getTransportConnection()).getChannel()); } - securityStore.authenticate(username, password, certificates); + validatedUser = securityStore.authenticate(username, password, certificates); } - checkSessionLimit(username); + checkSessionLimit(validatedUser); final OperationContext context = storageManager.newContext(getExecutorFactory().getExecutor()); - final ServerSessionImpl session = internalCreateSession(name, username, password, minLargeMessageSize, connection, autoCommitSends, autoCommitAcks, preAcknowledge, xa, defaultAddress, callback, context, autoCreateQueues); + final ServerSessionImpl session = internalCreateSession(name, username, password, validatedUser, minLargeMessageSize, connection, autoCommitSends, autoCommitAcks, preAcknowledge, xa, defaultAddress, callback, context, autoCreateQueues); sessions.put(name, session); @@ -1237,6 +1238,7 @@ public class ActiveMQServerImpl implements ActiveMQServer { protected ServerSessionImpl internalCreateSession(String name, String username, String password, + String validatedUser, int minLargeMessageSize, RemotingConnection connection, boolean autoCommitSends, @@ -1247,7 +1249,7 @@ public class ActiveMQServerImpl implements ActiveMQServer { SessionCallback callback, OperationContext context, boolean autoCreateJMSQueues) throws Exception { - return new ServerSessionImpl(name, username, password, minLargeMessageSize, autoCommitSends, autoCommitAcks, preAcknowledge, configuration.isPersistDeliveryCountBeforeDelivery(), xa, connection, storageManager, postOffice, resourceManager, securityStore, managementService, this, configuration.getManagementAddress(), defaultAddress == null ? null : new SimpleString(defaultAddress), callback, context, autoCreateJMSQueues ? jmsQueueCreator : null); + 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, autoCreateJMSQueues ? jmsQueueCreator : null); } @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 50ccb50d1f..883f4992f3 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 @@ -105,6 +105,8 @@ public class ServerSessionImpl implements ServerSession, FailureListener { protected final String password; + protected final String validatedUser; + private final int minLargeMessageSize; protected boolean autoCommitSends; @@ -176,6 +178,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener { public ServerSessionImpl(final String name, final String username, final String password, + final String validatedUser, final int minLargeMessageSize, final boolean autoCommitSends, final boolean autoCommitAcks, @@ -198,6 +201,8 @@ public class ServerSessionImpl implements ServerSession, FailureListener { this.password = password; + this.validatedUser = validatedUser; + this.minLargeMessageSize = minLargeMessageSize; this.autoCommitSends = autoCommitSends; @@ -1230,6 +1235,10 @@ public class ServerSessionImpl implements ServerSession, FailureListener { message.encodeMessageIDToBuffer(); } + if (server.getConfiguration().isPopulateValidatedUser() && validatedUser != null) { + message.putStringProperty(Message.HDR_VALIDATED_USER, SimpleString.toSimpleString(validatedUser)); + } + SimpleString address = message.getAddress(); if (defaultAddress == null && address != null) { 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 a2d31bf80c..943851ece0 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 @@ -34,6 +34,7 @@ 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.jaas.JaasCallbackHandler; import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; +import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal; import org.apache.activemq.artemis.utils.CertificateUtil; import org.jboss.logging.Logger; @@ -43,7 +44,7 @@ import org.jboss.logging.Logger; * 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 ActiveMQSecurityManager2 { +public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 { private static final Logger logger = Logger.getLogger(ActiveMQJAASSecurityManager.class); @@ -81,30 +82,40 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 { @Override public boolean validateUser(String user, String password) { - return validateUser(user, password, null); + throw new UnsupportedOperationException("Invoke validateUser(String, String, X509Certificate[]) instead"); } @Override - public boolean validateUser(final String user, final String password, X509Certificate[] certificates) { + public String validateUser(final String user, final String password, X509Certificate[] certificates) { try { - getAuthenticatedSubject(user, password, certificates); - return true; + return getUserFromSubject(getAuthenticatedSubject(user, password, certificates)); } catch (LoginException e) { if (logger.isDebugEnabled()) { logger.debug("Couldn't validate user", e); } - return false; + return null; } } + public String getUserFromSubject(Subject subject) { + String validatedUser = ""; + Set users = subject.getPrincipals(UserPrincipal.class); + + // should only ever be 1 UserPrincipal + for (UserPrincipal userPrincipal : users) { + validatedUser = userPrincipal.getName(); + } + return validatedUser; + } + @Override public boolean validateUserAndRole(String user, String password, Set roles, CheckType checkType) { throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set, CheckType, String, RemotingConnection) instead"); } @Override - public boolean validateUserAndRole(final String user, + public String validateUserAndRole(final String user, final String password, final Set roles, final CheckType checkType, @@ -122,7 +133,7 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 { if (logger.isDebugEnabled()) { logger.debug("Couldn't validate user", e); } - return false; + return null; } boolean authorized = false; @@ -155,7 +166,12 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 { } } - return authorized; + if (authorized) { + return getUserFromSubject(localSubject); + } + else { + return null; + } } private Subject getAuthenticatedSubject(final String user, final String password, final X509Certificate[] certificates) throws LoginException { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager3.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager3.java new file mode 100644 index 0000000000..192f5dd402 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager3.java @@ -0,0 +1,64 @@ +/* + * 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 javax.security.cert.X509Certificate; +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 destinations. + * + * This is an evolution of {@link ActiveMQSecurityManager} and + * {@link ActiveMQSecurityManager2} that adds the ability to determine + * the identity of the validated user. + */ +public interface ActiveMQSecurityManager3 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 + * @return the name of the validated user or null if the user isn't validated + */ + String validateUser(String user, String password, X509Certificate[] certificates); + + /** + * 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 connection the user's connection + * @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 connection); +} diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd index 0d369bada8..53e5aa89e5 100644 --- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd +++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd @@ -324,6 +324,14 @@ + + + + true means that the server will add the name of the validated user to messages it sends + + + + diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java index f7db6e7f09..3f4edd8140 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java @@ -102,6 +102,7 @@ public class FileConfigurationTest extends ConfigurationImplTest { Assert.assertEquals(33, conf.getJournalCompactPercentage()); Assert.assertEquals(true, conf.isGracefulShutdownEnabled()); Assert.assertEquals(12345, conf.getGracefulShutdownTimeout()); + Assert.assertEquals(true, conf.isPopulateValidatedUser()); Assert.assertEquals("largemessagesdir", conf.getLargeMessagesDirectory()); Assert.assertEquals(95, conf.getMemoryWarningThreshold()); diff --git a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml index 930474580e..8bd540deae 100644 --- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml +++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml @@ -50,6 +50,7 @@ 8 127 true + true org.apache.activemq.artemis.tests.unit.core.config.impl.TestInterceptor1 org.apache.activemq.artemis.tests.unit.core.config.impl.TestInterceptor2 diff --git a/docs/user-manual/en/configuration-index.md b/docs/user-manual/en/configuration-index.md index 175a5dc658..57b36e0bef 100644 --- a/docs/user-manual/en/configuration-index.md +++ b/docs/user-manual/en/configuration-index.md @@ -81,6 +81,7 @@ Name | Description [scheduled-thread-pool-max-size](thread-pooling.md#server.scheduled.thread.pool "Server Scheduled Thread Pool")| Maximum number of threads to use for the scheduled thread pool. Default=5 [security-enabled](security.md "Security") | true means that security is enabled. Default=true [security-invalidation-interval](security.md "Security") | how long (in ms) to wait before invalidating the security cache. Default=10000 +[populate-validated-user](security.md "Security") | whether or not to add the name of the validated user to the messages that user sends. Default=false [security-settings](security.md "Role based security for addresses") | [a list of security-setting](#security-setting-type) [thread-pool-max-size](thread-pooling.md "Server Scheduled Thread Pool") | Maximum number of threads to use for the thread pool. -1 means 'no limits'.. Default=30 [transaction-timeout](transaction-config.md "Resource Manager Configuration") | how long (in ms) before a transaction can be removed from the resource manager after create time. Default=300000 diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md index 6c0d07895c..32c9a35901 100644 --- a/docs/user-manual/en/security.md +++ b/docs/user-manual/en/security.md @@ -10,6 +10,13 @@ long. To change this period set the property `security-invalidation-interval`, which is in milliseconds. The default is `10000` ms. +To assist in security auditing the `populate-validated-user` option exists. If this is `true` then +the server will add the name of the validated user to the message using the key `_AMQ_VALIDATED_USER`. +For JMS and Stomp clients this is mapped to the key `JMSXUserID`. For users authenticated based on +their SSL certificate this name is the name to which their certificate's DN maps. If `security-enabled` +is `false` and `populate-validated-user` is `true` then the server will simply use whatever user name +(if any) the client provides. This option is `false` by default. + ## Role based security for addresses Apache ActiveMQ Artemis contains a flexible role-based security model for applying 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 2be294744a..bbccbf163a 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 @@ -577,6 +577,7 @@ public class HangConsumerTest extends ActiveMQTestBase { protected ServerSessionImpl internalCreateSession(String name, String username, String password, + String validatedUser, int minLargeMessageSize, RemotingConnection connection, boolean autoCommitSends, @@ -587,7 +588,7 @@ public class HangConsumerTest extends ActiveMQTestBase { SessionCallback callback, OperationContext context, boolean autoCreateQueue) throws Exception { - return new ServerSessionImpl(name, username, password, 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, null); + 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, null); } } diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/management/JMSQueueControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/management/JMSQueueControlTest.java index 7636248896..6acd9ae35d 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/management/JMSQueueControlTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/jms/server/management/JMSQueueControlTest.java @@ -236,8 +236,8 @@ public class JMSQueueControlTest extends ManagementTestBase { CompositeData[] data = queueControl.browse(); Assert.assertEquals(1, data.length); - Assert.assertNotNull(data[0].get("JMSCorrelationID")); - Assert.assertEquals("foo", data[0].get("JMSCorrelationID")); + Assert.assertNotNull(data[0].get(MessageUtil.CORRELATIONID_HEADER_NAME.toString())); + Assert.assertEquals("foo", data[0].get(MessageUtil.CORRELATIONID_HEADER_NAME.toString())); System.out.println(data[0]); JMSUtil.consumeMessages(1, queue); @@ -248,8 +248,8 @@ public class JMSQueueControlTest extends ManagementTestBase { data = queueControl.browse(); Assert.assertEquals(1, data.length); - Assert.assertNotNull(data[0].get("JMSXGroupID")); - Assert.assertEquals("myGroupID", data[0].get("JMSXGroupID")); + Assert.assertNotNull(data[0].get(MessageUtil.JMSXGROUPID.toString())); + Assert.assertEquals("myGroupID", data[0].get(MessageUtil.JMSXGROUPID.toString())); System.out.println(data[0]); JMSUtil.consumeMessages(1, queue); @@ -266,14 +266,14 @@ public class JMSQueueControlTest extends ManagementTestBase { JMSUtil.consumeMessages(1, queue); - JMSUtil.sendMessageWithProperty(session, queue, "JMSXUserID", "theheadhonch"); + JMSUtil.sendMessageWithProperty(session, queue, MessageUtil.JMSXUSERID.toString(), "theheadhonch"); Assert.assertEquals(1, getMessageCount(queueControl)); data = queueControl.browse(); Assert.assertEquals(1, data.length); - Assert.assertNotNull(data[0].get("JMSXUserID")); - Assert.assertEquals("theheadhonch", data[0].get("JMSXUserID")); + Assert.assertNotNull(data[0].get(MessageUtil.JMSXUSERID.toString())); + Assert.assertEquals("theheadhonch", data[0].get(MessageUtil.JMSXUSERID.toString())); System.out.println(data[0]); JMSUtil.consumeMessages(1, queue); 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 71cd369948..fd61c00d84 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 @@ -52,6 +52,7 @@ 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.ActiveMQSecurityManager2; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.CreateMessage; import org.junit.Assert; @@ -1648,39 +1649,39 @@ public class SecurityTest extends ActiveMQTestBase { public void testCustomSecurityManager() throws Exception { final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager() { - @Override - public boolean validateUser(final String username, final String password) { - return (username.equals("foo") || username.equals("bar") || username.equals("all")) && - password.equals("frobnicate"); - } - @Override - public boolean validateUserAndRole( - final String username, - final String password, - final Set requiredRoles, - final CheckType checkType) { + @Override + public boolean validateUser(final String username, final String password) { + return (username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate"); + } + @Override + public boolean validateUserAndRole( + final String username, + final String password, + final Set requiredRoles, + final CheckType checkType) { - if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && - password.equals("frobnicate")) { + if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate")) { - if (username.equals("all")) { - return true; - } - else if (username.equals("foo")) { - return checkType == CheckType.CONSUME || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; - } - else if (username.equals("bar")) { - return checkType == CheckType.SEND || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; - } - else { - return false; - } + if (username.equals("all")) { + return true; + } + else if (username.equals("foo")) { + return checkType == CheckType.CONSUME || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; + } + else if (username.equals("bar")) { + return checkType == CheckType.SEND || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; } else { return false; } } - }; + else { + return false; + } + } + }; final ActiveMQServer server = addServer(new ActiveMQServerImpl(configuration, customSecurityManager)); server.start(); @@ -1734,61 +1735,207 @@ public class SecurityTest extends ActiveMQTestBase { public void testCustomSecurityManager2() throws Exception { final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager2() { - @Override - public boolean validateUser(final String username, final String password) { - fail("Unexpected call to overridden method"); - return false; - } - @Override - public boolean validateUser(final String username, final String password, final X509Certificate[] certificates) { - return (username.equals("foo") || username.equals("bar") || username.equals("all")) && - password.equals("frobnicate"); - } - @Override - public boolean validateUserAndRole( - final String username, - final String password, - final Set requiredRoles, - final CheckType checkType) { + @Override + public boolean validateUser(final String username, final String password) { + fail("Unexpected call to overridden method"); + return false; + } + @Override + public boolean validateUser(final String username, final String password, final X509Certificate[] certificates) { + return (username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate"); + } + @Override + public boolean validateUserAndRole( + final String username, + final String password, + final Set requiredRoles, + final CheckType checkType) { - fail("Unexpected call to overridden method"); + fail("Unexpected call to overridden method"); + return false; + } + + @Override + public boolean validateUserAndRole( + final String username, + final String password, + final Set requiredRoles, + final CheckType checkType, + final String address, + final RemotingConnection connection) { + + if (!(connection.getTransportConnection() instanceof InVMConnection)) { return false; } - @Override - public boolean validateUserAndRole( - final String username, - final String password, - final Set requiredRoles, - final CheckType checkType, - final String address, - final RemotingConnection connection) { + if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate")) { - if (!(connection.getTransportConnection() instanceof InVMConnection)) { - return false; + if (username.equals("all")) { + return true; } - - if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && - password.equals("frobnicate")) { - - if (username.equals("all")) { - return true; - } - else if (username.equals("foo")) { - return address.equals("test.queue") && checkType == CheckType.CONSUME; - } - else if (username.equals("bar")) { - return address.equals("test.queue") && checkType == CheckType.SEND; - } - else { - return false; - } + else if (username.equals("foo")) { + return address.equals("test.queue") && checkType == CheckType.CONSUME; + } + else if (username.equals("bar")) { + return address.equals("test.queue") && checkType == CheckType.SEND; } else { return false; } } - }; + else { + return false; + } + } + }; + 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(queueName, queueName, false); + + final String otherQueueName = "other.queue"; + adminSession.createQueue(otherQueueName, otherQueueName, 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); + } + } + + @Test + public void testCustomSecurityManager3() throws Exception { + final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); + final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager3() { + @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 X509Certificate[] certificates) { + 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) { + + 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(); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestBase.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestBase.java index 827bf68647..f2def067d6 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestBase.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestBase.java @@ -30,6 +30,7 @@ import java.net.Socket; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -50,8 +51,6 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import org.apache.activemq.artemis.api.core.TransportConfiguration; -import org.apache.activemq.artemis.tests.unit.util.InVMNamingContext; -import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.protocol.stomp.StompProtocolManagerFactory; import org.apache.activemq.artemis.core.registry.JndiBindingRegistry; @@ -59,6 +58,7 @@ import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory; import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +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.jms.client.ActiveMQConnectionFactory; @@ -69,6 +69,9 @@ import org.apache.activemq.artemis.jms.server.config.impl.JMSConfigurationImpl; import org.apache.activemq.artemis.jms.server.config.impl.JMSQueueConfigurationImpl; import org.apache.activemq.artemis.jms.server.config.impl.TopicConfigurationImpl; import org.apache.activemq.artemis.jms.server.impl.JMSServerManagerImpl; +import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; +import org.apache.activemq.artemis.tests.unit.util.InVMNamingContext; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.junit.After; import org.junit.Before; @@ -116,7 +119,12 @@ public abstract class StompTestBase extends ActiveMQTestBase { connectionFactory = createConnectionFactory(); createBootstrap(); - connection = connectionFactory.createConnection(); + if (isSecurityEnabled()) { + connection = connectionFactory.createConnection("brianm", "wombats"); + } + else { + connection = connectionFactory.createConnection(); + } session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); queue = session.createQueue(getQueueName()); topic = session.createTopic(getTopicName()); @@ -185,11 +193,23 @@ public abstract class StompTestBase extends ActiveMQTestBase { TransportConfiguration stompTransport = new TransportConfiguration(NettyAcceptorFactory.class.getName(), params); TransportConfiguration allTransport = new TransportConfiguration(NettyAcceptorFactory.class.getName()); - Configuration config = createBasicConfig().setPersistenceEnabled(false).addAcceptorConfiguration(stompTransport).addAcceptorConfiguration(new TransportConfiguration(InVMAcceptorFactory.class.getName())); + Configuration config = createBasicConfig().setSecurityEnabled(isSecurityEnabled()).setPersistenceEnabled(false).addAcceptorConfiguration(stompTransport).addAcceptorConfiguration(new TransportConfiguration(InVMAcceptorFactory.class.getName())); config.addAcceptorConfiguration(allTransport); ActiveMQServer activeMQServer = addServer(ActiveMQServers.newActiveMQServer(config, defUser, defPass)); + if (isSecurityEnabled()) { + ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) activeMQServer.getSecurityManager(); + + final String role = "testRole"; + securityManager.getConfiguration().addRole(defUser, role); + config.getSecurityRoles().put("#", new HashSet() { + { + add(new Role(role, true, true, true, true, true, true, true)); + } + }); + } + JMSConfiguration jmsConfig = new JMSConfigurationImpl(); jmsConfig.getQueueConfigurations().add(new JMSQueueConfigurationImpl().setName(getQueueName()).setDurable(false).setBindings(getQueueName())); jmsConfig.getTopicConfigurations().add(new TopicConfigurationImpl().setName(getTopicName()).setBindings(getTopicName())); @@ -319,6 +339,10 @@ public abstract class StompTestBase extends ActiveMQTestBase { Thread.sleep(500); } + public boolean isSecurityEnabled() { + return false; + } + class StompClientHandler extends SimpleChannelInboundHandler { StringBuffer currentMessage = new StringBuffer(""); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestWithSecurity.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestWithSecurity.java new file mode 100644 index 0000000000..40e8a6369f --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompTestWithSecurity.java @@ -0,0 +1,61 @@ +/* + * 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 javax.jms.MessageConsumer; +import javax.jms.TextMessage; + +import org.apache.activemq.artemis.core.protocol.stomp.Stomp; +import org.junit.Assert; +import org.junit.Test; + +public class StompTestWithSecurity extends StompTestBase { + + @Test + public void testJMSXUserID() throws Exception { + server.getActiveMQServer().getConfiguration().setPopulateValidatedUser(true); + + MessageConsumer consumer = session.createConsumer(queue); + + String frame = "CONNECT\n" + "login: brianm\n" + "passcode: wombats\n\n" + Stomp.NULL; + sendFrame(frame); + + frame = receiveFrame(10000); + Assert.assertTrue(frame.startsWith("CONNECTED")); + + frame = "SEND\n" + "destination:" + getQueuePrefix() + getQueueName() + "\n\n" + "Hello World" + Stomp.NULL; + + sendFrame(frame); + + TextMessage message = (TextMessage) consumer.receive(1000); + Assert.assertNotNull(message); + Assert.assertEquals("Hello World", message.getText()); + // Assert default priority 4 is used when priority header is not set + Assert.assertEquals("getJMSPriority", 4, message.getJMSPriority()); + Assert.assertEquals("JMSXUserID", "brianm", message.getStringProperty("JMSXUserID")); + + // Make sure that the timestamp is valid - should + // be very close to the current time. + long tnow = System.currentTimeMillis(); + long tmsg = message.getJMSTimestamp(); + Assert.assertTrue(Math.abs(tnow - tmsg) < 1000); + } + + public boolean isSecurityEnabled() { + return true; + } +}