ARTEMIS-3790 Support masked JMS credentials
Adds standard username and password value unmasking for JMS ActiveMQConnectionFactory instances
This commit is contained in:
parent
56111ebfef
commit
bb8761fcd2
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
|
|
Loading…
Reference in New Issue