ARTEMIS-2243 user/role ops for PropertiesLoginModule via mgmnt

This commit is contained in:
Justin Bertram 2019-01-29 18:35:16 -06:00 committed by Clebert Suconic
parent 3f1a3ceb77
commit 4a1fc61fcc
19 changed files with 600 additions and 105 deletions

View File

@ -118,13 +118,12 @@
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>${commons.config.version}</version>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang.version}</version>
<artifactId>commons-configuration2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.winsw</groupId>

View File

@ -20,6 +20,7 @@ import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.activemq.artemis.cli.commands.ActionContext;
import org.apache.activemq.artemis.cli.commands.util.HashUtil;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
import org.apache.commons.lang3.StringUtils;
/**
@ -53,7 +54,7 @@ public class AddUser extends PasswordAction {
* @throws IllegalArgumentException if user exists
*/
private void add(String hash, String... role) throws Exception {
FileBasedSecStoreConfig config = getConfiguration();
PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc());
config.addNewUser(username, hash, role);
config.save();
context.out.println("User added successfully.");

View File

@ -16,10 +16,12 @@
*/
package org.apache.activemq.artemis.cli.commands.user;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.airlift.airline.Command;
import org.apache.activemq.artemis.cli.commands.ActionContext;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
/**
* list existing users, example:
@ -42,11 +44,24 @@ public class ListUser extends UserAction {
* if username is not specified
*/
private void list() throws Exception {
FileBasedSecStoreConfig config = getConfiguration();
List<String> result = config.listUser(username);
for (String str : result) {
context.out.println(str);
PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc());
Map<String, Set<String>> result = config.listUser(username);
StringBuilder logMessage = new StringBuilder("--- \"user\"(roles) ---\n");
int userCount = 0;
for (Map.Entry<String, Set<String>> entry : result.entrySet()) {
logMessage.append("\"").append(entry.getKey()).append("\"(");
int roleCount = 0;
for (String role : entry.getValue()) {
logMessage.append(role);
if (++roleCount < entry.getValue().size()) {
logMessage.append(",");
}
}
logMessage.append(")\n");
userCount++;
}
logMessage.append("\n Total: ").append(userCount);
context.out.println(logMessage);
}
}

View File

@ -18,6 +18,7 @@ package org.apache.activemq.artemis.cli.commands.user;
import io.airlift.airline.Command;
import org.apache.activemq.artemis.cli.commands.ActionContext;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
/**
* Remove a user, example:
@ -35,7 +36,7 @@ public class RemoveUser extends UserAction {
}
private void remove() throws Exception {
FileBasedSecStoreConfig config = getConfiguration();
PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc());
config.removeUser(username);
config.save();
context.out.println("User removed.");

View File

@ -20,6 +20,7 @@ import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.activemq.artemis.cli.commands.ActionContext;
import org.apache.activemq.artemis.cli.commands.util.HashUtil;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
import org.apache.commons.lang3.StringUtils;
/**
@ -57,7 +58,7 @@ public class ResetUser extends PasswordAction {
context.err.println("Nothing to update.");
return;
}
FileBasedSecStoreConfig config = getConfiguration();
PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc());
config.updateUser(username, password, roles);
config.save();
context.out.println("User updated");

View File

@ -16,16 +16,8 @@
*/
package org.apache.activemq.artemis.cli.commands.user;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import java.io.File;
import io.airlift.airline.Option;
import org.apache.activemq.artemis.cli.commands.InputAbstract;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule;
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 abstract class UserAction extends InputAbstract {
@ -50,30 +42,6 @@ public abstract class UserAction extends InputAbstract {
}
}
FileBasedSecStoreConfig getConfiguration() throws Exception {
Configuration securityConfig = Configuration.getConfiguration();
AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry(entry);
for (AppConfigurationEntry entry : entries) {
if (entry.getLoginModuleName().equals(PropertiesLoginModule.class.getName())) {
String userFileName = (String) entry.getOptions().get(USER_FILE_PROP_NAME);
String roleFileName = (String) entry.getOptions().get(ROLE_FILE_PROP_NAME);
File etcDir = new File(getBrokerEtc());
File userFile = new File(etcDir, userFileName);
File roleFile = new File(etcDir, roleFileName);
if (!userFile.exists() || !roleFile.exists()) {
throw new IllegalArgumentException("Couldn't find user file or role file!");
}
return new FileBasedSecStoreConfig(userFile, roleFile);
}
}
throw new IllegalArgumentException("Failed to load security file");
}
public void setUsername(String username) {
this.username = username;
}

View File

@ -23,6 +23,8 @@ import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@ -34,20 +36,24 @@ import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
import org.apache.activemq.artemis.api.core.JsonUtil;
import org.apache.activemq.artemis.api.core.Pair;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
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.api.core.management.ActiveMQServerControl;
import org.apache.activemq.artemis.cli.Artemis;
import org.apache.activemq.artemis.cli.CLIException;
import org.apache.activemq.artemis.cli.commands.ActionContext;
import org.apache.activemq.artemis.cli.commands.Create;
import org.apache.activemq.artemis.cli.commands.Mask;
import org.apache.activemq.artemis.cli.commands.queue.StatQueue;
import org.apache.activemq.artemis.cli.commands.Run;
import org.apache.activemq.artemis.cli.commands.queue.StatQueue;
import org.apache.activemq.artemis.cli.commands.user.AddUser;
import org.apache.activemq.artemis.cli.commands.user.ListUser;
import org.apache.activemq.artemis.cli.commands.user.RemoveUser;
@ -389,6 +395,173 @@ public class ArtemisTest extends CliTestBase {
assertTrue(result.contains("Total: 0"));
}
@Test
public void testUserCommandViaManagement() 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");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
ActiveMQServer activeMQServer = ((Pair<ManagementContext, ActiveMQServer>)result).getB();
ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl();
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
//default only one user admin with role amq
String jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
checkRole("admin", roleFile, "amq");
//add a simple user
activeMQServerControl.addUser("guest", "guest123", "admin", true, "activemq");
//verify add
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin");
checkRole("guest", roleFile, "admin");
assertTrue(checkPassword("guest", "guest123", userFile));
//add a user with 2 roles
activeMQServerControl.addUser("scott", "tiger", "admin,operator", true, "activemq");
//verify add
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator");
checkRole("scott", roleFile, "admin", "operator");
assertTrue(checkPassword("scott", "tiger", userFile));
try {
activeMQServerControl.addUser("scott", "password", "visitor", true, "activemq");
fail("should throw an exception if adding a existing user");
} catch (IllegalArgumentException expected) {
}
//check existing users are intact
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator");
//check listing with just one user
jsonResult = activeMQServerControl.listUser("admin", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin", false);
contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin", false);
contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator", false);
//check listing with another single user
jsonResult = activeMQServerControl.listUser("guest", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq", false);
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin", false);
contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator", false);
//remove a user
activeMQServerControl.removeUser("guest", "activemq");
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin", false);
contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator");
//remove another
activeMQServerControl.removeUser("scott", "activemq");
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin", false);
contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin", false);
contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator", false);
//remove non-exist
try {
activeMQServerControl.removeUser("alien", "activemq");
fail("should throw exception when removing a non-existing user");
} catch (IllegalArgumentException expected) {
}
//check
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
//now remove last
activeMQServerControl.removeUser("admin", "activemq");
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq", false);
stopServer();
}
@Test
public void testBadSecurityEntryNameViaManagement() 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");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
ActiveMQServer activeMQServer = ((Pair<ManagementContext, ActiveMQServer>)result).getB();
ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl();
try {
activeMQServerControl.listUser("", "activemqx");
fail();
} catch (ActiveMQIllegalStateException expected) {
}
stopServer();
}
@Test
public void testMissingUserFileViaManagement() 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");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
ActiveMQServer activeMQServer = ((Pair<ManagementContext, ActiveMQServer>)result).getB();
ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl();
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
userFile.delete();
// File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
try {
activeMQServerControl.listUser("", "activemq");
fail();
} catch (ActiveMQIllegalStateException expected) {
}
stopServer();
}
@Test
public void testMissingRoleFileViaManagement() 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");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
ActiveMQServer activeMQServer = ((Pair<ManagementContext, ActiveMQServer>)result).getB();
ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl();
File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
roleFile.delete();
try {
activeMQServerControl.listUser("", "activemq");
fail();
} catch (ActiveMQIllegalStateException expected) {
}
stopServer();
}
@Test
public void testUserCommandReset() throws Exception {
Run.setEmbedded(true);
@ -454,9 +627,9 @@ public class ArtemisTest extends CliTestBase {
assertTrue(result.contains("Total: 4"));
assertTrue(result.contains("\"guest\"(admin)"));
assertTrue(result.contains("\"user1\"(admin,manager)"));
assertTrue(result.contains("\"user2\"(admin,manager,master)"));
assertTrue(result.contains("\"user3\"(master,system)"));
assertTrue(Pattern.compile("\"user1\"\\((admin|manager),(admin|manager)\\)").matcher(result).find());
assertTrue(Pattern.compile("\"user2\"\\((admin|manager|master),(admin|manager|master),(admin|manager|master)\\)").matcher(result).find());
assertTrue(Pattern.compile("\"user3\"\\((master|system),(master|system)\\)").matcher(result).find());
checkRole("user1", roleFile, "admin", "manager");
@ -488,6 +661,72 @@ public class ArtemisTest extends CliTestBase {
assertTrue(checkPassword("user3", "newpassword3", userFile));
}
@Test
public void testUserCommandResetViaManagement() 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");
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
ActiveMQServer activeMQServer = ((Pair<ManagementContext, ActiveMQServer>)result).getB();
ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl();
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
//default only one user admin with role amq
String jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq");
checkRole("admin", roleFile, "amq");
//remove a user
activeMQServerControl.removeUser("admin", "activemq");
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq", false);
//add some users
activeMQServerControl.addUser("guest", "guest123", "admin", true, "activemq");
activeMQServerControl.addUser("user1", "password1", "admin,manager", true, "activemq");
assertTrue(checkPassword("user1", "password1", userFile));
activeMQServerControl.addUser("user2", "password2", "admin,manager,master", true, "activemq");
activeMQServerControl.addUser("user3", "password3", "system,master", true, "activemq");
//verify use list cmd
jsonResult = activeMQServerControl.listUser("", "activemq");
contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "user1", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "user1", "manager");
contains(JsonUtil.readJsonArray(jsonResult), "user2", "admin");
contains(JsonUtil.readJsonArray(jsonResult), "user2", "manager");
contains(JsonUtil.readJsonArray(jsonResult), "user2", "master");
contains(JsonUtil.readJsonArray(jsonResult), "user3", "master");
contains(JsonUtil.readJsonArray(jsonResult), "user3", "system");
checkRole("user1", roleFile, "admin", "manager");
//reset password
activeMQServerControl.resetUser("user1", "newpassword1", null, "activemq");
checkRole("user1", roleFile, "admin", "manager");
assertFalse(checkPassword("user1", "password1", userFile));
assertTrue(checkPassword("user1", "newpassword1", userFile));
//reset role
activeMQServerControl.resetUser("user2", null, "manager,master,operator", "activemq");
checkRole("user2", roleFile, "manager", "master", "operator");
assertTrue(checkPassword("user2", "password2", userFile));
//reset both
activeMQServerControl.resetUser("user3", "newpassword3", "admin,system", "activemq");
checkRole("user3", roleFile, "admin", "system");
assertTrue(checkPassword("user3", "newpassword3", userFile));
stopServer();
}
@Test
public void testMaskCommand() throws Exception {
@ -1074,4 +1313,33 @@ public class ArtemisTest extends CliTestBase {
return processor.compare(password.toCharArray(), storedPassword);
}
private void contains(JsonArray users, String username, String role) {
contains(users, username, role, true);
}
private void contains(JsonArray users, String username, String role, boolean contains) {
boolean userFound = false;
boolean roleFound = false;
for (int i = 0; i < users.size(); i++) {
JsonObject user = users.getJsonObject(i);
if (user.getString("username").equals(username)) {
userFound = true;
JsonArray roles = user.getJsonArray("roles");
for (int j = 0; j < roles.size(); j++) {
if (roles.getString(j).equals(role)) {
roleFound = true;
break;
}
}
}
}
if (contains) {
assertTrue("user " + username + " not found", userFound);
assertTrue("role " + role + " not found", roleFound);
} else {
assertFalse("user " + username + " found", userFound);
assertFalse("role " + role + " found", roleFound);
}
}
}

View File

@ -1341,5 +1341,59 @@ public interface ActiveMQServerControl {
*/
@Operation(desc = "Names of the cluster-connections deployed on this server", impact = MBeanOperationInfo.INFO)
String[] getClusterConnectionNames();
/**
* Add a user (only applicable when using the JAAS PropertiesLoginModule)
*
* @param username
* @param password
* @param roles
* @param entryName
* @throws Exception
*/
@Operation(desc = "add a user (only applicable when using the JAAS PropertiesLoginModule)", 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 (comma separated)", desc = "User's role") String roles,
@Parameter(name = "plaintext", desc = "whether or not to store the password in plaintext or hash it") boolean plaintext,
@Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception;
/**
* List the information about a user or all users if no username is supplied (only applicable when using the JAAS PropertiesLoginModule).
*
* @param username
* @param entryName
* @return JSON array of user & 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)
String listUser(@Parameter(name = "username", desc = "Name of the user; leave null to list all known users") String username,
@Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception;
/**
* Remove a user (only applicable when using the JAAS PropertiesLoginModule).
*
* @param username
* @param entryName
* @throws Exception
*/
@Operation(desc = "remove a user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION)
void removeUser(@Parameter(name = "username", desc = "Name of the user") String username,
@Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception;
/**
* Set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule).
*
* @param username
* @param password
* @param roles
* @param entryName
* @throws Exception
*/
@Operation(desc = "set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule)", 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 (comma separated)", desc = "User's role") String roles,
@Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception;
}

View File

@ -46,7 +46,7 @@ fi
# Set Defaults Properties
JAVA_ARGS="-XX:+UseParallelGC -Xms512M -Xmx1024M"
CLASSPATH="$ARTEMIS_HOME/lib/artemis-boot.jar"
CLASSPATH="$ARTEMIS_HOME/lib/*"
# OS specific support.
cygwin=false;

View File

@ -50,7 +50,7 @@ set JAVA_ARGS=-XX:+UseParallelGC -Xms512M -Xmx1024M
rem "Create full JVM Args"
set JVM_ARGS=%JAVA_ARGS%
if not "%ARTEMIS_CLUSTER_PROPS%"=="" set JVM_ARGS=%JVM_ARGS% %ARTEMIS_CLUSTER_PROPS%
set JVM_ARGS=%JVM_ARGS% -classpath %ARTEMIS_HOME%\lib\artemis-boot.jar
set JVM_ARGS=%JVM_ARGS% -classpath %ARTEMIS_HOME%\lib\*
set JVM_ARGS=%JVM_ARGS% -Dartemis.home=%ARTEMIS_HOME%
if not "%DEBUG_ARGS%"=="" set JVM_ARGS=%JVM_ARGS% %DEBUG_ARGS%

View File

@ -62,6 +62,9 @@
<configfile finalname="etc/artemis.xml">mvn:org.apache.activemq/artemis-features/${pom.version}/xml/artemis</configfile>
<bundle dependency="true">mvn:org.apache.geronimo.specs/geronimo-jms_2.0_spec/${geronimo.jms.2.spec.version}</bundle>
<bundle dependency="true">mvn:org.apache.commons/commons-configuration2/${commons.config.version}</bundle>
<bundle dependency="true">mvn:org.apache.commons/commons-text/1.6</bundle>
<bundle dependency="true">mvn:org.apache.commons/commons-lang3/${commons.lang.version}</bundle>
<bundle>mvn:org.apache.activemq/artemis-native/${pom.version}</bundle>
<bundle>mvn:org.apache.activemq/artemis-server-osgi/${pom.version}</bundle>

View File

@ -127,6 +127,10 @@
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>

View File

@ -30,6 +30,7 @@ import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.transaction.xa.Xid;
import java.net.URL;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
@ -118,8 +119,10 @@ import org.apache.activemq.artemis.core.transaction.TransactionDetailFactory;
import org.apache.activemq.artemis.core.transaction.impl.CoreTransactionDetail;
import org.apache.activemq.artemis.core.transaction.impl.XidImpl;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
import org.apache.activemq.artemis.utils.JsonLoader;
import org.apache.activemq.artemis.utils.ListUtil;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SecurityFormatter;
import org.apache.activemq.artemis.utils.collections.TypedProperties;
import org.jboss.logging.Logger;
@ -2956,5 +2959,52 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
this.broadcaster.sendNotification(new Notification(type.toString(), this, notifSeq.incrementAndGet(), notification.toString()));
}
@Override
public void addUser(String username, String password, String roles, boolean plaintext, String entryName) throws Exception {
PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName);
config.addNewUser(username, plaintext ? password : PasswordMaskingUtil.getHashProcessor().hash(password), roles.split(","));
config.save();
}
@Override
public String listUser(String username, String entryName) throws Exception {
PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName);
Map<String, Set<String>> info = config.listUser(username);
JsonArrayBuilder users = JsonLoader.createArrayBuilder();
for (Entry<String, Set<String>> 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, String entryName) throws Exception {
PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName);
config.removeUser(username);
config.save();
}
@Override
public void resetUser(String username, String password, String roles, String entryName) throws Exception {
PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName);
config.updateUser(username, password, roles == null ? null : roles.split(","));
config.save();
}
private PropertiesLoginModuleConfigurator getPropertiesLoginModuleConfigurator(String entryName) throws Exception {
URL configurationUrl = server.getConfiguration().getConfigurationUrl();
if (configurationUrl == null) {
throw ActiveMQMessageBundle.BUNDLE.failedToLocateConfigURL();
}
String path = configurationUrl.getPath();
return new PropertiesLoginModuleConfigurator(entryName, path.substring(0, path.lastIndexOf("/")));
}
}

View File

@ -449,4 +449,24 @@ public interface ActiveMQMessageBundle {
@Message(id = 119217, value = "Can't write to closed file: {0}", format = Message.Format.MESSAGE_FORMAT)
ActiveMQIOErrorException cannotWriteToClosedFile(SequentialFile file);
@Message(id = 229218, value = "Failed to locate broker configuration URL")
ActiveMQIllegalStateException failedToLocateConfigURL();
@Message(id = 229219, value = "Failed to load security configuration")
ActiveMQIllegalStateException failedToLoadSecurityConfig();
@Message(id = 229220, value = "Failed to load user file: {0}", format = Message.Format.MESSAGE_FORMAT)
ActiveMQIllegalStateException failedToLoadUserFile(String path);
@Message(id = 229221, value = "Failed to load role file: {0}", format = Message.Format.MESSAGE_FORMAT)
ActiveMQIllegalStateException failedToLoadRoleFile(String path);
@Message(id = 229222, value = "Failed to find login module entry {0} from JAAS configuration", format = Message.Format.MESSAGE_FORMAT)
ActiveMQIllegalStateException failedToFindLoginModuleEntry(String entry);
@Message(id = 229223, value = "User {0} already exists", format = Message.Format.MESSAGE_FORMAT)
IllegalArgumentException userAlreadyExists(String user);
@Message(id = 229224, value = "User {0} does not exist", format = Message.Format.MESSAGE_FORMAT)
IllegalArgumentException userDoesNotExist(String user);
}

View File

@ -14,20 +14,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.cli.commands.user;
package org.apache.activemq.artemis.spi.core.security.jaas;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.utils.StringUtil;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Configurations;
class FileBasedSecStoreConfig {
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 {
private static final String LICENSE_HEADER =
"## ---------------------------------------------------------------------------\n" +
@ -51,80 +61,110 @@ class FileBasedSecStoreConfig {
private PropertiesConfiguration userConfig;
private PropertiesConfiguration roleConfig;
FileBasedSecStoreConfig(File userFile, File roleFile) throws Exception {
Configurations configs = new Configurations();
userBuilder = configs.propertiesBuilder(userFile);
roleBuilder = configs.propertiesBuilder(roleFile);
userConfig = userBuilder.getConfiguration();
roleConfig = roleBuilder.getConfiguration();
public PropertiesLoginModuleConfigurator(String entryName, String brokerEtc) throws Exception {
if (entryName == null || entryName.length() == 0) {
entryName = "activemq";
}
String roleHeader = roleConfig.getLayout().getHeaderComment();
String userHeader = userConfig.getLayout().getHeaderComment();
Configuration securityConfig = Configuration.getConfiguration();
AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry(entryName);
if (userHeader == null) {
if (userConfig.isEmpty()) {
//clean and reset header
userConfig.clear();
userConfig.setHeader(LICENSE_HEADER);
if (entries == null || entries.length == 0) {
throw ActiveMQMessageBundle.BUNDLE.failedToLoadSecurityConfig();
}
int entriesInspected = 0;
for (AppConfigurationEntry entry : entries) {
entriesInspected++;
if (entry.getLoginModuleName().equals(PropertiesLoginModule.class.getName())) {
String userFileName = (String) entry.getOptions().get(USER_FILE_PROP_NAME);
String roleFileName = (String) entry.getOptions().get(ROLE_FILE_PROP_NAME);
File etcDir = new File(brokerEtc);
File userFile = new File(etcDir, userFileName);
File roleFile = new File(etcDir, roleFileName);
if (!userFile.exists()) {
throw ActiveMQMessageBundle.BUNDLE.failedToLoadUserFile(brokerEtc + userFileName);
}
if (!roleFile.exists()) {
throw ActiveMQMessageBundle.BUNDLE.failedToLoadRoleFile(brokerEtc + roleFileName);
}
Configurations configs = new Configurations();
userBuilder = configs.propertiesBuilder(userFile);
roleBuilder = configs.propertiesBuilder(roleFile);
userConfig = userBuilder.getConfiguration();
roleConfig = roleBuilder.getConfiguration();
String roleHeader = roleConfig.getLayout().getHeaderComment();
String userHeader = userConfig.getLayout().getHeaderComment();
if (userHeader == null) {
if (userConfig.isEmpty()) {
//clean and reset header
userConfig.clear();
userConfig.setHeader(LICENSE_HEADER);
}
}
if (roleHeader == null) {
if (roleConfig.isEmpty()) {
//clean and reset header
roleConfig.clear();
roleConfig.setHeader(LICENSE_HEADER);
}
}
return;
}
}
if (roleHeader == null) {
if (roleConfig.isEmpty()) {
//clean and reset header
roleConfig.clear();
roleConfig.setHeader(LICENSE_HEADER);
}
if (entriesInspected == entries.length) {
throw ActiveMQMessageBundle.BUNDLE.failedToFindLoginModuleEntry(entryName);
}
}
void addNewUser(String username, String hash, String... roles) throws Exception {
public void addNewUser(String username, String hash, String... roles) throws Exception {
if (userConfig.getString(username) != null) {
throw new IllegalArgumentException("User already exist: " + username);
throw ActiveMQMessageBundle.BUNDLE.userAlreadyExists(username);
}
userConfig.addProperty(username, hash);
addRoles(username, roles);
}
void save() throws Exception {
public void save() throws Exception {
userBuilder.save();
roleBuilder.save();
}
void removeUser(String username) throws Exception {
public void removeUser(String username) {
if (userConfig.getProperty(username) == null) {
throw new IllegalArgumentException("user " + username + " doesn't exist.");
throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(username);
}
userConfig.clearProperty(username);
removeRoles(username);
}
List<String> listUser(String username) {
List<String> result = new ArrayList<>();
result.add("--- \"user\"(roles) ---\n");
public Map<String, Set<String>> listUser(String username) {
Map<String, Set<String>> result = new HashMap<>();
int totalUsers = 0;
if (username != null) {
String roles = findRoles(username);
result.add("\"" + username + "\"(" + roles + ")");
totalUsers++;
if (username != null && username.length() > 0) {
result.put(username, findRoles(username));
} else {
Iterator<String> iter = userConfig.getKeys();
while (iter.hasNext()) {
String keyUser = iter.next();
String roles = findRoles(keyUser);
result.add("\"" + keyUser + "\"(" + roles + ")");
totalUsers++;
result.put(keyUser, findRoles(keyUser));
}
}
result.add("\n Total: " + totalUsers);
return result;
}
void updateUser(String username, String password, String[] roles) {
public void updateUser(String username, String password, String[] roles) {
String oldPassword = (String) userConfig.getProperty(username);
if (oldPassword == null) {
throw new IllegalArgumentException("user " + username + " doesn't exist.");
throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(username);
}
if (password != null) {
@ -138,33 +178,28 @@ class FileBasedSecStoreConfig {
}
}
private String findRoles(String uname) {
private Set<String> findRoles(String username) {
Iterator<String> iter = roleConfig.getKeys();
StringBuilder builder = new StringBuilder();
boolean first = true;
Set<String> roles = new HashSet();
while (iter.hasNext()) {
String role = iter.next();
List<String> names = roleConfig.getList(String.class, role);
for (String value : names) {
//each value may be a comma separated list
String[] items = value.split(",");
for (String roleList : roleConfig.getList(String.class, role)) {
//each roleList may be a comma separated list
String[] items = roleList.split(",");
for (String item : items) {
if (item.equals(uname)) {
if (!first) {
builder.append(",");
}
builder.append(role);
first = false;
if (item.equals(username)) {
roles.add(role);
}
}
}
}
return builder.toString();
return roles;
}
private void addRoles(String username, String[] roles) {
for (String role : roles) {
role = role.trim();
List<String> users = roleConfig.getList(String.class, role);
if (users == null) {
users = new ArrayList<>();

View File

@ -546,6 +546,17 @@ users=system,user
guests=guest
```
As mentioned above, the Artemis command-line interface supports a command to
`add` a user. Commands to `list` (one or all) users, `remove` a user, and `reset`
a user's password and/or role(s) are also supported via the command-line
interface as well as the normal management interfaces (e.g. JMX, web console,
etc.).
> **Warning**
>
> Management and CLI operations to manipulate user & role data are only available
> when using the `PropertiesLoginModule`.
#### LDAPLoginModule
The LDAP login module enables you to perform authentication and authorization

View File

@ -77,7 +77,7 @@
<karaf.version>4.0.6</karaf.version>
<pax.exam.version>4.9.1</pax.exam.version>
<commons.config.version>2.1</commons.config.version>
<commons.config.version>2.4</commons.config.version>
<commons.lang.version>3.0</commons.lang.version>
<activemq5-version>5.14.5</activemq5-version>
<apache.derby.version>10.11.1.1</apache.derby.version>

View File

@ -2683,6 +2683,46 @@ public class ActiveMQServerControlTest extends ManagementTestBase {
Assert.assertTrue(((org.apache.activemq.artemis.jms.client.ActiveMQMessageConsumer)JMSclient).isClosed());
}
@Test
public void testAddUser() throws Exception {
ActiveMQServerControl serverControl = createManagementControl();
try {
serverControl.addUser("x", "x", "x", true, "x");
fail();
} catch (Exception expected) {
}
}
@Test
public void testRemoveUser() throws Exception {
ActiveMQServerControl serverControl = createManagementControl();
try {
serverControl.removeUser("x", "x");
fail();
} catch (Exception expected) {
}
}
@Test
public void testListUser() throws Exception {
ActiveMQServerControl serverControl = createManagementControl();
try {
serverControl.listUser("x", "x");
fail();
} catch (Exception expected) {
}
}
@Test
public void testResetUser() throws Exception {
ActiveMQServerControl serverControl = createManagementControl();
try {
serverControl.resetUser("x","x","x", "x");
fail();
} catch (Exception expected) {
}
}
protected void scaleDown(ScaleDownHandler handler) throws Exception {
SimpleString address = new SimpleString("testQueue");

View File

@ -335,6 +335,31 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes
return null;
}
@Override
public void addUser(String username,
String password,
String role,
boolean plaintext,
String entryName) throws Exception {
proxy.invokeOperation("addUser", username, password, role, plaintext, entryName);
}
@Override
public String listUser(String username, String entryName) throws Exception {
return (String) proxy.invokeOperation("listUser", username, entryName, String.class);
}
@Override
public void removeUser(String username, String entryName) throws Exception {
proxy.invokeOperation("removeUser", username, entryName);
}
@Override
public void resetUser(String username, String password, String roles, String entryName) throws Exception {
proxy.invokeOperation("resetUser", username, password, roles, entryName);
}
@Override
public String getUptime() {
return null;