This commit is contained in:
Justin Bertram 2018-01-18 08:59:00 -06:00
commit 0d9a114a96
34 changed files with 1057 additions and 166 deletions

View File

@ -0,0 +1,47 @@
/*
* 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.cli.test;
import org.apache.activemq.artemis.cli.commands.util.HashUtil;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class HashUtilTest {
@Test
public void testDefaultHashFormat() throws Exception {
final String password = "helloworld";
String hash = HashUtil.tryHash(new TestActionContext(), password);
String hashStr = PasswordMaskingUtil.unwrap(hash);
System.out.println("hashString: " + hashStr);
String[] parts = hashStr.split(":");
assertEquals(3, parts.length);
//first part should be able to convert to an int
Integer.parseInt(parts[0]);
//second and third parts are all hex values
checkHexBytes(parts[1], parts[2]);
}
private void checkHexBytes(String... parts) throws Exception {
for (String p : parts) {
assertTrue(p.matches("^[0-9A-F]+$"));
}
}
}

View File

@ -28,14 +28,54 @@ import org.apache.activemq.artemis.logs.ActiveMQUtilBundle;
public final class PasswordMaskingUtil {
public static final String BEGIN_ENC = "ENC(";
public static final String END_ENC = ")";
private PasswordMaskingUtil() {
}
/**
* 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
* masked. If it is false, the password is interpreted as plain text.
* if it is null, the password will be interpreted as masked if the
* password is wrapped in ENC(), or as plain text otherwise.
* @param password : the original value of password string
* @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(Boolean maskPassword, String password, String codecClass) throws Exception {
String plainText = password;
if (maskPassword == null) {
if (isEncMasked(password)) {
//masked
String bareMaskedPassword = unwrap(password);
plainText = getCodec(codecClass).decode(bareMaskedPassword);
}
} else if (maskPassword) {
plainText = getCodec(codecClass).decode(password);
}
return plainText;
}
public static boolean isEncMasked(String password) {
return (password.startsWith(BEGIN_ENC) && password.endsWith(END_ENC));
}
//remove ENC() from the password body
public static String unwrap(String password) {
return password.substring(4, password.length() - 1);
}
public static String wrap(String password) {
return BEGIN_ENC + password + END_ENC;
}
private static final class LazyPlainTextProcessorHolder {
private LazyPlainTextProcessorHolder() {
}
private static final HashProcessor INSTANCE = new NoHashProcessor();
@ -83,7 +123,7 @@ public final class PasswordMaskingUtil {
}
private static boolean isEncoded(String storedPassword) {
return storedPassword == null || (storedPassword.startsWith(SecureHashProcessor.BEGIN_HASH) && storedPassword.endsWith(SecureHashProcessor.END_HASH));
return storedPassword == null || (isEncMasked(storedPassword));
}
public static HashProcessor getHashProcessor() {
@ -107,6 +147,10 @@ public final class PasswordMaskingUtil {
public static SensitiveDataCodec<String> getCodec(String codecDesc) throws ActiveMQException {
SensitiveDataCodec<String> codecInstance;
if (codecDesc == null) {
return getDefaultCodec();
}
// semi colons
String[] parts = codecDesc.split(";");
if (parts.length < 1)

View File

@ -18,9 +18,6 @@ package org.apache.activemq.artemis.utils;
public class SecureHashProcessor implements HashProcessor {
public static final String BEGIN_HASH = "ENC(";
public static final String END_HASH = ")";
private DefaultSensitiveStringCodec codec;
public SecureHashProcessor(DefaultSensitiveStringCodec codec) {
@ -29,13 +26,13 @@ public class SecureHashProcessor implements HashProcessor {
@Override
public String hash(String plainText) throws Exception {
return BEGIN_HASH + codec.encode(plainText) + END_HASH;
return PasswordMaskingUtil.wrap(codec.encode(plainText));
}
@Override
//storedValue must take form of ENC(...)
public boolean compare(char[] inputValue, String storedValue) {
String storedHash = storedValue.substring(4, storedValue.length() - 2);
String storedHash = storedValue.substring(4, storedValue.length() - 1);
return codec.verify(inputValue, storedHash);
}
}

View File

@ -31,5 +31,6 @@ public interface SensitiveDataCodec<T> {
T encode(Object secret) throws Exception;
void init(Map<String, String> params) throws Exception;
default void init(Map<String, String> params) throws Exception {
};
}

View File

@ -0,0 +1,116 @@
/*
* 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 org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class MaskPasswordResolvingTest {
private static final String plainPassword = "password";
private static final String defaultMaskPassword = "defaultmasked";
private static final String customizedCodecPassword = "secret";
private static final String oldDefaultMaskedPassword = "oldmasked";
private static final String oldCustomizedCodecPassword = "secret";
private static final String oldExplicitPlainPassword = "PASSWORD";
@Parameterized.Parameters(name = "mask({0})password({1})codec({2})")
public static Collection<Object[]> params() {
return Arrays.asList(new Object[][]{{null, plainPassword, null},
{null, "ENC(3bdfd94fe8cdf710e7fefa72f809ea90)", null},
{null, "ENC(momsword)", "org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec"},
{true, "662d05f5a83f9e073af6b8dc081d34aa", null},
{true, "momsword", "org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec"},
{false, oldExplicitPlainPassword, null},
{false, oldExplicitPlainPassword, "org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec"}});
}
private Boolean maskPassword;
private String password;
private String codec;
public MaskPasswordResolvingTest(Boolean maskPassword, String password, String codec) {
this.maskPassword = maskPassword;
this.password = password;
this.codec = codec;
}
@Test
public void testPasswordResolving() throws Exception {
String resolved = PasswordMaskingUtil.resolveMask(maskPassword, password, codec);
System.out.println("resolved: " + resolved);
checkResult(resolved);
}
private void checkResult(String resolved) throws Exception {
if (this.maskPassword == null) {
if (PasswordMaskingUtil.isEncMasked(this.password)) {
if (this.codec != null) {
assertEquals(customizedCodecPassword, resolved);
} else {
assertEquals(defaultMaskPassword, resolved);
}
} else {
assertEquals(plainPassword, resolved);
}
} else {
if (this.maskPassword) {
if (this.codec != null) {
assertEquals(oldCustomizedCodecPassword, resolved);
} else {
assertEquals(oldDefaultMaskedPassword, resolved);
}
} else {
assertEquals(oldExplicitPlainPassword, resolved);
}
}
}
public static class SimplePasswordCodec implements SensitiveDataCodec<String> {
private Map<String, String> passwordBook = new HashMap<>();
public SimplePasswordCodec() {
passwordBook.put("momsword", "secret");
passwordBook.put("youneverknow", "keypass");
passwordBook.put("youcanguess", "trustpass");
}
@Override
public String decode(Object mask) throws Exception {
String password = passwordBook.get(mask);
if (password == null) {
throw new IllegalArgumentException("I don't know the password " + mask);
}
return password;
}
@Override
public String encode(Object secret) throws Exception {
return null;
}
}
}

View File

@ -179,7 +179,7 @@ public final class ActiveMQDefaultConfiguration {
private static String DEFAULT_CLUSTER_PASSWORD = "CHANGE ME!!";
// This option controls whether passwords in server configuration need be masked. If set to "true" the passwords are masked.
private static boolean DEFAULT_MASK_PASSWORD = false;
private static Boolean DEFAULT_MASK_PASSWORD = null;
// true means that the management API is available via JMX
private static boolean DEFAULT_JMX_MANAGEMENT_ENABLED = true;
@ -622,7 +622,7 @@ public final class ActiveMQDefaultConfiguration {
/**
* This option controls whether passwords in server configuration need be masked. If set to "true" the passwords are masked.
*/
public static boolean isDefaultMaskPassword() {
public static Boolean isDefaultMaskPassword() {
return DEFAULT_MASK_PASSWORD;
}

View File

@ -178,9 +178,6 @@ public interface ActiveMQClientMessageBundle {
@Message(id = 119043, value = "Invalid argument null handler")
IllegalArgumentException nullHandler();
@Message(id = 119044, value = "No available codec to decode password!")
IllegalArgumentException noCodec();
@Message(id = 119045, value = "the first node to be compared is null")
IllegalArgumentException firstNodeNull();
@ -214,9 +211,6 @@ public interface ActiveMQClientMessageBundle {
@Message(id = 119055, value = "Element {0} requires a valid Long value, but ''{1}'' cannot be parsed as a Long", format = Message.Format.MESSAGE_FORMAT)
IllegalArgumentException mustBeLong(Node element, String value);
@Message(id = 119056, value = "Failed to get decoder")
IllegalArgumentException failedToGetDecoder(@Cause Exception e);
@Message(id = 119057, value = "Error decoding password")
IllegalArgumentException errordecodingPassword(@Cause Exception e);

View File

@ -20,7 +20,6 @@ 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.core.client.ActiveMQClientLogger;
import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
@ -164,25 +163,9 @@ public class ConfigurationHelper {
String value = prop.toString();
Boolean useMask = (Boolean) props.get(defaultMaskPassword);
if (useMask == null || (!useMask)) {
return value;
}
final String classImpl = (String) props.get(defaultPasswordCodec);
if (classImpl == null) {
throw ActiveMQClientMessageBundle.BUNDLE.noCodec();
}
SensitiveDataCodec<String> codec = null;
try {
codec = PasswordMaskingUtil.getCodec(classImpl);
} catch (ActiveMQException e1) {
throw ActiveMQClientMessageBundle.BUNDLE.failedToGetDecoder(e1);
}
try {
return codec.decode(value);
return PasswordMaskingUtil.resolveMask(useMask, value, classImpl);
} catch (Exception e) {
throw ActiveMQClientMessageBundle.BUNDLE.errordecodingPassword(e);
}

View File

@ -31,6 +31,14 @@
<activemq.basedir>${project.basedir}/..</activemq.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>

View File

@ -16,6 +16,8 @@
*/
package org.apache.activemq.artemis.dto;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
@ -37,21 +39,44 @@ public class WebServerDTO extends ComponentDTO {
public Boolean clientAuth;
@XmlAttribute
public String keyStorePath;
public String passwordCodec;
@XmlAttribute
public String keyStorePassword;
public String keyStorePath;
@XmlAttribute
public String trustStorePath;
@XmlAttribute
public String trustStorePassword;
@XmlElementRef
public List<AppDTO> apps;
@XmlAttribute
private String keyStorePassword;
@XmlAttribute
private String trustStorePassword;
public WebServerDTO() {
componentClassName = "org.apache.activemq.artemis.component.WebServerComponent";
}
public String getKeyStorePassword() throws Exception {
return getPassword(this.keyStorePassword);
}
private String getPassword(String password) throws Exception {
return PasswordMaskingUtil.resolveMask(null, password, this.passwordCodec);
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public String getTrustStorePassword() throws Exception {
return getPassword(this.trustStorePassword);
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
}

View File

@ -69,9 +69,7 @@ import org.apache.activemq.artemis.jms.server.ActiveMQJMSServerBundle;
import org.apache.activemq.artemis.service.extensions.ServiceUtils;
import org.apache.activemq.artemis.service.extensions.xa.recovery.ActiveMQRegistry;
import org.apache.activemq.artemis.service.extensions.xa.recovery.XARecoveryConfig;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
public final class JMSBridgeImpl implements JMSBridge {
@ -168,7 +166,7 @@ public final class JMSBridgeImpl implements JMSBridge {
private ObjectName objectName;
private boolean useMaskedPassword = false;
private Boolean useMaskedPassword;
private String passwordCodec;
@ -440,26 +438,17 @@ public final class JMSBridgeImpl implements JMSBridge {
}
private void initPasswords() throws ActiveMQException {
if (useMaskedPassword) {
SensitiveDataCodec<String> codecInstance = new DefaultSensitiveStringCodec();
if (passwordCodec != null) {
codecInstance = PasswordMaskingUtil.getCodec(passwordCodec);
}
try {
if (this.sourcePassword != null) {
sourcePassword = codecInstance.decode(sourcePassword);
sourcePassword = PasswordMaskingUtil.resolveMask(useMaskedPassword, sourcePassword, passwordCodec);
}
if (this.targetPassword != null) {
targetPassword = codecInstance.decode(targetPassword);
targetPassword = PasswordMaskingUtil.resolveMask(useMaskedPassword, targetPassword, passwordCodec);
}
} catch (Exception e) {
throw ActiveMQJMSServerBundle.BUNDLE.errorDecodingPassword(e);
}
}
}
@Override

View File

@ -20,9 +20,7 @@ import java.io.Serializable;
import java.util.Hashtable;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
/**
* The RA default properties - these are set in the ra.xml file
@ -66,14 +64,12 @@ public class ActiveMQRAProperties extends ConnectionFactoryProperties implements
private boolean useJNDI;
private boolean useMaskedPassword = false;
private Boolean useMaskedPassword = null;
private String passwordCodec;
private boolean initialized = false;
private transient SensitiveDataCodec<String> codecInstance;
/**
* Class used to get a JChannel
*/
@ -217,11 +213,11 @@ public class ActiveMQRAProperties extends ConnectionFactoryProperties implements
this.setupInterval = setupInterval;
}
public boolean isUseMaskedPassword() {
public Boolean isUseMaskedPassword() {
return useMaskedPassword;
}
public void setUseMaskedPassword(boolean useMaskedPassword) {
public void setUseMaskedPassword(Boolean useMaskedPassword) {
this.useMaskedPassword = useMaskedPassword;
}
@ -243,27 +239,19 @@ public class ActiveMQRAProperties extends ConnectionFactoryProperties implements
if (initialized)
return;
if (useMaskedPassword) {
codecInstance = new DefaultSensitiveStringCodec();
if (passwordCodec != null) {
codecInstance = PasswordMaskingUtil.getCodec(passwordCodec);
}
try {
if (password != null) {
password = codecInstance.decode(password);
}
try {
password = PasswordMaskingUtil.resolveMask(useMaskedPassword, password, passwordCodec);
} catch (Exception e) {
throw ActiveMQRABundle.BUNDLE.errorDecodingPassword(e);
}
}
initialized = true;
}
public SensitiveDataCodec<String> getCodecInstance() {
return codecInstance;
public String getCodec() {
return passwordCodec;
}
public String getJgroupsChannelLocatorClass() {

View File

@ -59,7 +59,6 @@ import org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec;
import org.apache.activemq.artemis.ra.recovery.RecoveryManager;
import org.apache.activemq.artemis.service.extensions.ServiceUtils;
import org.apache.activemq.artemis.service.extensions.xa.recovery.XARecoveryConfig;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
import org.jboss.logging.Logger;
import org.jgroups.JChannel;
@ -2037,8 +2036,8 @@ public class ActiveMQResourceAdapter implements ResourceAdapter, Serializable {
managedConnectionFactories.add(activeMQRAManagedConnectionFactory);
}
public SensitiveDataCodec<String> getCodecInstance() {
return raProperties.getCodecInstance();
public String getCodec() {
return raProperties.getCodec();
}
public synchronized void closeConnectionFactory(ConnectionFactoryProperties properties) {

View File

@ -57,7 +57,7 @@ import org.apache.activemq.artemis.ra.ActiveMQRaUtils;
import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter;
import org.apache.activemq.artemis.service.extensions.xa.recovery.XARecoveryConfig;
import org.apache.activemq.artemis.utils.FutureLatch;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.jboss.logging.Logger;
/**
@ -148,18 +148,14 @@ public class ActiveMQActivation {
logger.trace("constructor(" + ra + ", " + endpointFactory + ", " + spec + ")");
}
if (ra.isUseMaskedPassword()) {
String pass = spec.getOwnPassword();
if (pass != null) {
SensitiveDataCodec<String> codec = ra.getCodecInstance();
try {
spec.setPassword(codec.decode(pass));
spec.setPassword(PasswordMaskingUtil.resolveMask(ra.isUseMaskedPassword(), pass, ra.getCodec()));
} catch (Exception e) {
throw new ResourceException(e);
}
}
}
this.ra = ra;
this.endpointFactory = endpointFactory;

View File

@ -1000,12 +1000,12 @@ public interface Configuration {
/**
* Sets if passwords should be masked or not. True means the passwords should be masked.
*/
Configuration setMaskPassword(boolean maskPassword);
Configuration setMaskPassword(Boolean maskPassword);
/**
* If passwords are masked. True means the passwords are masked.
*/
boolean isMaskPassword();
Boolean isMaskPassword();
/*
* Whether or not that ActiveMQ Artemis should use all protocols available on the classpath. If false only the core protocol will

View File

@ -249,7 +249,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
protected List<ConnectorServiceConfiguration> connectorServiceConfigurations = new ArrayList<>();
private boolean maskPassword = ActiveMQDefaultConfiguration.isDefaultMaskPassword();
private Boolean maskPassword = ActiveMQDefaultConfiguration.isDefaultMaskPassword();
private transient String passwordCodec;
@ -1461,12 +1461,12 @@ public class ConfigurationImpl implements Configuration, Serializable {
}
@Override
public boolean isMaskPassword() {
public Boolean isMaskPassword() {
return maskPassword;
}
@Override
public ConfigurationImpl setMaskPassword(boolean maskPassword) {
public ConfigurationImpl setMaskPassword(Boolean maskPassword) {
this.maskPassword = maskPassword;
return this;
}
@ -1673,7 +1673,6 @@ public class ConfigurationImpl implements Configuration, Serializable {
return false;
if (asyncConnectionExecutionEnabled != other.asyncConnectionExecutionEnabled)
return false;
if (bindingsDirectory == null) {
if (other.bindingsDirectory != null)
return false;
@ -1689,11 +1688,13 @@ public class ConfigurationImpl implements Configuration, Serializable {
return false;
} else if (!broadcastGroupConfigurations.equals(other.broadcastGroupConfigurations))
return false;
if (clusterConfigurations == null) {
if (other.clusterConfigurations != null)
return false;
} else if (!clusterConfigurations.equals(other.clusterConfigurations))
return false;
if (clusterPassword == null) {
if (other.clusterPassword != null)
return false;
@ -1720,6 +1721,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
return false;
if (createJournalDir != other.createJournalDir)
return false;
if (discoveryGroupConfigurations == null) {
if (other.discoveryGroupConfigurations != null)
return false;
@ -1801,8 +1803,15 @@ public class ConfigurationImpl implements Configuration, Serializable {
return false;
} else if (!managementNotificationAddress.equals(other.managementNotificationAddress))
return false;
if (maskPassword != other.maskPassword)
if (this.maskPassword == null) {
if (other.maskPassword != null)
return false;
} else {
if (!this.maskPassword.equals(other.maskPassword))
return false;
}
if (maxConcurrentPageIO != other.maxConcurrentPageIO)
return false;
if (memoryMeasureInterval != other.memoryMeasureInterval)
@ -1824,7 +1833,6 @@ public class ConfigurationImpl implements Configuration, Serializable {
return false;
} else if (!name.equals(other.name))
return false;
if (outgoingInterceptorClassNames == null) {
if (other.outgoingInterceptorClassNames != null)
return false;
@ -1881,6 +1889,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
if (journalDatasync != other.journalDatasync) {
return false;
}
if (globalMaxSize != null && !globalMaxSize.equals(other.globalMaxSize)) {
return false;
}

View File

@ -22,7 +22,6 @@ import java.util.Set;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
@Deprecated
public class FileSecurityConfiguration extends SecurityConfiguration {
@ -31,7 +30,7 @@ public class FileSecurityConfiguration extends SecurityConfiguration {
private final String rolesUrl;
private boolean maskPassword;
private Boolean maskPassword;
private String passwordCodec;
@ -65,14 +64,7 @@ public class FileSecurityConfiguration extends SecurityConfiguration {
if (started) {
return;
}
SensitiveDataCodec<String> codec = null;
if (maskPassword) {
if (passwordCodec != null) {
codec = PasswordMaskingUtil.getDefaultCodec();
} else {
codec = PasswordMaskingUtil.getCodec(passwordCodec);
}
}
URL theUsersUrl = getClass().getClassLoader().getResource(usersUrl);
if (theUsersUrl == null) {
@ -94,9 +86,7 @@ public class FileSecurityConfiguration extends SecurityConfiguration {
for (String username : keys) {
String password = userProps.getProperty(username);
if (codec != null) {
password = codec.decode(password);
}
password = PasswordMaskingUtil.resolveMask(this.maskPassword, password, passwordCodec);
addUser(username, password);
}

View File

@ -80,7 +80,6 @@ import org.apache.activemq.artemis.utils.ByteUtil;
import org.apache.activemq.artemis.utils.ClassloadingUtil;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
import org.apache.activemq.artemis.utils.XMLConfigurationUtil;
import org.apache.activemq.artemis.utils.XMLUtil;
import org.apache.activemq.artemis.utils.critical.CriticalAnalyzerPolicy;
@ -339,7 +338,7 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
config.setManagementNotificationAddress(new SimpleString(getString(e, "management-notification-address", config.getManagementNotificationAddress().toString(), Validators.NOT_NULL_OR_EMPTY)));
config.setMaskPassword(getBoolean(e, "mask-password", false));
config.setMaskPassword(getBoolean(e, "mask-password", null));
config.setPasswordCodec(getString(e, "password-codec", DefaultSensitiveStringCodec.class.getName(), Validators.NOT_NULL_OR_EMPTY));
@ -369,15 +368,11 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
// parsing cluster password
String passwordText = getString(e, "cluster-password", null, Validators.NO_CHECK);
final boolean maskText = config.isMaskPassword();
final Boolean maskText = config.isMaskPassword();
if (passwordText != null) {
if (maskText) {
SensitiveDataCodec<String> codec = PasswordMaskingUtil.getCodec(config.getPasswordCodec());
config.setClusterPassword(codec.decode(passwordText));
} else {
config.setClusterPassword(passwordText);
}
String resolvedPassword = PasswordMaskingUtil.resolveMask(maskText, passwordText, config.getPasswordCodec());
config.setClusterPassword(resolvedPassword);
}
config.setClusterUser(getString(e, "cluster-user", config.getClusterUser(), Validators.NO_CHECK));
@ -1164,13 +1159,11 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
Map<String, Object> params = configurations.get(0).getParams();
if (mainConfig.isMaskPassword()) {
params.put(ActiveMQDefaultConfiguration.getPropMaskPassword(), mainConfig.isMaskPassword());
if (mainConfig.getPasswordCodec() != null) {
params.put(ActiveMQDefaultConfiguration.getPropPasswordCodec(), mainConfig.getPasswordCodec());
}
}
return configurations.get(0);
}
@ -1187,13 +1180,11 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
Map<String, Object> params = configurations.get(0).getParams();
if (mainConfig.isMaskPassword()) {
params.put(ActiveMQDefaultConfiguration.getPropMaskPassword(), mainConfig.isMaskPassword());
if (mainConfig.getPasswordCodec() != null) {
params.put(ActiveMQDefaultConfiguration.getPropPasswordCodec(), mainConfig.getPasswordCodec());
}
}
return configurations.get(0);
}
@ -1720,9 +1711,6 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
NodeList clusterPassNodes = brNode.getElementsByTagName("password");
String password = null;
boolean maskPassword = mainConfig.isMaskPassword();
SensitiveDataCodec<String> codec = null;
if (clusterPassNodes.getLength() > 0) {
Node passNode = clusterPassNodes.item(0);
@ -1730,10 +1718,7 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
}
if (password != null) {
if (maskPassword) {
codec = PasswordMaskingUtil.getCodec(mainConfig.getPasswordCodec());
password = codec.decode(password);
}
password = PasswordMaskingUtil.resolveMask(mainConfig.isMaskPassword(), password, mainConfig.getPasswordCodec());
} else {
password = ActiveMQDefaultConfiguration.getDefaultClusterPassword();
}

View File

@ -58,6 +58,7 @@ import java.util.Queue;
import java.util.Set;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.jboss.logging.Logger;
public class LDAPLoginModule implements LoginModule {
@ -83,6 +84,8 @@ public class LDAPLoginModule implements LoginModule {
private static final String SASL_LOGIN_CONFIG_SCOPE = "saslLoginConfigScope";
private static final String AUTHENTICATE_USER = "authenticateUser";
private static final String REFERRAL = "referral";
private static final String MASK_PASSWORD = "maskPassword";
private static final String PASSWORD_CODEC = "passwordCodec";
protected DirContext context;
@ -97,6 +100,8 @@ public class LDAPLoginModule implements LoginModule {
private boolean isRoleAttributeSet = false;
private String roleAttributeName = null;
private String codecClass = null;
@Override
public void initialize(Subject subject,
CallbackHandler callbackHandler,
@ -105,12 +110,38 @@ public class LDAPLoginModule implements LoginModule {
this.subject = subject;
this.handler = callbackHandler;
config = new LDAPLoginProperty[]{new LDAPLoginProperty(INITIAL_CONTEXT_FACTORY, (String) options.get(INITIAL_CONTEXT_FACTORY)), new LDAPLoginProperty(CONNECTION_URL, (String) options.get(CONNECTION_URL)), new LDAPLoginProperty(CONNECTION_USERNAME, (String) options.get(CONNECTION_USERNAME)), new LDAPLoginProperty(CONNECTION_PASSWORD, (String) options.get(CONNECTION_PASSWORD)), new LDAPLoginProperty(CONNECTION_PROTOCOL, (String) options.get(CONNECTION_PROTOCOL)), new LDAPLoginProperty(AUTHENTICATION, (String) options.get(AUTHENTICATION)), new LDAPLoginProperty(USER_BASE, (String) options.get(USER_BASE)), new LDAPLoginProperty(USER_SEARCH_MATCHING, (String) options.get(USER_SEARCH_MATCHING)), new LDAPLoginProperty(USER_SEARCH_SUBTREE, (String) options.get(USER_SEARCH_SUBTREE)), new LDAPLoginProperty(ROLE_BASE, (String) options.get(ROLE_BASE)), new LDAPLoginProperty(ROLE_NAME, (String) options.get(ROLE_NAME)), new LDAPLoginProperty(ROLE_SEARCH_MATCHING, (String) options.get(ROLE_SEARCH_MATCHING)), new LDAPLoginProperty(ROLE_SEARCH_SUBTREE, (String) options.get(ROLE_SEARCH_SUBTREE)), new LDAPLoginProperty(USER_ROLE_NAME, (String) options.get(USER_ROLE_NAME)), new LDAPLoginProperty(EXPAND_ROLES, (String) options.get(EXPAND_ROLES)), new LDAPLoginProperty(EXPAND_ROLES_MATCHING, (String) options.get(EXPAND_ROLES_MATCHING)), new LDAPLoginProperty(REFERRAL, (String) options.get(REFERRAL))};
config = new LDAPLoginProperty[]{new LDAPLoginProperty(INITIAL_CONTEXT_FACTORY, (String) options.get(INITIAL_CONTEXT_FACTORY)),
new LDAPLoginProperty(CONNECTION_URL, (String) options.get(CONNECTION_URL)),
new LDAPLoginProperty(CONNECTION_USERNAME, (String) options.get(CONNECTION_USERNAME)),
new LDAPLoginProperty(CONNECTION_PASSWORD, (String) options.get(CONNECTION_PASSWORD)),
new LDAPLoginProperty(CONNECTION_PROTOCOL, (String) options.get(CONNECTION_PROTOCOL)),
new LDAPLoginProperty(AUTHENTICATION, (String) options.get(AUTHENTICATION)),
new LDAPLoginProperty(USER_BASE, (String) options.get(USER_BASE)),
new LDAPLoginProperty(USER_SEARCH_MATCHING, (String) options.get(USER_SEARCH_MATCHING)),
new LDAPLoginProperty(USER_SEARCH_SUBTREE, (String) options.get(USER_SEARCH_SUBTREE)),
new LDAPLoginProperty(ROLE_BASE, (String) options.get(ROLE_BASE)),
new LDAPLoginProperty(ROLE_NAME, (String) options.get(ROLE_NAME)),
new LDAPLoginProperty(ROLE_SEARCH_MATCHING, (String) options.get(ROLE_SEARCH_MATCHING)),
new LDAPLoginProperty(ROLE_SEARCH_SUBTREE, (String) options.get(ROLE_SEARCH_SUBTREE)),
new LDAPLoginProperty(USER_ROLE_NAME, (String) options.get(USER_ROLE_NAME)),
new LDAPLoginProperty(EXPAND_ROLES, (String) options.get(EXPAND_ROLES)),
new LDAPLoginProperty(EXPAND_ROLES_MATCHING, (String) options.get(EXPAND_ROLES_MATCHING)),
new LDAPLoginProperty(REFERRAL, (String) options.get(REFERRAL))};
if (isLoginPropertySet(AUTHENTICATE_USER)) {
authenticateUser = Boolean.valueOf(getLDAPPropertyValue(AUTHENTICATE_USER));
}
isRoleAttributeSet = isLoginPropertySet(ROLE_NAME);
roleAttributeName = getLDAPPropertyValue(ROLE_NAME);
codecClass = (String) options.get(PASSWORD_CODEC);
}
private String getPlainPassword(String password) {
try {
return PasswordMaskingUtil.resolveMask(null, password, codecClass);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to decode password", e);
}
}
@Override
@ -511,7 +542,7 @@ public class LDAPLoginModule implements LoginModule {
context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
}
if (isLoginPropertySet(CONNECTION_PASSWORD)) {
context.addToEnvironment(Context.SECURITY_CREDENTIALS, getLDAPPropertyValue(CONNECTION_PASSWORD));
context.addToEnvironment(Context.SECURITY_CREDENTIALS, getPlainPassword(getLDAPPropertyValue(CONNECTION_PASSWORD)));
} else {
context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
}
@ -575,7 +606,7 @@ public class LDAPLoginModule implements LoginModule {
}
if (isLoginPropertySet(CONNECTION_PASSWORD)) {
env.put(Context.SECURITY_CREDENTIALS, getLDAPPropertyValue(CONNECTION_PASSWORD));
env.put(Context.SECURITY_CREDENTIALS, getPlainPassword(getLDAPPropertyValue(CONNECTION_PASSWORD)));
} else {
throw new NamingException("Empty password is not allowed");
}

View File

@ -116,7 +116,7 @@ public class XMLConfigurationUtil {
return getTextBytesAsLongBytes(e, name, def, validator).intValue();
}
public static final Boolean getBoolean(final Element e, final String name, final boolean def) {
public static final Boolean getBoolean(final Element e, final String name, final Boolean def) {
NodeList nl = e.getElementsByTagName(name);
if (nl.getLength() > 0) {
return XMLUtil.parseBoolean(nl.item(0));

View File

@ -19,10 +19,12 @@ package org.apache.activemq.artemis.core.config.impl;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.BridgeConfiguration;
import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.config.FileDeploymentManager;
import org.apache.activemq.artemis.core.config.HAPolicyConfiguration;
@ -32,6 +34,7 @@ import org.apache.activemq.artemis.core.deployers.impl.FileConfigurationParser;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.junit.Assert;
import org.junit.Test;
@ -190,6 +193,85 @@ public class FileConfigurationParserTest extends ActiveMQTestBase {
assertEquals("newpassword", config.getClusterPassword());
}
@Test
public void testParsingDefaultServerConfigWithENCMaskedPwd() throws Exception {
FileConfigurationParser parser = new FileConfigurationParser();
String configStr = firstPart + lastPart;
ByteArrayInputStream input = new ByteArrayInputStream(configStr.getBytes(StandardCharsets.UTF_8));
Configuration config = parser.parseMainConfig(input);
String clusterPassword = config.getClusterPassword();
assertEquals(ActiveMQDefaultConfiguration.getDefaultClusterPassword(), clusterPassword);
//if we add cluster-password, it should be default plain text
String clusterPasswordPart = "<cluster-password>ENC(5aec0780b12bf225a13ab70c6c76bc8e)</cluster-password>";
configStr = firstPart + clusterPasswordPart + lastPart;
config = parser.parseMainConfig(new ByteArrayInputStream(configStr.getBytes(StandardCharsets.UTF_8)));
assertEquals("helloworld", config.getClusterPassword());
//if we add mask, it should be able to decode correctly
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
String mask = (String) codec.encode("helloworld");
clusterPasswordPart = "<cluster-password>" + PasswordMaskingUtil.wrap(mask) + "</cluster-password>";
configStr = firstPart + clusterPasswordPart + lastPart;
config = parser.parseMainConfig(new ByteArrayInputStream(configStr.getBytes(StandardCharsets.UTF_8)));
assertEquals("helloworld", config.getClusterPassword());
//if we change key, it should be able to decode correctly
codec = new DefaultSensitiveStringCodec();
Map<String, String> prop = new HashMap<>();
prop.put("key", "newkey");
codec.init(prop);
mask = (String) codec.encode("newpassword");
clusterPasswordPart = "<cluster-password>" + PasswordMaskingUtil.wrap(mask) + "</cluster-password>";
String codecPart = "<password-codec>" + "org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec" +
";key=newkey</password-codec>";
configStr = firstPart + clusterPasswordPart + codecPart + lastPart;
config = parser.parseMainConfig(new ByteArrayInputStream(configStr.getBytes(StandardCharsets.UTF_8)));
assertEquals("newpassword", config.getClusterPassword());
configStr = firstPart + bridgePart + lastPart;
config = parser.parseMainConfig(new ByteArrayInputStream(configStr.getBytes(StandardCharsets.UTF_8)));
List<BridgeConfiguration> bridgeConfigs = config.getBridgeConfigurations();
assertEquals(1, bridgeConfigs.size());
BridgeConfiguration bconfig = bridgeConfigs.get(0);
assertEquals("helloworld", bconfig.getPassword());
}
private static String bridgePart = "<bridges>\n" +
" <bridge name=\"my-bridge\">\n" +
" <queue-name>sausage-factory</queue-name>\n" +
" <forwarding-address>mincing-machine</forwarding-address>\n" +
" <filter string=\"name='aardvark'\"/>\n" +
" <transformer-class-name>org.apache.activemq.artemis.jms.example.HatColourChangeTransformer</transformer-class-name>\n" +
" <reconnect-attempts>-1</reconnect-attempts>\n" +
" <user>bridge-user</user>" +
" <password>ENC(5aec0780b12bf225a13ab70c6c76bc8e)</password>" +
" <static-connectors>\n" +
" <connector-ref>remote-connector</connector-ref>\n" +
" </static-connectors>\n" +
" </bridge>\n" +
"</bridges>\n";
private static String firstPart = "<core xmlns=\"urn:activemq:core\">" + "\n" +
"<name>ActiveMQ.main.config</name>" + "\n" +
"<log-delegate-factory-class-name>org.apache.activemq.artemis.integration.logging.Log4jLogDelegateFactory</log-delegate-factory-class-name>" + "\n" +

View File

@ -0,0 +1,156 @@
/*
* 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.security.jaas;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)})
@ApplyLdifFiles("test.ldif")
public class LDAPLoginModuleMaskPasswordTest extends AbstractLdapTestUnit {
private final String loginConfigSysPropName = "java.security.auth.login.config";
private String oldLoginConfig;
@Before
public void setLoginConfigSysProperty() {
oldLoginConfig = System.getProperty(loginConfigSysPropName, null);
System.setProperty(loginConfigSysPropName, "src/test/resources/login.config");
}
@After
public void resetLoginConfigSysProperty() {
if (oldLoginConfig != null) {
System.setProperty(loginConfigSysPropName, oldLoginConfig);
}
}
@Test
public void testLoginMaskedPassword() throws LoginException {
LoginContext context = new LoginContext("LDAPLoginMaskedPassword", callbacks -> {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName("first");
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
});
context.login();
context.logout();
}
@Test
public void testLoginMaskedPasswordUnauthenticated() throws LoginException {
LoginContext context = new LoginContext("LDAPLoginMaskedPassword", callbacks -> {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName("first");
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword("nosecret".toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
});
try {
context.login();
} catch (FailedLoginException le) {
assertEquals(le.getMessage(), "Password does not match for user: first");
return;
}
fail("Should have failed authenticating");
}
@Test
public void testLoginExternalCodec() throws LoginException {
LoginContext context = new LoginContext("LDAPLoginExternalPasswordCodec", callbacks -> {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName("first");
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
});
context.login();
context.logout();
}
@Test
public void testLoginExternalCodec2() throws LoginException {
LoginContext context = new LoginContext("LDAPLoginExternalPasswordCodec2", callbacks -> {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName("first");
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
});
context.login();
context.logout();
}
@Test
public void testLoginExternalCodecUnauthenticated() throws LoginException {
LoginContext context = new LoginContext("LDAPLoginExternalPasswordCodec", callbacks -> {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName("first");
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword("nosecret".toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
});
try {
context.login();
} catch (FailedLoginException le) {
assertEquals(le.getMessage(), "Password does not match for user: first");
return;
}
fail("Should have failed authenticating");
}
}

View File

@ -76,6 +76,13 @@ public class PropertiesLoginModuleTest extends Assert {
assertEquals("Should have zero principals", 0, subject.getPrincipals().size());
}
@Test
public void testLoginMasked() throws LoginException {
LoginContext context = new LoginContext("PropertiesLogin", new UserPassHandler("third", "helloworld"));
context.login();
context.logout();
}
@Test
public void testLoginReload() throws Exception {
File targetPropDir = new File("target/loginReloadTest");

View File

@ -125,3 +125,62 @@ OpenLdapConfiguration {
roleSearchSubtree=true
;
};
LDAPLoginMaskedPassword {
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
debug=true
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
connectionURL="ldap://localhost:1024"
connectionUsername="uid=admin,ou=system"
connectionPassword="ENC(-41e444c3ed07d6dd)"
connectionProtocol=s
authentication=simple
userBase="ou=system"
userSearchMatching="(uid={0})"
userSearchSubtree=false
roleBase="ou=system"
roleName=cn
roleSearchMatching="(member=uid={1},ou=system)"
roleSearchSubtree=false
;
};
LDAPLoginExternalPasswordCodec {
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
debug=true
passwordCodec="org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;key=helloworld"
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
connectionURL="ldap://localhost:1024"
connectionUsername="uid=admin,ou=system"
connectionPassword="ENC(-170b9ef34d79ed12)"
connectionProtocol=s
authentication=simple
userBase="ou=system"
userSearchMatching="(uid={0})"
userSearchSubtree=false
roleBase="ou=system"
roleName=dummyRoleName
roleSearchMatching="(uid={1})"
roleSearchSubtree=false
;
};
LDAPLoginExternalPasswordCodec2 {
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
debug=true
passwordCodec="org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec"
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
connectionURL="ldap://localhost:1024"
connectionUsername="uid=admin,ou=system"
connectionPassword="ENC(momsword)"
connectionProtocol=s
authentication=simple
userBase="ou=system"
userSearchMatching="(uid={0})"
userSearchSubtree=false
roleBase="ou=system"
roleName=dummyRoleName
roleSearchMatching="(uid={1})"
roleSearchSubtree=false
;
};

View File

@ -17,3 +17,4 @@
first=secret
second=password
third=ENC(1024:439F45267508BB4F02150B9AAFB8D774AB7FCDDE9B19D096F318787487F7BD17:78818E53AEE8AF26A34A38C1498D1CDA5636861D2FE9804FEDE3656D0BC05696191A027F095DF109EC8F6385FAE9971915449EC808945A0F5907B29D5F9D44B7)

View File

@ -68,6 +68,13 @@
<artifactId>artemis-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-commons</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>

View File

@ -68,12 +68,12 @@ public class WebServerComponent implements ExternalComponent {
if ("https".equals(scheme)) {
SslContextFactory sslFactory = new SslContextFactory();
sslFactory.setKeyStorePath(webServerConfig.keyStorePath == null ? artemisInstance + "/etc/keystore.jks" : webServerConfig.keyStorePath);
sslFactory.setKeyStorePassword(webServerConfig.keyStorePassword == null ? "password" : webServerConfig.keyStorePassword);
sslFactory.setKeyStorePassword(webServerConfig.getKeyStorePassword() == null ? "password" : webServerConfig.getKeyStorePassword());
if (webServerConfig.clientAuth != null) {
sslFactory.setNeedClientAuth(webServerConfig.clientAuth);
if (webServerConfig.clientAuth) {
sslFactory.setTrustStorePath(webServerConfig.trustStorePath);
sslFactory.setTrustStorePassword(webServerConfig.trustStorePassword);
sslFactory.setTrustStorePassword(webServerConfig.getTrustStorePassword());
}
}

View File

@ -18,6 +18,7 @@ package org.apache.activemq.cli.test;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
@ -43,9 +44,11 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.CharsetUtil;
import org.apache.activemq.artemis.cli.factory.xml.XmlBrokerFactoryHandler;
import org.apache.activemq.artemis.component.WebServerComponent;
import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.dto.BrokerDTO;
import org.apache.activemq.artemis.dto.WebServerDTO;
import org.junit.After;
import org.junit.Assert;
@ -163,7 +166,7 @@ public class WebServerComponentTest extends Assert {
webServerDTO.bind = "https://localhost:0";
webServerDTO.path = "webapps";
webServerDTO.keyStorePath = "./src/test/resources/server.keystore";
webServerDTO.keyStorePassword = "password";
webServerDTO.setKeyStorePassword("password");
WebServerComponent webServerComponent = new WebServerComponent();
Assert.assertFalse(webServerComponent.isStarted());
@ -174,7 +177,7 @@ public class WebServerComponentTest extends Assert {
// Make the connection attempt.
String keyStoreProvider = "JKS";
SSLContext context = SSLSupport.createContext(keyStoreProvider, webServerDTO.keyStorePath, webServerDTO.keyStorePassword, keyStoreProvider, webServerDTO.keyStorePath, webServerDTO.keyStorePassword);
SSLContext context = SSLSupport.createContext(keyStoreProvider, webServerDTO.keyStorePath, webServerDTO.getKeyStorePassword(), keyStoreProvider, webServerDTO.keyStorePath, webServerDTO.getKeyStorePassword());
SSLEngine engine = context.createSSLEngine();
engine.setUseClientMode(true);
@ -215,10 +218,10 @@ public class WebServerComponentTest extends Assert {
webServerDTO.bind = "https://localhost:0";
webServerDTO.path = "webapps";
webServerDTO.keyStorePath = "./src/test/resources/server.keystore";
webServerDTO.keyStorePassword = "password";
webServerDTO.setKeyStorePassword("password");
webServerDTO.clientAuth = true;
webServerDTO.trustStorePath = "./src/test/resources/server.keystore";
webServerDTO.trustStorePassword = "password";
webServerDTO.setTrustStorePassword("password");
WebServerComponent webServerComponent = new WebServerComponent();
Assert.assertFalse(webServerComponent.isStarted());
@ -229,7 +232,7 @@ public class WebServerComponentTest extends Assert {
// Make the connection attempt.
String keyStoreProvider = "JKS";
SSLContext context = SSLSupport.createContext(keyStoreProvider, webServerDTO.keyStorePath, webServerDTO.keyStorePassword, keyStoreProvider, webServerDTO.trustStorePath, webServerDTO.trustStorePassword);
SSLContext context = SSLSupport.createContext(keyStoreProvider, webServerDTO.keyStorePath, webServerDTO.getKeyStorePassword(), keyStoreProvider, webServerDTO.trustStorePath, webServerDTO.getTrustStorePassword());
SSLEngine engine = context.createSSLEngine();
engine.setUseClientMode(true);
@ -264,6 +267,44 @@ public class WebServerComponentTest extends Assert {
Assert.assertFalse(webServerComponent.isStarted());
}
@Test
public void testDefaultMaskPasswords() throws Exception {
File bootstrap = new File("./target/test-classes/bootstrap_web.xml");
File brokerHome = new File("./target");
XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler();
BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI());
assertNotNull(broker.web);
assertNull(broker.web.passwordCodec);
}
@Test
public void testMaskPasswords() throws Exception {
final String keyPassword = "keypass";
final String trustPassword = "trustpass";
File bootstrap = new File("./target/test-classes/bootstrap_secure_web.xml");
File brokerHome = new File("./target");
XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler();
BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI());
assertNotNull(broker.web);
assertEquals(keyPassword, broker.web.getKeyStorePassword());
assertEquals(trustPassword, broker.web.getTrustStorePassword());
}
@Test
public void testMaskPasswordCodec() throws Exception {
final String keyPassword = "keypass";
final String trustPassword = "trustpass";
File bootstrap = new File("./target/test-classes/bootstrap_web_codec.xml");
File brokerHome = new File("./target");
XmlBrokerFactoryHandler xmlHandler = new XmlBrokerFactoryHandler();
BrokerDTO broker = xmlHandler.createBroker(bootstrap.toURI(), brokerHome.getAbsolutePath(), brokerHome.getAbsolutePath(), brokerHome.toURI());
assertNotNull(broker.web);
assertNotNull("password codec not picked up!", broker.web.passwordCodec);
assertEquals(keyPassword, broker.web.getKeyStorePassword());
assertEquals(trustPassword, broker.web.getTrustStorePassword());
}
class ClientHandler extends SimpleChannelInboundHandler<HttpObject> {
private CountDownLatch latch;

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ 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.
-->
<broker xmlns="http://activemq.org/schema">
<jaas-security domain="activemq"/>
<!-- 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 -->
<server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<!-- The web server is only bound to localhost by default -->
<web bind="https://localhost:8443" path="web" keyStorePassword="ENC(-5a2376c61c668aaf)" trustStorePassword="ENC(3d617352d12839eb71208edf41d66b34)">
<app url="activemq-branding" war="activemq-branding.war"/>
</web>
</broker>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ 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.
-->
<broker xmlns="http://activemq.org/schema">
<jaas-security domain="activemq"/>
<!-- 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 -->
<server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<!-- The web server is only bound to localhost by default -->
<web bind="http://localhost:8161" path="web">
<app url="activemq-branding" war="activemq-branding.war"/>
</web>
</broker>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ 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.
-->
<broker xmlns="http://activemq.org/schema">
<jaas-security domain="activemq"/>
<!-- 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 -->
<server configuration="${artemis.URI.instance}/etc/broker.xml"/>
<!-- The web server is only bound to localhost by default -->
<web bind="https://localhost:8443" path="web" passwordCodec="org.apache.activemq.artemis.utils.MaskPasswordResolvingTest$SimplePasswordCodec" keyStorePassword="ENC(youneverknow)" trustStorePassword="ENC(youcanguess)">
<app url="console" war="console.war"/>
</web>
</broker>

View File

@ -18,12 +18,33 @@ Apache ActiveMQ Artemis provides a default password encoder and decoder. Optiona
users can use or implement their own encoder and decoder for masking the
passwords.
In general, a masked password can be identified using one of two ways. The first one
is the ENC() syntax, i.e. any string value wrapped in ENC() is to be treated as
a masked password. For example
`ENC(xyz)`
The above indicates that the password is masked and the masked value is `xyz`.
The ENC() syntax is the preferred way to indicating a masked password and is
universally supported in every password configuration in Artemis.
The other way is to use a `mask-password` attribute to tell that a password
in a configuration file should be treated as 'masked'. For example:
```
<mask-password>true</mask-password>
<cluster-password>xyz</cluster-password>
```
This method is now deprecated and exists only to maintain backward-compatibility.
Newer configurations may not support it.
### Password Masking in Server Configuration File
#### General Masking Configuration
The server configuration file (i.e. broker.xml )has a property that defines the
default masking behaviors over the entire file scope.
Besides supporting the ENC() syntax, the server configuration file (i.e. broker.xml)
has a property that defines the default masking behaviors over the entire file scope.
`mask-password`: this boolean type property indicates if a password
should be masked or not. Set it to "true" if you want your passwords
@ -38,6 +59,8 @@ will be used.
##### cluster-password
If it is specified in ENC() syntax it will be treated as masked, or
If `mask-password` is `true` the `cluster-password` will be treated as masked.
##### Passwords in connectors and acceptors
@ -55,16 +78,16 @@ and `activemq.passwordcodec` respectively. The Netty and InVM implementations
will use these as needed and any other implementations will have access to
these to use if they so wish.
The preferred way, however, is to use the ENC() syntax.
##### Passwords in bridge configurations
Core Bridges are configured in the server configuration file and so the
masking of its `password` properties follows the same rules as that of
`cluster-password`.
`cluster-password`. It supports ENC() syntax.
#### Examples
The following table summarizes the relations among the above-mentioned
properties
For using 'mask-password' property, the following table summarizes the
relations among the above-mentioned properties
mask-password | cluster-password | acceptor/connector passwords | bridge password
:------------- | :---------------- | :--------------------------- | :---------------
@ -72,7 +95,9 @@ properties
false | plain text | plain text | plain text
true | masked | masked | masked
Examples
It is recommended that you use the ENC() syntax for new applications/deployments.
#### Examples
Note: In the following examples if related attributed or properties are
absent, it means they are not specified in the configure file.
@ -87,6 +112,14 @@ This indicates the cluster password is a plain text value ("bbc").
example 2
```xml
<cluster-password>ENC(xyz)</cluster-password>
```
This indicates the cluster password is a masked value ("xyz").
example 3
```xml
<mask-password>true</mask-password>
<cluster-password>80cf731af62c290</cluster-password>
@ -97,27 +130,75 @@ use its built-in decoder to decode it. All other passwords in the
configuration file, Connectors, Acceptors and Bridges, will also use
masked passwords.
#### Passwords in bootstrap.xml
The broker embeds a web-server for hosting some web applications such as a
management console. It is configured in bootstrap.xml as a web
component. The web server can be secured using https protocol, and it can be
configured with a keystore password and/or truststore password which by
default are specified in plain text forms.
To mask these passwords you need to use ENC() syntax. The `mask-password` is
not supported here.
You can also set the `passwordCodec` attribute if you want to use a password codec
other than the default one. For example
```xml
<web bind="https://localhost:8443" path="web"
keyStorePassword="ENC(-5a2376c61c668aaf)"
trustStorePassword="ENC(3d617352d12839eb71208edf41d66b34)">
<app url="activemq-branding" war="activemq-branding.war"/>
</web>
```
### Masking passwords in ActiveMQ Artemis JCA ResourceAdapter and MDB activation configurations
Both ra.xml and MDB activation configuration have a `password` property
that can be masked. They are controlled by the following two optional
Resource Adapter properties in ra.xml:
that can be masked preferably using ENC() syntax.
Alternatively it can use a optional attribute in ra.xml to indicate that a password
is masked:
`UseMaskedPassword` -- If setting to "true" the passwords are masked.
Default is false.
There is another property in ra.xml that can specify a codec:
`PasswordCodec` -- Class name and its parameters for the Decoder used to
decode the masked password. Ignored if UseMaskedPassword is false. The
format of this property is a full qualified class name optionally
followed by key/value pairs. It is the same format as that for JMS
Bridges. Example:
Example 1 Using the ENC() syntax:
```xml
<config-property>
<config-property-name>password</config-property-name>
<config-property-type>String</config-property-type>
<config-property-value>ENC(xyz)</config-property-value>
</config-property>
<config-property>
<config-property-name>PasswordCodec</config-property-name>
<config-property-type>java.lang.String</config-property-type>
<config-property-value>com.foo.ADecoder;key=helloworld</config-property-value>
</config-property>
```
Example 2 Using the "UseMaskedPassword" property:
```xml
<config-property>
<config-property-name>UseMaskedPassword</config-property-name>
<config-property-type>boolean</config-property-type>
<config-property-value>true</config-property-value>
</config-property>
<config-property>
<config-property-name>password</config-property-name>
<config-property-type>String</config-property-type>
<config-property-value>xyz</config-property-value>
</config-property>
<config-property>
<config-property-name>PasswordCodec</config-property-name>
<config-property-type>java.lang.String</config-property-type>
@ -150,6 +231,43 @@ Passwords in `artemis-users.properties` are automatically detected as hashed or
by looking for the syntax `ENC(<hash>)`. The `mask-password` parameter does not need
to be `true` to use hashed passwords here.
### Masking password in JAAS login config file (login.config)
Artemis supports LDAP login modules to be configured in JAAS configuration
file (default name is `login.config`). When connecting to a LDAP server usually
you need to supply a connection password in the config file. By default this
password is in plain text form.
To mask it you need to configure the passwords in your login module
using ENC() syntax. To specify a codec using the following property:
`passwordCodec` - the password codec class name. (the default codec
will be used if it is absent)
For example:
```
LDAPLoginExternalPasswordCodec {
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
debug=true
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
connectionURL="ldap://localhost:1024"
connectionUsername="uid=admin,ou=system"
connectionPassword="ENC(-170b9ef34d79ed12)"
passwordCodec="org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;key=helloworld"
connectionProtocol=s
authentication=simple
userBase="ou=system"
userSearchMatching="(uid={0})"
userSearchSubtree=false
roleBase="ou=system"
roleName=dummyRoleName
roleSearchMatching="(uid={1})"
roleSearchSubtree=false
;
};
```
### Choosing a decoder for password masking
As described in the previous sections, all password masking requires a
@ -206,8 +324,7 @@ pairs when configuring. For instance if your decoder needs say a
Then configure your cluster-password like this:
```xml
<mask-password>true</mask-password>
<cluster-password>masked_password</cluster-password>
<cluster-password>ENC(masked_password)</cluster-password>
```
When Apache ActiveMQ Artemis reads the cluster-password it will initialize the

View File

@ -41,6 +41,7 @@ import org.apache.activemq.artemis.service.extensions.xa.recovery.XARecoveryConf
import org.apache.activemq.artemis.tests.unit.ra.BootstrapContext;
import org.apache.activemq.artemis.tests.unit.ra.MessageEndpointFactory;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.junit.Test;
public class ResourceAdapterTest extends ActiveMQRATestBase {
@ -565,7 +566,7 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
ActiveMQRATestBase.MyBootstrapContext ctx = new ActiveMQRATestBase.MyBootstrapContext();
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
String mask = (String) codec.encode("helloworld");
String mask = codec.encode("helloworld");
qResourceAdapter.setUseMaskedPassword(true);
qResourceAdapter.setPassword(mask);
@ -594,6 +595,41 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
assertTrue(endpoint.released);
}
@Test
public void testMaskPasswordENC() throws Exception {
ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter();
qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY);
ActiveMQRATestBase.MyBootstrapContext ctx = new ActiveMQRATestBase.MyBootstrapContext();
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
String mask = codec.encode("helloworld");
qResourceAdapter.setPassword(PasswordMaskingUtil.wrap(mask));
qResourceAdapter.start(ctx);
assertEquals("helloworld", qResourceAdapter.getPassword());
ActiveMQActivationSpec spec = new ActiveMQActivationSpec();
spec.setResourceAdapter(qResourceAdapter);
spec.setUseJNDI(false);
spec.setDestinationType("javax.jms.Queue");
spec.setDestination(MDBQUEUE);
mask = codec.encode("mdbpassword");
spec.setPassword(PasswordMaskingUtil.wrap(mask));
qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY);
CountDownLatch latch = new CountDownLatch(1);
DummyMessageEndpoint endpoint = new DummyMessageEndpoint(latch);
DummyMessageEndpointFactory endpointFactory = new DummyMessageEndpointFactory(endpoint, false);
qResourceAdapter.endpointActivation(endpointFactory, spec);
assertEquals("mdbpassword", spec.getPassword());
qResourceAdapter.stop();
assertTrue(endpoint.released);
}
@Test
public void testMaskPassword2() throws Exception {
ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter();
@ -609,7 +645,7 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
prop.put("key", "anotherkey");
codec.init(prop);
String mask = (String) codec.encode("helloworld");
String mask = codec.encode("helloworld");
qResourceAdapter.setPassword(mask);
@ -623,7 +659,7 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
spec.setDestinationType("javax.jms.Queue");
spec.setDestination(MDBQUEUE);
mask = (String) codec.encode("mdbpassword");
mask = codec.encode("mdbpassword");
spec.setPassword(mask);
qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY);
CountDownLatch latch = new CountDownLatch(1);
@ -637,6 +673,48 @@ public class ResourceAdapterTest extends ActiveMQRATestBase {
assertTrue(endpoint.released);
}
@Test
public void testMaskPassword2ENC() throws Exception {
ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter();
qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY);
ActiveMQRATestBase.MyBootstrapContext ctx = new ActiveMQRATestBase.MyBootstrapContext();
qResourceAdapter.setPasswordCodec(DefaultSensitiveStringCodec.class.getName() + ";key=anotherkey");
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
Map<String, String> prop = new HashMap<>();
prop.put("key", "anotherkey");
codec.init(prop);
String mask = codec.encode("helloworld");
qResourceAdapter.setPassword(PasswordMaskingUtil.wrap(mask));
qResourceAdapter.start(ctx);
assertEquals("helloworld", qResourceAdapter.getPassword());
ActiveMQActivationSpec spec = new ActiveMQActivationSpec();
spec.setResourceAdapter(qResourceAdapter);
spec.setUseJNDI(false);
spec.setDestinationType("javax.jms.Queue");
spec.setDestination(MDBQUEUE);
mask = codec.encode("mdbpassword");
spec.setPassword(PasswordMaskingUtil.wrap(mask));
qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY);
CountDownLatch latch = new CountDownLatch(1);
DummyMessageEndpoint endpoint = new DummyMessageEndpoint(latch);
DummyMessageEndpointFactory endpointFactory = new DummyMessageEndpointFactory(endpoint, false);
qResourceAdapter.endpointActivation(endpointFactory, spec);
assertEquals("mdbpassword", spec.getPassword());
qResourceAdapter.stop();
assertTrue(endpoint.released);
}
@Test
public void testConnectionParameterStringParsing() throws Exception {
ActiveMQResourceAdapter resourceAdapter = new ActiveMQResourceAdapter();

View File

@ -287,6 +287,45 @@ public class JMSBridgeImplTest extends ActiveMQTestBase {
* we receive only 1 message. The message is sent when the maxBatchTime
* expires even if the maxBatchSize is not reached
*/
@Test
public void testBridgeWithMaskPasswords() throws Exception {
ConnectionFactoryFactory sourceCFF = JMSBridgeImplTest.newConnectionFactoryFactory(JMSBridgeImplTest.createConnectionFactory());
ConnectionFactoryFactory targetCFF = JMSBridgeImplTest.newConnectionFactoryFactory(JMSBridgeImplTest.createConnectionFactory());
DestinationFactory sourceDF = JMSBridgeImplTest.newDestinationFactory(ActiveMQJMSClient.createQueue(JMSBridgeImplTest.SOURCE));
DestinationFactory targetDF = JMSBridgeImplTest.newDestinationFactory(ActiveMQJMSClient.createQueue(JMSBridgeImplTest.TARGET));
TransactionManager tm = JMSBridgeImplTest.newTransactionManager();
JMSBridgeImpl bridge = new JMSBridgeImpl();
Assert.assertNotNull(bridge);
bridge.setSourceConnectionFactoryFactory(sourceCFF);
bridge.setSourceDestinationFactory(sourceDF);
bridge.setTargetConnectionFactoryFactory(targetCFF);
bridge.setTargetDestinationFactory(targetDF);
bridge.setFailureRetryInterval(10);
bridge.setMaxRetries(1);
bridge.setMaxBatchSize(1);
bridge.setMaxBatchTime(-1);
bridge.setTransactionManager(tm);
bridge.setQualityOfServiceMode(QualityOfServiceMode.AT_MOST_ONCE);
bridge.setSourceUsername("sourceuser");
bridge.setSourcePassword("ENC(5493dd76567ee5ec269d11823973462f)");
bridge.setTargetUsername("targetuser");
bridge.setTargetPassword("ENC(56a0db3b71043054269d11823973462f)");
Assert.assertFalse(bridge.isStarted());
bridge.start();
Assert.assertTrue(bridge.isStarted());
assertEquals("sourcepassword", bridge.getSourcePassword());
assertEquals("targetpassword", bridge.getTargetPassword());
bridge.stop();
Assert.assertFalse(bridge.isStarted());
}
@Test
public void testSendMessagesWhenMaxBatchTimeExpires() throws Exception {
int maxBatchSize = 2;