This closes #3571
This commit is contained in:
commit
8d0f2f8505
|
@ -122,6 +122,10 @@ public final class ClassloadingUtil {
|
||||||
return (String)properties.get(name);
|
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) {
|
public static Properties loadProperties(ClassLoader loader, String propertiesFile) {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.activemq.artemis.core.persistence.StorageManager;
|
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.protocol.RemotingConnection;
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
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.spi.core.security.jaas.UserPrincipal;
|
||||||
|
import org.apache.activemq.artemis.utils.ClassloadingUtil;
|
||||||
import org.apache.activemq.artemis.utils.SecurityManagerUtil;
|
import org.apache.activemq.artemis.utils.SecurityManagerUtil;
|
||||||
import org.jboss.logging.Logger;
|
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_USER = "bootstrapUser";
|
||||||
public static final String BOOTSTRAP_PASSWORD = "bootstrapPassword";
|
public static final String BOOTSTRAP_PASSWORD = "bootstrapPassword";
|
||||||
public static final String BOOTSTRAP_ROLE = "bootstrapRole";
|
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 Map<String, String> properties;
|
||||||
private String rolePrincipalClass = RolePrincipal.class.getName();
|
private String rolePrincipalClass = RolePrincipal.class.getName();
|
||||||
|
@ -55,7 +59,7 @@ public class ActiveMQBasicSecurityManager implements ActiveMQSecurityManager5, U
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActiveMQBasicSecurityManager init(Map<String, String> properties) {
|
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();
|
ActiveMQServerLogger.LOGGER.noBootstrapCredentialsFound();
|
||||||
} else {
|
} else {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
@ -180,18 +184,48 @@ public class ActiveMQBasicSecurityManager implements ActiveMQSecurityManager5, U
|
||||||
public void completeInit(StorageManager storageManager) {
|
public void completeInit(StorageManager storageManager) {
|
||||||
this.storageManager = storageManager;
|
this.storageManager = storageManager;
|
||||||
|
|
||||||
// add/update the bootstrap user now that the StorageManager is set
|
// add/update the bootstrap credentials now that the StorageManager is set
|
||||||
if (properties != null && properties.containsKey(BOOTSTRAP_USER) && properties.containsKey(BOOTSTRAP_PASSWORD) && properties.containsKey(BOOTSTRAP_ROLE)) {
|
if (properties != null && properties.containsKey(BOOTSTRAP_USER_FILE) && properties.containsKey(BOOTSTRAP_ROLE_FILE)) {
|
||||||
try {
|
Properties users = ClassloadingUtil.loadProperties(properties.get(BOOTSTRAP_USER_FILE));
|
||||||
if (userExists(properties.get(BOOTSTRAP_USER))) {
|
Map<String, Set<String>> rolesByUser = invertProperties(ClassloadingUtil.loadProperties(properties.get(BOOTSTRAP_ROLE_FILE)));
|
||||||
updateUser(properties.get(BOOTSTRAP_USER), properties.get(BOOTSTRAP_PASSWORD), new String[]{properties.get(BOOTSTRAP_ROLE)});
|
for (String user : users.stringPropertyNames()) {
|
||||||
} else {
|
addOrUpdateUser(user, users.getProperty(user), rolesByUser.get(user).toArray(new String[0]));
|
||||||
addNewUser(properties.get(BOOTSTRAP_USER), properties.get(BOOTSTRAP_PASSWORD), new String[]{properties.get(BOOTSTRAP_ROLE)});
|
}
|
||||||
|
} 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) {
|
tempRoles.add((String) val.getKey());
|
||||||
ActiveMQServerLogger.LOGGER.failedToCreateBootstrapCredentials(e, properties.get(BOOTSTRAP_USER));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return invertedProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean userExists(String user) {
|
private boolean userExists(String user) {
|
||||||
|
|
|
@ -1160,7 +1160,28 @@ the basic security manager.
|
||||||
|
|
||||||
The configuration for the `ActiveMQBasicSecurityManager` happens in
|
The configuration for the `ActiveMQBasicSecurityManager` happens in
|
||||||
`bootstrap.xml` just like it does for all security manager implementations.
|
`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
|
```xml
|
||||||
<broker xmlns="http://activemq.org/schema">
|
<broker xmlns="http://activemq.org/schema">
|
||||||
|
@ -1175,18 +1196,39 @@ Here's an example:
|
||||||
</broker>
|
</broker>
|
||||||
```
|
```
|
||||||
|
|
||||||
Because the bindings data which holds the user & role data cannot be modified
|
- `bootstrapUser` - The name of the bootstrap user.
|
||||||
manually and because the broker must be running to manage users and because
|
- `bootstrapPassword` - The password for the bootstrap user; supports masking.
|
||||||
the broker often needs to be secured from first boot the
|
- `bootstrapRole` - The role of the bootstrap user.
|
||||||
`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
|
If your use-case requires *multiple* users to be available when the broker
|
||||||
- `bootstrapPassword` - the password for the bootstrap user; supports masking
|
starts then you can use a configuration like this:
|
||||||
- `bootstrapRole` - the role of the bootstrap user
|
|
||||||
|
|
||||||
The value specified in the `bootstrapRole` will need the following permissions
|
```xml
|
||||||
on the `activemq.management` address:
|
<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`
|
- `createNonDurableQueue`
|
||||||
- `createAddress`
|
- `createAddress`
|
||||||
|
@ -1208,9 +1250,8 @@ For example:
|
||||||
|
|
||||||
> **Note:**
|
> **Note:**
|
||||||
>
|
>
|
||||||
> If the 3 `bootstrap` properties are defined then those credentials will be
|
> Any `bootstrap` credentials will be set **whenever** you start the broker no
|
||||||
> set whenever you start the broker no matter what changes may have been made
|
> matter what changes may have been made to them at runtime previously.
|
||||||
> to them at runtime previously.
|
|
||||||
|
|
||||||
## Mapping external roles
|
## 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:
|
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;
|
package org.apache.activemq.artemis.tests.integration.security;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -41,10 +43,24 @@ import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
public class BasicSecurityManagerTest extends ActiveMQTestBase {
|
public class BasicSecurityManagerTest extends ActiveMQTestBase {
|
||||||
|
|
||||||
private ServerLocator locator;
|
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
|
@Override
|
||||||
@Before
|
@Before
|
||||||
|
@ -56,9 +72,14 @@ public class BasicSecurityManagerTest extends ActiveMQTestBase {
|
||||||
|
|
||||||
public ActiveMQServer initializeServer() throws Exception {
|
public ActiveMQServer initializeServer() throws Exception {
|
||||||
Map<String, String> initProperties = new HashMap<>();
|
Map<String, String> initProperties = new HashMap<>();
|
||||||
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_USER, "first");
|
if (bootstrapProperties) {
|
||||||
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_PASSWORD, "secret");
|
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_USER_FILE, "users.properties");
|
||||||
initProperties.put(ActiveMQBasicSecurityManager.BOOTSTRAP_ROLE, "programmers");
|
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);
|
ActiveMQBasicSecurityManager securityManager = new ActiveMQBasicSecurityManager().init(initProperties);
|
||||||
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, true));
|
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, true));
|
||||||
return server;
|
return server;
|
||||||
|
|
Loading…
Reference in New Issue