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
This commit is contained in:
Stephen Higgs 2017-04-18 13:16:30 -04:00 committed by Justin Bertram
parent 4edc3297f0
commit 24e3799347
6 changed files with 307 additions and 73 deletions

View File

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

View File

@ -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();
@ -1293,6 +1295,21 @@ public class ConfigurationImpl implements Configuration, Serializable {
return this;
}
@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;

View File

@ -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++) {
Pair<String, Set<Role>> securityItem = parseSecurityRoles(list.item(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), 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<>();
@ -804,6 +835,38 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
return new Pair<>(securitySettingPlugin, settings);
}
/**
* 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

View File

@ -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: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:sequence>
<xsd:choice>
<xsd:element name="security-setting" maxOccurs="unbounded" minOccurs="0">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
regular expression for matching security roles against addresses
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:attribute name="from" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
the name of the external role
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="to" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
the comma delimited name of the internal role(s)
</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:sequence>
</xsd:complexType>
</xsd:element>

View File

@ -476,6 +476,90 @@ public class FileConfigurationTest extends ConfigurationImplTest {
assertEquals(legacyLDAPSecuritySettingPlugin.isEnableListener(), false);
}
@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 {

View File

@ -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!@#$%^&amp;*()"/>
<!-- 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!@#$%^&amp;*(),amq6" />
<role-mapping from="myrole5" to="amq4" />
</security-settings>
</core>
</configuration>