ARTEMIS-3790 Support masked JMS credentials

Adds standard username and password value unmasking for JMS
ActiveMQConnectionFactory instances
This commit is contained in:
Ryan Highley 2022-12-21 13:55:53 -06:00 committed by Justin Bertram
parent 56111ebfef
commit bb8761fcd2
4 changed files with 302 additions and 10 deletions

View File

@ -59,6 +59,7 @@ import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManagerFactor
import org.apache.activemq.artemis.uri.ConnectionFactoryParser;
import org.apache.activemq.artemis.uri.ServerLocatorParser;
import org.apache.activemq.artemis.utils.ClassloadingUtil;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.uri.BeanSupport;
import org.apache.activemq.artemis.utils.uri.URISupport;
@ -86,6 +87,8 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
private String password;
private String passwordCodec;
private String protocolManagerFactoryStr;
private String deserializationBlackList;
@ -244,6 +247,10 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
if (getPassword() == null) {
setPassword(DefaultConnectionProperties.DEFAULT_PASSWORD);
}
if (getPasswordCodec() == null) {
setPasswordCodec(DefaultConnectionProperties.DEFAULT_PASSWORD_CODEC);
}
}
/**
@ -822,6 +829,16 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
return this;
}
public String getPasswordCodec() {
return passwordCodec;
}
public ActiveMQConnectionFactory setPasswordCodec(String passwordCodec) {
checkWrite();
this.passwordCodec = passwordCodec;
return this;
}
public void setGroupID(final String groupID) {
serverLocator.setGroupID(groupID);
}
@ -853,8 +870,21 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
return JMSFactoryType.CF.intValue();
}
protected synchronized ActiveMQConnection createConnectionInternal(final String username,
final String password,
private String unmaskSensitiveString(String secret) throws JMSException {
try {
return PasswordMaskingUtil.resolveMask(secret, passwordCodec);
} catch (Exception e) {
JMSException jmse = new JMSException("Failed to resolve masked sensitive string");
jmse.initCause(e);
jmse.setLinkedException(e);
throw jmse;
}
}
protected synchronized ActiveMQConnection createConnectionInternal(final String rawUsername,
final String rawPassword,
final boolean isXA,
final int type) throws JMSException {
makeReadOnly();
@ -873,6 +903,8 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
}
ActiveMQConnection connection = null;
final String username = unmaskSensitiveString(rawUsername);
final String password = unmaskSensitiveString(rawPassword);
if (isXA) {
if (type == ActiveMQConnection.TYPE_GENERIC_CONNECTION) {

View File

@ -39,6 +39,7 @@ public class DefaultConnectionProperties {
public static final String AMQ_PORT = "AMQ_PORT";
public static final String AMQ_USER = "AMQ_USER";
public static final String AMQ_PASSWORD = "AMQ_PASSWORD";
public static final String AMQ_PASSWORD_CODEC = "AMQ_PASSWORD_CODEC";
public static final String BROKER_BIND_URL = "BROKER_BIND_URL";
public static final String PREFIX = "org.apache.activemq.";
@ -48,6 +49,7 @@ public class DefaultConnectionProperties {
public static String DEFAULT_BROKER_URL;
public static String DEFAULT_USER;
public static String DEFAULT_PASSWORD;
public static String DEFAULT_PASSWORD_CODEC;
static String getProperty(final String defaultValue, final String... propertyNames) {
return AccessController.doPrivileged(new PrivilegedAction<String>() {
@ -76,6 +78,7 @@ public class DefaultConnectionProperties {
String url = getProperty("tcp://" + host + ":" + port, PREFIX + BROKER_BIND_URL, BROKER_BIND_URL);
DEFAULT_USER = getProperty(null, AMQ_USER, PREFIX + AMQ_USER);
DEFAULT_PASSWORD = getProperty(null, AMQ_PASSWORD, PREFIX + AMQ_PASSWORD);
DEFAULT_PASSWORD_CODEC = getProperty(null, AMQ_PASSWORD_CODEC, PREFIX + AMQ_PASSWORD_CODEC);
DEFAULT_BROKER_BIND_URL = url;
// TODO: improve this once we implement failover:// as ActiveMQ5 does

View File

@ -16,19 +16,29 @@
*/
package org.apache.activemq.artemis.tests.integration.jms;
import javax.jms.Connection;
import javax.jms.JMSContext;
import javax.jms.JMSSecurityException;
import javax.jms.JMSSecurityRuntimeException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
import org.apache.activemq.artemis.tests.util.JMSTestBase;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
import org.junit.Before;
import org.junit.Test;
public class JMSSecurityTest extends JMSTestBase {
private static final String GOOD_USER = "IDo";
private static final String GOOD_PASSWORD = "Exist";
private static final String BAD_USER = "Idont";
private static final String BAD_PASSWORD = "exist";
@Override
public boolean useSecurity() {
return true;
@ -42,30 +52,276 @@ public class JMSSecurityTest extends JMSTestBase {
@Test
public void testSecurityOnJMSContext() throws Exception {
ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
securityManager.getConfiguration().addUser("IDo", "Exist");
addUserToSecurityManager();
try {
JMSContext ctx = cf.createContext("Idont", "exist");
JMSContext ctx = cf.createContext(BAD_USER, BAD_PASSWORD);
ctx.close();
} catch (JMSSecurityRuntimeException e) {
// expected
}
JMSContext ctx = cf.createContext("IDo", "Exist");
try {
JMSContext ctx = cf.createContext(BAD_USER, GOOD_PASSWORD);
ctx.close();
} catch (JMSSecurityRuntimeException e) {
// expected
}
try {
JMSContext ctx = cf.createContext(GOOD_USER, BAD_PASSWORD);
ctx.close();
} catch (JMSSecurityRuntimeException e) {
// expected
}
JMSContext ctx = cf.createContext(GOOD_USER, GOOD_PASSWORD);
ctx.close();
}
@Test
public void testCreateQueueConnection() throws Exception {
ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
securityManager.getConfiguration().addUser("IDo", "Exist");
addUserToSecurityManager();
try {
QueueConnection queueC = ((QueueConnectionFactory) cf).createQueueConnection("IDont", "Exist");
QueueConnection queueC = ((QueueConnectionFactory) cf).createQueueConnection(BAD_USER, BAD_PASSWORD);
fail("supposed to throw exception");
queueC.close();
} catch (JMSSecurityException e) {
// expected
}
JMSContext ctx = cf.createContext("IDo", "Exist");
try {
QueueConnection queueC = ((QueueConnectionFactory) cf).createQueueConnection(BAD_USER, GOOD_PASSWORD);
fail("supposed to throw exception");
queueC.close();
} catch (JMSSecurityException e) {
// expected
}
try {
QueueConnection queueC = ((QueueConnectionFactory) cf).createQueueConnection(GOOD_USER, BAD_PASSWORD);
fail("supposed to throw exception");
queueC.close();
} catch (JMSSecurityException e) {
// expected
}
QueueConnection queueC = ((QueueConnectionFactory) cf).createQueueConnection(GOOD_USER, GOOD_PASSWORD);
queueC.close();
}
@Test
public void testMaskedPasswordOnJMSContext() throws Exception {
String maskedPassword = PasswordMaskingUtil.getDefaultCodec().encode(GOOD_PASSWORD);
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager();
try {
JMSContext ctx = cf.createContext(BAD_USER, wrappedPassword);
ctx.close();
} catch (JMSSecurityRuntimeException e) {
// expected
}
JMSContext ctx = cf.createContext(GOOD_USER, wrappedPassword);
ctx.close();
}
@Test
public void testMaskedPasswordCreateQueueConnection() throws Exception {
String maskedPassword = PasswordMaskingUtil.getDefaultCodec().encode(GOOD_PASSWORD);
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager();
try {
QueueConnection queueC = ((QueueConnectionFactory) cf).createQueueConnection(BAD_USER, wrappedPassword);
fail("supposed to throw exception");
queueC.close();
} catch (JMSSecurityException e) {
// expected
}
QueueConnection queueC = ((QueueConnectionFactory) cf).createQueueConnection(GOOD_USER, wrappedPassword);
queueC.close();
}
@Test
public void testMaskedPasswordURL() throws Exception {
String maskedPassword = PasswordMaskingUtil.getDefaultCodec().encode(GOOD_PASSWORD);
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager();
String brokerURL = "tcp://localhost:61616";
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL)) {
testCF.setUser(BAD_USER).setPassword(wrappedPassword);
Connection conn = testCF.createConnection();
fail("supposed to throw exception");
conn.close();
} catch (JMSSecurityException e) {
// expected
}
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL)) {
testCF.setUser(GOOD_USER).setPassword(wrappedPassword);
JMSContext ctx = testCF.createContext();
ctx.close();
}
}
@Test
public void testMaskedPasswordURLUsernamePassword() throws Exception {
String maskedPassword = PasswordMaskingUtil.getDefaultCodec().encode(GOOD_PASSWORD);
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager();
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory("tcp://localhost:61616", BAD_USER, wrappedPassword)) {
Connection conn = testCF.createConnection();
fail("supposed to throw exception");
conn.close();
} catch (JMSSecurityException e) {
// expected
}
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory("tcp://localhost:61616", GOOD_USER, wrappedPassword)) {
JMSContext ctx = testCF.createContext();
ctx.close();
}
}
@Test
public void testMaskedPasswordCodec() throws Exception {
String maskedPassword = JMSSecurityTestPasswordCodec.MASK;
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager(GOOD_USER, JMSSecurityTestPasswordCodec.CLEARTEXT);
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory()) {
testCF.setUser(BAD_USER).setPassword(wrappedPassword).setPasswordCodec(JMSSecurityTestPasswordCodec.class.getName());
Connection conn = testCF.createConnection();
fail("supposed to throw exception");
conn.close();
} catch (JMSSecurityException e) {
// expected
}
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory()) {
testCF.setUser(GOOD_USER).setPassword(wrappedPassword).setPasswordCodec(JMSSecurityTestPasswordCodec.class.getName());
JMSContext ctx = testCF.createContext();
ctx.close();
}
}
@Test
public void testMaskedPasswordCodecURL() throws Exception {
String maskedPassword = JMSSecurityTestPasswordCodec.MASK;
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager(GOOD_USER, JMSSecurityTestPasswordCodec.CLEARTEXT);
String brokerURL = "tcp://localhost:61616?passwordCodec=" + JMSSecurityTestPasswordCodec.class.getName();
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL)) {
testCF.setUser(BAD_USER).setPassword(wrappedPassword);
Connection conn = testCF.createConnection();
fail("supposed to throw exception");
conn.close();
} catch (JMSSecurityException e) {
// expected
}
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL)) {
testCF.setUser(GOOD_USER).setPassword(wrappedPassword);
JMSContext ctx = testCF.createContext();
ctx.close();
}
}
@Test
public void testMaskedPasswordCodecURLUsernamePassword() throws Exception {
String maskedPassword = JMSSecurityTestPasswordCodec.MASK;
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager(GOOD_USER, JMSSecurityTestPasswordCodec.CLEARTEXT);
String brokerURL = "tcp://localhost:61616?passwordCodec=" + JMSSecurityTestPasswordCodec.class.getName();
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL, BAD_USER, wrappedPassword)) {
Connection conn = testCF.createConnection();
fail("supposed to throw exception");
conn.close();
} catch (JMSSecurityException e) {
// expected
}
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL, GOOD_USER, wrappedPassword)) {
JMSContext ctx = testCF.createContext();
ctx.close();
}
}
@Test
public void testBadMaskedPasswordCodecURL() throws Exception {
String maskedPassword = JMSSecurityTestPasswordCodec.MASK + "ThisIsDesignedToFail";
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager(GOOD_USER, JMSSecurityTestPasswordCodec.CLEARTEXT);
String brokerURL = "tcp://localhost:61616?passwordCodec=" + JMSSecurityTestPasswordCodec.class.getName();
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL)) {
testCF.setUser(GOOD_USER).setPassword(wrappedPassword);
Connection conn = testCF.createConnection();
fail("supposed to throw exception");
conn.close();
} catch (JMSSecurityException e) {
// expected
}
}
@Test
public void testBadMaskedPasswordCodecURLUsernamePassword() throws Exception {
String maskedPassword = JMSSecurityTestPasswordCodec.MASK + "ThisIsDesignedToFail";
String wrappedPassword = PasswordMaskingUtil.wrap(maskedPassword);
addUserToSecurityManager(GOOD_USER, JMSSecurityTestPasswordCodec.CLEARTEXT);
String brokerURL = "tcp://localhost:61616?passwordCodec=" + JMSSecurityTestPasswordCodec.class.getName();
try (ActiveMQConnectionFactory testCF = new ActiveMQConnectionFactory(brokerURL, GOOD_USER, wrappedPassword)) {
Connection conn = testCF.createConnection();
fail("supposed to throw exception");
conn.close();
} catch (JMSSecurityException e) {
// expected
}
}
private void addUserToSecurityManager() {
addUserToSecurityManager(GOOD_USER, GOOD_PASSWORD);
}
private void addUserToSecurityManager(String user, String password) {
ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
securityManager.getConfiguration().addUser(user, password);
}
public static class JMSSecurityTestPasswordCodec implements SensitiveDataCodec<String> {
private static final String MASK = "supersecureish";
private static final String CLEARTEXT = "securepass";
@Override
public String decode(Object mask) throws Exception {
if (!MASK.equals(mask)) {
return mask.toString();
}
return CLEARTEXT;
}
@Override
public String encode(Object secret) throws Exception {
if (!CLEARTEXT.equals(secret)) {
return secret.toString();
}
return MASK;
}
}
}

View File

@ -48,6 +48,7 @@ public class ConnectionFactoryPropertiesTest extends ActiveMQTestBase {
UNSUPPORTED_CF_PROPERTIES.add("user");
UNSUPPORTED_CF_PROPERTIES.add("userName");
UNSUPPORTED_CF_PROPERTIES.add("password");
UNSUPPORTED_CF_PROPERTIES.add("passwordCodec");
UNSUPPORTED_CF_PROPERTIES.add("enableSharedClientID");
UNSUPPORTED_RA_PROPERTIES = new TreeSet<>();