ARTEMIS-3794 System Property Encryption Support

Adds support for standard Java TLS and ActiveMQ Artemis-specific override
encrypted system property values for the key store and trust store
passwords, including a separate codec property
This commit is contained in:
Ryan Highley 2022-06-27 20:54:24 -05:00 committed by Justin Bertram
parent 30f6d80696
commit 51c1504b05
3 changed files with 256 additions and 5 deletions

View File

@ -123,6 +123,7 @@ import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvid
import org.apache.activemq.artemis.utils.ConfigurationHelper;
import org.apache.activemq.artemis.utils.FutureLatch;
import org.apache.activemq.artemis.utils.IPV6Util;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
@ -153,6 +154,7 @@ public class NettyConnector extends AbstractConnector {
public static final String ACTIVEMQ_TRUSTSTORE_TYPE_PROP_NAME = "org.apache.activemq.ssl.trustStoreType";
public static final String ACTIVEMQ_TRUSTSTORE_PATH_PROP_NAME = "org.apache.activemq.ssl.trustStore";
public static final String ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME = "org.apache.activemq.ssl.trustStorePassword";
public static final String ACTIVEMQ_SSL_PASSWORD_CODEC_CLASS_PROP_NAME = "org.apache.activemq.ssl.passwordCodec";
// Constants for HTTP upgrade
// These constants are exposed publicly as they are used on the server-side to fetch
@ -178,7 +180,6 @@ public class NettyConnector extends AbstractConnector {
DEFAULT_CONFIG = Collections.unmodifiableMap(config);
}
private final boolean serverConnection;
private Class<? extends Channel> channelClazz;
@ -230,6 +231,8 @@ public class NettyConnector extends AbstractConnector {
private int localPort;
private String passwordCodecClass;
private String keyStoreProvider;
private String keyStoreType;
@ -324,6 +327,7 @@ public class NettyConnector extends AbstractConnector {
final ClientProtocolManager protocolManager) {
this(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager, false);
}
public NettyConnector(final Map<String, Object> configuration,
final BufferHandler handler,
final BaseConnectionLifeCycleListener<?> listener,
@ -395,6 +399,8 @@ public class NettyConnector extends AbstractConnector {
localPort = ConfigurationHelper.getIntProperty(TransportConstants.LOCAL_PORT_PROP_NAME, TransportConstants.DEFAULT_LOCAL_PORT, configuration);
if (sslEnabled) {
passwordCodecClass = ConfigurationHelper.getStringProperty(ActiveMQDefaultConfiguration.getPropPasswordCodec(), TransportConstants.DEFAULT_PASSWORD_CODEC_CLASS, configuration);
keyStoreProvider = ConfigurationHelper.getStringProperty(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_PROVIDER, configuration);
keyStoreType = ConfigurationHelper.getStringProperty(TransportConstants.KEYSTORE_TYPE_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_TYPE, configuration);
@ -438,6 +444,7 @@ public class NettyConnector extends AbstractConnector {
keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH;
keyStorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD;
keyStoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS;
passwordCodecClass = TransportConstants.DEFAULT_PASSWORD_CODEC_CLASS;
trustStoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER;
trustStoreType = TransportConstants.DEFAULT_TRUSTSTORE_TYPE;
trustStorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH;
@ -592,8 +599,14 @@ public class NettyConnector extends AbstractConnector {
realTrustStoreType = trustStoreType;
realTrustStorePassword = trustStorePassword;
} else {
String tempPasswordCodecClass = Stream.of(System.getProperty(ACTIVEMQ_SSL_PASSWORD_CODEC_CLASS_PROP_NAME), passwordCodecClass).map(v -> useDefaultSslContext ? passwordCodecClass : v).filter(Objects::nonNull).findFirst().orElse(null);
realKeyStorePath = Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_PATH_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_PATH_PROP_NAME), keyStorePath).map(v -> useDefaultSslContext ? keyStorePath : v).filter(Objects::nonNull).findFirst().orElse(null);
realKeyStorePassword = Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_PASSWORD_PROP_NAME), keyStorePassword).map(v -> useDefaultSslContext ? keyStorePassword : v).filter(Objects::nonNull).findFirst().orElse(null);
String tempKeyStorePassword = Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_PASSWORD_PROP_NAME), keyStorePassword).map(v -> useDefaultSslContext ? keyStorePassword : v).filter(Objects::nonNull).findFirst().orElse(null);
if (tempKeyStorePassword != null && !tempKeyStorePassword.equals(keyStorePassword)) {
tempKeyStorePassword = processSslPasswordProperty(tempKeyStorePassword, tempPasswordCodecClass);
}
realKeyStorePassword = tempKeyStorePassword;
realKeyStoreAlias = keyStoreAlias;
Pair<String, String> keyStoreCompat = SSLSupport.getValidProviderAndType(Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_PROVIDER_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_PROVIDER_PROP_NAME), keyStoreProvider).map(v -> useDefaultSslContext ? keyStoreProvider : v).filter(Objects::nonNull).findFirst().orElse(null),
@ -602,7 +615,11 @@ public class NettyConnector extends AbstractConnector {
realKeyStoreType = keyStoreCompat.getB();
realTrustStorePath = Stream.of(System.getProperty(ACTIVEMQ_TRUSTSTORE_PATH_PROP_NAME), System.getProperty(JAVAX_TRUSTSTORE_PATH_PROP_NAME), trustStorePath).map(v -> useDefaultSslContext ? trustStorePath : v).filter(Objects::nonNull).findFirst().orElse(null);
realTrustStorePassword = Stream.of(System.getProperty(ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME), System.getProperty(JAVAX_TRUSTSTORE_PASSWORD_PROP_NAME), trustStorePassword).map(v -> useDefaultSslContext ? trustStorePassword : v).filter(Objects::nonNull).findFirst().orElse(null);
String tempTrustStorePassword = Stream.of(System.getProperty(ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME), System.getProperty(JAVAX_TRUSTSTORE_PASSWORD_PROP_NAME), trustStorePassword).map(v -> useDefaultSslContext ? trustStorePassword : v).filter(Objects::nonNull).findFirst().orElse(null);
if (tempTrustStorePassword != null && !tempTrustStorePassword.equals(trustStorePassword)) {
tempTrustStorePassword = processSslPasswordProperty(tempTrustStorePassword, tempPasswordCodecClass);
}
realTrustStorePassword = tempTrustStorePassword;
Pair<String, String> trustStoreCompat = SSLSupport.getValidProviderAndType(Stream.of(System.getProperty(ACTIVEMQ_TRUSTSTORE_PROVIDER_PROP_NAME), System.getProperty(JAVAX_TRUSTSTORE_PROVIDER_PROP_NAME), trustStoreProvider).map(v -> useDefaultSslContext ? trustStoreProvider : v).filter(Objects::nonNull).findFirst().orElse(null),
Stream.of(System.getProperty(ACTIVEMQ_TRUSTSTORE_TYPE_PROP_NAME), System.getProperty(JAVAX_TRUSTSTORE_TYPE_PROP_NAME), trustStoreType).map(v -> useDefaultSslContext ? trustStoreType : v).filter(Objects::nonNull).findFirst().orElse(null));
@ -755,6 +772,15 @@ public class NettyConnector extends AbstractConnector {
logger.debug("Started {} Netty Connector version {} to {}:{}", connectorType, TransportConstants.NETTY_VERSION, host, port);
}
private String processSslPasswordProperty(String password, String codecClass) {
try {
return PasswordMaskingUtil.resolveMask(password, codecClass);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorCreatingNettyConnection(e);
throw new RuntimeException(e);
}
}
private SSLEngine loadJdkSslEngine(final SSLContextConfig sslContextConfig) throws Exception {
final SSLContext context = SSLContextFactoryProvider.getSSLContextFactory()
.getSSLContext(sslContextConfig, configuration);
@ -1366,4 +1392,3 @@ public class NettyConnector extends AbstractConnector {
}
}
}

View File

@ -23,6 +23,7 @@ import java.util.Set;
import io.netty.handler.codec.socksx.SocksVersion;
import io.netty.util.Version;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
@ -264,6 +265,8 @@ public class TransportConstants {
public static final String DEFAULT_KEYSTORE_ALIAS = null;
public static final String DEFAULT_PASSWORD_CODEC_CLASS = DefaultSensitiveStringCodec.class.getName();
public static final boolean DEFAULT_TCP_NODELAY = true;
public static final int DEFAULT_TCP_SENDBUFFER_SIZE = 1024 * 1024;

View File

@ -38,6 +38,9 @@ import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManager;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -171,6 +174,70 @@ public class NettyConnectorTest extends ActiveMQTestBase {
}
/**
* that encrypted java system properties are read
*/
@Test
public void testEncryptedJavaSystemProperty() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
}
};
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
System.setProperty(NettyConnector.JAVAX_KEYSTORE_PATH_PROP_NAME, "client-keystore.jks");
System.setProperty(NettyConnector.JAVAX_KEYSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
System.setProperty(NettyConnector.JAVAX_TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks");
System.setProperty(NettyConnector.JAVAX_TRUSTSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
NettyConnector connector = new NettyConnector(params, handler, listener, executorService, Executors.newCachedThreadPool(ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())), Executors.newScheduledThreadPool(5, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())));
connector.start();
Assert.assertTrue(connector.isStarted());
Connection c = connector.createConnection();
assertNotNull(c);
c.close();
connector.close();
Assert.assertFalse(connector.isStarted());
}
/**
* that bad value encrypted java system properties are read but fail
*/
@Test
public void testEncryptedJavaSystemPropertyFail() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
}
};
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
System.setProperty(NettyConnector.JAVAX_KEYSTORE_PATH_PROP_NAME, "client-keystore.jks");
System.setProperty(NettyConnector.JAVAX_KEYSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("bad password")));
System.setProperty(NettyConnector.JAVAX_TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks");
System.setProperty(NettyConnector.JAVAX_TRUSTSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("bad password")));
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
NettyConnector connector = new NettyConnector(params, handler, listener, executorService, Executors.newCachedThreadPool(ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())), Executors.newScheduledThreadPool(5, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())));
connector.start();
Assert.assertTrue(connector.isStarted());
Assert.assertNull(connector.createConnection());
connector.close();
Assert.assertFalse(connector.isStarted());
}
@Test
public void testOverridesJavaSystemPropertyFail() throws Exception {
BufferHandler handler = new BufferHandler() {
@ -310,7 +377,7 @@ public class NettyConnectorTest extends ActiveMQTestBase {
}
@Test
public void tesActiveMQSystemProperties() throws Exception {
public void testActiveMQSystemProperties() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
@ -334,6 +401,162 @@ public class NettyConnectorTest extends ActiveMQTestBase {
Assert.assertFalse(connector.isStarted());
}
@Test
public void testEncryptedActiveMQSystemProperties() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
}
};
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
NettyConnector connector = new NettyConnector(params, handler, listener, executorService, Executors.newCachedThreadPool(ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())), Executors.newScheduledThreadPool(5, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())));
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PATH_PROP_NAME, "client-keystore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
connector.start();
Assert.assertTrue(connector.isStarted());
Connection c = connector.createConnection();
assertNotNull(c);
connector.close();
Assert.assertFalse(connector.isStarted());
}
@Test
public void testEncryptedActiveMQSystemPropertiesFail() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
}
};
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
NettyConnector connector = new NettyConnector(params, handler, listener, executorService, Executors.newCachedThreadPool(ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())), Executors.newScheduledThreadPool(5, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())));
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PATH_PROP_NAME, "client-keystore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("bad password")));
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("bad password")));
connector.start();
Assert.assertTrue(connector.isStarted());
Assert.assertNull(connector.createConnection());
connector.close();
Assert.assertFalse(connector.isStarted());
}
public static class NettyConnectorTestPasswordCodec implements SensitiveDataCodec<String> {
private static final String MASK = "supersecureish";
private static final String CLEARTEXT = "securepass";
@Override
public String decode(Object mask) throws Exception {
if (!MASK.equals(mask)) {
return mask.toString();
}
return CLEARTEXT;
}
@Override
public String encode(Object secret) throws Exception {
if (!CLEARTEXT.equals(secret)) {
return secret.toString();
}
return MASK;
}
}
@Test
public void testEncryptedActiveMQSystemPropertiesWithWrappedPasswordsAndCodec() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
}
};
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
NettyConnector connector = new NettyConnector(params, handler, listener, executorService, Executors.newCachedThreadPool(ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())), Executors.newScheduledThreadPool(5, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())));
NettyConnectorTestPasswordCodec codec = new NettyConnectorTestPasswordCodec();
System.setProperty(NettyConnector.ACTIVEMQ_SSL_PASSWORD_CODEC_CLASS_PROP_NAME, NettyConnectorTestPasswordCodec.class.getName());
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PATH_PROP_NAME, "client-keystore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
connector.start();
Assert.assertTrue(connector.isStarted());
Connection c = connector.createConnection();
assertNotNull(c);
connector.close();
Assert.assertFalse(connector.isStarted());
}
@Test
public void testEncryptedActiveMQSystemPropertiesWithBarePasswordsAndCodec() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
}
};
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
NettyConnector connector = new NettyConnector(params, handler, listener, executorService, Executors.newCachedThreadPool(ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())), Executors.newScheduledThreadPool(5, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())));
System.setProperty(NettyConnector.ACTIVEMQ_SSL_PASSWORD_CODEC_CLASS_PROP_NAME, NettyConnectorTestPasswordCodec.class.getName());
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PATH_PROP_NAME, "client-keystore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME, "securepass");
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME, "securepass");
connector.start();
Assert.assertTrue(connector.isStarted());
Connection c = connector.createConnection();
assertNotNull(c);
connector.close();
Assert.assertFalse(connector.isStarted());
}
@Test
public void testEncryptedActiveMQSystemPropertiesWithCodecFail() throws Exception {
BufferHandler handler = new BufferHandler() {
@Override
public void bufferReceived(final Object connectionID, final ActiveMQBuffer buffer) {
}
};
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
NettyConnector connector = new NettyConnector(params, handler, listener, executorService, Executors.newCachedThreadPool(ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())), Executors.newScheduledThreadPool(5, ActiveMQThreadFactory.defaultThreadFactory(getClass().getName())));
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
System.setProperty(NettyConnector.ACTIVEMQ_SSL_PASSWORD_CODEC_CLASS_PROP_NAME, NettyConnectorTestPasswordCodec.class.getName());
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PATH_PROP_NAME, "client-keystore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks");
System.setProperty(NettyConnector.ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME, PasswordMaskingUtil.wrap(codec.encode("securepass")));
connector.start();
Assert.assertTrue(connector.isStarted());
Assert.assertNull(connector.createConnection());
connector.close();
Assert.assertFalse(connector.isStarted());
}
@Test
public void testActiveMQOverridesSystemProperty() throws Exception {
BufferHandler handler = new BufferHandler() {