ARTEMIS-1116 map ldap roles to local roles adds general mapping between multiple amq internal roles and a external roles (e.g. LDAP), configured in broker.xml
Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/24e37993 Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/24e37993 Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/24e37993 Branch: refs/heads/master Commit: 24e37993478a54c3954687f4b332a33faaed3b58 Parents: 4edc329 Author: Stephen Higgs <shi...@redhat.com> Authored: Tue Apr 18 13:16:30 2017 -0400 Committer: Justin Bertram <jbert...@apache.org> Committed: Mon Apr 24 10:25:08 2017 -0500 ---------------------------------------------------------------------- .../artemis/core/config/Configuration.java | 4 + .../core/config/impl/ConfigurationImpl.java | 17 +++ .../deployers/impl/FileConfigurationParser.java | 75 +++++++++- .../resources/schema/artemis-configuration.xsd | 143 +++++++++++-------- .../core/config/impl/FileConfigurationTest.java | 84 +++++++++++ .../src/test/resources/securityRoleMappings.xml | 45 ++++++ 6 files changed, 301 insertions(+), 67 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/24e37993/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.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 30d6668..7dfb1a5 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 @@ -927,6 +927,10 @@ public interface Configuration { */ Map<String, Set<Role>> getSecurityRoles(); + Configuration addSecurityRoleNameMapping(String internalRole, Set<String> externalRoles); + + Map<String, Set<String>> getSecurityRoleNameMappings(); + Configuration putSecurityRoles(String match, Set<Role> roles); Configuration setConnectorServiceConfigurations(List<ConnectorServiceConfiguration> configs); http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/24e37993/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java ---------------------------------------------------------------------- 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 3ab7468..2a538ca 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 @@ -232,6 +232,8 @@ public class ConfigurationImpl implements Configuration, Serializable { private List<SecuritySettingPlugin> securitySettingPlugins = new ArrayList<>(); + private Map<String, Set<String>> securityRoleNameMappings = new HashMap<>(); + protected List<ConnectorServiceConfiguration> connectorServiceConfigurations = new ArrayList<>(); private boolean maskPassword = ActiveMQDefaultConfiguration.isDefaultMaskPassword(); @@ -1294,6 +1296,21 @@ public class ConfigurationImpl implements Configuration, Serializable { } @Override + public Configuration addSecurityRoleNameMapping(String internalRole, Set<String> externalRoles) { + if (securityRoleNameMappings.containsKey(internalRole)) { + securityRoleNameMappings.get(internalRole).addAll(externalRoles); + } else { + securityRoleNameMappings.put(internalRole, externalRoles); + } + return this; + } + + @Override + public Map<String, Set<String>> getSecurityRoleNameMappings() { + return securityRoleNameMappings; + } + + @Override public List<ConnectorServiceConfiguration> getConnectorServiceConfigurations() { return this.connectorServiceConfigurations; } http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/24e37993/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java ---------------------------------------------------------------------- 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 6f666b6..6f7f9c9 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 @@ -22,6 +22,7 @@ import java.io.Reader; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -94,6 +95,8 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { public static final String SECURITY_PLUGIN_ELEMENT_NAME = "security-setting-plugin"; + public static final String SECURITY_ROLE_MAPPING_NAME = "role-mapping"; + private static final String PERMISSION_ELEMENT_NAME = "permission"; private static final String SETTING_ELEMENT_NAME = "setting"; @@ -106,6 +109,10 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { private static final String VALUE_ATTR_NAME = "value"; + private static final String ROLE_FROM_ATTR_NAME = "from"; + + private static final String ROLE_TO_ATTR_NAME = "to"; + static final String CREATEDURABLEQUEUE_NAME = "createDurableQueue"; private static final String DELETEDURABLEQUEUE_NAME = "deleteDurableQueue"; @@ -618,12 +625,18 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { */ private void parseSecurity(final Element e, final Configuration config) { NodeList elements = e.getElementsByTagName("security-settings"); - if (elements.getLength() != 0) { Element node = (Element) elements.item(0); - NodeList list = node.getElementsByTagName(SECURITY_ELEMENT_NAME); + NodeList list = node.getElementsByTagName(SECURITY_ROLE_MAPPING_NAME); + for (int i = 0; i < list.getLength(); i++) { + Map<String, Set<String>> roleMappings = parseSecurityRoleMapping(list.item(i)); + for (Map.Entry<String, Set<String>> roleMapping : roleMappings.entrySet()) { + config.addSecurityRoleNameMapping(roleMapping.getKey(), roleMapping.getValue()); + } + } + list = node.getElementsByTagName(SECURITY_ELEMENT_NAME); for (int i = 0; i < list.getLength(); i++) { - Pair<String, Set<Role>> securityItem = parseSecurityRoles(list.item(i)); + Pair<String, Set<Role>> securityItem = parseSecurityRoles(list.item(i), config.getSecurityRoleNameMappings()); config.putSecurityRoles(securityItem.getA(), securityItem.getB()); } list = node.getElementsByTagName(SECURITY_PLUGIN_ELEMENT_NAME); @@ -711,7 +724,7 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { * @param node * @return */ - protected Pair<String, Set<Role>> parseSecurityRoles(final Node node) { + protected Pair<String, Set<Role>> parseSecurityRoles(final Node node, final Map<String, Set<String>> roleMappings) { final String match = node.getAttributes().getNamedItem("match").getNodeValue(); Set<Role> securityRoles = new HashSet<>(); @@ -737,7 +750,9 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { final String type = getAttributeValue(child, TYPE_ATTR_NAME); final String roleString = getAttributeValue(child, ROLES_ATTR_NAME); String[] roles = roleString.split(","); - for (String role : roles) { + String[] mappedRoles = getMappedRoleNames(roles, roleMappings); + + for (String role : mappedRoles) { if (SEND_NAME.equals(type)) { send.add(role.trim()); } else if (CONSUME_NAME.equals(type)) { @@ -770,7 +785,6 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { } } } - } for (String role : allRoles) { @@ -780,6 +794,23 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { return securityMatch; } + /** + * Translate and expand a set of role names to a set of mapped role names, also includes the original role names + * @param roles the original set of role names + * @param roleMappings a one-to-many mapping of original role names to mapped role names + * @return the final set of mapped role names + */ + private String[] getMappedRoleNames(String[] roles, Map<String, Set<String>> roleMappings) { + Set<String> mappedRoles = new HashSet<>(); + for (String role : roles) { + if (roleMappings.containsKey(role)) { + mappedRoles.addAll(roleMappings.get(role)); + } + mappedRoles.add(role); + } + return mappedRoles.toArray(new String[mappedRoles.size()]); + } + 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<>(); @@ -805,6 +836,38 @@ public final class FileConfigurationParser extends XMLConfigurationUtil { } /** + * Computes the map of internal ActiveMQ role names to sets of external (e.g. LDAP) role names. For example, given a role + * "myrole" with a DN of "cn=myrole,dc=local,dc=com": + * from="cn=myrole,dc=local,dc=com", to="amq,admin,guest" + * from="cn=myOtherRole,dc=local,dc=com", to="amq" + * The resulting map will consist of: + * amq => {"cn=myrole,dc=local,dc=com","cn=myOtherRole",dc=local,dc=com"} + * admin => {"cn=myrole,dc=local,dc=com"} + * guest => {"cn=myrole,dc=local,dc=com"} + * @param item the role-mapping node + * @return the map of local ActiveMQ role names to the set of mapped role names + */ + private Map<String, Set<String>> parseSecurityRoleMapping(Node item) { + Map<String, Set<String>> mappedRoleNames = new HashMap<>(); + String externalRoleName = getAttributeValue(item, ROLE_FROM_ATTR_NAME).trim(); + Set<String> internalRoleNames = new HashSet<>(); + Collections.addAll(internalRoleNames, getAttributeValue(item, ROLE_TO_ATTR_NAME).split(",")); + for (String internalRoleName : internalRoleNames) { + internalRoleName = internalRoleName.trim(); + if (mappedRoleNames.containsKey(internalRoleName)) { + mappedRoleNames.get(internalRoleName).add(externalRoleName); + } else { + Set<String> externalRoleNames = new HashSet<>(); + externalRoleNames.add(externalRoleName); + if ((internalRoleName.length() > 0) && (externalRoleName.length() > 0)) { + mappedRoleNames.put(internalRoleName, externalRoleNames); + } + } + } + return mappedRoleNames; + } + + /** * @param node * @return */ http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/24e37993/artemis-server/src/main/resources/schema/artemis-configuration.xsd ---------------------------------------------------------------------- diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd index e6eaf8b..1f64930 100644 --- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd +++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd @@ -754,81 +754,102 @@ a list of security settings </xsd:documentation> </xsd:annotation> + <xsd:complexType> - <xsd:choice> - <xsd:element name="security-setting" maxOccurs="unbounded" minOccurs="0"> + <xsd:sequence> + <xsd:choice> + <xsd:element name="security-setting" maxOccurs="unbounded" minOccurs="0"> + <xsd:complexType> + <xsd:annotation> + <xsd:documentation> + a permission to add to the matched addresses + </xsd:documentation> + </xsd:annotation> + <xsd:sequence> + <xsd:element name="permission" maxOccurs="unbounded" minOccurs="0"> + <xsd:complexType> + <xsd:attribute name="type" type="xsd:string" use="required"> + <xsd:annotation> + <xsd:documentation> + the type of permission + </xsd:documentation> + </xsd:annotation> + </xsd:attribute> + <xsd:attribute name="roles" type="xsd:string" use="required"> + <xsd:annotation> + <xsd:documentation> + a comma-separated list of roles to apply the permission to + </xsd:documentation> + </xsd:annotation> + </xsd:attribute> + </xsd:complexType> + </xsd:element> + </xsd:sequence> + <xsd:attribute name="match" type="xsd:string" use="required"> + <xsd:annotation> + <xsd:documentation> + regular expression for matching security roles against addresses + </xsd:documentation> + </xsd:annotation> + </xsd:attribute> + </xsd:complexType> + </xsd:element> + <xsd:element name="security-setting-plugin" maxOccurs="1" 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:choice> + <xsd:element name="role-mapping" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> - <xsd:annotation> - <xsd:documentation> - a permission to add to the matched addresses - </xsd:documentation> - </xsd:annotation> - <xsd:sequence> - <xsd:element name="permission" maxOccurs="unbounded" minOccurs="0"> - <xsd:complexType> - <xsd:attribute name="type" type="xsd:string" use="required"> - <xsd:annotation> - <xsd:documentation> - the type of permission - </xsd:documentation> - </xsd:annotation> - </xsd:attribute> - <xsd:attribute name="roles" type="xsd:string" use="required"> - <xsd:annotation> - <xsd:documentation> - a comma-separated list of roles to apply the permission to - </xsd:documentation> - </xsd:annotation> - </xsd:attribute> - </xsd:complexType> - </xsd:element> - </xsd:sequence> - <xsd:attribute name="match" type="xsd:string" use="required"> + <xsd:attribute name="from" type="xsd:string" use="required"> <xsd:annotation> <xsd:documentation> - regular expression for matching security roles against addresses + the name of the external role </xsd:documentation> </xsd:annotation> </xsd:attribute> - </xsd:complexType> - </xsd:element> - <xsd:element name="security-setting-plugin" maxOccurs="1" 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:attribute name="to" type="xsd:string" use="required"> <xsd:annotation> <xsd:documentation> - the name of the plugin class to instantiate + the comma delimited name of the internal role(s) </xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> </xsd:element> - </xsd:choice> + </xsd:sequence> </xsd:complexType> </xsd:element> http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/24e37993/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java ---------------------------------------------------------------------- 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 1cebc8c..c87ce34 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 @@ -477,6 +477,90 @@ public class FileConfigurationTest extends ConfigurationImplTest { } @Test + public void testSecurityRoleMapping() throws Exception { + FileConfiguration fc = new FileConfiguration(); + FileDeploymentManager deploymentManager = new FileDeploymentManager("securityRoleMappings.xml"); + deploymentManager.addDeployable(fc); + deploymentManager.readConfiguration(); + + Map<String, Set<Role>> securityRoles = fc.getSecurityRoles(); + Set<Role> roles = securityRoles.get("#"); + + //N.B. - FileConfigurationParser uses the constructor without createAddress and deleteAddress + //cn=mygroup,dc=local,dc=com = amq1 + Role testRole1 = new Role("cn=mygroup,dc=local,dc=com",false, false, false, + false, true, false, false, + false); + + //myrole1 = amq1 + amq2 + Role testRole2 = new Role("myrole1",false, false, false, + false, true, true, false, + false); + + //myrole3 = amq3 + amq4 + Role testRole3 = new Role("myrole3",false, false, true, + true, false, false, false, + false); + + //myrole4 = amq5 + amq!@#$%^&*() + amq6 + Role testRole4 = new Role("myrole4",true, true, false, + false, false, false, false, + true); + + //myrole5 = amq4 = amq3 + amq4 + Role testRole5 = new Role("myrole5",false, false, true, + true, false, false, false, + false); + + Role testRole6 = new Role("amq1",false, false, false, + false, true, false, false, + false); + + Role testRole7 = new Role("amq2",false, false, false, + false, false, true, false, + false); + + Role testRole8 = new Role("amq3",false, false, true, + false, false, false, false, + false); + + Role testRole9 = new Role("amq4",false, false, true, + true, false, false, false, + false); + + Role testRole10 = new Role("amq5",false, false, false, + false, false, false, false, + false); + + Role testRole11 = new Role("amq6",false, true, false, + false, false, false, false, + true); + + Role testRole12 = new Role("amq7",false, false, false, + false, false, false, true, + false); + + Role testRole13 = new Role("amq!@#$%^&*()",true, false, false, + false, false, false, false, + false); + + assertEquals(13, roles.size()); + assertTrue(roles.contains(testRole1)); + assertTrue(roles.contains(testRole2)); + assertTrue(roles.contains(testRole3)); + assertTrue(roles.contains(testRole4)); + assertTrue(roles.contains(testRole5)); + assertTrue(roles.contains(testRole6)); + assertTrue(roles.contains(testRole7)); + assertTrue(roles.contains(testRole8)); + assertTrue(roles.contains(testRole9)); + assertTrue(roles.contains(testRole10)); + assertTrue(roles.contains(testRole11)); + assertTrue(roles.contains(testRole12)); + assertTrue(roles.contains(testRole13)); + } + + @Test public void testContextClassLoaderUsage() throws Exception { final File customConfiguration = File.createTempFile("hornetq-unittest", ".xml"); http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/24e37993/artemis-server/src/test/resources/securityRoleMappings.xml ---------------------------------------------------------------------- diff --git a/artemis-server/src/test/resources/securityRoleMappings.xml b/artemis-server/src/test/resources/securityRoleMappings.xml new file mode 100644 index 0000000..6994ceb --- /dev/null +++ b/artemis-server/src/test/resources/securityRoleMappings.xml @@ -0,0 +1,45 @@ +<!-- + 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. +--> +<configuration + xmlns="urn:activemq" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="urn:activemq ../../../../activemq-server/src/main/resources/schema/artemis-server.xsd"> + <core xmlns="urn:activemq:core"> + <security-settings> + <security-setting match="#"> + <permission type="createNonDurableQueue" roles="amq1"/> + <permission type="deleteNonDurableQueue" roles="amq2"/> + <permission type="createDurableQueue" roles="amq3,amq4"/> + <permission type="deleteDurableQueue" roles="amq4"/> + <permission type="createAddress" roles="amq5"/> + <permission type="deleteAddress" roles="amq5"/> + <permission type="consume" roles="amq6"/> + <permission type="browse" roles="amq6"/> + <permission type="send" roles="amq!@#$%^&*()"/> + <!-- we need this otherwise ./artemis data imp wouldn't work --> + <permission type="manage" roles="amq7"/> + </security-setting> + <role-mapping from="cn=mygroup,dc=local,dc=com" to="amq1" /> + <role-mapping from="myrole1" to="amq1,amq2" /> + <role-mapping from="myrole2" to="" /> + <role-mapping from="myrole3" to="amq3" /> + <role-mapping from="myrole3" to="amq4" /> + <role-mapping from="myrole4" to="amq5,amq!@#$%^&*(),amq6" /> + <role-mapping from="myrole5" to="amq4" /> + </security-settings> + </core> +</configuration>