ARTEMIS-4274 push masked credential support into core client
This commit is contained in:
parent
5f476896e9
commit
c9c819aa88
|
@ -824,4 +824,8 @@ public interface ServerLocator extends AutoCloseable {
|
|||
ServerLocatorConfig getLocatorConfig();
|
||||
|
||||
void setLocatorConfig(ServerLocatorConfig serverLocatorConfig);
|
||||
|
||||
ServerLocator setPasswordCodec(String passwordCodec);
|
||||
|
||||
String getPasswordCodec();
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||
import org.apache.activemq.artemis.api.config.ServerLocatorConfig;
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQException;
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
|
||||
import org.apache.activemq.artemis.api.core.DisconnectReason;
|
||||
|
@ -67,6 +68,7 @@ import org.apache.activemq.artemis.spi.core.remoting.TopologyResponseHandler;
|
|||
import org.apache.activemq.artemis.utils.ClassloadingUtil;
|
||||
import org.apache.activemq.artemis.utils.ConfirmationWindowWarning;
|
||||
import org.apache.activemq.artemis.utils.ExecutorFactory;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
import org.apache.activemq.artemis.utils.UUIDGenerator;
|
||||
import org.apache.activemq.artemis.utils.actors.OrderedExecutorFactory;
|
||||
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
|
||||
|
@ -738,16 +740,25 @@ public class ClientSessionFactoryImpl implements ClientSessionFactoryInternal, C
|
|||
}
|
||||
}
|
||||
|
||||
private ClientSession createSessionInternal(final String username,
|
||||
final String password,
|
||||
private ClientSession createSessionInternal(final String rawUsername,
|
||||
final String rawPassword,
|
||||
final boolean xa,
|
||||
final boolean autoCommitSends,
|
||||
final boolean autoCommitAcks,
|
||||
final boolean preAcknowledge,
|
||||
final int ackBatchSize,
|
||||
final String clientID) throws ActiveMQException {
|
||||
String username;
|
||||
String password;
|
||||
String name = UUIDGenerator.getInstance().generateStringUUID();
|
||||
|
||||
try {
|
||||
username = PasswordMaskingUtil.resolveMask(rawUsername, serverLocator.getPasswordCodec());
|
||||
password = PasswordMaskingUtil.resolveMask(rawPassword, serverLocator.getPasswordCodec());
|
||||
} catch (Exception e) {
|
||||
throw new ActiveMQException(e.getMessage(), e, ActiveMQExceptionType.GENERIC_EXCEPTION);
|
||||
}
|
||||
|
||||
SessionContext context = createSessionChannel(name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, clientID);
|
||||
|
||||
ClientSessionInternal session = new ClientSessionImpl(this, name, username, password, xa, autoCommitSends, autoCommitAcks, preAcknowledge, serverLocator.isBlockOnAcknowledge(), serverLocator.isAutoGroup(), ackBatchSize, serverLocator.getConsumerWindowSize(), serverLocator.getConsumerMaxRate(), serverLocator.getConfirmationWindowSize(), serverLocator.getProducerWindowSize(), serverLocator.getProducerMaxRate(), serverLocator.isBlockOnNonDurableSend(), serverLocator.isBlockOnDurableSend(), serverLocator.isCacheLargeMessagesClient(), serverLocator.getMinLargeMessageSize(), serverLocator.isCompressLargeMessage(), serverLocator.getInitialMessagePacketSize(), serverLocator.getGroupID(), context, orderedExecutorFactory.getExecutor(), orderedExecutorFactory.getExecutor(), orderedExecutorFactory.getExecutor(), orderedExecutorFactory.getExecutor());
|
||||
|
|
|
@ -182,6 +182,8 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
|
|||
|
||||
private ServerLocatorConfig config = new ServerLocatorConfig();
|
||||
|
||||
private String passwordCodec;
|
||||
|
||||
public static synchronized void clearThreadPools() {
|
||||
ActiveMQClient.clearThreadPools();
|
||||
}
|
||||
|
@ -301,6 +303,17 @@ public final class ServerLocatorImpl implements ServerLocatorInternal, Discovery
|
|||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLocator setPasswordCodec(String passwordCodec) {
|
||||
this.passwordCodec = passwordCodec;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPasswordCodec() {
|
||||
return this.passwordCodec;
|
||||
}
|
||||
|
||||
private static DiscoveryGroup createDiscoveryGroup(String nodeID,
|
||||
DiscoveryGroupConfiguration config) throws Exception {
|
||||
return new DiscoveryGroup(nodeID, config.getName(), config.getRefreshTimeout(), config.getBroadcastEndpointFactory(), null);
|
||||
|
|
|
@ -59,7 +59,6 @@ import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManagerFactor
|
|||
import org.apache.activemq.artemis.uri.ConnectionFactoryParser;
|
||||
import org.apache.activemq.artemis.uri.ServerLocatorParser;
|
||||
import org.apache.activemq.artemis.utils.ClassloadingUtil;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
import org.apache.activemq.artemis.utils.uri.BeanSupport;
|
||||
import org.apache.activemq.artemis.utils.uri.URISupport;
|
||||
|
||||
|
@ -830,12 +829,12 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
|
|||
}
|
||||
|
||||
public String getPasswordCodec() {
|
||||
return passwordCodec;
|
||||
return serverLocator.getPasswordCodec();
|
||||
}
|
||||
|
||||
public ActiveMQConnectionFactory setPasswordCodec(String passwordCodec) {
|
||||
checkWrite();
|
||||
this.passwordCodec = passwordCodec;
|
||||
serverLocator.setPasswordCodec(passwordCodec);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -870,21 +869,8 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
|
|||
return JMSFactoryType.CF.intValue();
|
||||
}
|
||||
|
||||
private String unmaskSensitiveString(String secret) throws JMSException {
|
||||
try {
|
||||
return PasswordMaskingUtil.resolveMask(secret, passwordCodec);
|
||||
} catch (Exception e) {
|
||||
JMSException jmse = new JMSException("Failed to resolve masked sensitive string");
|
||||
|
||||
jmse.initCause(e);
|
||||
jmse.setLinkedException(e);
|
||||
|
||||
throw jmse;
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized ActiveMQConnection createConnectionInternal(final String rawUsername,
|
||||
final String rawPassword,
|
||||
protected synchronized ActiveMQConnection createConnectionInternal(final String username,
|
||||
final String password,
|
||||
final boolean isXA,
|
||||
final int type) throws JMSException {
|
||||
makeReadOnly();
|
||||
|
@ -903,8 +889,6 @@ public class ActiveMQConnectionFactory extends JNDIStorable implements Connectio
|
|||
}
|
||||
|
||||
ActiveMQConnection connection = null;
|
||||
final String username = unmaskSensitiveString(rawUsername);
|
||||
final String password = unmaskSensitiveString(rawPassword);
|
||||
|
||||
if (isXA) {
|
||||
if (type == ActiveMQConnection.TYPE_GENERIC_CONNECTION) {
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.activemq.artemis.tests.integration.security;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URL;
|
||||
|
||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
|
||||
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
|
||||
import org.apache.activemq.artemis.api.core.client.ServerLocator;
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServers;
|
||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
||||
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MaskedCredentialsTest extends ActiveMQTestBase {
|
||||
|
||||
static {
|
||||
String path = System.getProperty("java.security.auth.login.config");
|
||||
if (path == null) {
|
||||
URL resource = MaskedCredentialsTest.class.getClassLoader().getResource("login.config");
|
||||
if (resource != null) {
|
||||
path = resource.getFile();
|
||||
System.setProperty("java.security.auth.login.config", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ServerLocator locator;
|
||||
|
||||
ClientSessionFactory cf;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("PropertiesLogin");
|
||||
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||
server.start();
|
||||
|
||||
locator = createInVMNonHALocator();
|
||||
cf = createSessionFactory(locator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaskedCredentials() throws Exception {
|
||||
addClientSession(cf.createSession(getMaskedCredential("first"), getMaskedCredential("secret"), false, true, true, false, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaskedCredentialsWithCustomCodec() throws Exception {
|
||||
testMaskedCredentialsWithCustomCodec("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaskedCredentialsWithCustomCodecNegative() throws Exception {
|
||||
try {
|
||||
testMaskedCredentialsWithCustomCodec("xxx");
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private void testMaskedCredentialsWithCustomCodec(String password) throws Exception {
|
||||
ServerLocator locator = createInVMNonHALocator();
|
||||
locator.setPasswordCodec(DummyCodec.class.getName());
|
||||
testMaskedCredentialsWithCustomCodec(locator, password);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaskedCredentialsWithCustomCodecURL() throws Exception {
|
||||
testMaskedCredentialsWithCustomCodecURL("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaskedCredentialsWithCustomCodecNegativeURL() throws Exception {
|
||||
try {
|
||||
testMaskedCredentialsWithCustomCodecURL("xxx");
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private void testMaskedCredentialsWithCustomCodecURL(String password) throws Exception {
|
||||
ServerLocator locator = ActiveMQClient.createServerLocator("vm://0?passwordCodec=org.apache.activemq.artemis.tests.integration.security.MaskedCredentialsTest.DummyCodec");
|
||||
locator.setPasswordCodec(DummyCodec.class.getName());
|
||||
testMaskedCredentialsWithCustomCodec(locator, password);
|
||||
}
|
||||
|
||||
private void testMaskedCredentialsWithCustomCodec(ServerLocator locator, String password) throws Exception {
|
||||
cf = createSessionFactory(locator);
|
||||
String maskedPassword = PasswordMaskingUtil.wrap(PasswordMaskingUtil.resolveMask(password, DummyCodec.class.getName()));
|
||||
addClientSession(cf.createSession("first", maskedPassword, false, true, true, false, 0));
|
||||
}
|
||||
|
||||
private String getMaskedCredential(String credential) throws Exception {
|
||||
return PasswordMaskingUtil.wrap(PasswordMaskingUtil.getDefaultCodec().encode(credential));
|
||||
}
|
||||
|
||||
public static class DummyCodec implements SensitiveDataCodec<String> {
|
||||
|
||||
private static final String MASK = "===";
|
||||
private static final String CLEARTEXT = "secret";
|
||||
|
||||
@Override
|
||||
public String decode(Object mask) throws Exception {
|
||||
if (!MASK.equals(mask)) {
|
||||
return mask.toString();
|
||||
}
|
||||
return CLEARTEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(Object secret) throws Exception {
|
||||
if (!CLEARTEXT.equals(secret)) {
|
||||
return secret.toString();
|
||||
}
|
||||
return MASK;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue