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.
This commit is contained in:
jbertram 2016-06-28 21:33:44 -05:00
parent 2b62bc74fa
commit 765b225924
28 changed files with 545 additions and 123 deletions

View File

@ -390,6 +390,9 @@ public final class ActiveMQDefaultConfiguration {
// Will this backup server come live on a normal server shutdown // Will this backup server come live on a normal server shutdown
private static boolean DEFAULT_FAILOVER_ON_SERVER_SHUTDOWN = false; 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 // 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; private static boolean DEFAULT_SCALE_DOWN_ENABLED = true;
@ -1060,6 +1063,13 @@ public final class ActiveMQDefaultConfiguration {
return DEFAULT_FAILOVER_ON_SERVER_SHUTDOWN; 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 * 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
*/ */

View File

@ -106,6 +106,11 @@ public interface Message {
*/ */
SimpleString HDR_CONTENT_TYPE = new SimpleString("_AMQ_CONTENT_TYPE"); 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 DEFAULT_TYPE = 0;
byte OBJECT_TYPE = 2; byte OBJECT_TYPE = 2;

View File

@ -49,6 +49,8 @@ public class MessageUtil {
public static final String JMSXGROUPID = "JMSXGroupID"; 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 final SimpleString CONNECTION_ID_PROPERTY_NAME = new SimpleString("__AMQ_CID");
// public static ActiveMQBuffer getBodyBuffer(Message message) { // public static ActiveMQBuffer getBodyBuffer(Message message) {
@ -155,6 +157,7 @@ public class MessageUtil {
public static boolean propertyExists(Message message, String name) { public static boolean propertyExists(Message message, String name) {
return message.containsProperty(new SimpleString(name)) || name.equals(MessageUtil.JMSXDELIVERYCOUNT) || 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));
} }
} }

View File

@ -581,6 +581,9 @@ public class ActiveMQMessage implements javax.jms.Message {
if (MessageUtil.JMSXGROUPID.equals(name)) { if (MessageUtil.JMSXGROUPID.equals(name)) {
return message.getStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_GROUP_ID); 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 { else {
return message.getStringProperty(new SimpleString(name)); 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 { public void setStringProperty(final String name, final String value) throws JMSException {
checkProperty(name); 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; return;
} }
else { else {
@ -666,7 +672,11 @@ public class ActiveMQMessage implements javax.jms.Message {
@Override @Override
public void setObjectProperty(final String name, final Object value) throws JMSException { 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; 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; boolean result = false;
if (MessageUtil.JMSXGROUPID.equals(name)) { if (jmsPropertyName.equals(name)) {
message.putStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_GROUP_ID, SimpleString.toSimpleString(value.toString())); message.putStringProperty(corePropertyName, SimpleString.toSimpleString(value.toString()));
result = true; result = true;
} }

View File

@ -166,7 +166,7 @@ public final class JMSOpenTypeSupport {
rc.put(JMSCompositeDataConstants.JMS_REDELIVERED, data.get(CompositeDataConstants.REDELIVERED)); rc.put(JMSCompositeDataConstants.JMS_REDELIVERED, data.get(CompositeDataConstants.REDELIVERED));
putStringProperty(rc, data, JMSCompositeDataConstants.JMSXGROUP_ID, Message.HDR_GROUP_ID.toString()); putStringProperty(rc, data, JMSCompositeDataConstants.JMSXGROUP_ID, Message.HDR_GROUP_ID.toString());
putIntProperty(rc, data, JMSCompositeDataConstants.JMSXGROUP_SEQ, JMSCompositeDataConstants.JMSXGROUP_SEQ); 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()); putStringProperty(rc, data, JMSCompositeDataConstants.ORIGINAL_DESTINATION, Message.HDR_ORIGINAL_ADDRESS.toString());
rc.put(CompositeDataConstants.PROPERTIES, "" + data.get(CompositeDataConstants.PROPERTIES)); rc.put(CompositeDataConstants.PROPERTIES, "" + data.get(CompositeDataConstants.PROPERTIES));

View File

@ -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.Acceptor;
import org.apache.activemq.artemis.spi.core.remoting.Connection; 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.ActiveMQSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3;
import org.apache.activemq.artemis.utils.DataConstants; import org.apache.activemq.artemis.utils.DataConstants;
import org.apache.activemq.command.ActiveMQMessage; import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.command.ActiveMQTopic;
@ -442,7 +443,12 @@ public class OpenWireProtocolManager implements ProtocolManager<Interceptor>, Cl
ActiveMQSecurityManager sm = server.getSecurityManager(); ActiveMQSecurityManager sm = server.getSecurityManager();
if (sm != null && server.getConfiguration().isSecurityEnabled()) { 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; return validated;

View File

@ -125,6 +125,8 @@ public interface Stomp {
String ACK = "ack"; String ACK = "ack";
String PERSISTENT = "persistent"; String PERSISTENT = "persistent";
String VALIDATED_USER = "JMSXUserID";
} }
public interface Subscribe { public interface Subscribe {

View File

@ -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.Acceptor;
import org.apache.activemq.artemis.spi.core.remoting.Connection; 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.ActiveMQSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3;
import org.apache.activemq.artemis.utils.UUIDGenerator; import org.apache.activemq.artemis.utils.UUIDGenerator;
import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProtocolMessageBundle.BUNDLE; import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProtocolMessageBundle.BUNDLE;
@ -331,7 +332,12 @@ class StompProtocolManager extends AbstractProtocolManager<StompFrame,StompFrame
ActiveMQSecurityManager sm = server.getSecurityManager(); ActiveMQSecurityManager sm = server.getSecurityManager();
if (sm != null && server.getConfiguration().isSecurityEnabled()) { 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; return validated;

View File

@ -26,6 +26,7 @@ import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.client.impl.ClientMessageImpl; import org.apache.activemq.artemis.core.client.impl.ClientMessageImpl;
import org.apache.activemq.artemis.core.message.impl.MessageInternal; import org.apache.activemq.artemis.core.message.impl.MessageInternal;
import org.apache.activemq.artemis.core.server.impl.ServerMessageImpl; import org.apache.activemq.artemis.core.server.impl.ServerMessageImpl;
import org.apache.activemq.artemis.reader.MessageUtil;
public class StompUtils { public class StompUtils {
@ -51,11 +52,10 @@ public class StompUtils {
msg.setDurable(Boolean.parseBoolean(persistent)); msg.setDurable(Boolean.parseBoolean(persistent));
} }
// FIXME should use a proper constant msg.putObjectProperty(MessageUtil.CORRELATIONID_HEADER_NAME, headers.remove(Stomp.Headers.Send.CORRELATION_ID));
msg.putObjectProperty("JMSCorrelationID", headers.remove(Stomp.Headers.Send.CORRELATION_ID)); msg.putObjectProperty(MessageUtil.TYPE_HEADER_NAME, headers.remove(Stomp.Headers.Send.TYPE));
msg.putObjectProperty("JMSType", headers.remove(Stomp.Headers.Send.TYPE));
String groupID = headers.remove("JMSXGroupID"); String groupID = headers.remove(MessageUtil.JMSXGROUPID);
if (groupID != null) { if (groupID != null) {
msg.putStringProperty(Message.HDR_GROUP_ID, SimpleString.toSimpleString(groupID)); msg.putStringProperty(Message.HDR_GROUP_ID, SimpleString.toSimpleString(groupID));
} }
@ -86,8 +86,8 @@ public class StompUtils {
command.addHeader(Stomp.Headers.Message.MESSAGE_ID, String.valueOf(message.getMessageID())); command.addHeader(Stomp.Headers.Message.MESSAGE_ID, String.valueOf(message.getMessageID()));
command.addHeader(Stomp.Headers.Message.DESTINATION, message.getAddress().toString()); command.addHeader(Stomp.Headers.Message.DESTINATION, message.getAddress().toString());
if (message.getObjectProperty("JMSCorrelationID") != null) { if (message.getObjectProperty(MessageUtil.CORRELATIONID_HEADER_NAME) != null) {
command.addHeader(Stomp.Headers.Message.CORRELATION_ID, message.getObjectProperty("JMSCorrelationID").toString()); command.addHeader(Stomp.Headers.Message.CORRELATION_ID, message.getObjectProperty(MessageUtil.CORRELATIONID_HEADER_NAME).toString());
} }
command.addHeader(Stomp.Headers.Message.EXPIRATION_TIME, "" + message.getExpiration()); command.addHeader(Stomp.Headers.Message.EXPIRATION_TIME, "" + message.getExpiration());
command.addHeader(Stomp.Headers.Message.REDELIVERED, String.valueOf(deliveryCount > 1)); command.addHeader(Stomp.Headers.Message.REDELIVERED, String.valueOf(deliveryCount > 1));
@ -98,22 +98,25 @@ public class StompUtils {
} }
command.addHeader(Stomp.Headers.Message.TIMESTAMP, "" + message.getTimestamp()); command.addHeader(Stomp.Headers.Message.TIMESTAMP, "" + message.getTimestamp());
if (message.getObjectProperty("JMSType") != null) { if (message.getObjectProperty(MessageUtil.TYPE_HEADER_NAME) != null) {
command.addHeader(Stomp.Headers.Message.TYPE, message.getObjectProperty("JMSType").toString()); command.addHeader(Stomp.Headers.Message.TYPE, message.getObjectProperty(MessageUtil.TYPE_HEADER_NAME).toString());
} }
if (message.getStringProperty(Message.HDR_CONTENT_TYPE.toString()) != null) { if (message.getStringProperty(Message.HDR_CONTENT_TYPE.toString()) != null) {
command.addHeader(Stomp.Headers.CONTENT_TYPE, message.getStringProperty(Message.HDR_CONTENT_TYPE.toString())); 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<SimpleString> names = message.getPropertyNames(); Set<SimpleString> names = message.getPropertyNames();
for (SimpleString name : names) { for (SimpleString name : names) {
String value = name.toString();
if (name.equals(ClientMessageImpl.REPLYTO_HEADER_NAME) || if (name.equals(ClientMessageImpl.REPLYTO_HEADER_NAME) ||
name.equals(Message.HDR_CONTENT_TYPE) || name.equals(Message.HDR_CONTENT_TYPE) ||
value.equals("JMSType") || name.equals(Message.HDR_VALIDATED_USER) ||
value.equals("JMSCorrelationID") || name.equals(MessageUtil.TYPE_HEADER_NAME) ||
value.equals(Stomp.Headers.Message.DESTINATION)) { name.equals(MessageUtil.CORRELATIONID_HEADER_NAME) ||
name.toString().equals(Stomp.Headers.Message.DESTINATION)) {
continue; continue;
} }

View File

@ -948,6 +948,10 @@ public interface Configuration {
Configuration setStoreConfiguration(StoreConfiguration storeConfiguration); Configuration setStoreConfiguration(StoreConfiguration storeConfiguration);
boolean isPopulateValidatedUser();
Configuration setPopulateValidatedUser(boolean populateValidatedUser);
/** It will return all the connectors in a toString manner for debug purposes. */ /** It will return all the connectors in a toString manner for debug purposes. */
String debugConnectors(); String debugConnectors();

View File

@ -237,6 +237,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
private StoreConfiguration storeConfiguration; private StoreConfiguration storeConfiguration;
protected boolean populateValidatedUser = ActiveMQDefaultConfiguration.isDefaultPopulateValidatedUser();
/** /**
* Parent folder for all data folders. * Parent folder for all data folders.
*/ */
@ -1352,6 +1354,17 @@ public class ConfigurationImpl implements Configuration, Serializable {
return this; return this;
} }
@Override
public boolean isPopulateValidatedUser() {
return populateValidatedUser;
}
@Override
public ConfigurationImpl setPopulateValidatedUser(boolean populateValidatedUser) {
this.populateValidatedUser = populateValidatedUser;
return this;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
@ -1417,6 +1430,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
result = prime * result + (runSyncSpeedTest ? 1231 : 1237); result = prime * result + (runSyncSpeedTest ? 1231 : 1237);
result = prime * result + scheduledThreadPoolMaxSize; result = prime * result + scheduledThreadPoolMaxSize;
result = prime * result + (securityEnabled ? 1231 : 1237); result = prime * result + (securityEnabled ? 1231 : 1237);
result = prime * result + (populateValidatedUser ? 1231 : 1237);
result = prime * result + (int) (securityInvalidationInterval ^ (securityInvalidationInterval >>> 32)); result = prime * result + (int) (securityInvalidationInterval ^ (securityInvalidationInterval >>> 32));
result = prime * result + ((securitySettings == null) ? 0 : securitySettings.hashCode()); result = prime * result + ((securitySettings == null) ? 0 : securitySettings.hashCode());
result = prime * result + (int) (serverDumpInterval ^ (serverDumpInterval >>> 32)); result = prime * result + (int) (serverDumpInterval ^ (serverDumpInterval >>> 32));
@ -1654,6 +1668,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
return false; return false;
if (securityEnabled != other.securityEnabled) if (securityEnabled != other.securityEnabled)
return false; return false;
if (populateValidatedUser != other.populateValidatedUser)
return false;
if (securityInvalidationInterval != other.securityInvalidationInterval) if (securityInvalidationInterval != other.securityInvalidationInterval)
return false; return false;
if (securitySettings == null) { if (securitySettings == null) {

View File

@ -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.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 // parsing cluster password
String passwordText = getString(e, "cluster-password", null, Validators.NO_CHECK); String passwordText = getString(e, "cluster-password", null, Validators.NO_CHECK);

View File

@ -22,7 +22,7 @@ import org.apache.activemq.artemis.api.core.SimpleString;
public interface SecurityStore { 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; void check(SimpleString address, CheckType checkType, SecurityAuth session) throws Exception;

View File

@ -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.core.settings.HierarchicalRepositoryChangeListener;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; 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.ActiveMQSecurityManager2;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3;
import org.apache.activemq.artemis.utils.ConcurrentHashSet; import org.apache.activemq.artemis.utils.ConcurrentHashSet;
import org.apache.activemq.artemis.utils.TypedProperties; import org.apache.activemq.artemis.utils.TypedProperties;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -99,7 +100,7 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
} }
@Override @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 (securityEnabled) {
if (managementClusterUser.equals(user)) { if (managementClusterUser.equals(user)) {
@ -115,20 +116,24 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
throw ActiveMQMessageBundle.BUNDLE.unableToValidateClusterUser(user); throw ActiveMQMessageBundle.BUNDLE.unableToValidateClusterUser(user);
} }
else { else {
return; return managementClusterUser;
} }
} }
String validatedUser = null;
boolean userIsValid = false; 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); userIsValid = ((ActiveMQSecurityManager2)securityManager).validateUser(user, password, certificates);
} }
else { else {
userIsValid = securityManager.validateUser(user, password); userIsValid = securityManager.validateUser(user, password);
} }
if (!userIsValid) { if (!userIsValid && validatedUser == null) {
if (notificationService != null) { if (notificationService != null) {
TypedProperties props = new TypedProperties(); TypedProperties props = new TypedProperties();
@ -139,7 +144,11 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
throw ActiveMQMessageBundle.BUNDLE.unableToValidateUser(); throw ActiveMQMessageBundle.BUNDLE.unableToValidateUser();
} }
return validatedUser;
} }
return null;
} }
@Override @Override
@ -167,7 +176,11 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
} }
final boolean validated; 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; final ActiveMQSecurityManager2 securityManager2 = (ActiveMQSecurityManager2) securityManager;
validated = securityManager2.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection()); validated = securityManager2.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection());
} }

View File

@ -1161,19 +1161,20 @@ public class ActiveMQServerImpl implements ActiveMQServer {
final String defaultAddress, final String defaultAddress,
final SessionCallback callback, final SessionCallback callback,
final boolean autoCreateQueues) throws Exception { final boolean autoCreateQueues) throws Exception {
String validatedUser = "";
if (securityStore != null) { if (securityStore != null) {
X509Certificate[] certificates = null; X509Certificate[] certificates = null;
if (connection.getTransportConnection() instanceof NettyConnection) { if (connection.getTransportConnection() instanceof NettyConnection) {
certificates = CertificateUtil.getCertsFromChannel(((NettyConnection) connection.getTransportConnection()).getChannel()); 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 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); sessions.put(name, session);
@ -1237,6 +1238,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
protected ServerSessionImpl internalCreateSession(String name, protected ServerSessionImpl internalCreateSession(String name,
String username, String username,
String password, String password,
String validatedUser,
int minLargeMessageSize, int minLargeMessageSize,
RemotingConnection connection, RemotingConnection connection,
boolean autoCommitSends, boolean autoCommitSends,
@ -1247,7 +1249,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
SessionCallback callback, SessionCallback callback,
OperationContext context, OperationContext context,
boolean autoCreateJMSQueues) throws Exception { 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 @Override

View File

@ -105,6 +105,8 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
protected final String password; protected final String password;
protected final String validatedUser;
private final int minLargeMessageSize; private final int minLargeMessageSize;
protected boolean autoCommitSends; protected boolean autoCommitSends;
@ -176,6 +178,7 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
public ServerSessionImpl(final String name, public ServerSessionImpl(final String name,
final String username, final String username,
final String password, final String password,
final String validatedUser,
final int minLargeMessageSize, final int minLargeMessageSize,
final boolean autoCommitSends, final boolean autoCommitSends,
final boolean autoCommitAcks, final boolean autoCommitAcks,
@ -198,6 +201,8 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
this.password = password; this.password = password;
this.validatedUser = validatedUser;
this.minLargeMessageSize = minLargeMessageSize; this.minLargeMessageSize = minLargeMessageSize;
this.autoCommitSends = autoCommitSends; this.autoCommitSends = autoCommitSends;
@ -1230,6 +1235,10 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
message.encodeMessageIDToBuffer(); message.encodeMessageIDToBuffer();
} }
if (server.getConfiguration().isPopulateValidatedUser() && validatedUser != null) {
message.putStringProperty(Message.HDR_VALIDATED_USER, SimpleString.toSimpleString(validatedUser));
}
SimpleString address = message.getAddress(); SimpleString address = message.getAddress();
if (defaultAddress == null && address != null) { if (defaultAddress == null && address != null) {

View File

@ -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.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler; 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.RolePrincipal;
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
import org.apache.activemq.artemis.utils.CertificateUtil; import org.apache.activemq.artemis.utils.CertificateUtil;
import org.jboss.logging.Logger; 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 * The {@link Subject} returned by the login context is expecting to have a set of {@link RolePrincipal} for each
* role of the user. * role of the user.
*/ */
public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 { public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager3 {
private static final Logger logger = Logger.getLogger(ActiveMQJAASSecurityManager.class); private static final Logger logger = Logger.getLogger(ActiveMQJAASSecurityManager.class);
@ -81,30 +82,40 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 {
@Override @Override
public boolean validateUser(String user, String password) { public boolean validateUser(String user, String password) {
return validateUser(user, password, null); throw new UnsupportedOperationException("Invoke validateUser(String, String, X509Certificate[]) instead");
} }
@Override @Override
public boolean validateUser(final String user, final String password, X509Certificate[] certificates) { public String validateUser(final String user, final String password, X509Certificate[] certificates) {
try { try {
getAuthenticatedSubject(user, password, certificates); return getUserFromSubject(getAuthenticatedSubject(user, password, certificates));
return true;
} }
catch (LoginException e) { catch (LoginException e) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Couldn't validate user", e); logger.debug("Couldn't validate user", e);
} }
return false; return null;
} }
} }
public String getUserFromSubject(Subject subject) {
String validatedUser = "";
Set<UserPrincipal> users = subject.getPrincipals(UserPrincipal.class);
// should only ever be 1 UserPrincipal
for (UserPrincipal userPrincipal : users) {
validatedUser = userPrincipal.getName();
}
return validatedUser;
}
@Override @Override
public boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType) { public boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType) {
throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set<Role>, CheckType, String, RemotingConnection) instead"); throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set<Role>, CheckType, String, RemotingConnection) instead");
} }
@Override @Override
public boolean validateUserAndRole(final String user, public String validateUserAndRole(final String user,
final String password, final String password,
final Set<Role> roles, final Set<Role> roles,
final CheckType checkType, final CheckType checkType,
@ -122,7 +133,7 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Couldn't validate user", e); logger.debug("Couldn't validate user", e);
} }
return false; return null;
} }
boolean authorized = false; 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 { private Subject getAuthenticatedSubject(final String user, final String password, final X509Certificate[] certificates) throws LoginException {

View File

@ -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<Role> roles, CheckType checkType, String address, RemotingConnection connection);
}

View File

@ -324,6 +324,14 @@
</xsd:annotation> </xsd:annotation>
</xsd:element> </xsd:element>
<xsd:element name="populate-validated-user" type="xsd:boolean" default="false" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
true means that the server will add the name of the validated user to messages it sends
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="connectors" maxOccurs="1" minOccurs="0"> <xsd:element name="connectors" maxOccurs="1" minOccurs="0">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>

View File

@ -102,6 +102,7 @@ public class FileConfigurationTest extends ConfigurationImplTest {
Assert.assertEquals(33, conf.getJournalCompactPercentage()); Assert.assertEquals(33, conf.getJournalCompactPercentage());
Assert.assertEquals(true, conf.isGracefulShutdownEnabled()); Assert.assertEquals(true, conf.isGracefulShutdownEnabled());
Assert.assertEquals(12345, conf.getGracefulShutdownTimeout()); Assert.assertEquals(12345, conf.getGracefulShutdownTimeout());
Assert.assertEquals(true, conf.isPopulateValidatedUser());
Assert.assertEquals("largemessagesdir", conf.getLargeMessagesDirectory()); Assert.assertEquals("largemessagesdir", conf.getLargeMessagesDirectory());
Assert.assertEquals(95, conf.getMemoryWarningThreshold()); Assert.assertEquals(95, conf.getMemoryWarningThreshold());

View File

@ -50,6 +50,7 @@
<message-expiry-thread-priority>8</message-expiry-thread-priority> <message-expiry-thread-priority>8</message-expiry-thread-priority>
<id-cache-size>127</id-cache-size> <id-cache-size>127</id-cache-size>
<persist-id-cache>true</persist-id-cache> <persist-id-cache>true</persist-id-cache>
<populate-validated-user>true</populate-validated-user>
<remoting-incoming-interceptors> <remoting-incoming-interceptors>
<class-name>org.apache.activemq.artemis.tests.unit.core.config.impl.TestInterceptor1</class-name> <class-name>org.apache.activemq.artemis.tests.unit.core.config.impl.TestInterceptor1</class-name>
<class-name>org.apache.activemq.artemis.tests.unit.core.config.impl.TestInterceptor2</class-name> <class-name>org.apache.activemq.artemis.tests.unit.core.config.impl.TestInterceptor2</class-name>

View File

@ -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 [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-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 [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) [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 [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 [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

View File

@ -10,6 +10,13 @@ long. To change this period set the property
`security-invalidation-interval`, which is in milliseconds. The default `security-invalidation-interval`, which is in milliseconds. The default
is `10000` ms. 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 ## Role based security for addresses
Apache ActiveMQ Artemis contains a flexible role-based security model for applying Apache ActiveMQ Artemis contains a flexible role-based security model for applying

View File

@ -577,6 +577,7 @@ public class HangConsumerTest extends ActiveMQTestBase {
protected ServerSessionImpl internalCreateSession(String name, protected ServerSessionImpl internalCreateSession(String name,
String username, String username,
String password, String password,
String validatedUser,
int minLargeMessageSize, int minLargeMessageSize,
RemotingConnection connection, RemotingConnection connection,
boolean autoCommitSends, boolean autoCommitSends,
@ -587,7 +588,7 @@ public class HangConsumerTest extends ActiveMQTestBase {
SessionCallback callback, SessionCallback callback,
OperationContext context, OperationContext context,
boolean autoCreateQueue) throws Exception { 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);
} }
} }

View File

@ -236,8 +236,8 @@ public class JMSQueueControlTest extends ManagementTestBase {
CompositeData[] data = queueControl.browse(); CompositeData[] data = queueControl.browse();
Assert.assertEquals(1, data.length); Assert.assertEquals(1, data.length);
Assert.assertNotNull(data[0].get("JMSCorrelationID")); Assert.assertNotNull(data[0].get(MessageUtil.CORRELATIONID_HEADER_NAME.toString()));
Assert.assertEquals("foo", data[0].get("JMSCorrelationID")); Assert.assertEquals("foo", data[0].get(MessageUtil.CORRELATIONID_HEADER_NAME.toString()));
System.out.println(data[0]); System.out.println(data[0]);
JMSUtil.consumeMessages(1, queue); JMSUtil.consumeMessages(1, queue);
@ -248,8 +248,8 @@ public class JMSQueueControlTest extends ManagementTestBase {
data = queueControl.browse(); data = queueControl.browse();
Assert.assertEquals(1, data.length); Assert.assertEquals(1, data.length);
Assert.assertNotNull(data[0].get("JMSXGroupID")); Assert.assertNotNull(data[0].get(MessageUtil.JMSXGROUPID.toString()));
Assert.assertEquals("myGroupID", data[0].get("JMSXGroupID")); Assert.assertEquals("myGroupID", data[0].get(MessageUtil.JMSXGROUPID.toString()));
System.out.println(data[0]); System.out.println(data[0]);
JMSUtil.consumeMessages(1, queue); JMSUtil.consumeMessages(1, queue);
@ -266,14 +266,14 @@ public class JMSQueueControlTest extends ManagementTestBase {
JMSUtil.consumeMessages(1, queue); JMSUtil.consumeMessages(1, queue);
JMSUtil.sendMessageWithProperty(session, queue, "JMSXUserID", "theheadhonch"); JMSUtil.sendMessageWithProperty(session, queue, MessageUtil.JMSXUSERID.toString(), "theheadhonch");
Assert.assertEquals(1, getMessageCount(queueControl)); Assert.assertEquals(1, getMessageCount(queueControl));
data = queueControl.browse(); data = queueControl.browse();
Assert.assertEquals(1, data.length); Assert.assertEquals(1, data.length);
Assert.assertNotNull(data[0].get("JMSXUserID")); Assert.assertNotNull(data[0].get(MessageUtil.JMSXUSERID.toString()));
Assert.assertEquals("theheadhonch", data[0].get("JMSXUserID")); Assert.assertEquals("theheadhonch", data[0].get(MessageUtil.JMSXUSERID.toString()));
System.out.println(data[0]); System.out.println(data[0]);
JMSUtil.consumeMessages(1, queue); JMSUtil.consumeMessages(1, queue);

View File

@ -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.ActiveMQJAASSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; 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.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.ActiveMQTestBase;
import org.apache.activemq.artemis.tests.util.CreateMessage; import org.apache.activemq.artemis.tests.util.CreateMessage;
import org.junit.Assert; import org.junit.Assert;
@ -1648,39 +1649,39 @@ public class SecurityTest extends ActiveMQTestBase {
public void testCustomSecurityManager() throws Exception { public void testCustomSecurityManager() throws Exception {
final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true);
final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager() { final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager() {
@Override @Override
public boolean validateUser(final String username, final String password) { public boolean validateUser(final String username, final String password) {
return (username.equals("foo") || username.equals("bar") || username.equals("all")) && return (username.equals("foo") || username.equals("bar") || username.equals("all")) &&
password.equals("frobnicate"); password.equals("frobnicate");
} }
@Override @Override
public boolean validateUserAndRole( public boolean validateUserAndRole(
final String username, final String username,
final String password, final String password,
final Set<Role> requiredRoles, final Set<Role> requiredRoles,
final CheckType checkType) { final CheckType checkType) {
if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && if ((username.equals("foo") || username.equals("bar") || username.equals("all")) &&
password.equals("frobnicate")) { password.equals("frobnicate")) {
if (username.equals("all")) { if (username.equals("all")) {
return true; return true;
} }
else if (username.equals("foo")) { else if (username.equals("foo")) {
return checkType == CheckType.CONSUME || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; return checkType == CheckType.CONSUME || checkType == CheckType.CREATE_NON_DURABLE_QUEUE;
} }
else if (username.equals("bar")) { else if (username.equals("bar")) {
return checkType == CheckType.SEND || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; return checkType == CheckType.SEND || checkType == CheckType.CREATE_NON_DURABLE_QUEUE;
}
else {
return false;
}
} }
else { else {
return false; return false;
} }
} }
}; else {
return false;
}
}
};
final ActiveMQServer server = addServer(new ActiveMQServerImpl(configuration, customSecurityManager)); final ActiveMQServer server = addServer(new ActiveMQServerImpl(configuration, customSecurityManager));
server.start(); server.start();
@ -1734,61 +1735,207 @@ public class SecurityTest extends ActiveMQTestBase {
public void testCustomSecurityManager2() throws Exception { public void testCustomSecurityManager2() throws Exception {
final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true);
final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager2() { final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager2() {
@Override @Override
public boolean validateUser(final String username, final String password) { public boolean validateUser(final String username, final String password) {
fail("Unexpected call to overridden method"); fail("Unexpected call to overridden method");
return false; return false;
} }
@Override @Override
public boolean validateUser(final String username, final String password, final X509Certificate[] certificates) { public boolean validateUser(final String username, final String password, final X509Certificate[] certificates) {
return (username.equals("foo") || username.equals("bar") || username.equals("all")) && return (username.equals("foo") || username.equals("bar") || username.equals("all")) &&
password.equals("frobnicate"); password.equals("frobnicate");
} }
@Override @Override
public boolean validateUserAndRole( public boolean validateUserAndRole(
final String username, final String username,
final String password, final String password,
final Set<Role> requiredRoles, final Set<Role> requiredRoles,
final CheckType checkType) { 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<Role> requiredRoles,
final CheckType checkType,
final String address,
final RemotingConnection connection) {
if (!(connection.getTransportConnection() instanceof InVMConnection)) {
return false; return false;
} }
@Override if ((username.equals("foo") || username.equals("bar") || username.equals("all")) &&
public boolean validateUserAndRole( password.equals("frobnicate")) {
final String username,
final String password,
final Set<Role> requiredRoles,
final CheckType checkType,
final String address,
final RemotingConnection connection) {
if (!(connection.getTransportConnection() instanceof InVMConnection)) { if (username.equals("all")) {
return false; return true;
} }
else if (username.equals("foo")) {
if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && return address.equals("test.queue") && checkType == CheckType.CONSUME;
password.equals("frobnicate")) { }
else if (username.equals("bar")) {
if (username.equals("all")) { return address.equals("test.queue") && checkType == CheckType.SEND;
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 { else {
return false; 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<Role> requiredRoles,
final CheckType checkType) {
fail("Unexpected call to overridden method");
return false;
}
@Override
public String validateUserAndRole(
final String username,
final String password,
final Set<Role> 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)); final ActiveMQServer server = addServer(new ActiveMQServerImpl(configuration, customSecurityManager));
server.start(); server.start();

View File

@ -30,6 +30,7 @@ import java.net.Socket;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue; 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.StringDecoder;
import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.codec.string.StringEncoder;
import org.apache.activemq.artemis.api.core.TransportConfiguration; 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.config.Configuration;
import org.apache.activemq.artemis.core.protocol.stomp.StompProtocolManagerFactory; import org.apache.activemq.artemis.core.protocol.stomp.StompProtocolManagerFactory;
import org.apache.activemq.artemis.core.registry.JndiBindingRegistry; 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.invm.InVMConnectorFactory;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory; 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.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.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServers; import org.apache.activemq.artemis.core.server.ActiveMQServers;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; 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.JMSQueueConfigurationImpl;
import org.apache.activemq.artemis.jms.server.config.impl.TopicConfigurationImpl; import org.apache.activemq.artemis.jms.server.config.impl.TopicConfigurationImpl;
import org.apache.activemq.artemis.jms.server.impl.JMSServerManagerImpl; 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.After;
import org.junit.Before; import org.junit.Before;
@ -116,7 +119,12 @@ public abstract class StompTestBase extends ActiveMQTestBase {
connectionFactory = createConnectionFactory(); connectionFactory = createConnectionFactory();
createBootstrap(); createBootstrap();
connection = connectionFactory.createConnection(); if (isSecurityEnabled()) {
connection = connectionFactory.createConnection("brianm", "wombats");
}
else {
connection = connectionFactory.createConnection();
}
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
queue = session.createQueue(getQueueName()); queue = session.createQueue(getQueueName());
topic = session.createTopic(getTopicName()); topic = session.createTopic(getTopicName());
@ -185,11 +193,23 @@ public abstract class StompTestBase extends ActiveMQTestBase {
TransportConfiguration stompTransport = new TransportConfiguration(NettyAcceptorFactory.class.getName(), params); TransportConfiguration stompTransport = new TransportConfiguration(NettyAcceptorFactory.class.getName(), params);
TransportConfiguration allTransport = new TransportConfiguration(NettyAcceptorFactory.class.getName()); 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); config.addAcceptorConfiguration(allTransport);
ActiveMQServer activeMQServer = addServer(ActiveMQServers.newActiveMQServer(config, defUser, defPass)); 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<Role>() {
{
add(new Role(role, true, true, true, true, true, true, true));
}
});
}
JMSConfiguration jmsConfig = new JMSConfigurationImpl(); JMSConfiguration jmsConfig = new JMSConfigurationImpl();
jmsConfig.getQueueConfigurations().add(new JMSQueueConfigurationImpl().setName(getQueueName()).setDurable(false).setBindings(getQueueName())); jmsConfig.getQueueConfigurations().add(new JMSQueueConfigurationImpl().setName(getQueueName()).setDurable(false).setBindings(getQueueName()));
jmsConfig.getTopicConfigurations().add(new TopicConfigurationImpl().setName(getTopicName()).setBindings(getTopicName())); jmsConfig.getTopicConfigurations().add(new TopicConfigurationImpl().setName(getTopicName()).setBindings(getTopicName()));
@ -319,6 +339,10 @@ public abstract class StompTestBase extends ActiveMQTestBase {
Thread.sleep(500); Thread.sleep(500);
} }
public boolean isSecurityEnabled() {
return false;
}
class StompClientHandler extends SimpleChannelInboundHandler<String> { class StompClientHandler extends SimpleChannelInboundHandler<String> {
StringBuffer currentMessage = new StringBuffer(""); StringBuffer currentMessage = new StringBuffer("");

View File

@ -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;
}
}