ARTEMIS-3288 support bulk user loading with basic security manager
This commit is contained in:
parent
4de9e887f6
commit
e9c94e57d9
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
// 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(properties.get(BOOTSTRAP_USER))) {
|
||||
updateUser(properties.get(BOOTSTRAP_USER), properties.get(BOOTSTRAP_PASSWORD), new String[]{properties.get(BOOTSTRAP_ROLE)});
|
||||
if (userExists(user)) {
|
||||
updateUser(user, password, roles);
|
||||
} else {
|
||||
addNewUser(properties.get(BOOTSTRAP_USER), properties.get(BOOTSTRAP_PASSWORD), new String[]{properties.get(BOOTSTRAP_ROLE)});
|
||||
addNewUser(user, password, roles);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ActiveMQServerLogger.LOGGER.failedToCreateBootstrapCredentials(e, properties.get(BOOTSTRAP_USER));
|
||||
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);
|
||||
}
|
||||
tempRoles.add((String) val.getKey());
|
||||
}
|
||||
}
|
||||
return invertedProps;
|
||||
}
|
||||
|
||||
private boolean userExists(String user) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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<>();
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue