ARTEMIS-4274 push masked credential support into core client

This commit is contained in:
Justin Bertram 2023-05-08 19:44:32 -05:00 committed by clebertsuconic
parent 5f476896e9
commit c9c819aa88
5 changed files with 176 additions and 22 deletions

View File

@ -824,4 +824,8 @@ public interface ServerLocator extends AutoCloseable {
ServerLocatorConfig getLocatorConfig();
void setLocatorConfig(ServerLocatorConfig serverLocatorConfig);
ServerLocator setPasswordCodec(String passwordCodec);
String getPasswordCodec();
}

View File

@ -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());

View File

@ -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);

View File

@ -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) {

View File

@ -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;
}
}
}