This commit is contained in:
Clebert Suconic 2020-10-19 10:09:24 -04:00
commit 0be78c84c5
40 changed files with 1835 additions and 312 deletions

View File

@ -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;
}
}
}
}

View File

@ -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();

View File

@ -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;

View File

@ -0,0 +1,5 @@
<security-manager class-name="org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager">
<property key="bootstrapUser" value="${user}"/>
<property key="bootstrapPassword" value="${password}"/>
<property key="bootstrapRole" value="${role}"/>
</security-manager>

View File

@ -18,7 +18,7 @@
<broker xmlns="http://activemq.org/schema">
<jaas-security domain="activemq"/>
${security-manager-settings}
<!-- artemis.URI.instance is parsed from artemis.instance by the CLI startup.
This is to avoid situations where you could have spaces or special characters on this URI -->

View File

@ -0,0 +1 @@
<jaas-security domain="activemq"/>

View File

@ -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<ManagementContext, ActiveMQServer>)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<ManagementContext, ActiveMQServer>)result).getB();
ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl();
server = ((Pair<ManagementContext, ActiveMQServer>)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<ManagementContext, ActiveMQServer>)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<ManagementContext, ActiveMQServer>)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<PropertiesConfiguration> 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<PropertiesConfiguration> roleBuilder = configs.propertiesBuilder(roleFile);
PropertiesConfiguration roleConfig = roleBuilder.getConfiguration();
log.debug("users in role: " + r + " ; " + storedUsers);
List<String> 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<String> userList = StringUtil.splitStringList(storedUsers, ",");
assertTrue(userList.contains(user));
}
}
}
private String getStoredPassword(String user, File userFile) throws Exception {
Configurations configs = new Configurations();
FileBasedConfigurationBuilder<PropertiesConfiguration> 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<PropertiesConfiguration> 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);
}

View File

@ -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;

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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<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();
});
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<String, Set<String>> info) {
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) 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();
});
}
}

View File

@ -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<PersistedAddressSetting> 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<PersistedRoles> recoverPersistedRoles() throws Exception;
List<PersistedSecuritySetting> recoverSecuritySettings() throws Exception;
void storeDivertConfiguration(PersistedDivertConfiguration persistedDivertConfiguration) throws Exception;
void deleteDivertConfiguration(String divertName) throws Exception;
List<PersistedDivertConfiguration> recoverDivertConfigurations();
void storeUser(PersistedUser persistedUser) throws Exception;
void deleteUser(String username) throws Exception;
Map<String, PersistedUser> getPersistedUsers();
void storeRole(PersistedRole persistedRole) throws Exception;
void deleteRole(String role) throws Exception;
Map<String, PersistedRole> getPersistedRoles();
/**
* @return The ID with the stored counter
*/

View File

@ -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<String> roles;
public PersistedRole() {
}
public PersistedRole(String username, List<String> 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<String> 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();
}
}

View File

@ -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=" +

View File

@ -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=****" +
"]";
}
}

View File

@ -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<SimpleString, PersistedRoles> mapPersistedRoles = new ConcurrentHashMap<>();
protected final Map<SimpleString, PersistedSecuritySetting> mapPersistedSecuritySettings = new ConcurrentHashMap<>();
protected final Map<SimpleString, PersistedAddressSetting> mapPersistedAddressSettings = new ConcurrentHashMap<>();
protected final Map<String, PersistedDivertConfiguration> mapPersistedDivertConfigurations = new ConcurrentHashMap<>();
protected final Map<String, PersistedUser> mapPersistedUsers = new ConcurrentHashMap<>();
protected final Map<String, PersistedRole> mapPersistedRoles = new ConcurrentHashMap<>();
protected final ConcurrentLongHashMap<LargeServerMessage> largeMessagesToDelete = new ConcurrentLongHashMap<>();
public AbstractJournalStorageManager(final Configuration config,
@ -767,20 +773,20 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
}
@Override
public List<PersistedRoles> recoverPersistedRoles() throws Exception {
return new ArrayList<>(mapPersistedRoles.values());
public List<PersistedSecuritySetting> 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<String, PersistedUser> 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<String, PersistedRole> 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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<PersistedRoles> recoverPersistedRoles() throws Exception {
public List<PersistedSecuritySetting> 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<String, PersistedUser> getPersistedUsers() {
return null;
}
@Override
public void storeRole(PersistedRole persistedRole) throws Exception {
}
@Override
public void deleteRole(String role) throws Exception {
}
@Override
public Map<String, PersistedRole> 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

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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}.<br>

View File

@ -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);

View File

@ -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<PersistedRoles> roles = storageManager.recoverPersistedRoles();
List<PersistedSecuritySetting> roles = storageManager.recoverSecuritySettings();
for (PersistedRoles roleItem : roles) {
for (PersistedSecuritySetting roleItem : roles) {
Set<Role> 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);

View File

@ -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");
}
}
}

View File

@ -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<String, Object> 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;
}
}

View File

@ -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;
}
}

View File

@ -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<String, String> properties;
private String rolePrincipalClass = RolePrincipal.class.getName();
private StorageManager storageManager;
@Override
public ActiveMQBasicSecurityManager init(Map<String, String> 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<Role> roles, CheckType checkType) {
throw new UnsupportedOperationException("Invoke authorize(Subject, Set<Role>, CheckType, String) instead");
}
@Override
public boolean authorize(final Subject subject,
final Set<Role> 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<String, Set<String>> 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<String, Set<String>> 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);
}
}

View File

@ -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<Role> roles,
final CheckType checkType,
final String address) {
boolean authorized = false;
boolean authorized = SecurityManagerUtil.authorize(subject, roles, checkType, rolePrincipalClass);
if (subject != null) {
Set<RolePrincipal> rolesWithPermission = getPrincipalsInRole(checkType, roles);
// Check the caller's roles
Set<Principal> 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<Principal> rolesForSubjectIter = rolesForSubject.iterator();
while (!authorized && rolesForSubjectIter.hasNext()) {
Iterator<RolePrincipal> 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<RolePrincipal> getPrincipalsInRole(final CheckType checkType, final Set<Role> 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;
}
}

View File

@ -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<String, Set<String>> listUser(String user);
void updateUser(String username, String password, String... roles) throws Exception;
}

View File

@ -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);
}

View File

@ -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<String, Set<String>> listUser(String username) {
Map<String, Set<String>> 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);

View File

@ -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<RolePrincipal> getPrincipalsInRole(final CheckType checkType, final Set<Role> 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<Role> roles, final CheckType checkType, final String rolePrincipalClass) {
boolean authorized = false;
if (subject != null) {
Set<RolePrincipal> rolesWithPermission = getPrincipalsInRole(checkType, roles, rolePrincipalClass);
// Check the caller's roles
Set<Principal> 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<Principal> rolesForSubjectIter = rolesForSubject.iterator();
while (!authorized && rolesForSubjectIter.hasNext()) {
Iterator<RolePrincipal> rolesWithPermissionIter = rolesWithPermission.iterator();
Principal subjectRole = rolesForSubjectIter.next();
while (!authorized && rolesWithPermissionIter.hasNext()) {
Principal roleWithPermission = rolesWithPermissionIter.next();
authorized = subjectRole.equals(roleWithPermission);
}
}
}
}
return authorized;
}
}

View File

@ -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<PersistedRoles> recoverPersistedRoles() throws Exception {
public List<PersistedSecuritySetting> 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<String, PersistedUser> getPersistedUsers() {
return null;
}
@Override
public void storeRole(PersistedRole persistedRole) throws Exception {
}
@Override
public void deleteRole(String role) throws Exception {
}
@Override
public Map<String, PersistedRole> getPersistedRoles() {
return null;
}
@Override
public long storePageCounter(long txID, long queueID, long value, long size) throws Exception {
return 0;

View File

@ -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
<broker xmlns="http://activemq.org/schema">
<security-manager class-name="org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager">
<property key="bootstrapUser" value="myUser"/>
<property key="bootstrapPassword" value="myPass"/>
<property key="bootstrapRole" value="myRole"/>
</security-manager>
...
</broker>
```
Because the bindings data which holds the user & role data cannot be modified
manually and because the broker must be running to manage users and because
the broker often needs to be secured from first boot the
`ActiveMQBasicSecurityManager` has 3 properties to define a user whose
credentials can then be used to add other users.
- `bootstrapUser` - the name of the bootstrap user
- `bootstrapPassword` - the password for the bootstrap user; supports masking
- `bootstrapRole` - the role of the bootstrap user
The value specified in the `bootstrapRole` will need the following permissions
on the `activemq.management` address:
- `createNonDurableQueue`
- `createAddress`
- `consume`
- `manage`
- `send`
For example:
```xml
<security-setting match="activemq.management.#">
<permission type="createNonDurableQueue" roles="myRole"/>
<permission type="createAddress" roles="myRole"/>
<permission type="consume" roles="myRole"/>
<permission type="manage" roles="myRole"/>
<permission type="send" roles="myRole"/>
</security-setting>
```
> **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:

View File

@ -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<PersistedRoles> recoverPersistedRoles() throws Exception {
return manager.recoverPersistedRoles();
public List<PersistedSecuritySetting> 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<String, PersistedUser> 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<String, PersistedRole> 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);

View File

@ -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<SimpleString, PersistedRoles> mapExpectedSets;
private Map<SimpleString, PersistedSecuritySetting> 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<PersistedRoles> listSetting = journal.recoverPersistedRoles();
List<PersistedSecuritySetting> 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);
}

View File

@ -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");
}
}
}

View File

@ -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<String, String> 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<Role> 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<Role> 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<Role> 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");
}
}
}