From d94c044e900407134ca426827ccd7b73594aca6f Mon Sep 17 00:00:00 2001 From: jbertram Date: Mon, 11 Jan 2016 16:39:35 -0600 Subject: [PATCH] ARTEMIS-349 LDAP plugin listener This feature required a bit of refactoring to the plugin interface itself as well as a restriction on the configuration so that either only one plugin could be specified or an ulimited number of security-setting matches. This was done to prevent messy situations where a plugin could update settings from the XML or even another plugin if there were overlapping matches. --- .../artemis/core/config/Configuration.java | 2 + .../core/config/impl/ConfigurationImpl.java | 6 + .../impl/FileConfigurationParser.java | 4 +- .../core/server/SecuritySettingPlugin.java | 24 +- .../core/server/impl/ActiveMQServerImpl.java | 9 + .../impl/LegacyLDAPSecuritySettingPlugin.java | 385 ++++++++++++++---- .../schema/artemis-configuration.xsd | 6 +- .../impl/FileConfigurationParserTest.java | 12 +- .../config/impl/FileConfigurationTest.java | 25 +- .../ConfigurationTest-full-config.xml | 14 - .../resources/InvalidConfigurationTest6.xml | 45 ++ .../test/resources/securitySettingPlugin.xml | 40 ++ docs/user-manual/en/security.md | 47 +-- .../openwire/OpenWireTestBase.java | 23 +- .../security/LDAPSecurityTest.java | 4 +- ...LDAPSecuritySettingPluginListenerTest.java | 352 ++++++++++++++++ .../LegacyLDAPSecuritySettingPluginTest.java | 3 +- .../integration/security/SecurityTest.java | 10 +- 18 files changed, 822 insertions(+), 189 deletions(-) create mode 100644 artemis-server/src/test/resources/InvalidConfigurationTest6.xml create mode 100644 artemis-server/src/test/resources/securitySettingPlugin.xml create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java index 96d505031d..e493769fb7 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java @@ -868,6 +868,8 @@ public interface Configuration { */ Map> getSecurityRoles(); + Configuration putSecurityRoles(String match, Set roles); + Configuration setConnectorServiceConfigurations(List configs); Configuration addConnectorServiceConfiguration(ConnectorServiceConfiguration config); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java index fc36809298..0a6582fabd 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java @@ -1144,6 +1144,12 @@ public class ConfigurationImpl implements Configuration, Serializable { return securitySettings; } + @Override + public ConfigurationImpl putSecurityRoles(String match, Set roles) { + securitySettings.put(match, roles); + return this; + } + @Override public ConfigurationImpl setSecurityRoles(final Map> securitySettings) { this.securitySettings = securitySettings; diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java index 8b11b3b156..e34207ee39 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java @@ -541,12 +541,12 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { NodeList list = node.getElementsByTagName(SECURITY_ELEMENT_NAME); for (int i = 0; i < list.getLength(); i++) { Pair> securityItem = parseSecurityRoles(list.item(i)); - config.getSecurityRoles().put(securityItem.getA(), securityItem.getB()); + config.putSecurityRoles(securityItem.getA(), securityItem.getB()); } list = node.getElementsByTagName(SECURITY_PLUGIN_ELEMENT_NAME); for (int i = 0; i < list.getLength(); i++) { Pair> securityItem = parseSecuritySettingPlugins(list.item(i)); - config.addSecuritySettingPlugin(securityItem.getA().init(securityItem.getB()).populateSecurityRoles()); + config.addSecuritySettingPlugin(securityItem.getA().init(securityItem.getB())); } } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/SecuritySettingPlugin.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/SecuritySettingPlugin.java index 0803e2f342..7b22b61812 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/SecuritySettingPlugin.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/SecuritySettingPlugin.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Set; import org.apache.activemq.artemis.core.security.Role; +import org.apache.activemq.artemis.core.settings.HierarchicalRepository; public interface SecuritySettingPlugin extends Serializable { /** @@ -35,8 +36,14 @@ public interface SecuritySettingPlugin extends Serializable { SecuritySettingPlugin init(Map options); /** - * Once {@code #populateSecurityRoles} is invoked this method should return the security role information from the - * external environment (e.g. file, LDAP, etc.). + * Clean up all the associated resources associated with this plugin (e.g. LDAP connections, file handles, etc.) + * + * @return {@code this} instance + */ + SecuritySettingPlugin stop(); + + /** + * Fetch the security role information from the external environment (e.g. file, LDAP, etc.) and return it. * * @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 @@ -44,14 +51,11 @@ public interface SecuritySettingPlugin extends Serializable { Map> 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. + * This method is called by the broker during the start-up process. It's for plugins that might need to modify the + * security settings during runtime (e.g. LDAP plugin that uses a listener to receive updates, etc.). Any changes + * made to this {@code HierarchicalRepository} will be reflected in the broker. * - * @return {@code this} instance + * @param securityRepository */ - SecuritySettingPlugin populateSecurityRoles(); + void setSecurityRepository(HierarchicalRepository> securityRepository); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java index 58993fc218..abdf428154 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java @@ -104,6 +104,7 @@ import org.apache.activemq.artemis.core.server.NodeManager; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.QueueCreator; import org.apache.activemq.artemis.core.server.QueueFactory; +import org.apache.activemq.artemis.core.server.SecuritySettingPlugin; import org.apache.activemq.artemis.core.server.ServerSession; import org.apache.activemq.artemis.core.server.ServerSessionFactory; import org.apache.activemq.artemis.core.server.ServiceRegistry; @@ -696,6 +697,10 @@ public class ActiveMQServerImpl implements ActiveMQServer { stopComponent(memoryManager); + for (SecuritySettingPlugin securitySettingPlugin : configuration.getSecuritySettingPlugins()) { + securitySettingPlugin.stop(); + } + if (threadPool != null && !threadPoolSupplied) { threadPool.shutdown(); try { @@ -1739,6 +1744,10 @@ public class ActiveMQServerImpl implements ActiveMQServer { for (Map.Entry> entry : configuration.getSecurityRoles().entrySet()) { securityRepository.addMatch(entry.getKey(), entry.getValue(), true); } + + for (SecuritySettingPlugin securitySettingPlugin : configuration.getSecuritySettingPlugins()) { + securitySettingPlugin.setSecurityRepository(securityRepository); + } } private void deployQueuesFromConfiguration() throws Exception { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java index 21de3d7565..936ae9adb6 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java @@ -25,17 +25,25 @@ import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; +import javax.naming.event.EventDirContext; +import javax.naming.event.NamespaceChangeListener; +import javax.naming.event.NamingEvent; +import javax.naming.event.NamingExceptionEvent; +import javax.naming.event.ObjectChangeListener; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; +import java.util.List; 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; +import org.apache.activemq.artemis.core.server.SecuritySettingPlugin; +import org.apache.activemq.artemis.core.settings.HierarchicalRepository; public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin { private static final long serialVersionUID = 4793109879399750045L; @@ -52,6 +60,7 @@ public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin { public static final String ADMIN_PERMISSION_VALUE = "adminPermissionValue"; public static final String READ_PERMISSION_VALUE = "readPermissionValue"; public static final String WRITE_PERMISSION_VALUE = "writePermissionValue"; + public static final String ENABLE_LISTENER = "enableListener"; private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; private String connectionURL = "ldap://localhost:1024"; @@ -65,30 +74,43 @@ public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin { private String adminPermissionValue = "admin"; private String readPermissionValue = "read"; private String writePermissionValue = "write"; + private boolean enableListener = true; private DirContext context; - private Map> securityRoles = new HashMap<>(); + private EventDirContext eventContext; + private Map> securityRoles; + private HierarchicalRepository> securityRepository; @Override public LegacyLDAPSecuritySettingPlugin init(Map 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); + initialContextFactory = getOption(options, INITIAL_CONTEXT_FACTORY, initialContextFactory); + connectionURL = getOption(options, CONNECTION_URL, connectionURL); + connectionUsername = getOption(options, CONNECTION_USERNAME, connectionUsername); + connectionPassword = getOption(options, CONNECTION_PASSWORD, connectionPassword); + connectionProtocol = getOption(options, CONNECTION_PROTOCOL, connectionProtocol); + authentication = getOption(options, AUTHENTICATION, authentication); + destinationBase = getOption(options, DESTINATION_BASE, destinationBase); + filter = getOption(options, FILTER, filter); + roleAttribute = getOption(options, ROLE_ATTRIBUTE, roleAttribute); + adminPermissionValue = getOption(options, ADMIN_PERMISSION_VALUE, adminPermissionValue); + readPermissionValue = getOption(options, READ_PERMISSION_VALUE, readPermissionValue); + writePermissionValue = getOption(options, WRITE_PERMISSION_VALUE, writePermissionValue); + enableListener = getOption(options, ENABLE_LISTENER, Boolean.TRUE.toString()).equalsIgnoreCase(Boolean.TRUE.toString()); } return this; } + private String getOption(Map options, String key, String defaultValue) { + String result = options.get(key); + if (result == null) { + result = defaultValue; + } + + return result; + } + public String getRoleAttribute() { return roleAttribute; } @@ -197,11 +219,46 @@ public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin { return this; } - protected void open() throws NamingException { + public boolean isEnableListener() { + return enableListener; + } + + public LegacyLDAPSecuritySettingPlugin setEnableListener(boolean enableListener) { + this.enableListener = enableListener; + return this; + } + + protected boolean isContextAlive() { + boolean alive = false; if (context != null) { + try { + context.getAttributes(""); + alive = true; + } + catch (Exception e) { + } + } + return alive; + } + + protected void open() throws NamingException { + if (isContextAlive()) { return; } + context = createContext(); + eventContext = ((EventDirContext) context.lookup("")); + + SearchControls searchControls = new SearchControls(); + searchControls.setReturningAttributes(new String[]{roleAttribute}); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + if (enableListener) { + eventContext.addNamingListener(destinationBase, filter, searchControls, new LDAPNamespaceChangeListener()); + } + } + + private DirContext createContext() throws NamingException { Hashtable env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); if (connectionUsername != null && !"".equals(connectionUsername)) { @@ -219,16 +276,18 @@ public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin { env.put(Context.SECURITY_PROTOCOL, connectionProtocol); env.put(Context.PROVIDER_URL, connectionURL); env.put(Context.SECURITY_AUTHENTICATION, authentication); - context = new InitialDirContext(env); + return new InitialDirContext(env); } @Override public Map> getSecurityRoles() { + if (securityRoles == null) { + populateSecurityRoles(); + } return securityRoles; } - @Override - public LegacyLDAPSecuritySettingPlugin populateSecurityRoles() { + private LegacyLDAPSecuritySettingPlugin populateSecurityRoles() { ActiveMQServerLogger.LOGGER.populatingSecurityRolesFromLDAP(connectionURL); try { open(); @@ -242,80 +301,240 @@ public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin { searchControls.setReturningAttributes(new String[]{roleAttribute}); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); - Map> securityRoles = new HashMap<>(); + securityRoles = new HashMap<>(); try { NamingEnumeration 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 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); - } + processSearchResult(securityRoles, searchResults.next()); } } catch (Exception e) { ActiveMQServerLogger.LOGGER.errorPopulatingSecurityRolesFromLDAP(e); } - this.securityRoles = securityRoles; return this; } + + @Override + public void setSecurityRepository(HierarchicalRepository> securityRepository) { + this.securityRepository = securityRepository; + } + + private void processSearchResult(Map> securityRoles, SearchResult searchResult) throws NamingException { + Attributes attrs = searchResult.getAttributes(); + if (attrs == null || attrs.size() == 0) { + return; + } + LdapName searchResultLdapName = new LdapName(searchResult.getName()); + ActiveMQServerLogger.LOGGER.debug("LDAP search result : " + 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 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); + } + } + + public SecuritySettingPlugin stop() { + try { + eventContext.close(); + } + catch (NamingException e) { + // ignore + } + + try { + context.close(); + } + catch (NamingException e) { + // ignore + } + + return this; + } + + /** + * Handler for new policy entries in the directory. + * + * @param namingEvent + * the new entry event that occurred + */ + public void objectAdded(NamingEvent namingEvent) { + Map> newRoles = new HashMap<>(); + + try { + processSearchResult(newRoles, (SearchResult) namingEvent.getNewBinding()); + for (Map.Entry> entry : newRoles.entrySet()) { + Set existingRoles = securityRepository.getMatch(entry.getKey()); + for (Role role : entry.getValue()) { + existingRoles.add(role); + } + } + } + catch (NamingException e) { + e.printStackTrace(); + } + } + + /** + * Handler for removed policy entries in the directory. + * + * @param namingEvent + * the removed entry event that occurred + */ + public void objectRemoved(NamingEvent namingEvent) { + try { + LdapName ldapName = new LdapName(namingEvent.getOldBinding().getName()); + String match = null; + for (Rdn rdn : ldapName.getRdns()) { + if (rdn.getType().equals("uid")) { + match = rdn.getValue().toString(); + } + } + + Set roles = securityRepository.getMatch(match); + + List rolesToRemove = new ArrayList<>(); + + for (Rdn rdn : ldapName.getRdns()) { + if (rdn.getValue().equals(writePermissionValue)) { + ActiveMQServerLogger.LOGGER.debug("Removing write permission"); + for (Role role : roles) { + if (role.isSend()) { + rolesToRemove.add(role); + } + } + } + else if (rdn.getValue().equals(readPermissionValue)) { + ActiveMQServerLogger.LOGGER.debug("Removing read permission"); + for (Role role : roles) { + if (role.isConsume()) { + rolesToRemove.add(role); + } + } + } + else if (rdn.getValue().equals(adminPermissionValue)) { + ActiveMQServerLogger.LOGGER.debug("Removing admin permission"); + for (Role role : roles) { + if (role.isCreateDurableQueue() || role.isCreateNonDurableQueue() || role.isDeleteDurableQueue() || role.isDeleteNonDurableQueue()) { + rolesToRemove.add(role); + } + } + } + + for (Role roleToRemove : rolesToRemove) { + roles.remove(roleToRemove); + } + } + } + catch (NamingException e) { + e.printStackTrace(); + } + } + + /** + * @param namingEvent + * the renaming entry event that occurred + */ + public void objectRenamed(NamingEvent namingEvent) { + + } + + /** + * Handler for changed policy entries in the directory. + * + * @param namingEvent + * the changed entry event that occurred + */ + public void objectChanged(NamingEvent namingEvent) { + objectRemoved(namingEvent); + objectAdded(namingEvent); + } + + /** + * Handler for exception events from the registry. + * + * @param namingExceptionEvent + * the exception event + */ + public void namingExceptionThrown(NamingExceptionEvent namingExceptionEvent) { + context = null; + ActiveMQServerLogger.LOGGER.error("Caught unexpected exception.", namingExceptionEvent.getException()); + } + + protected class LDAPNamespaceChangeListener implements NamespaceChangeListener, ObjectChangeListener { + @Override + public void namingExceptionThrown(NamingExceptionEvent evt) { + LegacyLDAPSecuritySettingPlugin.this.namingExceptionThrown(evt); + } + + @Override + public void objectAdded(NamingEvent evt) { + LegacyLDAPSecuritySettingPlugin.this.objectAdded(evt); + } + + @Override + public void objectRemoved(NamingEvent evt) { + LegacyLDAPSecuritySettingPlugin.this.objectRemoved(evt); + } + + @Override + public void objectRenamed(NamingEvent evt) { + LegacyLDAPSecuritySettingPlugin.this.objectRenamed(evt); + } + + @Override + public void objectChanged(NamingEvent evt) { + LegacyLDAPSecuritySettingPlugin.this.objectChanged(evt); + } + } } diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd index 91740194d6..8703e7bbe3 100644 --- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd +++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd @@ -698,7 +698,7 @@ - + @@ -735,7 +735,7 @@ - + @@ -771,7 +771,7 @@ - + diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationParserTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationParserTest.java index 99a45efa23..84441a2fdd 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationParserTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationParserTest.java @@ -16,6 +16,11 @@ */ package org.apache.activemq.artemis.core.config.impl; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.config.FileDeploymentManager; @@ -24,11 +29,6 @@ import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec; import org.junit.Test; -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - public class FileConfigurationParserTest extends ActiveMQTestBase { /** @@ -43,7 +43,7 @@ public class FileConfigurationParserTest extends ActiveMQTestBase { */ @Test public void testSchemaValidation() throws Exception { - for (int i = 0; i < 6; i++) { + for (int i = 0; i < 7; i++) { String filename = "InvalidConfigurationTest" + i + ".xml"; FileConfiguration fc = new FileConfiguration(); FileDeploymentManager deploymentManager = new FileDeploymentManager(filename); diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java index 587d2813c3..c4831bc378 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java @@ -24,6 +24,8 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.activemq.artemis.api.core.BroadcastGroupConfiguration; import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration; @@ -316,13 +318,15 @@ public class FileConfigurationTest extends ConfigurationImplTest { assertEquals("color='blue'", conf.getQueueConfigurations().get(1).getFilterString()); assertEquals(false, conf.getQueueConfigurations().get(1).isDurable()); - assertEquals(2, conf.getSecurityRoles().size()); + Map> roles = conf.getSecurityRoles(); - assertTrue(conf.getSecurityRoles().containsKey("a1")); + assertEquals(2, roles.size()); - assertTrue(conf.getSecurityRoles().containsKey("a2")); + assertTrue(roles.containsKey("a1")); - Role a1Role = conf.getSecurityRoles().get("a1").toArray(new Role[1])[0]; + assertTrue(roles.containsKey("a2")); + + Role a1Role = roles.get("a1").toArray(new Role[1])[0]; assertFalse(a1Role.isSend()); assertFalse(a1Role.isConsume()); @@ -332,7 +336,7 @@ public class FileConfigurationTest extends ConfigurationImplTest { assertFalse(a1Role.isDeleteNonDurableQueue()); assertFalse(a1Role.isManage()); - Role a2Role = conf.getSecurityRoles().get("a2").toArray(new Role[1])[0]; + Role a2Role = roles.get("a2").toArray(new Role[1])[0]; assertFalse(a2Role.isSend()); assertFalse(a2Role.isConsume()); @@ -341,8 +345,16 @@ public class FileConfigurationTest extends ConfigurationImplTest { assertFalse(a2Role.isCreateNonDurableQueue()); assertTrue(a2Role.isDeleteNonDurableQueue()); assertFalse(a2Role.isManage()); + } - List securitySettingPlugins = conf.getSecuritySettingPlugins(); + @Test + public void testSecuritySettingPlugin() throws Exception { + FileConfiguration fc = new FileConfiguration(); + FileDeploymentManager deploymentManager = new FileDeploymentManager("securitySettingPlugin.xml"); + deploymentManager.addDeployable(fc); + deploymentManager.readConfiguration(); + + List securitySettingPlugins = fc.getSecuritySettingPlugins(); SecuritySettingPlugin securitySettingPlugin = securitySettingPlugins.get(0); assertTrue(securitySettingPlugin instanceof LegacyLDAPSecuritySettingPlugin); LegacyLDAPSecuritySettingPlugin legacyLDAPSecuritySettingPlugin = (LegacyLDAPSecuritySettingPlugin) securitySettingPlugin; @@ -358,6 +370,7 @@ public class FileConfigurationTest extends ConfigurationImplTest { assertEquals(legacyLDAPSecuritySettingPlugin.getAdminPermissionValue(), "testAdminPermissionValue"); assertEquals(legacyLDAPSecuritySettingPlugin.getReadPermissionValue(), "testReadPermissionValue"); assertEquals(legacyLDAPSecuritySettingPlugin.getWritePermissionValue(), "testWritePermissionValue"); + assertEquals(legacyLDAPSecuritySettingPlugin.isEnableListener(), false); } @Test diff --git a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml index 5c4a0a2dba..0514788152 100644 --- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml +++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml @@ -232,20 +232,6 @@ - - - - - - - - - - - - - - diff --git a/artemis-server/src/test/resources/InvalidConfigurationTest6.xml b/artemis-server/src/test/resources/InvalidConfigurationTest6.xml new file mode 100644 index 0000000000..db3799a797 --- /dev/null +++ b/artemis-server/src/test/resources/InvalidConfigurationTest6.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/artemis-server/src/test/resources/securitySettingPlugin.xml b/artemis-server/src/test/resources/securitySettingPlugin.xml new file mode 100644 index 0000000000..25207d50f4 --- /dev/null +++ b/artemis-server/src/test/resources/securitySettingPlugin.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md index 5bb3372183..f6e654d084 100644 --- a/docs/user-manual/en/security.md +++ b/docs/user-manual/en/security.md @@ -122,12 +122,10 @@ 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.: +Aside from configuring sets of permissions via XML these permissions can alternatively be +configured via a plugin which implements `org.apache.activemq.artemis.core.server.SecuritySettingPlugin` e.g.: - ... @@ -210,6 +208,9 @@ Here is an example of the plugin's configuration: - `writePermissionValue`. Specifies a value that matches the `write` permission. The default value is `write`. +- `enableListener`. Whether or not to enable a listener that will automatically receive updates made in the LDAP server + and update the broker's authorization configuration in real-time. The default value is `true`. + 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 @@ -254,46 +255,12 @@ This is the default security manager. - The flexible, pluggable `ActiveMQJAASSecurityManager` which supports any standard JAAS login module. Artemis ships with several login modules which will be discussed further down. - -### Non-JAAS Security Manager - -If you wish to use the legacy, deprecated `ActiveMQSecurityManager`, then it needs to be added to the `bootstrap.xml` -configuration. Lets take a look at what this might look like: - - - file:${activemq.home}/config/non-clustered/artemis-users.properties - file:${activemq.home}/config/non-clustered/artemis-roles.properties - guest - - -The first 2 elements `users` and `roles` define what properties files should be used to load in the users and passwords. - -The next thing to note is the element `defaultuser`. This defines what user will be assumed when the client does not -specify a username/password when creating a session. In this case they will be the user `guest`. Multiple roles can be -specified for a default user in the `artemis-roles.properties`. - -Lets now take a look at the `artemis-users.properties` file, this is basically just a set of key value pairs that define -the users and their password, like so: - - bill=activemq - andrew=activemq1 - frank=activemq2 - sam=activemq3 - -The `artemis-roles.properties` defines what groups these users belong too where the key is the user and the value is a -comma separated list of the groups the user belongs to, like so: - - bill=user - andrew=europe-user,user - frank=us-user,news-user,user - sam=news-user,user ### JAAS Security Manager When using JAAS much of the configuration depends on which login module is used. However, there are a few commonalities -for every case. Just like in the non-JAAS use-case, the first place to look is in `bootstrap.xml`. Here is an example -using the `PropertiesLogin` JAAS login module which reads user, password, and role information from properties files -much like the non-JAAS security manager implementation: +for every case. The first place to look is in `bootstrap.xml`. Here is an example using the `PropertiesLogin` JAAS login +module which reads user, password, and role information from properties files: diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireTestBase.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireTestBase.java index 275fad6b61..6a95bfc378 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireTestBase.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/openwire/OpenWireTestBase.java @@ -20,10 +20,8 @@ import javax.jms.ConnectionFactory; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.activemq.artemis.api.core.SimpleString; @@ -98,20 +96,13 @@ public class OpenWireTestBase extends ActiveMQTestBase { //guest cannot do anything Role destRole = new Role("manager", false, false, false, false, true, true, false); - Map> settings = server.getConfiguration().getSecurityRoles(); - if (settings == null) { - settings = new HashMap<>(); - server.getConfiguration().setSecurityRoles(settings); - } - Set anySet = settings.get("#"); - if (anySet == null) { - anySet = new HashSet<>(); - settings.put("#", anySet); - } - anySet.add(senderRole); - anySet.add(receiverRole); - anySet.add(guestRole); - anySet.add(destRole); + Set roles = new HashSet<>(); + roles.add(senderRole); + roles.add(receiverRole); + roles.add(guestRole); + roles.add(destRole); + + server.getConfiguration().putSecurityRoles("#", roles); } jmsServer = new JMSServerManagerImpl(server); namingContext = new InVMNamingContext(); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LDAPSecurityTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LDAPSecurityTest.java index e37414e153..41f6ab970e 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LDAPSecurityTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LDAPSecurityTest.java @@ -172,7 +172,7 @@ public class LDAPSecurityTest extends AbstractLdapTestUnit { ActiveMQServer server = getActiveMQServer(); Set roles = new HashSet<>(); roles.add(new Role("programmers", false, false, false, false, false, false, false)); - server.getConfiguration().getSecurityRoles().put("#", roles); + server.getConfiguration().putSecurityRoles("#", roles); server.start(); server.createQueue(ADDRESS, DURABLE_QUEUE, null, true, false); server.createQueue(ADDRESS, NON_DURABLE_QUEUE, null, false, false); @@ -260,7 +260,7 @@ public class LDAPSecurityTest extends AbstractLdapTestUnit { ActiveMQServer server = getActiveMQServer(); Set roles = new HashSet<>(); roles.add(new Role("admins", true, true, true, true, true, true, true)); - server.getConfiguration().getSecurityRoles().put("#", roles); + server.getConfiguration().putSecurityRoles("#", roles); server.start(); ClientSessionFactory cf = locator.createSessionFactory(); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java new file mode 100644 index 0000000000..8f237ab6d4 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java @@ -0,0 +1,352 @@ +/* + * 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.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +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.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Map; + +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 LegacyLDAPSecuritySettingPluginListenerTest extends AbstractLdapTestUnit { + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = LegacyLDAPSecuritySettingPluginListenerTest.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 LegacyLDAPSecuritySettingPluginListenerTest() { + 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 { + DirContext ctx = getContext(); + + 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")); + } + + private DirContext getContext() throws NamingException { + 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); + return new InitialDirContext(env); + } + + @Test + public void testProducerPermissionUpdate() throws Exception { + ActiveMQServer server = getActiveMQServer(); + server.getConfiguration().setSecurityInvalidationInterval(0); + server.start(); + ClientSessionFactory cf = locator.createSessionFactory(); + String name = "queue1"; + ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0); + ClientSession session2 = cf.createSession("second", "secret", false, true, true, false, 0); + session.createQueue(SimpleString.toSimpleString(name), SimpleString.toSimpleString(name)); + ClientProducer producer = session.createProducer(); + ClientProducer producer2 = session2.createProducer(); + producer.send(name, session.createMessage(true)); + + try { + producer2.send(name, session.createMessage(true)); + Assert.fail("Sending here should fail due to the original security data."); + } + catch (ActiveMQException e) { + // ok + } + + DirContext ctx = getContext(); + BasicAttributes basicAttributes = new BasicAttributes(); + basicAttributes.put("uniquemember", "uid=role2"); + ctx.modifyAttributes("cn=write,uid=queue1,ou=queues,ou=destinations,o=ActiveMQ,ou=system", DirContext.REPLACE_ATTRIBUTE, basicAttributes); + + producer2.send(name, session.createMessage(true)); + + try { + producer.send(name, session.createMessage(true)); + Assert.fail("Sending here should fail due to the modified security data."); + } + catch (ActiveMQException e) { + // ok + } + + cf.close(); + locator.close(); + server.stop(); + } + + @Test + public void testConsumerPermissionUpdate() throws Exception { + ActiveMQServer server = getActiveMQServer(); + server.getConfiguration().setSecurityInvalidationInterval(0); + server.start(); + ClientSessionFactory cf = locator.createSessionFactory(); + String queue = "queue1"; + ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0); + ClientSession session2 = cf.createSession("second", "secret", false, true, true, false, 0); + session.createQueue(SimpleString.toSimpleString(queue), SimpleString.toSimpleString(queue)); + ClientConsumer consumer = session.createConsumer(queue); + consumer.receiveImmediate(); + consumer.close(); + ClientConsumer consumer2 = null; + + try { + session2.createConsumer(queue); + Assert.fail("Consuming here should fail due to the original security data."); + } + catch (ActiveMQException e) { + // ok + } + + DirContext ctx = getContext(); + BasicAttributes basicAttributes = new BasicAttributes(); + basicAttributes.put("uniquemember", "uid=role2"); + ctx.modifyAttributes("cn=read,uid=queue1,ou=queues,ou=destinations,o=ActiveMQ,ou=system", DirContext.REPLACE_ATTRIBUTE, basicAttributes); + + consumer2 = session2.createConsumer(queue); + consumer2.receiveImmediate(); + consumer2.close(); + + try { + session.createConsumer(queue); + Assert.fail("Sending here should fail due to the modified security data."); + } + catch (ActiveMQException e) { + // ok + } + + cf.close(); + locator.close(); + server.stop(); + } + + @Test + public void testNewConsumerPermission() throws Exception { + ActiveMQServer server = getActiveMQServer(); + server.getConfiguration().setSecurityInvalidationInterval(0); + server.start(); + String queue = "queue2"; + server.createQueue(SimpleString.toSimpleString(queue), SimpleString.toSimpleString(queue), null, false, false); + ClientSessionFactory cf = locator.createSessionFactory(); + ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0); + ClientConsumer consumer; + + try { + session.createConsumer(queue); + Assert.fail("Consuming here should fail due to the original security data."); + } + catch (ActiveMQException e) { + // ok + } + + DirContext ctx = getContext(); + BasicAttributes basicAttributes = new BasicAttributes(); + basicAttributes.put("uniquemember", "uid=role1"); + Attribute objclass = new BasicAttribute("objectclass"); + objclass.add("top"); + objclass.add("groupOfUniqueNames"); + basicAttributes.put(objclass); + ctx.bind("cn=read,uid=" + queue + ",ou=queues,ou=destinations,o=ActiveMQ,ou=system", null, basicAttributes); + + consumer = session.createConsumer(queue); + consumer.receiveImmediate(); + + ctx.unbind("cn=read,uid=" + queue + ",ou=queues,ou=destinations,o=ActiveMQ,ou=system"); + ctx.close(); + + try { + session.createConsumer(queue); + Assert.fail("Consuming here should fail due to the modified security data."); + } + catch (ActiveMQException e) { + // ok + } + + consumer.close(); + + cf.close(); + locator.close(); + server.stop(); + } + + @Test + public void testNewProducerPermission() throws Exception { + ActiveMQServer server = getActiveMQServer(); + server.getConfiguration().setSecurityInvalidationInterval(0); + server.start(); + String queue = "queue2"; + server.createQueue(SimpleString.toSimpleString(queue), SimpleString.toSimpleString(queue), null, false, false); + ClientSessionFactory cf = locator.createSessionFactory(); + ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0); + ClientProducer producer = session.createProducer(SimpleString.toSimpleString(queue)); + + try { + producer.send(session.createMessage(true)); + Assert.fail("Producing here should fail due to the original security data."); + } + catch (ActiveMQException e) { + // ok + } + + DirContext ctx = getContext(); + BasicAttributes basicAttributes = new BasicAttributes(); + basicAttributes.put("uniquemember", "uid=role1"); + Attribute objclass = new BasicAttribute("objectclass"); + objclass.add("top"); + objclass.add("groupOfUniqueNames"); + basicAttributes.put(objclass); + ctx.bind("cn=write,uid=" + queue + ",ou=queues,ou=destinations,o=ActiveMQ,ou=system", null, basicAttributes); + + producer.send(session.createMessage(true)); + + ctx.unbind("cn=write,uid=" + queue + ",ou=queues,ou=destinations,o=ActiveMQ,ou=system"); + ctx.close(); + + try { + producer.send(session.createMessage(true)); + Assert.fail("Producing here should fail due to the modified security data."); + } + catch (ActiveMQException e) { + // ok + } + + producer.close(); + + cf.close(); + locator.close(); + server.stop(); + } + + private ActiveMQServer getActiveMQServer() { + LegacyLDAPSecuritySettingPlugin legacyLDAPSecuritySettingPlugin = new LegacyLDAPSecuritySettingPlugin(); + Map map = new HashMap<>(); + map.put(LegacyLDAPSecuritySettingPlugin.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + map.put(LegacyLDAPSecuritySettingPlugin.CONNECTION_URL, "ldap://localhost:1024"); + map.put(LegacyLDAPSecuritySettingPlugin.CONNECTION_USERNAME, "uid=admin,ou=system"); + map.put(LegacyLDAPSecuritySettingPlugin.CONNECTION_PASSWORD, "secret"); + map.put(LegacyLDAPSecuritySettingPlugin.CONNECTION_PROTOCOL, "s"); + map.put(LegacyLDAPSecuritySettingPlugin.AUTHENTICATION, "simple"); + map.put(LegacyLDAPSecuritySettingPlugin.ENABLE_LISTENER, "true"); + legacyLDAPSecuritySettingPlugin.init(map); +// .setInitialContextFactory("com.sun.jndi.ldap.LdapCtxFactory") +// .setConnectionURL("ldap://localhost:1024") +// .setConnectionUsername("uid=admin,ou=system") +// .setConnectionPassword("secret") +// .setConnectionProtocol("s") +// .setAuthentication("simple") +// .setEnableListener(true); + + ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("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); + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginTest.java index 48f98bba82..672d4c9848 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginTest.java @@ -297,8 +297,7 @@ public class LegacyLDAPSecuritySettingPluginTest extends AbstractLdapTestUnit { .setConnectionUsername("uid=admin,ou=system") .setConnectionPassword("secret") .setConnectionProtocol("s") - .setAuthentication("simple") - .populateSecurityRoles(); + .setAuthentication("simple"); ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("LDAPLogin"); Configuration configuration = new ConfigurationImpl() diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java index e5404efafc..71cd369948 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java @@ -229,7 +229,7 @@ public class SecurityTest extends ActiveMQTestBase { ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false)); Set roles = new HashSet<>(); roles.add(new Role("programmers", false, false, false, false, false, false, false)); - server.getConfiguration().getSecurityRoles().put("#", roles); + server.getConfiguration().putSecurityRoles("#", roles); server.start(); server.createQueue(ADDRESS, DURABLE_QUEUE, null, true, false); server.createQueue(ADDRESS, NON_DURABLE_QUEUE, null, false, false); @@ -324,7 +324,7 @@ public class SecurityTest extends ActiveMQTestBase { Set roles = new HashSet<>(); roles.add(new Role("programmers", false, false, false, false, false, false, false)); - server.getConfiguration().getSecurityRoles().put("#", roles); + server.getConfiguration().putSecurityRoles("#", roles); server.start(); @@ -418,7 +418,7 @@ public class SecurityTest extends ActiveMQTestBase { ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false)); Set roles = new HashSet<>(); roles.add(new Role("programmers", true, true, true, true, true, true, true)); - server.getConfiguration().getSecurityRoles().put("#", roles); + server.getConfiguration().putSecurityRoles("#", roles); server.start(); ClientSessionFactory cf = createSessionFactory(locator); @@ -506,7 +506,7 @@ public class SecurityTest extends ActiveMQTestBase { Set roles = new HashSet<>(); roles.add(new Role("programmers", true, true, true, true, true, true, true)); - server.getConfiguration().getSecurityRoles().put("#", roles); + server.getConfiguration().putSecurityRoles("#", roles); server.start(); TransportConfiguration tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY); @@ -590,7 +590,7 @@ public class SecurityTest extends ActiveMQTestBase { ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false)); Set roles = new HashSet<>(); roles.add(new Role("bar", true, true, true, true, true, true, true)); - server.getConfiguration().getSecurityRoles().put("#", roles); + server.getConfiguration().putSecurityRoles("#", roles); server.start(); ClientSessionFactory cf = createSessionFactory(locator);