diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
index 5cb6e8bc67..aaaad9b622 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
@@ -107,6 +107,8 @@ public class Create extends InputAbstract {
public static final String ETC_PING_TXT = "etc/ping-settings.txt";
public static final String ETC_COMMENTED_PING_TXT = "etc/commented-ping-settings.txt";
public static final String ETC_DATABASE_STORE_TXT = "etc/database-store.txt";
+ public static final String ETC_JAAS_SECURITY_MANAGER_TXT = "etc/jaas-security-manager.txt";
+ public static final String ETC_BASIC_SECURITY_MANAGER_TXT = "etc/basic-security-manager.txt";
public static final String ETC_GLOBAL_MAX_SPECIFIED_TXT = "etc/global-max-specified.txt";
public static final String ETC_GLOBAL_MAX_DEFAULT_TXT = "etc/global-max-default.txt";
@@ -289,6 +291,9 @@ public class Create extends InputAbstract {
}
}
+ @Option(name = "--security-manager", description = "Which security manager to use - jaas or basic (Default: jaas)")
+ private String securityManager = "jaas";
+
@Option(name = "--jdbc-bindings-table-name", description = "Name of the jdbc bindigns table")
private String jdbcBindings = ActiveMQDefaultConfiguration.getDefaultBindingsTableName();
@@ -821,6 +826,11 @@ public class Create extends InputAbstract {
// we want this variable to remain unchanged so that it will use the value set in the profile
filters.remove("${artemis.instance}");
+ if (SecurityManagerType.getType(securityManager) == SecurityManagerType.BASIC) {
+ filters.put("${security-manager-settings}", readTextFile(ETC_BASIC_SECURITY_MANAGER_TXT, filters));
+ } else {
+ filters.put("${security-manager-settings}", readTextFile(ETC_JAAS_SECURITY_MANAGER_TXT, filters));
+ }
writeEtc(ETC_BOOTSTRAP_XML, etcFolder, filters, false);
writeEtc(ETC_MANAGEMENT_XML, etcFolder, filters, false);
@@ -1193,4 +1203,20 @@ public class Create extends InputAbstract {
c = is.read(buffer);
}
}
+
+ private enum SecurityManagerType {
+ JAAS, BASIC;
+
+ public static SecurityManagerType getType(String type) {
+ type = type.toLowerCase();
+ switch (type) {
+ case "jaas":
+ return JAAS;
+ case "basic":
+ return BASIC;
+ default:
+ return null;
+ }
+ }
+ }
}
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java
index 552f7ef3a0..4f0da7cb84 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Run.java
@@ -72,17 +72,15 @@ public class Run extends LockAbstract {
super.execute(context);
try {
+ BrokerDTO broker = getBrokerDTO();
+ ActiveMQSecurityManager securityManager = SecurityManagerFactory.create(broker.security);
ManagementContextDTO managementDTO = getManagementDTO();
- managementContext = ManagementFactory.create(managementDTO);
+ managementContext = ManagementFactory.create(managementDTO, securityManager);
Artemis.printBanner();
- BrokerDTO broker = getBrokerDTO();
-
addShutdownHook(broker.server.getConfigurationFile().getParentFile());
- ActiveMQSecurityManager security = SecurityManagerFactory.create(broker.security);
-
ActivateCallback activateCallback = new ActivateCallback() {
@Override
public void preActivate() {
@@ -109,7 +107,7 @@ public class Run extends LockAbstract {
}
};
- server = BrokerFactory.createServer(broker.server, security, activateCallback);
+ server = BrokerFactory.createServer(broker.server, securityManager, activateCallback);
managementContext.start();
server.createComponents();
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/ManagementFactory.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/ManagementFactory.java
index 7baa0f5a77..826dc8efb3 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/ManagementFactory.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/factory/jmx/ManagementFactory.java
@@ -27,6 +27,7 @@ import org.apache.activemq.artemis.dto.JMXConnectorDTO;
import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.dto.MatchDTO;
import org.apache.activemq.artemis.core.server.management.JMXAccessControlList;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import org.apache.activemq.artemis.utils.FactoryFinder;
import java.io.IOException;
@@ -60,7 +61,7 @@ public class ManagementFactory {
return createJmxAclConfiguration(new URI(configuration), artemisHome, artemisInstance, artemisURIInstance);
}
- public static ManagementContext create(ManagementContextDTO config) throws Exception {
+ public static ManagementContext create(ManagementContextDTO config, ActiveMQSecurityManager securityManager) throws Exception {
ManagementContext context = new ManagementContext();
if (config.getAuthorisation() != null) {
@@ -130,6 +131,7 @@ public class ManagementFactory {
jmxConnectorConfiguration.setSecured(jmxConnector.isSecured());
}
context.setJmxConnectorConfiguration(jmxConnectorConfiguration);
+ context.setSecurityManager(securityManager);
}
return context;
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-security-manager.txt b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-security-manager.txt
new file mode 100644
index 0000000000..d7dfbd951a
--- /dev/null
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-security-manager.txt
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml
index f919428c80..69e2d95291 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml
@@ -18,7 +18,7 @@
-
+${security-manager-settings}
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-security-manager.txt b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-security-manager.txt
new file mode 100644
index 0000000000..2a0260ae87
--- /dev/null
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-security-manager.txt
@@ -0,0 +1 @@
+
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
index 5269e647d5..dab321abf5 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
@@ -105,6 +105,9 @@ import static org.junit.Assert.fail;
public class ArtemisTest extends CliTestBase {
private static final Logger log = Logger.getLogger(ArtemisTest.class);
+ // some tests will set this, as some security methods will need to know the server the CLI started
+ private ActiveMQServer server;
+
@Before
@Override
public void setup() throws Exception {
@@ -340,13 +343,23 @@ public class ArtemisTest extends CliTestBase {
}
@Test
- public void testUserCommand() throws Exception {
+ public void testUserCommandJAAS() throws Exception {
+ testUserCommand(false);
+ }
+
+ @Test
+ public void testUserCommandBasic() throws Exception {
+ testUserCommand(true);
+ }
+
+ private void testUserCommand(boolean basic) throws Exception {
Run.setEmbedded(true);
File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
- Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--require-login");
+ Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--require-login", "--security-manager", basic ? "basic" : "jaas");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
- Artemis.internalExecute("run");
+ Object runResult = Artemis.internalExecute("run");
+ server = ((Pair)runResult).getB();
try {
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
@@ -363,7 +376,7 @@ public class ArtemisTest extends CliTestBase {
//default only one user admin with role amq
assertTrue(result.contains("\"admin\"(amq)"));
- checkRole("admin", roleFile, "amq");
+ checkRole("admin", roleFile, basic, "amq");
//add a simple user
AddUser addCmd = new AddUser();
@@ -383,8 +396,8 @@ public class ArtemisTest extends CliTestBase {
assertTrue(result.contains("\"admin\"(amq)"));
assertTrue(result.contains("\"guest\"(admin)"));
- checkRole("guest", roleFile, "admin");
- assertTrue(checkPassword("guest", "guest123", userFile));
+ checkRole("guest", roleFile, basic, "admin");
+ assertTrue(checkPassword("guest", "guest123", userFile, basic));
//add a user with 2 roles
addCmd = new AddUser();
@@ -405,8 +418,8 @@ public class ArtemisTest extends CliTestBase {
assertTrue(result.contains("\"guest\"(admin)"));
assertTrue(result.contains("\"scott\"(admin,operator)"));
- checkRole("scott", roleFile, "admin", "operator");
- assertTrue(checkPassword("scott", "tiger", userFile));
+ checkRole("scott", roleFile, basic, "admin", "operator");
+ assertTrue(checkPassword("scott", "tiger", userFile, basic));
//add an existing user
addCmd = new AddUser();
@@ -504,24 +517,34 @@ public class ArtemisTest extends CliTestBase {
}
@Test
- public void testUserCommandViaManagementPlaintext() throws Exception {
- internalTestUserCommandViaManagement(true);
+ public void testUserCommandViaManagementPlaintextJAAS() throws Exception {
+ internalTestUserCommandViaManagement(true, false);
}
@Test
- public void testUserCommandViaManagementHashed() throws Exception {
- internalTestUserCommandViaManagement(false);
+ public void testUserCommandViaManagementHashedJAAS() throws Exception {
+ internalTestUserCommandViaManagement(false, false);
}
- private void internalTestUserCommandViaManagement(boolean plaintext) throws Exception {
+ @Test
+ public void testUserCommandViaManagementPlaintextBasic() throws Exception {
+ internalTestUserCommandViaManagement(true, true);
+ }
+
+ @Test
+ public void testUserCommandViaManagementHashedBasic() throws Exception {
+ internalTestUserCommandViaManagement(false, true);
+ }
+
+ private void internalTestUserCommandViaManagement(boolean plaintext, boolean basic) throws Exception {
Run.setEmbedded(true);
File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
- Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor");
+ Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor", "--security-manager", basic ? "basic" : "jaas");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
- ActiveMQServer activeMQServer = ((Pair)result).getB();
- ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl();
+ server = ((Pair)result).getB();
+ ActiveMQServerControl activeMQServerControl = server.getActiveMQServerControl();
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
@@ -529,7 +552,7 @@ public class ArtemisTest extends CliTestBase {
//default only one user admin with role amq
String jsonResult = activeMQServerControl.listUser("");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
- checkRole("admin", roleFile, "amq");
+ checkRole("admin", roleFile, basic, "amq");
//add a simple user
activeMQServerControl.addUser("guest", "guest123", "admin", plaintext);
@@ -537,9 +560,9 @@ public class ArtemisTest extends CliTestBase {
//verify add
jsonResult = activeMQServerControl.listUser("");
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin");
- checkRole("guest", roleFile, "admin");
- assertTrue(checkPassword("guest", "guest123", userFile));
- assertEquals(plaintext, !PasswordMaskingUtil.isEncMasked(getStoredPassword("guest", userFile)));
+ checkRole("guest", roleFile, basic, "admin");
+ assertTrue(checkPassword("guest", "guest123", userFile, basic));
+ assertEquals(plaintext, !PasswordMaskingUtil.isEncMasked(getStoredPassword("guest", userFile, basic)));
//add a user with 2 roles
activeMQServerControl.addUser("scott", "tiger", "admin,operator", plaintext);
@@ -548,9 +571,9 @@ public class ArtemisTest extends CliTestBase {
jsonResult = activeMQServerControl.listUser("");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator");
- checkRole("scott", roleFile, "admin", "operator");
- assertTrue(checkPassword("scott", "tiger", userFile));
- assertEquals(plaintext, !PasswordMaskingUtil.isEncMasked(getStoredPassword("scott", userFile)));
+ checkRole("scott", roleFile, basic, "admin", "operator");
+ assertTrue(checkPassword("scott", "tiger", userFile, basic));
+ assertEquals(plaintext, !PasswordMaskingUtil.isEncMasked(getStoredPassword("scott", userFile, basic)));
try {
activeMQServerControl.addUser("scott", "password", "visitor", plaintext);
@@ -615,11 +638,20 @@ public class ArtemisTest extends CliTestBase {
}
@Test
- public void testProperReloadWhenAddingUserViaManagement() throws Exception {
+ public void testProperReloadWhenAddingUserViaManagementJAAS() throws Exception {
+ testProperReloadWhenAddingUserViaManagement(false);
+ }
+
+ @Test
+ public void testProperReloadWhenAddingUserViaManagementBasic() throws Exception {
+ testProperReloadWhenAddingUserViaManagement(true);
+ }
+
+ private void testProperReloadWhenAddingUserViaManagement(boolean basic) throws Exception {
Run.setEmbedded(true);
File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
- Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor", "--require-login");
+ Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor", "--require-login", "--security-manager", basic ? "basic" : "jaas");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
ActiveMQServer activeMQServer = ((Pair)result).getB();
@@ -702,13 +734,23 @@ public class ArtemisTest extends CliTestBase {
}
@Test
- public void testUserCommandReset() throws Exception {
+ public void testUserCommandResetJAAS() throws Exception {
+ testUserCommandReset(false);
+ }
+
+ @Test
+ public void testUserCommandResetBasic() throws Exception {
+ testUserCommandReset(true);
+ }
+
+ private void testUserCommandReset(boolean basic) throws Exception {
Run.setEmbedded(true);
File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
- Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--require-login");
+ Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--require-login", "--security-manager", basic ? "basic" : "jaas");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
- Artemis.internalExecute("run");
+ Object runResult = Artemis.internalExecute("run");
+ server = ((Pair)runResult).getB();
try {
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
@@ -754,7 +796,7 @@ public class ArtemisTest extends CliTestBase {
addCmd.setUserCommandPassword("password1");
addCmd.setRole("admin,manager");
addCmd.execute(new TestActionContext());
- assertTrue(checkPassword("user1", "password1", userFile));
+ assertTrue(checkPassword("user1", "password1", userFile, basic));
addCmd.setUserCommandUser("user2");
addCmd.setUserCommandPassword("password2");
@@ -787,7 +829,7 @@ public class ArtemisTest extends CliTestBase {
.matcher(result)
.find());
- checkRole("user1", roleFile, "admin", "manager");
+ checkRole("user1", roleFile, basic, "admin", "manager");
//reset password
context = new TestActionContext();
@@ -798,16 +840,16 @@ public class ArtemisTest extends CliTestBase {
resetCommand.setPassword("admin");
resetCommand.execute(context);
- checkRole("user1", roleFile, "admin", "manager");
- assertFalse(checkPassword("user1", "password1", userFile));
- assertTrue(checkPassword("user1", "newpassword1", userFile));
+ checkRole("user1", roleFile, basic, "admin", "manager");
+ assertFalse(checkPassword("user1", "password1", userFile, basic));
+ assertTrue(checkPassword("user1", "newpassword1", userFile, basic));
//reset role
resetCommand.setUserCommandUser("user2");
resetCommand.setRole("manager,master,operator");
resetCommand.execute(new TestActionContext());
- checkRole("user2", roleFile, "manager", "master", "operator");
+ checkRole("user2", roleFile, basic, "manager", "master", "operator");
//reset both
resetCommand.setUserCommandUser("user3");
@@ -815,19 +857,28 @@ public class ArtemisTest extends CliTestBase {
resetCommand.setRole("admin,system");
resetCommand.execute(new ActionContext());
- checkRole("user3", roleFile, "admin", "system");
- assertTrue(checkPassword("user3", "newpassword3", userFile));
+ checkRole("user3", roleFile, basic, "admin", "system");
+ assertTrue(checkPassword("user3", "newpassword3", userFile, basic));
} finally {
stopServer();
}
}
@Test
- public void testConcurrentUserAdministration() throws Exception {
+ public void testConcurrentUserAdministrationJAAS() throws Exception {
+ testConcurrentUserAdministration(false);
+ }
+
+ @Test
+ public void testConcurrentUserAdministrationBasic() throws Exception {
+ testConcurrentUserAdministration(true);
+ }
+
+ private void testConcurrentUserAdministration(boolean basic) throws Exception {
Run.setEmbedded(true);
File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
- Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--require-login");
+ Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--require-login", "--security-manager", basic ? "basic" : "jaas");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Artemis.internalExecute("run");
@@ -1711,28 +1762,52 @@ public class ArtemisTest extends CliTestBase {
}
private void checkRole(String user, File roleFile, String... roles) throws Exception {
- Configurations configs = new Configurations();
- FileBasedConfigurationBuilder roleBuilder = configs.propertiesBuilder(roleFile);
- PropertiesConfiguration roleConfig = roleBuilder.getConfiguration();
+ checkRole(user, roleFile, false, roles);
+ }
- for (String r : roles) {
- String storedUsers = (String) roleConfig.getProperty(r);
+ private void checkRole(String user, File roleFile, boolean basicSecurityManager, String... roles) throws Exception {
+ if (basicSecurityManager) {
+ for (String r : roles) {
+ assertTrue(server.getStorageManager().getPersistedRoles().get(user).getRoles().contains(r));
+ }
+ } else {
+ Configurations configs = new Configurations();
+ FileBasedConfigurationBuilder roleBuilder = configs.propertiesBuilder(roleFile);
+ PropertiesConfiguration roleConfig = roleBuilder.getConfiguration();
- log.debug("users in role: " + r + " ; " + storedUsers);
- List userList = StringUtil.splitStringList(storedUsers, ",");
- assertTrue(userList.contains(user));
+ for (String r : roles) {
+ String storedUsers = (String) roleConfig.getProperty(r);
+
+ log.debug("users in role: " + r + " ; " + storedUsers);
+ List userList = StringUtil.splitStringList(storedUsers, ",");
+ assertTrue(userList.contains(user));
+ }
}
}
private String getStoredPassword(String user, File userFile) throws Exception {
- Configurations configs = new Configurations();
- FileBasedConfigurationBuilder userBuilder = configs.propertiesBuilder(userFile);
- PropertiesConfiguration userConfig = userBuilder.getConfiguration();
- return (String) userConfig.getProperty(user);
+ return getStoredPassword(user, userFile, false);
+ }
+
+ private String getStoredPassword(String user, File userFile, boolean basicSecurityManager) throws Exception {
+ String result;
+ if (basicSecurityManager) {
+ result = server.getStorageManager().getPersistedUsers().get(user).getPassword();
+ } else {
+ Configurations configs = new Configurations();
+ FileBasedConfigurationBuilder userBuilder = configs.propertiesBuilder(userFile);
+ PropertiesConfiguration userConfig = userBuilder.getConfiguration();
+ result = (String) userConfig.getProperty(user);
+ }
+ return result;
}
private boolean checkPassword(String user, String password, File userFile) throws Exception {
- String storedPassword = getStoredPassword(user, userFile);
+ return checkPassword(user, password, userFile,false);
+ }
+
+ private boolean checkPassword(String user, String password, File userFile, boolean basicSecurityManager) throws Exception {
+ String storedPassword = getStoredPassword(user, userFile, basicSecurityManager);
HashProcessor processor = PasswordMaskingUtil.getHashProcessor(storedPassword);
return processor.compare(password.toCharArray(), storedPassword);
}
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
index e9df0b263e..4c64a450f5 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
@@ -35,6 +35,18 @@ public final class PasswordMaskingUtil {
}
+ /**
+ * This method deals with password masking and returns the password in its plain text form.
+ * @param password : the original value of password string; interpreted as masked if wrapped in ENC()
+ * or as plain text otherwise.
+ * @param codecClass : the codec used to decode the password. Only when the password is interpreted
+ * as masked will this codec be used. Ignored otherwise.
+ * @return
+ */
+ public static String resolveMask(String password, String codecClass) throws Exception {
+ return resolveMask(null, password, codecClass);
+ }
+
/**
* This method deals with password masking and returns the password in its plain text form.
* @param maskPassword : explicit mask flag. If it's true, the password is interpreted as
@@ -109,7 +121,7 @@ public final class PasswordMaskingUtil {
}
//stored password takes 2 forms, ENC() or plain text
- public static HashProcessor getHashProcessor(String storedPassword) throws Exception {
+ public static HashProcessor getHashProcessor(String storedPassword) {
if (!isEncoded(storedPassword)) {
return LazyPlainTextProcessorHolder.INSTANCE;
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
index ae8e2668cc..5a22730989 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
@@ -1830,45 +1830,45 @@ public interface ActiveMQServerControl {
* @param roles
* @throws Exception
*/
- @Operation(desc = "add a user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION)
+ @Operation(desc = "add a user (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager)", impact = MBeanOperationInfo.ACTION)
void addUser(@Parameter(name = "username", desc = "Name of the user") String username,
@Parameter(name = "password", desc = "User's password") String password,
@Parameter(name = "roles", desc = "User's role (comma separated)") String roles,
@Parameter(name = "plaintext", desc = "whether or not to store the password in plaintext or hash it") boolean plaintext) throws Exception;
/**
- * List the information about a user or all users if no username is supplied (only applicable when using the JAAS PropertiesLoginModule).
+ * List the information about a user or all users if no username is supplied (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager).
*
* @param username
* @return JSON array of user and role information
* @throws Exception
*/
- @Operation(desc = "list info about a user or all users if no username is supplied (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION)
+ @Operation(desc = "list info about a user or all users if no username is supplied (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager)", impact = MBeanOperationInfo.ACTION)
String listUser(@Parameter(name = "username", desc = "Name of the user; leave null to list all known users") String username) throws Exception;
/**
- * Remove a user (only applicable when using the JAAS PropertiesLoginModule).
+ * Remove a user (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager).
*
* @param username
* @throws Exception
*/
- @Operation(desc = "remove a user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION)
+ @Operation(desc = "remove a user (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager)", impact = MBeanOperationInfo.ACTION)
void removeUser(@Parameter(name = "username", desc = "Name of the user") String username) throws Exception;
/**
- * Set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule).
+ * Set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager).
*
* @param username
* @param password
* @param roles
* @throws Exception
*/
- @Operation(desc = "set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION)
+ @Operation(desc = "set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager)", impact = MBeanOperationInfo.ACTION)
void resetUser(@Parameter(name = "username", desc = "Name of the user") String username,
@Parameter(name = "password", desc = "User's password") String password,
@Parameter(name = "roles", desc = "User's role (comma separated)") String roles) throws Exception;
/**
- * Set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule).
+ * Set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager).
*
* @param username
* @param password
@@ -1877,7 +1877,7 @@ public interface ActiveMQServerControl {
* @throws Exception
*/
- @Operation(desc = "set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION)
+ @Operation(desc = "set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule or the ActiveMQBasicSecurityManager)", impact = MBeanOperationInfo.ACTION)
void resetUser(@Parameter(name = "username", desc = "Name of the user") String username,
@Parameter(name = "password", desc = "User's password") String password,
@Parameter(name = "roles", desc = "User's role (comma separated)") String roles,
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java
index 965de9392d..48055510a9 100644
--- a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JMXConnectorDTO.java
@@ -123,6 +123,6 @@ public class JMXConnectorDTO {
}
private String getPassword(String password) throws Exception {
- return PasswordMaskingUtil.resolveMask(null, password, this.passwordCodec);
+ return PasswordMaskingUtil.resolveMask(password, this.passwordCodec);
}
}
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java
index 4cc627a151..749208545f 100644
--- a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java
@@ -83,7 +83,7 @@ public class WebServerDTO extends ComponentDTO {
}
private String getPassword(String password) throws Exception {
- return PasswordMaskingUtil.resolveMask(null, password, this.passwordCodec);
+ return PasswordMaskingUtil.resolveMask(password, this.passwordCodec);
}
public void setKeyStorePassword(String keyStorePassword) {
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
index 92608cc8e1..758a8b78af 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
@@ -83,7 +83,7 @@ import org.apache.activemq.artemis.core.messagecounter.impl.MessageCounterManage
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting;
import org.apache.activemq.artemis.core.persistence.config.PersistedDivertConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.Bindings;
import org.apache.activemq.artemis.core.postoffice.DuplicateIDCache;
@@ -96,9 +96,9 @@ import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType;
import org.apache.activemq.artemis.core.server.ConnectorServiceFactory;
import org.apache.activemq.artemis.core.server.Consumer;
-import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType;
import org.apache.activemq.artemis.core.server.Divert;
import org.apache.activemq.artemis.core.server.JournalType;
import org.apache.activemq.artemis.core.server.Queue;
@@ -127,6 +127,7 @@ import org.apache.activemq.artemis.core.transaction.impl.CoreTransactionDetail;
import org.apache.activemq.artemis.core.transaction.impl.XidImpl;
import org.apache.activemq.artemis.logs.AuditLogger;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
import org.apache.activemq.artemis.utils.JsonLoader;
import org.apache.activemq.artemis.utils.ListUtil;
@@ -2784,9 +2785,9 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
server.getSecurityRepository().addMatch(addressMatch, roles);
- PersistedRoles persistedRoles = new PersistedRoles(addressMatch, sendRoles, consumeRoles, createDurableQueueRoles, deleteDurableQueueRoles, createNonDurableQueueRoles, deleteNonDurableQueueRoles, manageRoles, browseRoles, createAddressRoles, deleteAddressRoles);
+ PersistedSecuritySetting persistedRoles = new PersistedSecuritySetting(addressMatch, sendRoles, consumeRoles, createDurableQueueRoles, deleteDurableQueueRoles, createNonDurableQueueRoles, deleteNonDurableQueueRoles, manageRoles, browseRoles, createAddressRoles, deleteAddressRoles);
- storageManager.storeSecurityRoles(persistedRoles);
+ storageManager.storeSecuritySetting(persistedRoles);
} finally {
blockOnIO();
}
@@ -2802,7 +2803,7 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
clearIO();
try {
server.getSecurityRepository().removeMatch(addressMatch);
- storageManager.deleteSecurityRoles(new SimpleString(addressMatch));
+ storageManager.deleteSecuritySetting(new SimpleString(addressMatch));
} finally {
blockOnIO();
}
@@ -4189,15 +4190,22 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
@Override
public void addUser(String username, String password, String roles, boolean plaintext) throws Exception {
- synchronized (userLock) {
- if (AuditLogger.isEnabled()) {
- AuditLogger.addUser(this.server, username, "****", roles, plaintext);
+ if (AuditLogger.isEnabled()) {
+ AuditLogger.addUser(this.server, username, "****", roles, plaintext);
+ }
+
+ String passwordToUse = plaintext ? password : PasswordMaskingUtil.getHashProcessor().hash(password);
+
+ if (server.getSecurityManager() instanceof ActiveMQBasicSecurityManager) {
+ ((ActiveMQBasicSecurityManager) server.getSecurityManager()).addNewUser(username, passwordToUse, roles.split(","));
+ } else {
+ synchronized (userLock) {
+ tcclInvoke(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
+ PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator();
+ config.addNewUser(username, passwordToUse, roles.split(","));
+ config.save();
+ });
}
- tcclInvoke(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
- PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator();
- config.addNewUser(username, plaintext ? password : PasswordMaskingUtil.getHashProcessor().hash(password), roles.split(","));
- config.save();
- });
}
}
@@ -4207,58 +4215,72 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
@Override
public String listUser(String username) throws Exception {
- synchronized (userLock) {
- if (AuditLogger.isEnabled()) {
- AuditLogger.listUser(this.server, username);
- }
-
- return (String) tcclCall(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
- PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator();
- Map> info = config.listUser(username);
- JsonArrayBuilder users = JsonLoader.createArrayBuilder();
- for (Entry> entry : info.entrySet()) {
- JsonObjectBuilder user = JsonLoader.createObjectBuilder();
- user.add("username", entry.getKey());
- JsonArrayBuilder roles = JsonLoader.createArrayBuilder();
- for (String role : entry.getValue()) {
- roles.add(role);
- }
- user.add("roles", roles);
- users.add(user);
- }
- return users.build().toString();
- });
+ if (AuditLogger.isEnabled()) {
+ AuditLogger.listUser(this.server, username);
}
+ if (server.getSecurityManager() instanceof ActiveMQBasicSecurityManager) {
+ return buildJsonUserList(((ActiveMQBasicSecurityManager) server.getSecurityManager()).listUser(username));
+ } else {
+ synchronized (userLock) {
+ return (String) tcclCall(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
+ return buildJsonUserList(getPropertiesLoginModuleConfigurator().listUser(username));
+ });
+ }
+ }
+ }
+
+ private String buildJsonUserList(Map> info) {
+ JsonArrayBuilder users = JsonLoader.createArrayBuilder();
+ for (Entry> entry : info.entrySet()) {
+ JsonObjectBuilder user = JsonLoader.createObjectBuilder();
+ user.add("username", entry.getKey());
+ JsonArrayBuilder roles = JsonLoader.createArrayBuilder();
+ for (String role : entry.getValue()) {
+ roles.add(role);
+ }
+ user.add("roles", roles);
+ users.add(user);
+ }
+ return users.build().toString();
}
@Override
public void removeUser(String username) throws Exception {
- synchronized (userLock) {
- if (AuditLogger.isEnabled()) {
- AuditLogger.removeUser(this.server, username);
+ if (AuditLogger.isEnabled()) {
+ AuditLogger.removeUser(this.server, username);
+ }
+ if (server.getSecurityManager() instanceof ActiveMQBasicSecurityManager) {
+ ((ActiveMQBasicSecurityManager) server.getSecurityManager()).removeUser(username);
+ } else {
+ synchronized (userLock) {
+ tcclInvoke(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
+ PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator();
+ config.removeUser(username);
+ config.save();
+ });
}
- tcclInvoke(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
- PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator();
- config.removeUser(username);
- config.save();
- });
}
}
@Override
public void resetUser(String username, String password, String roles, boolean plaintext) throws Exception {
- synchronized (userLock) {
- if (AuditLogger.isEnabled()) {
- AuditLogger.resetUser(this.server, username, "****", roles, plaintext);
+ if (AuditLogger.isEnabled()) {
+ AuditLogger.resetUser(this.server, username, "****", roles, plaintext);
+ }
+
+ String passwordToUse = password == null ? password : plaintext ? password : PasswordMaskingUtil.getHashProcessor().hash(password);
+
+ if (server.getSecurityManager() instanceof ActiveMQBasicSecurityManager) {
+ ((ActiveMQBasicSecurityManager) server.getSecurityManager()).updateUser(username, passwordToUse, roles == null ? null : roles.split(","));
+ } else {
+ synchronized (userLock) {
+ tcclInvoke(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
+ PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator();
+ // don't hash a null password even if plaintext = false
+ config.updateUser(username, passwordToUse, roles == null ? null : roles.split(","));
+ config.save();
+ });
}
- tcclInvoke(ActiveMQServerControlImpl.class.getClassLoader(), () -> {
- PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator();
- // don't hash a null password even if plaintext = false
- config.updateUser(username, password == null ? password : plaintext ? password : PasswordMaskingUtil
- .getHashProcessor()
- .hash(password), roles == null ? null : roles.split(","));
- config.save();
- });
}
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java
index 5ee4478d05..a35d2a934e 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/StorageManager.java
@@ -41,7 +41,9 @@ import org.apache.activemq.artemis.core.paging.PagingStore;
import org.apache.activemq.artemis.core.paging.cursor.PagePosition;
import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting;
import org.apache.activemq.artemis.core.persistence.config.PersistedDivertConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedRole;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
+import org.apache.activemq.artemis.core.persistence.config.PersistedUser;
import org.apache.activemq.artemis.core.persistence.impl.PageCountPending;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.PostOffice;
@@ -354,17 +356,30 @@ public interface StorageManager extends IDGenerator, ActiveMQComponent {
List recoverAddressSettings() throws Exception;
- void storeSecurityRoles(PersistedRoles persistedRoles) throws Exception;
+ void storeSecuritySetting(PersistedSecuritySetting persistedRoles) throws Exception;
- void deleteSecurityRoles(SimpleString addressMatch) throws Exception;
+ void deleteSecuritySetting(SimpleString addressMatch) throws Exception;
- List recoverPersistedRoles() throws Exception;
+ List recoverSecuritySettings() throws Exception;
void storeDivertConfiguration(PersistedDivertConfiguration persistedDivertConfiguration) throws Exception;
void deleteDivertConfiguration(String divertName) throws Exception;
List recoverDivertConfigurations();
+
+ void storeUser(PersistedUser persistedUser) throws Exception;
+
+ void deleteUser(String username) throws Exception;
+
+ Map getPersistedUsers();
+
+ void storeRole(PersistedRole persistedRole) throws Exception;
+
+ void deleteRole(String role) throws Exception;
+
+ Map getPersistedRoles();
+
/**
* @return The ID with the stored counter
*/
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedRole.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedRole.java
new file mode 100644
index 0000000000..1af8864731
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedRole.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+package org.apache.activemq.artemis.core.persistence.config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.core.journal.EncodingSupport;
+import org.apache.activemq.artemis.utils.BufferHelper;
+import org.apache.activemq.artemis.utils.DataConstants;
+
+public class PersistedRole implements EncodingSupport {
+
+ private long storeId;
+
+ private String username;
+
+ private List roles;
+
+ public PersistedRole() {
+ }
+
+ public PersistedRole(String username, List roles) {
+ this.username = username;
+ this.roles = roles;
+ }
+
+ public void setStoreId(long id) {
+ this.storeId = id;
+ }
+
+ public long getStoreId() {
+ return storeId;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ @Override
+ public int getEncodeSize() {
+ int size = 0;
+ size += BufferHelper.sizeOfString(username);
+ size += DataConstants.SIZE_INT;
+ for (String role : roles) {
+ size += BufferHelper.sizeOfString(role);
+ }
+ return size;
+ }
+
+ @Override
+ public void encode(ActiveMQBuffer buffer) {
+ buffer.writeString(username);
+ buffer.writeInt(roles.size());
+ for (String user : roles) {
+ buffer.writeString(user);
+ }
+ }
+
+ @Override
+ public void decode(ActiveMQBuffer buffer) {
+ username = buffer.readString();
+ roles = new ArrayList<>();
+ int size = buffer.readInt();
+ for (int i = 0; i < size; i++) {
+ roles.add(buffer.readString());
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("PersistedRole [storeId=").append(storeId);
+ result.append(", username=").append(username);
+ result.append(", roles [");
+ for (int i = 0; i < roles.size(); i++) {
+ result.append(roles.get(i));
+ if (i < roles.size() - 1) {
+ result.append(", ");
+ }
+ }
+ result.append("]]");
+
+ return result.toString();
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedRoles.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedSecuritySetting.java
similarity index 92%
rename from artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedRoles.java
rename to artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedSecuritySetting.java
index 86fdc14d17..f175be4249 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedRoles.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedSecuritySetting.java
@@ -20,7 +20,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
-public class PersistedRoles implements EncodingSupport {
+public class PersistedSecuritySetting implements EncodingSupport {
// Constants -----------------------------------------------------
@@ -54,7 +54,7 @@ public class PersistedRoles implements EncodingSupport {
// Constructors --------------------------------------------------
- public PersistedRoles() {
+ public PersistedSecuritySetting() {
}
/**
@@ -70,17 +70,17 @@ public class PersistedRoles implements EncodingSupport {
* @param createAddressRoles
* @param deleteAddressRoles
*/
- public PersistedRoles(final String addressMatch,
- final String sendRoles,
- final String consumeRoles,
- final String createDurableQueueRoles,
- final String deleteDurableQueueRoles,
- final String createNonDurableQueueRoles,
- final String deleteNonDurableQueueRoles,
- final String manageRoles,
- final String browseRoles,
- final String createAddressRoles,
- final String deleteAddressRoles) {
+ public PersistedSecuritySetting(final String addressMatch,
+ final String sendRoles,
+ final String consumeRoles,
+ final String createDurableQueueRoles,
+ final String deleteDurableQueueRoles,
+ final String createNonDurableQueueRoles,
+ final String deleteNonDurableQueueRoles,
+ final String manageRoles,
+ final String browseRoles,
+ final String createAddressRoles,
+ final String deleteAddressRoles) {
super();
this.addressMatch = SimpleString.toSimpleString(addressMatch);
this.sendRoles = SimpleString.toSimpleString(sendRoles);
@@ -259,7 +259,7 @@ public class PersistedRoles implements EncodingSupport {
return false;
if (getClass() != obj.getClass())
return false;
- PersistedRoles other = (PersistedRoles) obj;
+ PersistedSecuritySetting other = (PersistedSecuritySetting) obj;
if (addressMatch == null) {
if (other.addressMatch != null)
return false;
@@ -325,7 +325,7 @@ public class PersistedRoles implements EncodingSupport {
*/
@Override
public String toString() {
- return "PersistedRoles [storeId=" + storeId +
+ return "PersistedSecuritySetting [storeId=" + storeId +
", addressMatch=" +
addressMatch +
", sendRoles=" +
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedUser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedUser.java
new file mode 100644
index 0000000000..aef5dec3b8
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/config/PersistedUser.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+package org.apache.activemq.artemis.core.persistence.config;
+
+import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
+import org.apache.activemq.artemis.core.journal.EncodingSupport;
+import org.apache.activemq.artemis.utils.BufferHelper;
+
+public class PersistedUser implements EncodingSupport {
+
+ private long storeId;
+
+ private String username;
+
+ private String password;
+
+ public PersistedUser() {
+ }
+
+ public PersistedUser(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ public void setStoreId(long id) {
+ this.storeId = id;
+ }
+
+ public long getStoreId() {
+ return storeId;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public int getEncodeSize() {
+ int size = 0;
+ size += BufferHelper.sizeOfString(username);
+ size += BufferHelper.sizeOfString(password);
+ return size;
+ }
+
+ @Override
+ public void encode(ActiveMQBuffer buffer) {
+ buffer.writeString(username);
+ buffer.writeString(password);
+ }
+
+ @Override
+ public void decode(ActiveMQBuffer buffer) {
+ username = buffer.readString();
+ password = buffer.readString();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "PersistedUser [storeId=" + storeId +
+ ", username=" +
+ username +
+ ", password=****" +
+ "]";
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java
index 1cb39ab790..daa795759c 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/AbstractJournalStorageManager.java
@@ -75,7 +75,9 @@ import org.apache.activemq.artemis.core.persistence.AddressQueueStatus;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting;
import org.apache.activemq.artemis.core.persistence.config.PersistedDivertConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedRole;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
+import org.apache.activemq.artemis.core.persistence.config.PersistedUser;
import org.apache.activemq.artemis.core.persistence.impl.PageCountPending;
import org.apache.activemq.artemis.core.persistence.impl.journal.codec.AddressStatusEncoding;
import org.apache.activemq.artemis.core.persistence.impl.journal.codec.CursorAckRecordEncoding;
@@ -195,12 +197,16 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
protected final Configuration config;
// Persisted core configuration
- protected final Map mapPersistedRoles = new ConcurrentHashMap<>();
+ protected final Map mapPersistedSecuritySettings = new ConcurrentHashMap<>();
protected final Map mapPersistedAddressSettings = new ConcurrentHashMap<>();
protected final Map mapPersistedDivertConfigurations = new ConcurrentHashMap<>();
+ protected final Map mapPersistedUsers = new ConcurrentHashMap<>();
+
+ protected final Map mapPersistedRoles = new ConcurrentHashMap<>();
+
protected final ConcurrentLongHashMap largeMessagesToDelete = new ConcurrentLongHashMap<>();
public AbstractJournalStorageManager(final Configuration config,
@@ -767,20 +773,20 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
}
@Override
- public List recoverPersistedRoles() throws Exception {
- return new ArrayList<>(mapPersistedRoles.values());
+ public List recoverSecuritySettings() throws Exception {
+ return new ArrayList<>(mapPersistedSecuritySettings.values());
}
@Override
- public void storeSecurityRoles(PersistedRoles persistedRoles) throws Exception {
+ public void storeSecuritySetting(PersistedSecuritySetting persistedRoles) throws Exception {
- deleteSecurityRoles(persistedRoles.getAddressMatch());
+ deleteSecuritySetting(persistedRoles.getAddressMatch());
readLock();
try {
final long id = idGenerator.generateID();
persistedRoles.setStoreId(id);
- bindingsJournal.appendAddRecord(id, JournalRecordIds.SECURITY_RECORD, persistedRoles, true);
- mapPersistedRoles.put(persistedRoles.getAddressMatch(), persistedRoles);
+ bindingsJournal.appendAddRecord(id, JournalRecordIds.SECURITY_SETTING_RECORD, persistedRoles, true);
+ mapPersistedSecuritySettings.put(persistedRoles.getAddressMatch(), persistedRoles);
} finally {
readUnLock();
}
@@ -818,6 +824,70 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
return new ArrayList<>(mapPersistedDivertConfigurations.values());
}
+ @Override
+ public void storeUser(PersistedUser persistedUser) throws Exception {
+ deleteUser(persistedUser.getUsername());
+ readLock();
+ try {
+ final long id = idGenerator.generateID();
+ persistedUser.setStoreId(id);
+ bindingsJournal.appendAddRecord(id, JournalRecordIds.USER_RECORD, persistedUser, true);
+ mapPersistedUsers.put(persistedUser.getUsername(), persistedUser);
+ } finally {
+ readUnLock();
+ }
+ }
+
+ @Override
+ public void deleteUser(String username) throws Exception {
+ PersistedUser oldUser = mapPersistedUsers.remove(username);
+ if (oldUser != null) {
+ readLock();
+ try {
+ bindingsJournal.appendDeleteRecord(oldUser.getStoreId(), false);
+ } finally {
+ readUnLock();
+ }
+ }
+ }
+
+ @Override
+ public Map getPersistedUsers() {
+ return new HashMap<>(mapPersistedUsers);
+ }
+
+ @Override
+ public void storeRole(PersistedRole persistedRole) throws Exception {
+ deleteRole(persistedRole.getUsername());
+ readLock();
+ try {
+ final long id = idGenerator.generateID();
+ persistedRole.setStoreId(id);
+ bindingsJournal.appendAddRecord(id, JournalRecordIds.ROLE_RECORD, persistedRole, true);
+ mapPersistedRoles.put(persistedRole.getUsername(), persistedRole);
+ } finally {
+ readUnLock();
+ }
+ }
+
+ @Override
+ public void deleteRole(String username) throws Exception {
+ PersistedRole oldRole = mapPersistedRoles.remove(username);
+ if (oldRole != null) {
+ readLock();
+ try {
+ bindingsJournal.appendDeleteRecord(oldRole.getStoreId(), false);
+ } finally {
+ readUnLock();
+ }
+ }
+ }
+
+ @Override
+ public Map getPersistedRoles() {
+ return new HashMap<>(mapPersistedRoles);
+ }
+
@Override
public void storeID(final long journalID, final long id) throws Exception {
readLock();
@@ -852,8 +922,8 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
}
@Override
- public void deleteSecurityRoles(SimpleString addressMatch) throws Exception {
- PersistedRoles oldRoles = mapPersistedRoles.remove(addressMatch);
+ public void deleteSecuritySetting(SimpleString addressMatch) throws Exception {
+ PersistedSecuritySetting oldRoles = mapPersistedSecuritySettings.remove(addressMatch);
if (oldRoles != null) {
readLock();
try {
@@ -1560,9 +1630,9 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
} else if (rec == JournalRecordIds.ADDRESS_SETTING_RECORD) {
PersistedAddressSetting setting = newAddressEncoding(id, buffer);
mapPersistedAddressSettings.put(setting.getAddressMatch(), setting);
- } else if (rec == JournalRecordIds.SECURITY_RECORD) {
- PersistedRoles roles = newSecurityRecord(id, buffer);
- mapPersistedRoles.put(roles.getAddressMatch(), roles);
+ } else if (rec == JournalRecordIds.SECURITY_SETTING_RECORD) {
+ PersistedSecuritySetting roles = newSecurityRecord(id, buffer);
+ mapPersistedSecuritySettings.put(roles.getAddressMatch(), roles);
} else if (rec == JournalRecordIds.QUEUE_STATUS_RECORD) {
QueueStatusEncoding statusEncoding = newQueueStatusEncoding(id, buffer);
PersistentQueueBindingEncoding queueBindingEncoding = mapBindings.get(statusEncoding.queueID);
@@ -1586,6 +1656,12 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
} else if (rec == JournalRecordIds.DIVERT_RECORD) {
PersistedDivertConfiguration divertConfiguration = newDivertEncoding(id, buffer);
mapPersistedDivertConfigurations.put(divertConfiguration.getName(), divertConfiguration);
+ } else if (rec == JournalRecordIds.USER_RECORD) {
+ PersistedUser user = newUserEncoding(id, buffer);
+ mapPersistedUsers.put(user.getUsername(), user);
+ } else if (rec == JournalRecordIds.ROLE_RECORD) {
+ PersistedRole role = newRoleEncoding(id, buffer);
+ mapPersistedRoles.put(role.getUsername(), role);
} else {
// unlikely to happen
ActiveMQServerLogger.LOGGER.invalidRecordType(rec, new Exception("invalid record type " + rec));
@@ -2042,8 +2118,8 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
* @param buffer
* @return
*/
- protected static PersistedRoles newSecurityRecord(long id, ActiveMQBuffer buffer) {
- PersistedRoles roles = new PersistedRoles();
+ protected static PersistedSecuritySetting newSecurityRecord(long id, ActiveMQBuffer buffer) {
+ PersistedSecuritySetting roles = new PersistedSecuritySetting();
roles.decode(buffer);
roles.setStoreId(id);
return roles;
@@ -2074,6 +2150,20 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
persistedDivertConfiguration.setStoreId(id);
return persistedDivertConfiguration;
}
+
+ static PersistedUser newUserEncoding(long id, ActiveMQBuffer buffer) {
+ PersistedUser persistedUser = new PersistedUser();
+ persistedUser.decode(buffer);
+ persistedUser.setStoreId(id);
+ return persistedUser;
+ }
+
+ static PersistedRole newRoleEncoding(long id, ActiveMQBuffer buffer) {
+ PersistedRole persistedRole = new PersistedRole();
+ persistedRole.decode(buffer);
+ persistedRole.setStoreId(id);
+ return persistedRole;
+ }
/**
* @param id
* @param buffer
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java
index 3ac704e388..d4e3cde9a4 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/DescribeJournal.java
@@ -88,9 +88,11 @@ import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalR
import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.PAGE_TRANSACTION;
import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.QUEUE_BINDING_RECORD;
import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.QUEUE_STATUS_RECORD;
-import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.SECURITY_RECORD;
+import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.ROLE_RECORD;
+import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.SECURITY_SETTING_RECORD;
import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.SET_SCHEDULED_DELIVERY_TIME;
import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.UPDATE_DELIVERY_COUNT;
+import static org.apache.activemq.artemis.core.persistence.impl.journal.JournalRecordIds.USER_RECORD;
/**
* Outputs a String description of the Journals contents.
@@ -685,7 +687,7 @@ public final class DescribeJournal {
case ADDRESS_SETTING_RECORD:
return AbstractJournalStorageManager.newAddressEncoding(id, buffer);
- case SECURITY_RECORD:
+ case SECURITY_SETTING_RECORD:
return AbstractJournalStorageManager.newSecurityRecord(id, buffer);
case ADDRESS_BINDING_RECORD:
@@ -694,6 +696,12 @@ public final class DescribeJournal {
case ADDRESS_STATUS_RECORD:
return AbstractJournalStorageManager.newAddressStatusEncoding(id, buffer);
+ case USER_RECORD:
+ return AbstractJournalStorageManager.newUserEncoding(id, buffer);
+
+ case ROLE_RECORD:
+ return AbstractJournalStorageManager.newRoleEncoding(id, buffer);
+
default:
return null;
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalRecordIds.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalRecordIds.java
index b18d360af3..5644324c92 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalRecordIds.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/journal/JournalRecordIds.java
@@ -45,7 +45,7 @@ public final class JournalRecordIds {
public static final byte ADDRESS_SETTING_RECORD = 25;
- public static final byte SECURITY_RECORD = 26;
+ public static final byte SECURITY_SETTING_RECORD = 26;
public static final byte DIVERT_RECORD = 27;
@@ -92,4 +92,8 @@ public final class JournalRecordIds {
public static final byte ADDRESS_STATUS_RECORD = 46;
+ public static final byte USER_RECORD = 47;
+
+ public static final byte ROLE_RECORD = 48;
+
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java
index 2660b0d50d..d1783b2f22 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/persistence/impl/nullpm/NullStorageManager.java
@@ -49,7 +49,9 @@ import org.apache.activemq.artemis.core.persistence.AddressQueueStatus;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting;
import org.apache.activemq.artemis.core.persistence.config.PersistedDivertConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedRole;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
+import org.apache.activemq.artemis.core.persistence.config.PersistedUser;
import org.apache.activemq.artemis.core.persistence.impl.PageCountPending;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.PostOffice;
@@ -438,7 +440,7 @@ public class NullStorageManager implements StorageManager {
}
@Override
- public List recoverPersistedRoles() throws Exception {
+ public List recoverSecuritySettings() throws Exception {
return Collections.emptyList();
}
@@ -456,7 +458,33 @@ public class NullStorageManager implements StorageManager {
}
@Override
- public void storeSecurityRoles(final PersistedRoles persistedRoles) throws Exception {
+ public void storeUser(PersistedUser persistedUser) throws Exception {
+ }
+
+ @Override
+ public void deleteUser(String username) throws Exception {
+ }
+
+ @Override
+ public Map getPersistedUsers() {
+ return null;
+ }
+
+ @Override
+ public void storeRole(PersistedRole persistedRole) throws Exception {
+ }
+
+ @Override
+ public void deleteRole(String role) throws Exception {
+ }
+
+ @Override
+ public Map getPersistedRoles() {
+ return null;
+ }
+
+ @Override
+ public void storeSecuritySetting(final PersistedSecuritySetting persistedRoles) throws Exception {
}
@Override
@@ -464,7 +492,7 @@ public class NullStorageManager implements StorageManager {
}
@Override
- public void deleteSecurityRoles(final SimpleString addressMatch) throws Exception {
+ public void deleteSecuritySetting(final SimpleString addressMatch) throws Exception {
}
@Override
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/User.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/User.java
index 9e6bacb129..3fa4761086 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/User.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/User.java
@@ -16,11 +16,13 @@
*/
package org.apache.activemq.artemis.core.security;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
+
public class User {
final String user;
- final String password;
+ String password;
public User(final String user, final String password) {
this.user = user;
@@ -54,7 +56,8 @@ public class User {
if (user == null) {
return false;
}
- return this.user.equals(user) && this.password.equals(password);
+
+ return this.user.equals(user) && PasswordMaskingUtil.getHashProcessor(this.password).compare(password != null ? password.toCharArray() : null, this.password);
}
public String getUser() {
@@ -64,4 +67,8 @@ public class User {
public String getPassword() {
return password;
}
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
index b25919444c..3ae3eb9a0e 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java
@@ -495,4 +495,7 @@ public interface ActiveMQMessageBundle {
@Message(id = 229232, value = "Cannot create consumer on {0}. Session is closed.", format = Message.Format.MESSAGE_FORMAT)
ActiveMQIllegalStateException cannotCreateConsumerOnClosedSession(SimpleString queueName);
+
+ @Message(id = 229233, value = "Cannot set ActiveMQSecurityManager during startup or while started")
+ IllegalStateException cannotSetSecurityManager();
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
index 77ff502f55..7b65bf2830 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServer.java
@@ -833,6 +833,10 @@ public interface ActiveMQServer extends ServiceComponent {
void setMBeanServer(MBeanServer mBeanServer);
+ MBeanServer getMBeanServer();
+
+ void setSecurityManager(ActiveMQSecurityManager securityManager);
+
/**
* Adding external components is allowed only if the state
* isn't {@link SERVER_STATE#STOPPED} or {@link SERVER_STATE#STOPPING}.
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
index d95e3a186c..1d6b8dd09d 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
@@ -1717,6 +1717,14 @@ public interface ActiveMQServerLogger extends BasicLogger {
format = Message.Format.MESSAGE_FORMAT)
void unableStartManagementContext(@Cause Exception e);
+ @LogMessage(level = Logger.Level.WARN)
+ @Message(id = 222298, value = "Failed to create bootstrap user \"{0}\". User management may not function.", format = Message.Format.MESSAGE_FORMAT)
+ void failedToCreateBootstrapCredentials(@Cause Exception e, String user);
+
+ @LogMessage(level = Logger.Level.WARN)
+ @Message(id = 222299, value = "No bootstrap credentials found. User management may not function.", format = Message.Format.MESSAGE_FORMAT)
+ void noBootstrapCredentialsFound();
+
@LogMessage(level = Logger.Level.ERROR)
@Message(id = 224000, value = "Failure in initialisation", format = Message.Format.MESSAGE_FORMAT)
void initializationError(@Cause Throwable e);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
index 4591b308ae..c538569021 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
@@ -94,7 +94,7 @@ import org.apache.activemq.artemis.core.persistence.QueueBindingInfo;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting;
import org.apache.activemq.artemis.core.persistence.config.PersistedDivertConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
import org.apache.activemq.artemis.core.persistence.impl.PageCountPending;
import org.apache.activemq.artemis.core.persistence.impl.journal.JDBCJournalStorageManager;
import org.apache.activemq.artemis.core.persistence.impl.journal.JournalStorageManager;
@@ -186,6 +186,7 @@ import org.apache.activemq.artemis.logs.AuditLogger;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.protocol.SessionCallback;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.ActiveMQThreadPoolExecutor;
@@ -236,7 +237,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
private final Version version;
- private final ActiveMQSecurityManager securityManager;
+ private ActiveMQSecurityManager securityManager;
private final Configuration configuration;
@@ -869,6 +870,19 @@ public class ActiveMQServerImpl implements ActiveMQServer {
this.mbeanServer = mbeanServer;
}
+ @Override
+ public MBeanServer getMBeanServer() {
+ return mbeanServer;
+ }
+
+ @Override
+ public void setSecurityManager(ActiveMQSecurityManager securityManager) {
+ if (state == SERVER_STATE.STARTING || state == SERVER_STATE.STARTED) {
+ throw ActiveMQMessageBundle.BUNDLE.cannotSetSecurityManager();
+ }
+ this.securityManager = securityManager;
+ }
+
private void validateAddExternalComponent(ActiveMQComponent externalComponent) {
final SERVER_STATE state = this.state;
if (state == SERVER_STATE.STOPPED || state == SERVER_STATE.STOPPING) {
@@ -3048,6 +3062,10 @@ public class ActiveMQServerImpl implements ActiveMQServer {
removeExtraAddressStores();
+ if (securityManager instanceof ActiveMQBasicSecurityManager) {
+ ((ActiveMQBasicSecurityManager)securityManager).completeInit(storageManager);
+ }
+
final ServerInfo dumper = new ServerInfo(this, pagingManager);
long dumpInfoInterval = configuration.getServerDumpInterval();
@@ -3381,6 +3399,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
}
}
+ // TODO load users/roles
+
journalLoader.cleanUp();
return journalInfo;
@@ -3395,9 +3415,9 @@ public class ActiveMQServerImpl implements ActiveMQServer {
addressSettingsRepository.addMatch(set.getAddressMatch().toString(), set.getSetting());
}
- List roles = storageManager.recoverPersistedRoles();
+ List roles = storageManager.recoverSecuritySettings();
- for (PersistedRoles roleItem : roles) {
+ for (PersistedSecuritySetting roleItem : roles) {
Set setRoles = SecurityFormatter.createSecurity(roleItem.getSendRoles(), roleItem.getConsumeRoles(), roleItem.getCreateDurableQueueRoles(), roleItem.getDeleteDurableQueueRoles(), roleItem.getCreateNonDurableQueueRoles(), roleItem.getDeleteNonDurableQueueRoles(), roleItem.getManageRoles(), roleItem.getBrowseRoles(), roleItem.getCreateAddressRoles(), roleItem.getDeleteAddressRoles());
securityRepository.addMatch(roleItem.getAddressMatch().toString(), setRoles);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/BasicAuthenticator.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/BasicAuthenticator.java
new file mode 100644
index 0000000000..eda88a4645
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/BasicAuthenticator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+package org.apache.activemq.artemis.core.server.management;
+
+import javax.management.remote.JMXAuthenticator;
+import javax.security.auth.Subject;
+
+import org.apache.activemq.artemis.logs.AuditLogger;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
+
+/**
+ * JMXAuthenticator implementation to be used with ActiveMQBasicSecurityManager
+ */
+public class BasicAuthenticator implements JMXAuthenticator {
+
+ ActiveMQBasicSecurityManager securityManager;
+
+ public BasicAuthenticator(ActiveMQBasicSecurityManager securityManager) {
+ this.securityManager = securityManager;
+ }
+
+ @Override
+ public Subject authenticate(final Object credentials) throws SecurityException {
+ Subject result;
+ String[] params = null;
+ if (credentials instanceof String[] && ((String[]) credentials).length == 2) {
+ params = (String[]) credentials;
+ }
+ result = securityManager.authenticate(params[0], params[1], null, null);
+ if (result != null) {
+ if (AuditLogger.isResourceLoggingEnabled()) {
+ AuditLogger.userSuccesfullyLoggedInAudit(result);
+ }
+ return result;
+ } else {
+ if (AuditLogger.isResourceLoggingEnabled()) {
+ AuditLogger.userFailedLoggedInAudit(result, null);
+ }
+ throw new SecurityException("Authentication failed");
+ }
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java
index 36113c684e..a73c3f1876 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementConnector.java
@@ -19,9 +19,12 @@ package org.apache.activemq.artemis.core.server.management;
import org.apache.activemq.artemis.core.config.JMXConnectorConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import javax.management.remote.JMXAuthenticator;
import java.util.HashMap;
import java.util.Map;
@@ -32,9 +35,11 @@ public class ManagementConnector implements ActiveMQComponent {
private ConnectorServerFactory connectorServerFactory;
private RmiRegistryFactory rmiRegistryFactory;
private MBeanServerFactory mbeanServerFactory;
+ private ActiveMQSecurityManager securityManager;
- public ManagementConnector(JMXConnectorConfiguration configuration) {
+ public ManagementConnector(JMXConnectorConfiguration configuration, ActiveMQSecurityManager securityManager) {
this.configuration = configuration;
+ this.securityManager = securityManager;
}
@Override
@@ -54,8 +59,15 @@ public class ManagementConnector implements ActiveMQComponent {
MBeanServer mbeanServer = mbeanServerFactory.getServer();
- JaasAuthenticator jaasAuthenticator = new JaasAuthenticator();
- jaasAuthenticator.setRealm(configuration.getJmxRealm());
+ JMXAuthenticator authenticator;
+
+ if (securityManager != null && securityManager instanceof ActiveMQBasicSecurityManager) {
+ authenticator = new BasicAuthenticator((ActiveMQBasicSecurityManager) securityManager);
+ } else {
+ JaasAuthenticator jaasAuthenticator = new JaasAuthenticator();
+ jaasAuthenticator.setRealm(configuration.getJmxRealm());
+ authenticator = jaasAuthenticator;
+ }
connectorServerFactory = new ConnectorServerFactory();
connectorServerFactory.setServer(mbeanServer);
@@ -63,7 +75,7 @@ public class ManagementConnector implements ActiveMQComponent {
connectorServerFactory.setRmiServerHost(configuration.getConnectorHost());
connectorServerFactory.setObjectName(new ObjectName(configuration.getObjectName()));
Map environment = new HashMap<>();
- environment.put("jmx.remote.authenticator", jaasAuthenticator);
+ environment.put("jmx.remote.authenticator", authenticator);
try {
connectorServerFactory.setEnvironment(environment);
connectorServerFactory.setAuthenticatorType(configuration.getAuthenticatorType());
@@ -108,4 +120,7 @@ public class ManagementConnector implements ActiveMQComponent {
}
}
+ public ConnectorServerFactory getConnectorServerFactory() {
+ return connectorServerFactory;
+ }
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java
index 1476e4ed9b..42fa2fdc9d 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ManagementContext.java
@@ -25,6 +25,7 @@ import org.apache.activemq.artemis.core.server.ServiceComponent;
import org.apache.activemq.artemis.core.server.management.impl.HawtioSecurityControlImpl;
import javax.management.NotCompliantMBeanException;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
public class ManagementContext implements ServiceComponent {
private AtomicBoolean isStarted = new AtomicBoolean(false);
@@ -32,6 +33,7 @@ public class ManagementContext implements ServiceComponent {
private JMXConnectorConfiguration jmxConnectorConfiguration;
private ManagementConnector mBeanServer;
private ArtemisMBeanServerGuard guardHandler;
+ private ActiveMQSecurityManager securityManager;
@Override
public void start() throws Exception {
@@ -44,7 +46,7 @@ public class ManagementContext implements ServiceComponent {
}
if (jmxConnectorConfiguration != null) {
- mBeanServer = new ManagementConnector(jmxConnectorConfiguration);
+ mBeanServer = new ManagementConnector(jmxConnectorConfiguration, securityManager);
mBeanServer.start();
}
isStarted.set(true);
@@ -99,4 +101,16 @@ public class ManagementContext implements ServiceComponent {
public ArtemisMBeanServerGuard getArtemisMBeanServerGuard() {
return guardHandler;
}
+
+ public void setSecurityManager(ActiveMQSecurityManager securityManager) {
+ this.securityManager = securityManager;
+ }
+
+ public ActiveMQSecurityManager getSecurityManager() {
+ return securityManager;
+ }
+
+ public ManagementConnector getManagementConnector() {
+ return mBeanServer;
+ }
}
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
new file mode 100644
index 0000000000..e6558a7dd5
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQBasicSecurityManager.java
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ */
+package org.apache.activemq.artemis.spi.core.security;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.persistence.StorageManager;
+import org.apache.activemq.artemis.core.persistence.config.PersistedRole;
+import org.apache.activemq.artemis.core.persistence.config.PersistedUser;
+import org.apache.activemq.artemis.core.security.CheckType;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.security.User;
+import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.logs.AuditLogger;
+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.SecurityManagerUtil;
+import org.jboss.logging.Logger;
+
+/**
+ * All user and role state (both in memory and on disk) is maintained by the underlying StorageManager
+ */
+public class ActiveMQBasicSecurityManager implements ActiveMQSecurityManager5, UserManagement {
+
+ private static final Logger logger = Logger.getLogger(ActiveMQBasicSecurityManager.class);
+
+ public static final String BOOTSTRAP_USER = "bootstrapUser";
+ public static final String BOOTSTRAP_PASSWORD = "bootstrapPassword";
+ public static final String BOOTSTRAP_ROLE = "bootstrapRole";
+
+ private Map properties;
+ private String rolePrincipalClass = RolePrincipal.class.getName();
+ private StorageManager storageManager;
+
+ @Override
+ public ActiveMQBasicSecurityManager init(Map properties) {
+ if (!properties.containsKey(BOOTSTRAP_USER) || !properties.containsKey(BOOTSTRAP_PASSWORD) || !properties.containsKey(BOOTSTRAP_ROLE)) {
+ ActiveMQServerLogger.LOGGER.noBootstrapCredentialsFound();
+ } else {
+ this.properties = properties;
+ }
+ return this;
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ throw new UnsupportedOperationException("Invoke authenticate(String, String, RemotingConnection, String) instead");
+ }
+
+ @Override
+ public Subject authenticate(final String userToAuthenticate, final String passwordToAuthenticate, RemotingConnection remotingConnection, final String securityDomain) {
+ try {
+ if (storageManager.isStarted() && storageManager.getPersistedUsers() != null) {
+ PersistedUser persistedUser = storageManager.getPersistedUsers().get(userToAuthenticate);
+ if (persistedUser != null) {
+ User user = new User(persistedUser.getUsername(), persistedUser.getPassword());
+ if (user.isValid(userToAuthenticate, passwordToAuthenticate)) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(userToAuthenticate));
+ for (String role : getRole(userToAuthenticate).getRoles()) {
+ subject.getPrincipals().add((Principal) SecurityManagerUtil.createGroupPrincipal(role, rolePrincipalClass));
+ }
+ if (AuditLogger.isAnyLoggingEnabled() && remotingConnection != null) {
+ remotingConnection.setAuditSubject(subject);
+ }
+ if (AuditLogger.isResourceLoggingEnabled()) {
+ AuditLogger.userSuccesfullyLoggedInAudit(subject);
+ }
+ return subject;
+ }
+ }
+ }
+ } catch (Exception e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Couldn't validate user", e);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password, Set roles, CheckType checkType) {
+ throw new UnsupportedOperationException("Invoke authorize(Subject, Set, CheckType, String) instead");
+ }
+
+ @Override
+ public boolean authorize(final Subject subject,
+ final Set roles,
+ final CheckType checkType,
+ final String address) {
+ boolean authorized = SecurityManagerUtil.authorize(subject, roles, checkType, rolePrincipalClass);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("user " + (authorized ? " is " : " is NOT ") + "authorized");
+ }
+
+ return authorized;
+ }
+
+ @Override
+ public synchronized void addNewUser(String user, String password, String... roles) throws Exception {
+ if (user == null) {
+ throw ActiveMQMessageBundle.BUNDLE.nullUser();
+ }
+ if (password == null) {
+ throw ActiveMQMessageBundle.BUNDLE.nullPassword();
+ }
+ if (userExists(user)) {
+ throw ActiveMQMessageBundle.BUNDLE.userAlreadyExists(user);
+ }
+
+ storageManager.storeUser(new PersistedUser(user, password));
+ storageManager.storeRole(new PersistedRole(user, Arrays.asList(roles)));
+ }
+
+ @Override
+ public synchronized void removeUser(final String user) throws Exception {
+ if (!userExists(user)) {
+ throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(user);
+ }
+
+ storageManager.deleteUser(user);
+ storageManager.deleteRole(user);
+ }
+
+ @Override
+ public synchronized Map> listUser(String user) {
+ // a null or empty user is actually valid here
+ if (user != null && user.length() != 0 && !userExists(user)) {
+ throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(user);
+ }
+
+ Map> result = new HashMap<>();
+
+ if (user != null && user.length() > 0) {
+ result.put(user, new HashSet<>(getRole(user).getRoles()));
+ } else {
+ for (String thisUser : storageManager.getPersistedUsers().keySet()) {
+ result.put(thisUser, new HashSet<>(getRole(thisUser).getRoles()));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public synchronized void updateUser(String user, String password, String... roles) throws Exception {
+ if (!userExists(user)) {
+ throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(user);
+ }
+
+ // potentially update the user's password
+ if (password != null) {
+ storageManager.deleteUser(user);
+ storageManager.storeUser(new PersistedUser(user, password));
+ }
+
+ // potentially update the user's role(s)
+ if (roles != null && roles.length > 0) {
+ storageManager.deleteRole(user);
+ storageManager.storeRole(new PersistedRole(user, Arrays.asList(roles)));
+ }
+ }
+
+ 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)});
+ }
+ } catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.failedToCreateBootstrapCredentials(e, properties.get(BOOTSTRAP_USER));
+ }
+ }
+ }
+
+ private boolean userExists(String user) {
+ return user != null && storageManager.getPersistedUsers() != null && storageManager.getPersistedUsers().containsKey(user);
+ }
+
+ private PersistedRole getRole(String user) {
+ return storageManager.getPersistedRoles().get(user);
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
index 18fada53a4..d5129a23fa 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
@@ -19,21 +19,16 @@ package org.apache.activemq.artemis.spi.core.security;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.security.Principal;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.Set;
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
-import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.logs.AuditLogger;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
+import org.apache.activemq.artemis.utils.SecurityManagerUtil;
import org.jboss.logging.Logger;
import static org.apache.activemq.artemis.core.remoting.CertificateUtil.getCertsFromConnection;
@@ -48,8 +43,6 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager5 {
private static final Logger logger = Logger.getLogger(ActiveMQJAASSecurityManager.class);
- private static final String WILDCARD = "*";
-
private String configurationName;
private String certificateConfigurationName;
private SecurityConfiguration configuration;
@@ -115,33 +108,10 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager5 {
final Set roles,
final CheckType checkType,
final String address) {
- boolean authorized = false;
+ boolean authorized = SecurityManagerUtil.authorize(subject, roles, checkType, rolePrincipalClass);
- if (subject != null) {
- Set rolesWithPermission = getPrincipalsInRole(checkType, roles);
-
- // Check the caller's roles
- Set rolesForSubject = new HashSet<>();
- try {
- rolesForSubject.addAll(subject.getPrincipals(Class.forName(rolePrincipalClass).asSubclass(Principal.class)));
- } catch (Exception e) {
- ActiveMQServerLogger.LOGGER.failedToFindRolesForTheSubject(e);
- }
- if (rolesForSubject.size() > 0 && rolesWithPermission.size() > 0) {
- Iterator rolesForSubjectIter = rolesForSubject.iterator();
- while (!authorized && rolesForSubjectIter.hasNext()) {
- Iterator rolesWithPermissionIter = rolesWithPermission.iterator();
- Principal subjectRole = rolesForSubjectIter.next();
- while (!authorized && rolesWithPermissionIter.hasNext()) {
- Principal roleWithPermission = rolesWithPermissionIter.next();
- authorized = subjectRole.equals(roleWithPermission);
- }
- }
- }
-
- if (logger.isTraceEnabled()) {
- logger.trace("user " + (authorized ? " is " : " is NOT ") + "authorized");
- }
+ if (logger.isTraceEnabled()) {
+ logger.trace("user " + (authorized ? " is " : " is NOT ") + "authorized");
}
return authorized;
@@ -187,20 +157,6 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager5 {
}
}
- private Set getPrincipalsInRole(final CheckType checkType, final Set roles) {
- Set principals = new HashSet<>();
- for (Role role : roles) {
- if (checkType.hasRole(role)) {
- try {
- principals.add(createGroupPrincipal(role.getName(), rolePrincipalClass));
- } catch (Exception e) {
- ActiveMQServerLogger.LOGGER.failedAddRolePrincipal(e);
- }
- }
- }
- return principals;
- }
-
public void setConfigurationName(final String configurationName) {
this.configurationName = configurationName;
}
@@ -240,60 +196,4 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager5 {
public void setRolePrincipalClass(String rolePrincipalClass) {
this.rolePrincipalClass = rolePrincipalClass;
}
-
- public static Object createGroupPrincipal(String name, String groupClass) throws Exception {
- if (WILDCARD.equals(name)) {
- // simple match all group principal - match any name and class
- return new Principal() {
- @Override
- public String getName() {
- return WILDCARD;
- }
-
- @Override
- public boolean equals(Object other) {
- return true;
- }
-
- @Override
- public int hashCode() {
- return WILDCARD.hashCode();
- }
- };
- }
- Object[] param = new Object[]{name};
-
- Class> cls = Class.forName(groupClass);
-
- Constructor>[] constructors = cls.getConstructors();
- int i;
- Object instance;
- for (i = 0; i < constructors.length; i++) {
- Class>[] paramTypes = constructors[i].getParameterTypes();
- if (paramTypes.length != 0 && paramTypes[0].equals(String.class)) {
- break;
- }
- }
- if (i < constructors.length) {
- instance = constructors[i].newInstance(param);
- } else {
- instance = cls.newInstance();
- Method[] methods = cls.getMethods();
- i = 0;
- for (i = 0; i < methods.length; i++) {
- Class>[] paramTypes = methods[i].getParameterTypes();
- if (paramTypes.length != 0 && methods[i].getName().equals("setName") && paramTypes[0].equals(String.class)) {
- break;
- }
- }
-
- if (i < methods.length) {
- methods[i].invoke(instance, param);
- } else {
- throw new NoSuchMethodException();
- }
- }
-
- return instance;
- }
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/UserManagement.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/UserManagement.java
new file mode 100644
index 0000000000..743f86f545
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/UserManagement.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+package org.apache.activemq.artemis.spi.core.security;
+
+import java.util.Map;
+import java.util.Set;
+
+public interface UserManagement {
+
+ void addNewUser(String user, String password, String... roles) throws Exception;
+
+ void removeUser(String user) throws Exception;
+
+ Map> listUser(String user);
+
+ void updateUser(String username, String password, String... roles) throws Exception;
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
index 392fdc727f..212dbb3870 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
@@ -148,7 +148,7 @@ public class LDAPLoginModule implements AuditLoginModule {
private String getPlainPassword(String password) {
try {
- return PasswordMaskingUtil.resolveMask(null, password, codecClass);
+ return PasswordMaskingUtil.resolveMask(password, codecClass);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to decode password", e);
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java
index 245360c5be..21c238a948 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java
@@ -29,6 +29,7 @@ import java.util.Set;
import org.apache.activemq.artemis.api.core.Pair;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
+import org.apache.activemq.artemis.spi.core.security.UserManagement;
import org.apache.activemq.artemis.utils.StringUtil;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
@@ -37,7 +38,7 @@ import org.apache.commons.configuration2.builder.fluent.Configurations;
import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.ROLE_FILE_PROP_NAME;
import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.USER_FILE_PROP_NAME;
-public class PropertiesLoginModuleConfigurator {
+public class PropertiesLoginModuleConfigurator implements UserManagement {
private static final String LICENSE_HEADER =
"## ---------------------------------------------------------------------------\n" +
@@ -125,7 +126,8 @@ public class PropertiesLoginModuleConfigurator {
}
}
- public void addNewUser(String username, String hash, String... roles) throws Exception {
+ @Override
+ public void addNewUser(String username, String hash, String... roles) {
if (userConfig.getString(username) != null) {
throw ActiveMQMessageBundle.BUNDLE.userAlreadyExists(username);
}
@@ -143,6 +145,7 @@ public class PropertiesLoginModuleConfigurator {
}
}
+ @Override
public void removeUser(String username) {
if (userConfig.getProperty(username) == null) {
throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(username);
@@ -151,6 +154,7 @@ public class PropertiesLoginModuleConfigurator {
removeRoles(username);
}
+ @Override
public Map> listUser(String username) {
Map> result = new HashMap<>();
@@ -166,7 +170,8 @@ public class PropertiesLoginModuleConfigurator {
return result;
}
- public void updateUser(String username, String password, String[] roles) {
+ @Override
+ public void updateUser(String username, String password, String... roles) {
String oldPassword = (String) userConfig.getProperty(username);
if (oldPassword == null) {
throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(username);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/utils/SecurityManagerUtil.java b/artemis-server/src/main/java/org/apache/activemq/artemis/utils/SecurityManagerUtil.java
new file mode 100644
index 0000000000..bb8d90f696
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/utils/SecurityManagerUtil.java
@@ -0,0 +1,138 @@
+/**
+ * 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.
+ */
+
+package org.apache.activemq.artemis.utils;
+
+import javax.security.auth.Subject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.security.CheckType;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
+
+public class SecurityManagerUtil {
+
+ private static final String WILDCARD = "*";
+
+ public static Set getPrincipalsInRole(final CheckType checkType, final Set roles, final String rolePrincipalClass) {
+ Set principals = new HashSet<>();
+ for (Role role : roles) {
+ if (checkType.hasRole(role)) {
+ try {
+ principals.add(SecurityManagerUtil.createGroupPrincipal(role.getName(), rolePrincipalClass));
+ } catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.failedAddRolePrincipal(e);
+ }
+ }
+ }
+ return principals;
+ }
+
+ public static Object createGroupPrincipal(String name, String groupClass) throws Exception {
+ if (WILDCARD.equals(name)) {
+ // simple match all group principal - match any name and class
+ return new Principal() {
+ @Override
+ public String getName() {
+ return WILDCARD;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return WILDCARD.hashCode();
+ }
+ };
+ }
+ Object[] param = new Object[]{name};
+
+ Class> cls = Class.forName(groupClass);
+
+ Constructor>[] constructors = cls.getConstructors();
+ int i;
+ Object instance;
+ for (i = 0; i < constructors.length; i++) {
+ Class>[] paramTypes = constructors[i].getParameterTypes();
+ if (paramTypes.length != 0 && paramTypes[0].equals(String.class)) {
+ break;
+ }
+ }
+ if (i < constructors.length) {
+ instance = constructors[i].newInstance(param);
+ } else {
+ instance = cls.newInstance();
+ Method[] methods = cls.getMethods();
+ i = 0;
+ for (i = 0; i < methods.length; i++) {
+ Class>[] paramTypes = methods[i].getParameterTypes();
+ if (paramTypes.length != 0 && methods[i].getName().equals("setName") && paramTypes[0].equals(String.class)) {
+ break;
+ }
+ }
+
+ if (i < methods.length) {
+ methods[i].invoke(instance, param);
+ } else {
+ throw new NoSuchMethodException();
+ }
+ }
+
+ return instance;
+ }
+
+ /**
+ * This method tries to match the RolePrincipals in the Subject with the provided Set of Roles and CheckType
+ */
+ public static boolean authorize(final Subject subject, final Set roles, final CheckType checkType, final String rolePrincipalClass) {
+ boolean authorized = false;
+
+ if (subject != null) {
+ Set rolesWithPermission = getPrincipalsInRole(checkType, roles, rolePrincipalClass);
+
+ // Check the caller's roles
+ Set rolesForSubject = new HashSet<>();
+ try {
+ rolesForSubject.addAll(subject.getPrincipals(Class.forName(rolePrincipalClass).asSubclass(Principal.class)));
+ } catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.failedToFindRolesForTheSubject(e);
+ }
+ if (rolesForSubject.size() > 0 && rolesWithPermission.size() > 0) {
+ Iterator rolesForSubjectIter = rolesForSubject.iterator();
+ while (!authorized && rolesForSubjectIter.hasNext()) {
+ Iterator rolesWithPermissionIter = rolesWithPermission.iterator();
+ Principal subjectRole = rolesForSubjectIter.next();
+ while (!authorized && rolesWithPermissionIter.hasNext()) {
+ Principal roleWithPermission = rolesWithPermissionIter.next();
+ authorized = subjectRole.equals(roleWithPermission);
+ }
+ }
+ }
+ }
+
+ return authorized;
+ }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java
index db68346687..1ff7892d2c 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/transaction/impl/TransactionImplTest.java
@@ -48,7 +48,9 @@ import org.apache.activemq.artemis.core.persistence.QueueBindingInfo;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting;
import org.apache.activemq.artemis.core.persistence.config.PersistedDivertConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedRole;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
+import org.apache.activemq.artemis.core.persistence.config.PersistedUser;
import org.apache.activemq.artemis.core.persistence.impl.PageCountPending;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.PostOffice;
@@ -597,17 +599,17 @@ public class TransactionImplTest extends ActiveMQTestBase {
}
@Override
- public void storeSecurityRoles(PersistedRoles persistedRoles) throws Exception {
+ public void storeSecuritySetting(PersistedSecuritySetting persistedRoles) throws Exception {
}
@Override
- public void deleteSecurityRoles(SimpleString addressMatch) throws Exception {
+ public void deleteSecuritySetting(SimpleString addressMatch) throws Exception {
}
@Override
- public List recoverPersistedRoles() throws Exception {
+ public List recoverSecuritySettings() throws Exception {
return null;
}
@@ -626,6 +628,36 @@ public class TransactionImplTest extends ActiveMQTestBase {
return null;
}
+ @Override
+ public void storeUser(PersistedUser persistedUser) throws Exception {
+
+ }
+
+ @Override
+ public void deleteUser(String username) throws Exception {
+
+ }
+
+ @Override
+ public Map getPersistedUsers() {
+ return null;
+ }
+
+ @Override
+ public void storeRole(PersistedRole persistedRole) throws Exception {
+
+ }
+
+ @Override
+ public void deleteRole(String role) throws Exception {
+
+ }
+
+ @Override
+ public Map getPersistedRoles() {
+ return null;
+ }
+
@Override
public long storePageCounter(long txID, long queueID, long value, long size) throws Exception {
return 0;
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 43d47a73b6..269ae6f500 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -352,16 +352,23 @@ the Transport](configuring-transports.md).
## User credentials
-Apache ActiveMQ Artemis ships with two security manager implementations:
-
-- The legacy, deprecated `ActiveMQSecurityManager` that reads user credentials,
- i.e. user names, passwords and role information from properties files on the
- classpath called `artemis-users.properties` and `artemis-roles.properties`.
+Apache ActiveMQ Artemis ships with three security manager implementations:
- The flexible, pluggable `ActiveMQJAASSecurityManager` which supports any
standard JAAS login module. Artemis ships with several login modules which
will be discussed further down. This is the default security manager.
+- The `ActiveMQBasicSecurityManager` which doesn't use JAAS and only supports
+ auth via username & password credentials. It also supports adding, removing,
+ and updating users via the management API. All user & role data is stored
+ in the broker's bindings journal which means any changes made to a live
+ broker will be available on its backup.
+
+- The legacy, deprecated `ActiveMQSecurityManagerImpl` that reads user
+ credentials, i.e. user names, passwords and role information from properties
+ files on the classpath called `artemis-users.properties` and
+ `artemis-roles.properties`.
+
### JAAS Security Manager
When using the Java Authentication and Authorization Service (JAAS) much of the
@@ -1099,6 +1106,90 @@ superseded by SASL GSSAPI. However, for clients that don't support SASL (core
client), using TLS can provide Kerberos authentication over an *unsecure*
channel.
+### Basic Security Manager
+
+As the name suggests, the `ActiveMQBasicSecurityManager` is _basic_. It is not
+pluggable like the JAAS security manager and it _only_ supports authentication
+via username and password credentials. Furthermore, the the Hawtio-based web
+console requires JAAS. Therefore you will *still need* to configure a
+`login.config` if you plan on using the web console. However, this security
+manager *may* still may have a couple of advantages depending on your use-case.
+
+All user & role data is stored in the bindings journal (or bindings table if
+using JDBC). The advantage here is that in a live/backup use-case any user
+management performed on the live broker will be reflected on the backup upon
+failover. Typically LDAP would be employed for this kind of use-case, but not
+everyone wants or is able to administer an independent LDAP server.
+
+User management is provided by the broker's management API. This includes the
+ability to add, list, update, and remove users & roles. As with all management
+functions, this is available via JMX, management messages, HTTP (via Jolokia),
+web console, etc. These functions are also available from the ActiveMQ Artemis
+command-line interface. Having the broker store this data directly means that
+it must be running in order to manage users. There is no way to modify the
+bindings data manually.
+
+To be clear, any management access via HTTP (e.g. web console or Jolokia) will
+go through Hawtio JAAS. MBean access via JConsole or other remote JMX tool will
+go through the basic security manager. Management messages will also go through
+the basic security manager.
+
+#### Configuration
+
+The configuration for the `ActiveMQBasicSecurityManager` happens in
+`bootstrap.xml` just like it does for all security manager implementations.
+Here's an example:
+
+```xml
+
+
+
+
+
+
+
+
+ ...
+
+```
+
+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
+
+The value specified in the `bootstrapRole` will need the following permissions
+on the `activemq.management` address:
+
+- `createNonDurableQueue`
+- `createAddress`
+- `consume`
+- `manage`
+- `send`
+
+For example:
+
+```xml
+
+
+
+
+
+
+
+```
+
+> **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.
+
## 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/client/SendAckFailTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/SendAckFailTest.java
index 222a0bb584..786de34427 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/SendAckFailTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/SendAckFailTest.java
@@ -62,7 +62,9 @@ import org.apache.activemq.artemis.core.persistence.AddressQueueStatus;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting;
import org.apache.activemq.artemis.core.persistence.config.PersistedDivertConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedRole;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
+import org.apache.activemq.artemis.core.persistence.config.PersistedUser;
import org.apache.activemq.artemis.core.persistence.impl.PageCountPending;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.PostOffice;
@@ -694,18 +696,18 @@ public class SendAckFailTest extends SpawnedTestBase {
}
@Override
- public void storeSecurityRoles(PersistedRoles persistedRoles) throws Exception {
- manager.storeSecurityRoles(persistedRoles);
+ public void storeSecuritySetting(PersistedSecuritySetting persistedRoles) throws Exception {
+ manager.storeSecuritySetting(persistedRoles);
}
@Override
- public void deleteSecurityRoles(SimpleString addressMatch) throws Exception {
- manager.deleteSecurityRoles(addressMatch);
+ public void deleteSecuritySetting(SimpleString addressMatch) throws Exception {
+ manager.deleteSecuritySetting(addressMatch);
}
@Override
- public List recoverPersistedRoles() throws Exception {
- return manager.recoverPersistedRoles();
+ public List recoverSecuritySettings() throws Exception {
+ return manager.recoverSecuritySettings();
}
@Override
@@ -723,6 +725,36 @@ public class SendAckFailTest extends SpawnedTestBase {
return null;
}
+ @Override
+ public void storeUser(PersistedUser persistedUser) throws Exception {
+ manager.storeUser(persistedUser);
+ }
+
+ @Override
+ public void deleteUser(String username) throws Exception {
+ manager.deleteUser(username);
+ }
+
+ @Override
+ public Map getPersistedUsers() {
+ return manager.getPersistedUsers();
+ }
+
+ @Override
+ public void storeRole(PersistedRole persistedRole) throws Exception {
+ manager.storeRole(persistedRole);
+ }
+
+ @Override
+ public void deleteRole(String role) throws Exception {
+ manager.deleteRole(role);
+ }
+
+ @Override
+ public Map getPersistedRoles() {
+ return manager.getPersistedRoles();
+ }
+
@Override
public long storePageCounter(long txID, long queueID, long value, long size) throws Exception {
return manager.storePageCounter(txID, queueID, value, size);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/persistence/RolesConfigurationStorageTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/persistence/RolesConfigurationStorageTest.java
index b525e9d8e0..66263e2a3f 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/persistence/RolesConfigurationStorageTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/persistence/RolesConfigurationStorageTest.java
@@ -22,13 +22,13 @@ import java.util.Map;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.StoreConfiguration;
-import org.apache.activemq.artemis.core.persistence.config.PersistedRoles;
+import org.apache.activemq.artemis.core.persistence.config.PersistedSecuritySetting;
import org.junit.Before;
import org.junit.Test;
public class RolesConfigurationStorageTest extends StorageManagerTestBase {
- private Map mapExpectedSets;
+ private Map mapExpectedSets;
public RolesConfigurationStorageTest(StoreConfiguration.StoreType storeType) {
super(storeType);
@@ -41,18 +41,18 @@ public class RolesConfigurationStorageTest extends StorageManagerTestBase {
mapExpectedSets = new HashMap<>();
}
- protected void addSetting(PersistedRoles setting) throws Exception {
+ protected void addSetting(PersistedSecuritySetting setting) throws Exception {
mapExpectedSets.put(setting.getAddressMatch(), setting);
- journal.storeSecurityRoles(setting);
+ journal.storeSecuritySetting(setting);
}
@Test
public void testStoreSecuritySettings() throws Exception {
createStorage();
- addSetting(new PersistedRoles("a#", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
+ addSetting(new PersistedSecuritySetting("a#", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
- addSetting(new PersistedRoles("a2", "a1", null, "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
+ addSetting(new PersistedSecuritySetting("a2", "a1", null, "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
journal.stop();
@@ -62,9 +62,9 @@ public class RolesConfigurationStorageTest extends StorageManagerTestBase {
checkSettings();
- addSetting(new PersistedRoles("a2", "a1", null, "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
+ addSetting(new PersistedSecuritySetting("a2", "a1", null, "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
- addSetting(new PersistedRoles("a3", "a1", null, "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
+ addSetting(new PersistedSecuritySetting("a3", "a1", null, "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
checkSettings();
@@ -92,7 +92,7 @@ public class RolesConfigurationStorageTest extends StorageManagerTestBase {
checkSettings();
- addSetting(new PersistedRoles("a#", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
+ addSetting(new PersistedSecuritySetting("a#", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1", "a1"));
journal.stop();
@@ -112,12 +112,12 @@ public class RolesConfigurationStorageTest extends StorageManagerTestBase {
* @throws Exception
*/
private void checkSettings() throws Exception {
- List listSetting = journal.recoverPersistedRoles();
+ List listSetting = journal.recoverSecuritySettings();
assertEquals(mapExpectedSets.size(), listSetting.size());
- for (PersistedRoles el : listSetting) {
- PersistedRoles el2 = mapExpectedSets.get(el.getAddressMatch());
+ for (PersistedSecuritySetting el : listSetting) {
+ PersistedSecuritySetting el2 = mapExpectedSets.get(el.getAddressMatch());
assertEquals(el, el2);
}
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerFailoverTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerFailoverTest.java
new file mode 100644
index 0000000000..6f162d81ed
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerFailoverTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+package org.apache.activemq.artemis.tests.integration.security;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.api.core.client.ClientSession;
+import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
+import org.apache.activemq.artemis.core.config.ha.SharedStoreMasterPolicyConfiguration;
+import org.apache.activemq.artemis.core.config.ha.SharedStoreSlavePolicyConfiguration;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
+import org.apache.activemq.artemis.tests.integration.cluster.failover.FailoverTestBase;
+import org.apache.activemq.artemis.tests.util.ReplicatedBackupUtils;
+import org.apache.activemq.artemis.tests.util.TransportConfigurationUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(value = Parameterized.class)
+public class BasicSecurityManagerFailoverTest extends FailoverTestBase {
+
+ private boolean replicated;
+
+ @Parameterized.Parameters(name = "replicated={0}")
+ public static Collection getParameters() {
+ return Arrays.asList(new Object[][]{{true}, {false}});
+ }
+
+ public BasicSecurityManagerFailoverTest(boolean replicated) {
+ this.replicated = replicated;
+ }
+
+ @Override
+ protected void createConfigs() throws Exception {
+ if (replicated) {
+ createReplicatedConfigs();
+ } else {
+ createSharedStoreConfigs();
+ }
+ }
+
+ protected void createSharedStoreConfigs() throws Exception {
+ nodeManager = createNodeManager();
+ TransportConfiguration liveConnector = getConnectorTransportConfiguration(true);
+ TransportConfiguration backupConnector = getConnectorTransportConfiguration(false);
+
+ backupConfig = super
+ .createDefaultInVMConfig()
+ .setSecurityEnabled(true)
+ .clearAcceptorConfigurations()
+ .addAcceptorConfiguration(getAcceptorTransportConfiguration(false))
+ .setHAPolicyConfiguration(new SharedStoreSlavePolicyConfiguration())
+ .addConnectorConfiguration(liveConnector.getName(), liveConnector)
+ .addConnectorConfiguration(backupConnector.getName(), backupConnector)
+ .addClusterConfiguration(createBasicClusterConfig(backupConnector.getName(), liveConnector.getName()));
+
+ backupServer = createTestableServer(backupConfig);
+
+ backupServer.getServer().setSecurityManager(new ActiveMQBasicSecurityManager());
+
+ liveConfig = super
+ .createDefaultInVMConfig()
+ .setSecurityEnabled(true)
+ .clearAcceptorConfigurations()
+ .addAcceptorConfiguration(getAcceptorTransportConfiguration(true))
+ .setHAPolicyConfiguration(new SharedStoreMasterPolicyConfiguration())
+ .addClusterConfiguration(createBasicClusterConfig(liveConnector.getName()))
+ .addConnectorConfiguration(liveConnector.getName(), liveConnector);
+
+ liveServer = createTestableServer(liveConfig);
+
+ liveServer.getServer().setSecurityManager(new ActiveMQBasicSecurityManager());
+ }
+
+ @Override
+ protected void createReplicatedConfigs() throws Exception {
+ final TransportConfiguration liveConnector = getConnectorTransportConfiguration(true);
+ final TransportConfiguration backupConnector = getConnectorTransportConfiguration(false);
+ final TransportConfiguration backupAcceptor = getAcceptorTransportConfiguration(false);
+
+ backupConfig = createDefaultInVMConfig();
+ liveConfig = createDefaultInVMConfig();
+
+ ReplicatedBackupUtils.configureReplicationPair(backupConfig, backupConnector, backupAcceptor, liveConfig, liveConnector, null);
+
+ backupConfig
+ .setSecurityEnabled(true)
+ .setBindingsDirectory(getBindingsDir(0, true))
+ .setJournalDirectory(getJournalDir(0, true))
+ .setPagingDirectory(getPageDir(0, true))
+ .setLargeMessagesDirectory(getLargeMessagesDir(0, true));
+
+ setupHAPolicyConfiguration();
+ nodeManager = createReplicatedBackupNodeManager(backupConfig);
+
+ backupServer = createTestableServer(backupConfig);
+
+ backupServer.getServer().setSecurityManager(new ActiveMQBasicSecurityManager());
+
+ liveConfig
+ .setSecurityEnabled(true)
+ .clearAcceptorConfigurations()
+ .addAcceptorConfiguration(getAcceptorTransportConfiguration(true));
+
+ liveServer = createTestableServer(liveConfig);
+
+ liveServer.getServer().setSecurityManager(new ActiveMQBasicSecurityManager());
+ }
+
+ @Override
+ protected TransportConfiguration getAcceptorTransportConfiguration(final boolean live) {
+ return TransportConfigurationUtils.getInVMAcceptor(live);
+ }
+
+ @Override
+ protected TransportConfiguration getConnectorTransportConfiguration(final boolean live) {
+ return TransportConfigurationUtils.getInVMConnector(live);
+ }
+
+ @Test
+ public void testFailover() throws Exception {
+
+ liveServer.getServer().getActiveMQServerControl().addUser("foo", "bar", "baz", false);
+
+ ClientSessionFactory cf = createSessionFactory(getServerLocator());
+ ClientSession session = null;
+
+ try {
+ session = cf.createSession("foo", "bar", false, true, true, false, 0);
+ } catch (ActiveMQException e) {
+ e.printStackTrace();
+ Assert.fail("should not throw exception");
+ }
+
+ crash(session);
+ waitForServerToStart(backupServer.getServer());
+
+ try {
+ cf = createSessionFactory(getServerLocator());
+ session = cf.createSession("foo", "bar", false, true, true, false, 0);
+ } catch (ActiveMQException e) {
+ e.printStackTrace();
+ Assert.fail("should not throw exception");
+ }
+ }
+}
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
new file mode 100644
index 0000000000..79855a0535
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/BasicSecurityManagerTest.java
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+package org.apache.activemq.artemis.tests.integration.security;
+
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.QueueConfiguration;
+import org.apache.activemq.artemis.api.core.RoutingType;
+import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.api.core.client.ClientConsumer;
+import org.apache.activemq.artemis.api.core.client.ClientMessage;
+import org.apache.activemq.artemis.api.core.client.ClientProducer;
+import org.apache.activemq.artemis.api.core.client.ClientSession;
+import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
+import org.apache.activemq.artemis.api.core.client.ServerLocator;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.ActiveMQServers;
+import org.apache.activemq.artemis.core.server.impl.AddressInfo;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BasicSecurityManagerTest extends ActiveMQTestBase {
+
+ private ServerLocator locator;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ locator = createInVMNonHALocator();
+ }
+
+ public ActiveMQServer initializeServer() throws Exception {
+ Map initProperties = new HashMap<>();
+ 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;
+ }
+
+ @Test
+ public void testAuthenticationForBootstrapUser() throws Exception {
+ ActiveMQServer server = initializeServer();
+ server.start();
+ ClientSessionFactory cf = createSessionFactory(locator);
+
+ try {
+ ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
+ session.close();
+ } catch (ActiveMQException e) {
+ e.printStackTrace();
+ Assert.fail("should not throw exception");
+ }
+ }
+
+ @Test
+ public void testAuthenticationForAddedUserHashed() throws Exception {
+ internalTestAuthenticationForAddedUser(false);
+ }
+
+ @Test
+ public void testAuthenticationForAddedUserPlainText() throws Exception {
+ internalTestAuthenticationForAddedUser(true);
+ }
+
+ private void internalTestAuthenticationForAddedUser(boolean plaintext) throws Exception {
+ ActiveMQServer server = initializeServer();
+ server.start();
+ ClientSessionFactory cf = createSessionFactory(locator);
+
+ server.getActiveMQServerControl().addUser("foo", "bar", "baz", plaintext);
+
+ try {
+ ClientSession session = cf.createSession("foo", "bar", false, true, true, false, 0);
+ session.close();
+ } catch (ActiveMQException e) {
+ e.printStackTrace();
+ Assert.fail("should not throw exception");
+ }
+ }
+
+ @Test
+ public void testWithValidatedUser() throws Exception {
+ ActiveMQServer server = initializeServer();
+ server.getConfiguration().setPopulateValidatedUser(true);
+ server.start();
+ Role role = new Role("programmers", true, true, true, true, true, true, true, true, true, true);
+ Set roles = new HashSet<>();
+ roles.add(role);
+ server.getSecurityRepository().addMatch("#", roles);
+ ClientSessionFactory cf = createSessionFactory(locator);
+
+ try {
+ ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
+ server.createQueue(new QueueConfiguration("queue").setAddress("address").setRoutingType(RoutingType.ANYCAST));
+ ClientProducer producer = session.createProducer("address");
+ producer.send(session.createMessage(true));
+ session.commit();
+ producer.close();
+ ClientConsumer consumer = session.createConsumer("queue");
+ session.start();
+ ClientMessage message = consumer.receive(1000);
+ assertNotNull(message);
+ assertEquals("first", message.getValidatedUserID());
+ session.close();
+ } catch (ActiveMQException e) {
+ e.printStackTrace();
+ Assert.fail("should not throw exception");
+ }
+ }
+
+ @Test
+ public void testAuthenticationBadPassword() throws Exception {
+ ActiveMQServer server = initializeServer();
+ server.start();
+ ClientSessionFactory cf = createSessionFactory(locator);
+
+ try {
+ cf.createSession("first", "badpassword", false, true, true, false, 0);
+ Assert.fail("should throw exception here");
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ @Test
+ public void testAuthorizationNegative() throws Exception {
+ final SimpleString ADDRESS = new SimpleString("address");
+ final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
+ final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
+
+ ActiveMQServer server = initializeServer();
+ Set roles = new HashSet<>();
+ roles.add(new Role("programmers", false, false, false, false, false, false, false, false, false, false));
+ server.getConfiguration().putSecurityRoles("#", roles);
+ server.start();
+ server.addAddressInfo(new AddressInfo(ADDRESS, RoutingType.ANYCAST));
+ server.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS).setRoutingType(RoutingType.ANYCAST));
+ server.createQueue(new QueueConfiguration(NON_DURABLE_QUEUE).setAddress(ADDRESS).setRoutingType(RoutingType.ANYCAST).setDurable(false));
+
+ ClientSessionFactory cf = createSessionFactory(locator);
+ ClientSession session = addClientSession(cf.createSession("first", "secret", false, true, true, false, 0));
+
+ // CREATE_DURABLE_QUEUE
+ try {
+ session.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS));
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='CREATE_DURABLE_QUEUE' for queue durableQueue on address address"));
+ }
+
+ // DELETE_DURABLE_QUEUE
+ try {
+ session.deleteQueue(DURABLE_QUEUE);
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='DELETE_DURABLE_QUEUE' for queue durableQueue on address address"));
+ }
+
+ // CREATE_NON_DURABLE_QUEUE
+ try {
+ session.createQueue(new QueueConfiguration(NON_DURABLE_QUEUE).setAddress(ADDRESS).setDurable(false));
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='CREATE_NON_DURABLE_QUEUE' for queue nonDurableQueue on address address"));
+ }
+
+ // DELETE_NON_DURABLE_QUEUE
+ try {
+ session.deleteQueue(NON_DURABLE_QUEUE);
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='DELETE_NON_DURABLE_QUEUE' for queue nonDurableQueue on address address"));
+ }
+
+ // PRODUCE
+ try {
+ ClientProducer producer = session.createProducer(ADDRESS);
+ producer.send(session.createMessage(true));
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='SEND' on address address"));
+ }
+
+ // CONSUME
+ try {
+ ClientConsumer consumer = session.createConsumer(DURABLE_QUEUE);
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='CONSUME' for queue durableQueue on address address"));
+ }
+
+ // MANAGE
+ try {
+ ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
+ producer.send(session.createMessage(true));
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='MANAGE' on address activemq.management"));
+ }
+
+ // BROWSE
+ try {
+ ClientConsumer browser = session.createConsumer(DURABLE_QUEUE, true);
+ Assert.fail("should throw exception here");
+ } catch (ActiveMQException e) {
+ assertTrue(e.getMessage().contains("User: first does not have permission='BROWSE' for queue durableQueue on address address"));
+ }
+ }
+
+ @Test
+ public void testAuthorizationPositive() throws Exception {
+ final SimpleString ADDRESS = new SimpleString("address");
+ final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
+ final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
+
+ ActiveMQServer server = initializeServer();
+ Set roles = new HashSet<>();
+ roles.add(new Role("programmers", true, true, true, true, true, true, true, true, true, true));
+ server.getConfiguration().putSecurityRoles("#", roles);
+ server.start();
+
+ ClientSessionFactory cf = createSessionFactory(locator);
+ ClientSession session = addClientSession(cf.createSession("first", "secret", false, true, true, false, 0));
+
+ // CREATE_DURABLE_QUEUE
+ try {
+ session.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS));
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+
+ // DELETE_DURABLE_QUEUE
+ try {
+ session.deleteQueue(DURABLE_QUEUE);
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+
+ // CREATE_NON_DURABLE_QUEUE
+ try {
+ session.createQueue(new QueueConfiguration(NON_DURABLE_QUEUE).setAddress(ADDRESS).setDurable(false));
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+
+ // DELETE_NON_DURABLE_QUEUE
+ try {
+ session.deleteQueue(NON_DURABLE_QUEUE);
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+
+ session.createQueue(new QueueConfiguration(DURABLE_QUEUE).setAddress(ADDRESS));
+
+ // PRODUCE
+ try {
+ ClientProducer producer = session.createProducer(ADDRESS);
+ producer.send(session.createMessage(true));
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+
+ // CONSUME
+ try {
+ session.createConsumer(DURABLE_QUEUE);
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+
+ // MANAGE
+ try {
+ ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
+ producer.send(session.createMessage(true));
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+
+ // BROWSE
+ try {
+ session.createConsumer(DURABLE_QUEUE, true);
+ } catch (ActiveMQException e) {
+ Assert.fail("should not throw exception here");
+ }
+ }
+}