ARTEMIS-3288 support bulk user loading with basic security manager

This commit is contained in:
Justin Bertram 2021-05-07 21:31:47 -05:00
parent 4de9e887f6
commit e9c94e57d9
4 changed files with 127 additions and 27 deletions

View File

@ -122,6 +122,10 @@ public final class ClassloadingUtil {
return (String)properties.get(name);
}
public static Properties loadProperties(String propertiesFile) {
return loadProperties(ClassloadingUtil.class.getClassLoader(), propertiesFile);
}
public static Properties loadProperties(ClassLoader loader, String propertiesFile) {
Properties properties = new Properties();

View File

@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.activemq.artemis.core.persistence.StorageManager;
@ -35,6 +36,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
import org.apache.activemq.artemis.utils.ClassloadingUtil;
import org.apache.activemq.artemis.utils.SecurityManagerUtil;
import org.jboss.logging.Logger;
@ -48,6 +50,8 @@ public class ActiveMQBasicSecurityManager implements ActiveMQSecurityManager5, U
public static final String BOOTSTRAP_USER = "bootstrapUser";
public static final String BOOTSTRAP_PASSWORD = "bootstrapPassword";
public static final String BOOTSTRAP_ROLE = "bootstrapRole";
public static final String BOOTSTRAP_USER_FILE = "bootstrapUserFile";
public static final String BOOTSTRAP_ROLE_FILE = "bootstrapRoleFile";
private Map<String, String> properties;
private String rolePrincipalClass = RolePrincipal.class.getName();
@ -55,7 +59,7 @@ public class ActiveMQBasicSecurityManager implements ActiveMQSecurityManager5, U
@Override
public ActiveMQBasicSecurityManager init(Map<String, String> properties) {
if (!properties.containsKey(BOOTSTRAP_USER) || !properties.containsKey(BOOTSTRAP_PASSWORD) || !properties.containsKey(BOOTSTRAP_ROLE)) {
if ((!properties.containsKey(BOOTSTRAP_USER) || !properties.containsKey(BOOTSTRAP_PASSWORD) || !properties.containsKey(BOOTSTRAP_ROLE)) && (!properties.containsKey(BOOTSTRAP_USER_FILE) || !properties.containsKey(BOOTSTRAP_ROLE_FILE))) {
ActiveMQServerLogger.LOGGER.noBootstrapCredentialsFound();
} else {
this.properties = properties;
@ -180,18 +184,48 @@ public class ActiveMQBasicSecurityManager implements ActiveMQSecurityManager5, U
public void completeInit(StorageManager storageManager) {
this.storageManager = storageManager;
// add/update the bootstrap user now that the StorageManager is set
if (properties != null && properties.containsKey(BOOTSTRAP_USER) && properties.containsKey(BOOTSTRAP_PASSWORD) && properties.containsKey(BOOTSTRAP_ROLE)) {
try {
if (userExists(properties.get(BOOTSTRAP_USER))) {
updateUser(properties.get(BOOTSTRAP_USER), properties.get(BOOTSTRAP_PASSWORD), new String[]{properties.get(BOOTSTRAP_ROLE)});
} else {
addNewUser(properties.get(BOOTSTRAP_USER), properties.get(BOOTSTRAP_PASSWORD), new String[]{properties.get(BOOTSTRAP_ROLE)});
// add/update the bootstrap credentials now that the StorageManager is set
if (properties != null && properties.containsKey(BOOTSTRAP_USER_FILE) && properties.containsKey(BOOTSTRAP_ROLE_FILE)) {
Properties users = ClassloadingUtil.loadProperties(properties.get(BOOTSTRAP_USER_FILE));
Map<String, Set<String>> rolesByUser = invertProperties(ClassloadingUtil.loadProperties(properties.get(BOOTSTRAP_ROLE_FILE)));
for (String user : users.stringPropertyNames()) {
addOrUpdateUser(user, users.getProperty(user), rolesByUser.get(user).toArray(new String[0]));
}
} else if (properties != null && properties.containsKey(BOOTSTRAP_USER) && properties.containsKey(BOOTSTRAP_PASSWORD) && properties.containsKey(BOOTSTRAP_ROLE)) {
addOrUpdateUser(properties.get(BOOTSTRAP_USER), properties.get(BOOTSTRAP_PASSWORD), new String[]{properties.get(BOOTSTRAP_ROLE)});
}
}
private void addOrUpdateUser(String user, String password, String... roles) {
try {
if (userExists(user)) {
updateUser(user, password, roles);
} else {
addNewUser(user, password, roles);
}
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.failedToCreateBootstrapCredentials(e, user);
}
}
/*
* The roles properties file used by the PropertiesLoginModule is in the format role=user1,user2. We need to change
* that so we can look up a user and get their roles instead.
*/
private Map<String, Set<String>> invertProperties(Properties props) {
Map<String, Set<String>> invertedProps = new HashMap<>();
for (Map.Entry<Object, Object> val : props.entrySet()) {
for (String user : ((String) val.getValue()).split(",")) {
Set<String> tempRoles = invertedProps.get(user);
if (tempRoles == null) {
tempRoles = new HashSet<>();
invertedProps.put(user, tempRoles);
}
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.failedToCreateBootstrapCredentials(e, properties.get(BOOTSTRAP_USER));
tempRoles.add((String) val.getKey());
}
}
return invertedProps;
}
private boolean userExists(String user) {

View File

@ -1160,7 +1160,28 @@ the basic security manager.
The configuration for the `ActiveMQBasicSecurityManager` happens in
`bootstrap.xml` just like it does for all security manager implementations.
Here's an example:
The `ActiveMQBasicSecurityManager` requires some special configuration for the
following reasons:
- the bindings data which holds the user & role data cannot be modified
manually
- the broker must be running to manage users
- the broker often needs to be secured from first boot the
If, for example, the broker was configured to use the
`ActiveMQBasicSecurityManager` and was started from scratch then no clients
would be able to connect because there would be no users & roles configured.
However, in order to configure users & roles one would need to use the
management API which would require the proper credentials. It's a catch-22.
Therefore, it is possible to configure "bootstrap" credentials that will be
automatically created when the broker starts. There are properties to define
either:
- a single user whose credentials can then be used to add other users
- properties files from which to load users & roles in bulk
Here's an example of the single bootstrap user configuration:
```xml
<broker xmlns="http://activemq.org/schema">
@ -1175,18 +1196,39 @@ Here's an example:
</broker>
```
Because the bindings data which holds the user & role data cannot be modified
manually and because the broker must be running to manage users and because
the broker often needs to be secured from first boot the
`ActiveMQBasicSecurityManager` has 3 properties to define a user whose
credentials can then be used to add other users.
- `bootstrapUser` - The name of the bootstrap user.
- `bootstrapPassword` - The password for the bootstrap user; supports masking.
- `bootstrapRole` - The role of the bootstrap user.
- `bootstrapUser` - the name of the bootstrap user
- `bootstrapPassword` - the password for the bootstrap user; supports masking
- `bootstrapRole` - the role of the bootstrap user
If your use-case requires *multiple* users to be available when the broker
starts then you can use a configuration like this:
The value specified in the `bootstrapRole` will need the following permissions
on the `activemq.management` address:
```xml
<broker xmlns="http://activemq.org/schema">
<security-manager class-name="org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager">
<property key="bootstrapUserFile" value="artemis-users.properties"/>
<property key="bootstrapRoleFile" value="artemis-roles.properties"/>
</security-manager>
...
</broker>
```
- `bootstrapUserFile` - The name of the file from which to load users. This is
a *properties* file formatted exactly the same as the user properties file
used by the [`PropertiesLoginModule`](#propertiesloginmodule). This file
should be on the broker's classpath (e.g. in the `etc` directory).
- `bootstrapRoleFile` - The role of the bootstrap user. This is a *properties*
file formatted exactly the same as the role properties file used by the
[`PropertiesLoginModule`](#propertiesloginmodule). This file should be on the
broker's classpath (e.g. in the `etc` directory).
Regardless of whether you configure a single bootstrap user or load many users
from properties files, any user with which additional users are created should
be in a role with the appropriate permissions on the `activemq.management`
address. For example if you've specified a `bootstrapUser` then the
`bootstrapRole` will need the following permissions:
- `createNonDurableQueue`
- `createAddress`
@ -1208,9 +1250,8 @@ For example:
> **Note:**
>
> If the 3 `bootstrap` properties are defined then those credentials will be
> set whenever you start the broker no matter what changes may have been made
> to them at runtime previously.
> Any `bootstrap` credentials will be set **whenever** you start the broker no
> matter what changes may have been made to them at runtime previously.
## Mapping external roles
Roles from external authentication providers (i.e. LDAP) can be mapped to internally used roles. The is done through role-mapping entries in the security-settings block:

View File

@ -17,6 +17,8 @@
package org.apache.activemq.artemis.tests.integration.security;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -41,10 +43,24 @@ import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class BasicSecurityManagerTest extends ActiveMQTestBase {
private ServerLocator locator;
private boolean bootstrapProperties;
public BasicSecurityManagerTest(boolean bootstrapProperties) {
this.bootstrapProperties = bootstrapProperties;
}
@Parameterized.Parameters(name = "bootstrapProperties={0}")
public static Collection<Object[]> data() {
Object[][] params = new Object[][]{{false}, {true}};
return Arrays.asList(params);
}
@Override
@Before
@ -56,9 +72,14 @@ public class BasicSecurityManagerTest extends ActiveMQTestBase {
public ActiveMQServer initializeServer() throws Exception {
Map<String, String> initProperties = new HashMap<>();
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_USER, "first");
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_PASSWORD, "secret");
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_ROLE, "programmers");
if (bootstrapProperties) {
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_USER_FILE, "users.properties");
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_ROLE_FILE, "roles.properties");
} else {
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_USER, "first");
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_PASSWORD, "secret");
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_ROLE, "programmers");
}
ActiveMQBasicSecurityManager securityManager = new ActiveMQBasicSecurityManager().init(initProperties);
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, true));
return server;