You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by jb...@apache.org on 2021/05/25 16:17:02 UTC
[activemq-artemis] branch main updated: ARTEMIS-3288 support bulk
user loading with basic security manager
This is an automated email from the ASF dual-hosted git repository.
jbertram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git
The following commit(s) were added to refs/heads/main by this push:
new e9c94e5 ARTEMIS-3288 support bulk user loading with basic security manager
new 8d0f2f8 This closes #3571
e9c94e5 is described below
commit e9c94e57d9e2f6bb72f6217e9559caea2f292cab
Author: Justin Bertram <jb...@apache.org>
AuthorDate: Fri May 7 21:31:47 2021 -0500
ARTEMIS-3288 support bulk user loading with basic security manager
---
.../activemq/artemis/utils/ClassloadingUtil.java | 4 ++
.../security/ActiveMQBasicSecurityManager.java | 54 +++++++++++++----
docs/user-manual/en/security.md | 69 +++++++++++++++++-----
.../security/BasicSecurityManagerTest.java | 27 ++++++++-
4 files changed, 127 insertions(+), 27 deletions(-)
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ClassloadingUtil.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ClassloadingUtil.java
index 99c39e0..b56e3a0 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ClassloadingUtil.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ClassloadingUtil.java
@@ -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();
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQBasicSecurityManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQBasicSecurityManager.java
index 7964134..5bc3ddf 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQBasicSecurityManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQBasicSecurityManager.java
@@ -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) {
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 2c78558..3b84a05 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -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.
+
+If your use-case requires *multiple* users to be available when the broker
+starts then you can use a configuration like this:
-- `bootstrapUser` - the name of the bootstrap user
-- `bootstrapPassword` - the password for the bootstrap user; supports masking
-- `bootstrapRole` - the role of the bootstrap user
+```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>
+```
-The value specified in the `bootstrapRole` will need the following permissions
-on the `activemq.management` address:
+- `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:
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerTest.java
index 79855a0..e4ea013 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerTest.java
@@ -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;