This closes #220
This commit is contained in:
commit
739f3c51bf
|
@ -27,6 +27,7 @@ import org.apache.activemq.artemis.api.core.SimpleString;
|
|||
import org.apache.activemq.artemis.api.core.TransportConfiguration;
|
||||
import org.apache.activemq.artemis.core.security.Role;
|
||||
import org.apache.activemq.artemis.core.server.JournalType;
|
||||
import org.apache.activemq.artemis.core.server.SecuritySettingPlugin;
|
||||
import org.apache.activemq.artemis.core.server.group.impl.GroupingHandlerConfiguration;
|
||||
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
|
||||
import org.apache.activemq.artemis.core.settings.impl.ResourceLimitSettings;
|
||||
|
@ -852,11 +853,17 @@ public interface Configuration {
|
|||
|
||||
Configuration addConnectorServiceConfiguration(ConnectorServiceConfiguration config);
|
||||
|
||||
Configuration setSecuritySettingPlugins(final List<SecuritySettingPlugin> plugins);
|
||||
|
||||
Configuration addSecuritySettingPlugin(final SecuritySettingPlugin plugin);
|
||||
|
||||
/**
|
||||
* @return list of {@link ConnectorServiceConfiguration}
|
||||
*/
|
||||
List<ConnectorServiceConfiguration> getConnectorServiceConfigurations();
|
||||
|
||||
List<SecuritySettingPlugin> getSecuritySettingPlugins();
|
||||
|
||||
/**
|
||||
* The default password decoder
|
||||
*/
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.apache.activemq.artemis.core.config.ha.ReplicaPolicyConfiguration;
|
|||
import org.apache.activemq.artemis.core.config.ha.ReplicatedPolicyConfiguration;
|
||||
import org.apache.activemq.artemis.core.security.Role;
|
||||
import org.apache.activemq.artemis.core.server.JournalType;
|
||||
import org.apache.activemq.artemis.core.server.SecuritySettingPlugin;
|
||||
import org.apache.activemq.artemis.core.server.group.impl.GroupingHandlerConfiguration;
|
||||
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
|
||||
import org.apache.activemq.artemis.core.settings.impl.ResourceLimitSettings;
|
||||
|
@ -209,6 +210,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
|
||||
private Map<String, Set<Role>> securitySettings = new HashMap<String, Set<Role>>();
|
||||
|
||||
private List<SecuritySettingPlugin> securitySettingPlugins = new ArrayList<SecuritySettingPlugin>();
|
||||
|
||||
protected List<ConnectorServiceConfiguration> connectorServiceConfigurations = new ArrayList<ConnectorServiceConfiguration>();
|
||||
|
||||
private boolean maskPassword = ActiveMQDefaultConfiguration.isDefaultMaskPassword();
|
||||
|
@ -964,6 +967,9 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
|
||||
@Override
|
||||
public Map<String, Set<Role>> getSecurityRoles() {
|
||||
for (SecuritySettingPlugin securitySettingPlugin : securitySettingPlugins) {
|
||||
securitySettings.putAll(securitySettingPlugin.getSecurityRoles());
|
||||
}
|
||||
return securitySettings;
|
||||
}
|
||||
|
||||
|
@ -977,6 +983,10 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
return this.connectorServiceConfigurations;
|
||||
}
|
||||
|
||||
public List<SecuritySettingPlugin> getSecuritySettingPlugins() {
|
||||
return this.securitySettingPlugins;
|
||||
}
|
||||
|
||||
public File getBrokerInstance() {
|
||||
if (artemisInstance != null) {
|
||||
return artemisInstance;
|
||||
|
@ -1036,6 +1046,16 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
return this;
|
||||
}
|
||||
|
||||
public ConfigurationImpl setSecuritySettingPlugins(final List<SecuritySettingPlugin> plugins) {
|
||||
this.securitySettingPlugins = plugins;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigurationImpl addSecuritySettingPlugin(final SecuritySettingPlugin plugin) {
|
||||
this.securitySettingPlugins.add(plugin);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isMaskPassword() {
|
||||
return maskPassword;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.apache.activemq.artemis.core.deployers.impl;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -56,6 +58,7 @@ import org.apache.activemq.artemis.core.io.aio.AIOSequentialFileFactory;
|
|||
import org.apache.activemq.artemis.core.security.Role;
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
|
||||
import org.apache.activemq.artemis.core.server.JournalType;
|
||||
import org.apache.activemq.artemis.core.server.SecuritySettingPlugin;
|
||||
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
|
||||
import org.apache.activemq.artemis.core.server.group.impl.GroupingHandlerConfiguration;
|
||||
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy;
|
||||
|
@ -64,6 +67,7 @@ import org.apache.activemq.artemis.core.settings.impl.ResourceLimitSettings;
|
|||
import org.apache.activemq.artemis.core.settings.impl.SlowConsumerPolicy;
|
||||
import org.apache.activemq.artemis.uri.AcceptorTransportConfigurationParser;
|
||||
import org.apache.activemq.artemis.uri.ConnectorTransportConfigurationParser;
|
||||
import org.apache.activemq.artemis.utils.ClassloadingUtil;
|
||||
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
|
||||
|
@ -82,12 +86,20 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
|
|||
// Security Parsing
|
||||
public static final String SECURITY_ELEMENT_NAME = "security-setting";
|
||||
|
||||
public static final String SECURITY_PLUGIN_ELEMENT_NAME = "security-setting-plugin";
|
||||
|
||||
private static final String PERMISSION_ELEMENT_NAME = "permission";
|
||||
|
||||
private static final String SETTING_ELEMENT_NAME = "setting";
|
||||
|
||||
private static final String TYPE_ATTR_NAME = "type";
|
||||
|
||||
private static final String ROLES_ATTR_NAME = "roles";
|
||||
|
||||
private static final String NAME_ATTR_NAME = "name";
|
||||
|
||||
private static final String VALUE_ATTR_NAME = "value";
|
||||
|
||||
static final String CREATEDURABLEQUEUE_NAME = "createDurableQueue";
|
||||
|
||||
private static final String DELETEDURABLEQUEUE_NAME = "deleteDurableQueue";
|
||||
|
@ -517,6 +529,11 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
|
|||
Pair<String, Set<Role>> securityItem = parseSecurityRoles(list.item(i));
|
||||
config.getSecurityRoles().put(securityItem.getA(), securityItem.getB());
|
||||
}
|
||||
list = node.getElementsByTagName(SECURITY_PLUGIN_ELEMENT_NAME);
|
||||
for (int i = 0; i < list.getLength(); i++) {
|
||||
Pair<SecuritySettingPlugin, Map<String, String>> securityItem = parseSecuritySettingPlugins(list.item(i));
|
||||
config.addSecuritySettingPlugin(securityItem.getA().init(securityItem.getB()).populateSecurityRoles());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -643,6 +660,29 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
|
|||
return securityMatch;
|
||||
}
|
||||
|
||||
private Pair<SecuritySettingPlugin,Map<String,String>> parseSecuritySettingPlugins(Node item) {
|
||||
final String clazz = item.getAttributes().getNamedItem("class-name").getNodeValue();
|
||||
final Map<String, String> settings = new HashMap<>();
|
||||
NodeList children = item.getChildNodes();
|
||||
for (int j = 0; j < children.getLength(); j++) {
|
||||
Node child = children.item(j);
|
||||
final String nodeName = child.getNodeName();
|
||||
if (SETTING_ELEMENT_NAME.equalsIgnoreCase(nodeName)) {
|
||||
final String settingName = getAttributeValue(child, NAME_ATTR_NAME);
|
||||
final String settingValue = getAttributeValue(child, VALUE_ATTR_NAME);
|
||||
settings.put(settingName, settingValue);
|
||||
}
|
||||
}
|
||||
|
||||
SecuritySettingPlugin securitySettingPlugin = AccessController.doPrivileged(new PrivilegedAction<SecuritySettingPlugin>() {
|
||||
public SecuritySettingPlugin run() {
|
||||
return (SecuritySettingPlugin) ClassloadingUtil.newInstanceFromClassLoader(clazz);
|
||||
}
|
||||
});
|
||||
|
||||
return new Pair<SecuritySettingPlugin, Map<String, String>>(securitySettingPlugin, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param node
|
||||
* @return
|
||||
|
|
|
@ -301,6 +301,10 @@ public interface ActiveMQServerLogger extends BasicLogger {
|
|||
@Message(id = 221050, value = "Activating Shared Store Slave", format = Message.Format.MESSAGE_FORMAT)
|
||||
void activatingSharedStoreSlave();
|
||||
|
||||
@LogMessage(level = Logger.Level.INFO)
|
||||
@Message(id = 221051, value = "Populating security roles from LDAP at: {0}", format = Message.Format.MESSAGE_FORMAT)
|
||||
void populatingSecurityRolesFromLDAP(String url);
|
||||
|
||||
@LogMessage(level = Logger.Level.WARN)
|
||||
@Message(id = 222000, value = "ActiveMQServer is being finalized and has not been stopped. Please remember to stop the server before letting it go out of scope",
|
||||
format = Message.Format.MESSAGE_FORMAT)
|
||||
|
@ -1187,6 +1191,10 @@ public interface ActiveMQServerLogger extends BasicLogger {
|
|||
format = Message.Format.MESSAGE_FORMAT)
|
||||
void noProtocolManagerFound(String protocol, String host);
|
||||
|
||||
@LogMessage(level = Logger.Level.WARN)
|
||||
@Message(id = 222204, value = "Duplicated Acceptor {0} with parameters {1} classFactory={2} duplicated on the configuration", format = Message.Format.MESSAGE_FORMAT)
|
||||
void duplicatedAcceptor(String name, String parameters, String classFactory);
|
||||
|
||||
@LogMessage(level = Logger.Level.ERROR)
|
||||
@Message(id = 224000, value = "Failure in initialisation", format = Message.Format.MESSAGE_FORMAT)
|
||||
void initializationError(@Cause Throwable e);
|
||||
|
@ -1442,8 +1450,11 @@ public interface ActiveMQServerLogger extends BasicLogger {
|
|||
@Message(id = 224065, value = "Failed to remove auto-created queue {0}", format = Message.Format.MESSAGE_FORMAT)
|
||||
void errorRemovingAutoCreatedQueue(@Cause Exception e, SimpleString bindingName);
|
||||
|
||||
@LogMessage(level = Logger.Level.WARN)
|
||||
@Message(id = 224066, value = "Duplicated Acceptor {0} with parameters {1} classFactory={2} duplicated on the configuration", format = Message.Format.MESSAGE_FORMAT)
|
||||
void duplicatedAcceptor(String name, String parameters, String classFactory);
|
||||
@LogMessage(level = Logger.Level.ERROR)
|
||||
@Message(id = 224066, value = "Error opening context for LDAP", format = Message.Format.MESSAGE_FORMAT)
|
||||
void errorOpeningContextForLDAP(@Cause Exception e);
|
||||
|
||||
@LogMessage(level = Logger.Level.ERROR)
|
||||
@Message(id = 224067, value = "Error populating security roles from LDAP", format = Message.Format.MESSAGE_FORMAT)
|
||||
void errorPopulatingSecurityRolesFromLDAP(@Cause Exception e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.core.server;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.activemq.artemis.core.security.Role;
|
||||
|
||||
public interface SecuritySettingPlugin extends Serializable {
|
||||
/**
|
||||
* Initialize the plugin with the given configuration options. This method is called by the broker when the file-based
|
||||
* configuration is read (see {@code org.apache.activemq.artemis.core.deployers.impl.FileConfigurationParser#parseSecurity(org.w3c.dom.Element, org.apache.activemq.artemis.core.config.Configuration)}.
|
||||
* If you're creating/configuring the plugin programmatically then the recommended approach is to simply use the plugin's
|
||||
* getters/setters rather than this method.
|
||||
*
|
||||
* @param options name/value pairs used to configure the SecuritySettingPlugin instance
|
||||
* @return {@code this} instance
|
||||
*/
|
||||
SecuritySettingPlugin init(Map<String, String> options);
|
||||
|
||||
/**
|
||||
* Once {@code #populateSecurityRoles} is invoked this method should return the security role information from the
|
||||
* external environment (e.g. file, LDAP, etc.).
|
||||
*
|
||||
* @return the Map's key corresponds to the "match" for the security setting and the corresponding value is the set of
|
||||
* {@code org.apache.activemq.artemis.core.security.Role} objects defining the appropriate authorization
|
||||
*/
|
||||
Map<String, Set<Role>> getSecurityRoles();
|
||||
|
||||
/**
|
||||
* Fetch the security role information from the external environment (e.g. file, LDAP, etc.). This method should put
|
||||
* the security role information in the variable that is returned by {@code #getSecurityRoles()}. This method is
|
||||
* called by the broker when the file-based configuration is read (see {@code org.apache.activemq.artemis.core.deployers.impl.FileConfigurationParser#parseSecurity(org.w3c.dom.Element, org.apache.activemq.artemis.core.config.Configuration)}
|
||||
* so that later when {@code #getSecurityRoles()} is called by {@code org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl#deploySecurity()}
|
||||
* the necessary information will be present. If you're creating/configuring the plugin programmatically then you'll
|
||||
* want to invoke this method soon after instantiating and configuring it.
|
||||
*
|
||||
* @return {@code this} instance
|
||||
*/
|
||||
SecuritySettingPlugin populateSecurityRoles();
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* 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.core.server.impl;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.activemq.artemis.core.security.Role;
|
||||
import org.apache.activemq.artemis.core.server.SecuritySettingPlugin;
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
|
||||
|
||||
public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin {
|
||||
private static final long serialVersionUID = 4793109879399750045L;
|
||||
|
||||
public static final String INITIAL_CONTEXT_FACTORY = "initialContextFactory";
|
||||
public static final String CONNECTION_URL = "connectionURL";
|
||||
public static final String CONNECTION_USERNAME = "connectionUsername";
|
||||
public static final String CONNECTION_PASSWORD = "connectionPassword";
|
||||
public static final String CONNECTION_PROTOCOL = "connectionProtocol";
|
||||
public static final String AUTHENTICATION = "authentication";
|
||||
public static final String ROLE_ATTRIBUTE = "roleAttribute";
|
||||
public static final String FILTER = "filter";
|
||||
public static final String DESTINATION_BASE = "destinationBase";
|
||||
public static final String ADMIN_PERMISSION_VALUE = "adminPermissionValue";
|
||||
public static final String READ_PERMISSION_VALUE = "readPermissionValue";
|
||||
public static final String WRITE_PERMISSION_VALUE = "writePermissionValue";
|
||||
|
||||
private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
|
||||
private String connectionURL = "ldap://localhost:1024";
|
||||
private String connectionUsername;
|
||||
private String connectionPassword;
|
||||
private String connectionProtocol;
|
||||
private String authentication = "simple";
|
||||
private String destinationBase = "ou=destinations,o=ActiveMQ,ou=system";
|
||||
private String filter = "(cn=*)";
|
||||
private String roleAttribute = "uniqueMember";
|
||||
private String adminPermissionValue = "admin";
|
||||
private String readPermissionValue = "read";
|
||||
private String writePermissionValue = "write";
|
||||
|
||||
private DirContext context;
|
||||
private Map<String, Set<Role>> securityRoles = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public LegacyLDAPSecuritySettingPlugin init(Map<String, String> options) {
|
||||
if (options != null) {
|
||||
initialContextFactory = options.get(INITIAL_CONTEXT_FACTORY);
|
||||
connectionURL = options.get(CONNECTION_URL);
|
||||
connectionUsername = options.get(CONNECTION_USERNAME);
|
||||
connectionPassword = options.get(CONNECTION_PASSWORD);
|
||||
connectionProtocol = options.get(CONNECTION_PROTOCOL);
|
||||
authentication = options.get(AUTHENTICATION);
|
||||
destinationBase = options.get(DESTINATION_BASE);
|
||||
filter = options.get(FILTER);
|
||||
roleAttribute = options.get(ROLE_ATTRIBUTE);
|
||||
adminPermissionValue = options.get(ADMIN_PERMISSION_VALUE);
|
||||
readPermissionValue = options.get(READ_PERMISSION_VALUE);
|
||||
writePermissionValue = options.get(WRITE_PERMISSION_VALUE);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRoleAttribute() {
|
||||
return roleAttribute;
|
||||
}
|
||||
|
||||
public SecuritySettingPlugin setRoleAttribute(String roleAttribute) {
|
||||
this.roleAttribute = roleAttribute;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setFilter(String filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDestinationBase() {
|
||||
return destinationBase;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setDestinationBase(String destinationBase) {
|
||||
this.destinationBase = destinationBase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getAuthentication() {
|
||||
return authentication;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setAuthentication(String authentication) {
|
||||
this.authentication = authentication;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getConnectionPassword() {
|
||||
return connectionPassword;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setConnectionPassword(String connectionPassword) {
|
||||
this.connectionPassword = connectionPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getConnectionProtocol() {
|
||||
return connectionProtocol;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setConnectionProtocol(String connectionProtocol) {
|
||||
this.connectionProtocol = connectionProtocol;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getConnectionURL() {
|
||||
return connectionURL;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setConnectionURL(String connectionURL) {
|
||||
this.connectionURL = connectionURL;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getConnectionUsername() {
|
||||
return connectionUsername;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setConnectionUsername(String connectionUsername) {
|
||||
this.connectionUsername = connectionUsername;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getInitialContextFactory() {
|
||||
return initialContextFactory;
|
||||
}
|
||||
|
||||
public String getAdminPermissionValue() {
|
||||
return adminPermissionValue;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setAdminPermissionValue(String adminPermissionValue) {
|
||||
this.adminPermissionValue = adminPermissionValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getReadPermissionValue() {
|
||||
return readPermissionValue;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setReadPermissionValue(String readPermissionValue) {
|
||||
this.readPermissionValue = readPermissionValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getWritePermissionValue() {
|
||||
return writePermissionValue;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setWritePermissionValue(String writePermissionValue) {
|
||||
this.writePermissionValue = writePermissionValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LegacyLDAPSecuritySettingPlugin setInitialContextFactory(String initialContextFactory) {
|
||||
this.initialContextFactory = initialContextFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void open() throws NamingException {
|
||||
if (context != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Hashtable<String, String> env = new Hashtable<String, String>();
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
|
||||
if (connectionUsername != null && !"".equals(connectionUsername)) {
|
||||
env.put(Context.SECURITY_PRINCIPAL, connectionUsername);
|
||||
}
|
||||
else {
|
||||
throw new NamingException("Empty username is not allowed");
|
||||
}
|
||||
if (connectionPassword != null && !"".equals(connectionPassword)) {
|
||||
env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
|
||||
}
|
||||
else {
|
||||
throw new NamingException("Empty password is not allowed");
|
||||
}
|
||||
env.put(Context.SECURITY_PROTOCOL, connectionProtocol);
|
||||
env.put(Context.PROVIDER_URL, connectionURL);
|
||||
env.put(Context.SECURITY_AUTHENTICATION, authentication);
|
||||
context = new InitialDirContext(env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Set<Role>> getSecurityRoles() {
|
||||
return securityRoles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LegacyLDAPSecuritySettingPlugin populateSecurityRoles() {
|
||||
ActiveMQServerLogger.LOGGER.populatingSecurityRolesFromLDAP(connectionURL);
|
||||
try {
|
||||
open();
|
||||
}
|
||||
catch (Exception e) {
|
||||
ActiveMQServerLogger.LOGGER.errorOpeningContextForLDAP(e);
|
||||
return this;
|
||||
}
|
||||
|
||||
SearchControls searchControls = new SearchControls();
|
||||
searchControls.setReturningAttributes(new String[]{roleAttribute});
|
||||
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
|
||||
Map<String, Set<Role>> securityRoles = new HashMap<String, Set<Role>>();
|
||||
try {
|
||||
NamingEnumeration<SearchResult> searchResults = context.search(destinationBase, filter, searchControls);
|
||||
int i = 0;
|
||||
while (searchResults.hasMore()) {
|
||||
SearchResult searchResult = searchResults.next();
|
||||
Attributes attrs = searchResult.getAttributes();
|
||||
if (attrs == null || attrs.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
LdapName searchResultLdapName = new LdapName(searchResult.getName());
|
||||
ActiveMQServerLogger.LOGGER.debug("LDAP search result " + ++i + ": " + searchResultLdapName);
|
||||
String permissionType = null;
|
||||
String destination = null;
|
||||
String destinationType = "unknown";
|
||||
for (Rdn rdn : searchResultLdapName.getRdns()) {
|
||||
if (rdn.getType().equals("cn")) {
|
||||
ActiveMQServerLogger.LOGGER.debug("\tPermission type: " + rdn.getValue());
|
||||
permissionType = rdn.getValue().toString();
|
||||
}
|
||||
if (rdn.getType().equals("uid")) {
|
||||
ActiveMQServerLogger.LOGGER.debug("\tDestination name: " + rdn.getValue());
|
||||
destination = rdn.getValue().toString();
|
||||
}
|
||||
if (rdn.getType().equals("ou")) {
|
||||
String rawDestinationType = rdn.getValue().toString();
|
||||
if (rawDestinationType.toLowerCase().contains("queue")) {
|
||||
destinationType = "queue";
|
||||
}
|
||||
else if (rawDestinationType.toLowerCase().contains("topic")) {
|
||||
destinationType = "topic";
|
||||
}
|
||||
ActiveMQServerLogger.LOGGER.debug("\tDestination type: " + destinationType);
|
||||
}
|
||||
}
|
||||
ActiveMQServerLogger.LOGGER.debug("\tAttributes: " + attrs);
|
||||
Attribute attr = attrs.get(roleAttribute);
|
||||
NamingEnumeration<?> e = attr.getAll();
|
||||
Set<Role> roles = securityRoles.get(destination);
|
||||
boolean exists = false;
|
||||
if (roles == null) {
|
||||
roles = new HashSet<>();
|
||||
}
|
||||
else {
|
||||
exists = true;
|
||||
}
|
||||
|
||||
while (e.hasMore()) {
|
||||
String value = (String) e.next();
|
||||
LdapName ldapname = new LdapName(value);
|
||||
Rdn rdn = ldapname.getRdn(ldapname.size() - 1);
|
||||
String roleName = rdn.getValue().toString();
|
||||
ActiveMQServerLogger.LOGGER.debug("\tRole name: " + roleName);
|
||||
Role role = new Role(roleName,
|
||||
permissionType.equalsIgnoreCase(writePermissionValue),
|
||||
permissionType.equalsIgnoreCase(readPermissionValue),
|
||||
permissionType.equalsIgnoreCase(adminPermissionValue),
|
||||
permissionType.equalsIgnoreCase(adminPermissionValue),
|
||||
permissionType.equalsIgnoreCase(adminPermissionValue),
|
||||
permissionType.equalsIgnoreCase(adminPermissionValue),
|
||||
false); // there is no permission from ActiveMQ 5.x that corresponds to the "manage" permission in ActiveMQ Artemis
|
||||
roles.add(role);
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
securityRoles.put(destination, roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ActiveMQServerLogger.LOGGER.errorPopulatingSecurityRolesFromLDAP(e);
|
||||
}
|
||||
|
||||
this.securityRoles = securityRoles;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -711,6 +711,42 @@
|
|||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="security-setting-plugin" maxOccurs="unbounded" minOccurs="0">
|
||||
<xsd:complexType>
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
a plugin
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="setting" maxOccurs="unbounded" minOccurs="0">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
the name of the setting
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="value" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
the value for the setting
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="class-name" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
the name of the plugin class to instantiate
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.activemq.artemis.api.core.BroadcastGroupConfiguration;
|
||||
import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
|
||||
|
@ -39,7 +40,9 @@ import org.apache.activemq.artemis.core.config.ha.LiveOnlyPolicyConfiguration;
|
|||
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.JournalType;
|
||||
import org.apache.activemq.artemis.core.server.SecuritySettingPlugin;
|
||||
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
|
||||
import org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin;
|
||||
import org.apache.activemq.artemis.core.settings.impl.SlowConsumerPolicy;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
@ -339,6 +342,22 @@ public class FileConfigurationTest extends ConfigurationImplTest {
|
|||
assertTrue(a2Role.isDeleteNonDurableQueue());
|
||||
assertFalse(a2Role.isManage());
|
||||
|
||||
List<SecuritySettingPlugin> securitySettingPlugins = conf.getSecuritySettingPlugins();
|
||||
SecuritySettingPlugin securitySettingPlugin = securitySettingPlugins.get(0);
|
||||
assertTrue(securitySettingPlugin instanceof LegacyLDAPSecuritySettingPlugin);
|
||||
LegacyLDAPSecuritySettingPlugin legacyLDAPSecuritySettingPlugin = (LegacyLDAPSecuritySettingPlugin) securitySettingPlugin;
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getInitialContextFactory(), "testInitialContextFactory");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getConnectionURL(), "testConnectionURL");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getConnectionUsername(), "testConnectionUsername");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getConnectionPassword(), "testConnectionPassword");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getConnectionProtocol(), "testConnectionProtocol");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getAuthentication(), "testAuthentication");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getDestinationBase(), "testDestinationBase");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getFilter(), "testFilter");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getRoleAttribute(), "testRoleAttribute");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getAdminPermissionValue(), "testAdminPermissionValue");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getReadPermissionValue(), "testReadPermissionValue");
|
||||
assertEquals(legacyLDAPSecuritySettingPlugin.getWritePermissionValue(), "testWritePermissionValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -232,6 +232,20 @@
|
|||
<security-setting match="a2">
|
||||
<permission type="deleteNonDurableQueue" roles="a2.1"/>
|
||||
</security-setting>
|
||||
<security-setting-plugin class-name="org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin">
|
||||
<setting name="initialContextFactory" value="testInitialContextFactory"/>
|
||||
<setting name="connectionURL" value="testConnectionURL"/>
|
||||
<setting name="connectionUsername" value="testConnectionUsername"/>
|
||||
<setting name="connectionPassword" value="testConnectionPassword"/>
|
||||
<setting name="connectionProtocol" value="testConnectionProtocol"/>
|
||||
<setting name="authentication" value="testAuthentication"/>
|
||||
<setting name="destinationBase" value="testDestinationBase"/>
|
||||
<setting name="filter" value="testFilter"/>
|
||||
<setting name="roleAttribute" value="testRoleAttribute"/>
|
||||
<setting name="adminPermissionValue" value="testAdminPermissionValue"/>
|
||||
<setting name="readPermissionValue" value="testReadPermissionValue"/>
|
||||
<setting name="writePermissionValue" value="testWritePermissionValue"/>
|
||||
</security-setting-plugin>
|
||||
</security-settings>
|
||||
|
||||
<address-settings>
|
||||
|
|
|
@ -120,6 +120,121 @@ permissions in more specific security-setting blocks by simply not
|
|||
specifying them. Otherwise it would not be possible to deny permissions
|
||||
in sub-groups of addresses.
|
||||
|
||||
## Security Setting Plugin
|
||||
|
||||
Aside from configuring sets of permissions via XML these permissions can also be
|
||||
configured via plugins which implement `org.apache.activemq.artemis.core.server.SecuritySettingPlugin`.
|
||||
One or more plugins can be defined and configured alongside the normal XML, e.g.:
|
||||
|
||||
<security-settings>
|
||||
...
|
||||
<security-setting-plugin class-name="org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin">
|
||||
<setting name="initialContextFactory" value="com.sun.jndi.ldap.LdapCtxFactory"/>
|
||||
<setting name="connectionURL" value="ldap://localhost:1024"/>
|
||||
<setting name="connectionUsername" value="uid=admin,ou=system"/>
|
||||
<setting name="connectionPassword" value="secret"/>
|
||||
<setting name="connectionProtocol" value="s"/>
|
||||
<setting name="authentication" value="simple"/>
|
||||
</security-setting-plugin>
|
||||
</security-settings>
|
||||
|
||||
Most of this configuration is specific to the plugin implementation. However, there are two configuration details that
|
||||
will be specified for every implementation:
|
||||
|
||||
- `class-name`. This attribute of `security-setting-plugin` indicates the name of the class which implements
|
||||
`org.apache.activemq.artemis.core.server.SecuritySettingPlugin`.
|
||||
|
||||
- `setting`. Each of these elements represents a name/value pair that will be passed to the implementation for configuration
|
||||
purposes.
|
||||
|
||||
See the JavaDoc on `org.apache.activemq.artemis.core.server.SecuritySettingPlugin` for further details about the interface
|
||||
and what each method is expected to do.
|
||||
|
||||
### Available plugins
|
||||
|
||||
#### LegacyLDAPSecuritySettingPlugin
|
||||
|
||||
This plugin will read the security information that was previously handled by [`LDAPAuthorizationMap`](http://activemq.apache.org/security.html)
|
||||
and the [`cachedLDAPAuthorizationMap`](http://activemq.apache.org/cached-ldap-authorization-module.html) in Apache ActiveMQ 5.x
|
||||
and turn it into Artemis security settings where possible. The security implementations of ActiveMQ 5.x and Artemis don't
|
||||
match perfectly so some translation must occur to achieve near equivalent functionality.
|
||||
|
||||
Here is an example of the plugin's configuration:
|
||||
|
||||
<security-setting-plugin class-name="org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin">
|
||||
<setting name="initialContextFactory" value="com.sun.jndi.ldap.LdapCtxFactory"/>
|
||||
<setting name="connectionURL" value="ldap://localhost:1024"/>
|
||||
<setting name="connectionUsername" value="uid=admin,ou=system"/>
|
||||
<setting name="connectionPassword" value="secret"/>
|
||||
<setting name="connectionProtocol" value="s"/>
|
||||
<setting name="authentication" value="simple"/>
|
||||
</security-setting-plugin>
|
||||
|
||||
- `class-name`. The implementation is `org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin`.
|
||||
|
||||
- `initialContextFactory`. The initial context factory used to connect to LDAP. It must always be set to
|
||||
`com.sun.jndi.ldap.LdapCtxFactory` (i.e. the default value).
|
||||
|
||||
- `connectionURL`. Specifies the location of the directory server using an ldap URL, `ldap://Host:Port`. You can
|
||||
optionally qualify this URL, by adding a forward slash, `/`, followed by the DN of a particular node in the directory
|
||||
tree. For example, `ldap://ldapserver:10389/ou=system`. The default is `ldap://localhost:1024`.
|
||||
|
||||
- `connectionUsername`. The DN of the user that opens the connection to the directory server. For example, `uid=admin,ou=system`.
|
||||
Directory servers generally require clients to present username/password credentials in order to open a connection.
|
||||
|
||||
- `connectionPassword`. The password that matches the DN from `connectionUsername`. In the directory server, in the
|
||||
DIT, the password is normally stored as a `userPassword` attribute in the corresponding directory entry.
|
||||
|
||||
- `connectionProtocol`. Currently the only supported value is a blank string. In future, this option will allow you to
|
||||
select the Secure Socket Layer (SSL) for the connection to the directory server. Note: this option must be set
|
||||
explicitly to an empty string, because it has no default value.
|
||||
|
||||
- `authentication`. Specifies the authentication method used when binding to the LDAP server. Can take either of the
|
||||
values, `simple` (username and password, the default value) or `none` (anonymous). Note: Simple Authentication and
|
||||
Security Layer (SASL) authentication is currently not supported.
|
||||
|
||||
- `destinationBase`. Specifies the DN of the node whose children provide the permissions for all destinations. In this
|
||||
case the DN is a literal value (that is, no string substitution is performed on the property value). For example, a
|
||||
typical value of this property is `ou=destinations,o=ActiveMQ,ou=system` (i.e. the default value).
|
||||
|
||||
- `filter`. Specifies an LDAP search filter, which is used when looking up the permissions for any kind of destination.
|
||||
The search filter attempts to match one of the children or descendants of the queue or topic node. The default value
|
||||
is `(cn=*)`.
|
||||
|
||||
- `roleAttribute`. Specifies an attribute of the node matched by `filter`, whose value is the DN of a role. Default
|
||||
value is `uniqueMember`.
|
||||
|
||||
- `adminPermissionValue`. Specifies a value that matches the `admin` permission. The default value is `admin`.
|
||||
|
||||
- `readPermissionValue`. Specifies a value that matches the `read` permission. The default value is `read`.
|
||||
|
||||
- `writePermissionValue`. Specifies a value that matches the `write` permission. The default value is `write`.
|
||||
|
||||
The name of the queue or topic defined in LDAP will serve as the "match" for the security-setting, the permission value
|
||||
will be mapped from the ActiveMQ 5.x type to the Artemis type, and the role will be mapped as-is. It's worth noting that
|
||||
since the name of queue or topic coming from LDAP will server as the "match" for the security-setting the security-setting
|
||||
may not be applied as expected to JMS destinations since Artemis always prefixes JMS destinations with "jms.queue." or
|
||||
"jms.topic." as necessary.
|
||||
|
||||
ActiveMQ 5.x only has 3 permission types - `read`, `write`, and `admin`. These permission types are described on their
|
||||
[website](http://activemq.apache.org/security.html). However, as described previously, ActiveMQ Artemis has 6 permission
|
||||
types - `createDurableQueue`, `deleteDurableQueue`, `createNonDurableQueue`, `deleteNonDurableQueue`, `send`, `consume`,
|
||||
and `manage`. Here's how the old types are mapped to the new types:
|
||||
|
||||
- `read` - `consume`
|
||||
- `write` - `send`
|
||||
- `admin` - `createDurableQueue`, `deleteDurableQueue`, `createNonDurableQueue`, `deleteNonDurableQueue`
|
||||
|
||||
As mentioned, there are a few places where a translation was performed to achieve some equivalence.:
|
||||
|
||||
- This mapping doesn't include the Artemis `manage` permission type since there is no type analogous for that in ActiveMQ
|
||||
5.x.
|
||||
|
||||
- The `admin` permission in ActiveMQ 5.x relates to whether or not the broker will auto-create a destination if
|
||||
it doesn't exist and the user sends a message to it. Artemis automatically allows the automatic creation of a
|
||||
destination if the user has permission to send message to it. Therefore, the plugin will map the `admin` permission
|
||||
to the 4 aforementioned permissions in Artemis.
|
||||
|
||||
## Secure Sockets Layer (SSL) Transport
|
||||
|
||||
When messaging clients are connected to servers, or servers are
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.activemq.artemis.tests.integration.security;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NameClassPair;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.io.File;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URL;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQException;
|
||||
import org.apache.activemq.artemis.api.core.SimpleString;
|
||||
import org.apache.activemq.artemis.api.core.TransportConfiguration;
|
||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
|
||||
import org.apache.activemq.artemis.api.core.client.ClientConsumer;
|
||||
import org.apache.activemq.artemis.api.core.client.ClientProducer;
|
||||
import org.apache.activemq.artemis.api.core.client.ClientSession;
|
||||
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
|
||||
import org.apache.activemq.artemis.api.core.client.ServerLocator;
|
||||
import org.apache.activemq.artemis.core.config.Configuration;
|
||||
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
|
||||
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.server.ActiveMQServer;
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServers;
|
||||
import org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin;
|
||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
||||
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||
import org.apache.directory.server.annotations.CreateLdapServer;
|
||||
import org.apache.directory.server.annotations.CreateTransport;
|
||||
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
|
||||
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
|
||||
import org.apache.directory.server.core.integ.FrameworkRunner;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(FrameworkRunner.class)
|
||||
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)})
|
||||
@ApplyLdifFiles("AMQauth.ldif")
|
||||
public class LegacyLDAPSecuritySettingPluginTest extends AbstractLdapTestUnit {
|
||||
|
||||
static {
|
||||
String path = System.getProperty("java.security.auth.login.config");
|
||||
if (path == null) {
|
||||
URL resource = LegacyLDAPSecuritySettingPluginTest.class.getClassLoader().getResource("login.config");
|
||||
if (resource != null) {
|
||||
path = resource.getFile();
|
||||
System.setProperty("java.security.auth.login.config", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ServerLocator locator;
|
||||
|
||||
public static final String TARGET_TMP = "./target/tmp";
|
||||
private static final String PRINCIPAL = "uid=admin,ou=system";
|
||||
private static final String CREDENTIALS = "secret";
|
||||
|
||||
|
||||
public LegacyLDAPSecuritySettingPluginTest() {
|
||||
File parent = new File(TARGET_TMP);
|
||||
parent.mkdirs();
|
||||
temporaryFolder = new TemporaryFolder(parent);
|
||||
}
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder;
|
||||
private String testDir;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
locator = ActiveMQClient.createServerLocatorWithHA(new TransportConfiguration(InVMConnectorFactory.class.getCanonicalName()));
|
||||
testDir = temporaryFolder.getRoot().getAbsolutePath();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testRunning() throws Exception {
|
||||
Hashtable env = new Hashtable();
|
||||
env.put(Context.PROVIDER_URL, "ldap://localhost:1024");
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.put(Context.SECURITY_PRINCIPAL, PRINCIPAL);
|
||||
env.put(Context.SECURITY_CREDENTIALS, CREDENTIALS);
|
||||
DirContext ctx = new InitialDirContext(env);
|
||||
|
||||
HashSet set = new HashSet();
|
||||
|
||||
NamingEnumeration list = ctx.list("ou=system");
|
||||
|
||||
while (list.hasMore()) {
|
||||
NameClassPair ncp = (NameClassPair) list.next();
|
||||
set.add(ncp.getName());
|
||||
}
|
||||
|
||||
Assert.assertTrue(set.contains("uid=admin"));
|
||||
Assert.assertTrue(set.contains("ou=users"));
|
||||
Assert.assertTrue(set.contains("ou=groups"));
|
||||
Assert.assertTrue(set.contains("ou=configuration"));
|
||||
Assert.assertTrue(set.contains("prefNodeName=sysPrefRoot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicPluginAuthorization() throws Exception {
|
||||
ActiveMQServer server = getActiveMQServer();
|
||||
server.start();
|
||||
ClientSessionFactory cf = locator.createSessionFactory();
|
||||
String name = "queue1";
|
||||
|
||||
try {
|
||||
ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
|
||||
session.createQueue(SimpleString.toSimpleString(name), SimpleString.toSimpleString(name));
|
||||
ClientProducer producer = session.createProducer();
|
||||
producer.send(name, session.createMessage(false));
|
||||
session.close();
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
e.printStackTrace();
|
||||
Assert.fail("should not throw exception");
|
||||
}
|
||||
|
||||
cf.close();
|
||||
locator.close();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPluginAuthorizationNegative() throws Exception {
|
||||
final SimpleString ADDRESS = new SimpleString("queue2");
|
||||
final SimpleString QUEUE = new SimpleString("queue2");
|
||||
|
||||
ActiveMQServer server = getActiveMQServer();
|
||||
server.start();
|
||||
server.createQueue(ADDRESS, QUEUE, null, true, false);
|
||||
|
||||
ClientSessionFactory cf = locator.createSessionFactory();
|
||||
ClientSession session = cf.createSession("second", "secret", false, true, true, false, 0);
|
||||
|
||||
// CREATE_DURABLE_QUEUE
|
||||
try {
|
||||
session.createQueue(ADDRESS, QUEUE, true);
|
||||
Assert.fail("should throw exception here");
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// DELETE_DURABLE_QUEUE
|
||||
try {
|
||||
session.deleteQueue(QUEUE);
|
||||
Assert.fail("should throw exception here");
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// CREATE_NON_DURABLE_QUEUE
|
||||
try {
|
||||
session.createQueue(ADDRESS, QUEUE, false);
|
||||
Assert.fail("should throw exception here");
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// DELETE_NON_DURABLE_QUEUE
|
||||
try {
|
||||
session.deleteQueue(QUEUE);
|
||||
Assert.fail("should throw exception here");
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// PRODUCE
|
||||
try {
|
||||
ClientProducer producer = session.createProducer(ADDRESS);
|
||||
producer.send(session.createMessage(true));
|
||||
Assert.fail("should throw exception here");
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// CONSUME
|
||||
try {
|
||||
ClientConsumer consumer = session.createConsumer(QUEUE);
|
||||
Assert.fail("should throw exception here");
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
session.close();
|
||||
cf.close();
|
||||
locator.close();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPluginAuthorizationPositive() throws Exception {
|
||||
final SimpleString ADDRESS = new SimpleString("queue1");
|
||||
final SimpleString QUEUE = new SimpleString("queue1");
|
||||
|
||||
ActiveMQServer server = getActiveMQServer();
|
||||
server.start();
|
||||
|
||||
ClientSessionFactory cf = locator.createSessionFactory();
|
||||
ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
|
||||
|
||||
// CREATE_DURABLE_QUEUE
|
||||
try {
|
||||
session.createQueue(ADDRESS, QUEUE, true);
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
e.printStackTrace();
|
||||
Assert.fail("should not throw exception here");
|
||||
}
|
||||
|
||||
// DELETE_DURABLE_QUEUE
|
||||
try {
|
||||
session.deleteQueue(QUEUE);
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
e.printStackTrace();
|
||||
Assert.fail("should not throw exception here");
|
||||
}
|
||||
|
||||
// CREATE_NON_DURABLE_QUEUE
|
||||
try {
|
||||
session.createQueue(ADDRESS, QUEUE, false);
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
Assert.fail("should not throw exception here");
|
||||
}
|
||||
|
||||
// DELETE_NON_DURABLE_QUEUE
|
||||
try {
|
||||
session.deleteQueue(QUEUE);
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
Assert.fail("should not throw exception here");
|
||||
}
|
||||
|
||||
session.createQueue(ADDRESS, QUEUE, true);
|
||||
|
||||
// PRODUCE
|
||||
try {
|
||||
ClientProducer producer = session.createProducer(ADDRESS);
|
||||
producer.send(session.createMessage(true));
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
Assert.fail("should not throw exception here");
|
||||
}
|
||||
|
||||
// CONSUME
|
||||
try {
|
||||
session.createConsumer(QUEUE);
|
||||
}
|
||||
catch (ActiveMQException e) {
|
||||
Assert.fail("should not throw exception here");
|
||||
}
|
||||
|
||||
session.close();
|
||||
cf.close();
|
||||
locator.close();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
private ActiveMQServer getActiveMQServer() {
|
||||
LegacyLDAPSecuritySettingPlugin legacyLDAPSecuritySettingPlugin = new LegacyLDAPSecuritySettingPlugin()
|
||||
.setInitialContextFactory("com.sun.jndi.ldap.LdapCtxFactory")
|
||||
.setConnectionURL("ldap://localhost:1024")
|
||||
.setConnectionUsername("uid=admin,ou=system")
|
||||
.setConnectionPassword("secret")
|
||||
.setConnectionProtocol("s")
|
||||
.setAuthentication("simple")
|
||||
.populateSecurityRoles();
|
||||
|
||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||
securityManager.setConfigurationName("LDAPLogin");
|
||||
Configuration configuration = new ConfigurationImpl()
|
||||
.setSecurityEnabled(true)
|
||||
.addAcceptorConfiguration(new TransportConfiguration(InVMAcceptorFactory.class.getCanonicalName()))
|
||||
.setJournalDirectory(ActiveMQTestBase.getJournalDir(testDir, 0, false))
|
||||
.setBindingsDirectory(ActiveMQTestBase.getBindingsDir(testDir, 0, false))
|
||||
.setPagingDirectory(ActiveMQTestBase.getPageDir(testDir, 0, false))
|
||||
.setLargeMessagesDirectory(ActiveMQTestBase.getLargeMessagesDir(testDir, 0, false))
|
||||
.setPersistenceEnabled(false)
|
||||
.addSecuritySettingPlugin(legacyLDAPSecuritySettingPlugin);
|
||||
|
||||
return ActiveMQServers.newActiveMQServer(configuration, ManagementFactory.getPlatformMBeanServer(), securityManager, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
## ---------------------------------------------------------------------------
|
||||
## 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.
|
||||
## ---------------------------------------------------------------------------
|
||||
|
||||
dn: o=ActiveMQ,ou=system
|
||||
objectclass: organization
|
||||
objectclass: top
|
||||
o: ActiveMQ
|
||||
|
||||
dn: ou=users,o=ActiveMQ,ou=system
|
||||
objectclass: organizationalUnit
|
||||
objectclass: top
|
||||
ou: users
|
||||
|
||||
dn: uid=first,ou=system
|
||||
uid: first
|
||||
userPassword: secret
|
||||
objectClass: account
|
||||
objectClass: simpleSecurityObject
|
||||
objectClass: top
|
||||
|
||||
dn: uid=second,ou=system
|
||||
uid: second
|
||||
userPassword: secret
|
||||
objectClass: account
|
||||
objectClass: simpleSecurityObject
|
||||
objectClass: top
|
||||
|
||||
dn: cn=role1,ou=system
|
||||
cn: role1
|
||||
member: uid=first,ou=system
|
||||
objectClass: groupOfNames
|
||||
objectClass: top
|
||||
|
||||
dn: cn=role2,ou=system
|
||||
cn: role2
|
||||
member: uid=second,ou=system
|
||||
objectClass: groupOfNames
|
||||
objectClass: top
|
||||
|
||||
dn: ou=destinations,o=ActiveMQ,ou=system
|
||||
objectclass: organizationalUnit
|
||||
objectclass: top
|
||||
ou: destinations
|
||||
|
||||
dn: ou=queues,ou=destinations,o=ActiveMQ,ou=system
|
||||
objectclass: organizationalUnit
|
||||
objectclass: top
|
||||
ou: queues
|
||||
|
||||
dn: uid=queue1,ou=queues,ou=destinations,o=ActiveMQ,ou=system
|
||||
objectclass: applicationProcess
|
||||
objectclass: uidObject
|
||||
objectclass: top
|
||||
uid: queue1
|
||||
cn: queue1
|
||||
|
||||
dn: uid=queue2,ou=queues,ou=destinations,o=ActiveMQ,ou=system
|
||||
objectclass: applicationProcess
|
||||
objectclass: uidObject
|
||||
objectclass: top
|
||||
uid: queue2
|
||||
cn: queue2
|
||||
|
||||
dn: cn=read,uid=queue1,ou=queues,ou=destinations,o=ActiveMQ,ou=system
|
||||
objectclass: groupOfUniqueNames
|
||||
objectclass: top
|
||||
cn: read
|
||||
uniquemember: uid=role1
|
||||
|
||||
dn: cn=write,uid=queue1,ou=queues,ou=destinations,o=ActiveMQ,ou=system
|
||||
objectclass: groupOfUniqueNames
|
||||
objectclass: top
|
||||
cn: write
|
||||
uniquemember: uid=role1
|
||||
|
||||
dn: cn=admin,uid=queue1,ou=queues,ou=destinations,o=ActiveMQ,ou=system
|
||||
objectclass: groupOfUniqueNames
|
||||
objectclass: top
|
||||
cn: admin
|
||||
uniquemember: uid=role1
|
Loading…
Reference in New Issue