ARTEMIS-1600 Support masked passwords in bootstrap.xm and login.config
We provide a feature to mask passwords in the configuration files. However, passwords in the bootstrap.xml (when the console is secured with HTTPS) cannot be masked. This enhancement has been opened to allow passwords in the bootstrap.xml to be masked using the built-in masking feature provided by the broker. Also the LDAPLoginModule configuration (in login.config) has a connection password attribute that also needs this mask support. In addition the ENC() syntax is supported for password masking to replace the old 'mask-password' flag.
This commit is contained in:
parent
1d31227cdb
commit
bb84f67936
|
@ -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]+$"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
;
|
||||
};
|
||||
|
|
|
@ -17,3 +17,4 @@
|
|||
|
||||
first=secret
|
||||
second=password
|
||||
third=ENC(1024:439F45267508BB4F02150B9AAFB8D774AB7FCDDE9B19D096F318787487F7BD17:78818E53AEE8AF26A34A38C1498D1CDA5636861D2FE9804FEDE3656D0BC05696191A027F095DF109EC8F6385FAE9971915449EC808945A0F5907B29D5F9D44B7)
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue