ARTEMIS-3117 Provide CachingOpenSSLContextFactory

to mitigate performance degradation in JDK 11 during TLS connection
initialization.
This commit is contained in:
sebthom 2021-02-19 13:04:37 +01:00 committed by Justin Bertram
parent 2f5b9325f3
commit 026f3859a2
15 changed files with 668 additions and 145 deletions

View File

@ -104,6 +104,7 @@ import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetector.Level;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
@ -116,6 +117,9 @@ import org.apache.activemq.artemis.spi.core.remoting.BufferHandler;
import org.apache.activemq.artemis.spi.core.remoting.ClientConnectionLifeCycleListener;
import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManager;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactoryProvider;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
import org.apache.activemq.artemis.utils.FutureLatch;
import org.apache.activemq.artemis.utils.IPV6Util;
@ -123,9 +127,6 @@ import org.jboss.logging.Logger;
import static org.apache.activemq.artemis.utils.Base64.encodeBytes;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider;
public class NettyConnector extends AbstractConnector {
public static String NIO_CONNECTOR_TYPE = "NIO";
@ -621,11 +622,23 @@ public class NettyConnector extends AbstractConnector {
if (sslEnabled && !useServlet) {
SSLEngine engine;
final SSLContextConfig sslContextConfig = SSLContextConfig.builder()
.keystoreProvider(realKeyStoreProvider)
.keystorePath(realKeyStorePath)
.keystorePassword(realKeyStorePassword)
.truststoreProvider(realTrustStoreProvider)
.truststorePath(realTrustStorePath)
.truststorePassword(realTrustStorePassword)
.trustManagerFactoryPlugin(trustManagerFactoryPlugin)
.crlPath(crlPath)
.trustAll(trustAll)
.build();
final SSLEngine engine;
if (sslProvider.equals(TransportConstants.OPENSSL_PROVIDER)) {
engine = loadOpenSslEngine(channel.alloc(), realKeyStoreProvider, realKeyStorePath, realKeyStorePassword, realTrustStoreProvider, realTrustStorePath, realTrustStorePassword);
engine = loadOpenSslEngine(channel.alloc(), sslContextConfig);
} else {
engine = loadJdkSslEngine(realKeyStoreProvider, realKeyStorePath, realKeyStorePassword, realTrustStoreProvider, realTrustStorePath, realTrustStorePassword);
engine = loadJdkSslEngine(sslContextConfig);
}
engine.setUseClientMode(true);
@ -710,16 +723,10 @@ public class NettyConnector extends AbstractConnector {
ActiveMQClientLogger.LOGGER.startedNettyConnector(connectorType, TransportConstants.NETTY_VERSION, host, port);
}
private SSLEngine loadJdkSslEngine(String keystoreProvider,
String keystorePath,
String keystorePassword,
String truststoreProvider,
String truststorePath,
String truststorePassword) throws Exception {
SSLContext context = SSLContextFactoryProvider.getSSLContextFactory().getSSLContext(configuration,
keystoreProvider, keystorePath, keystorePassword,
truststoreProvider, truststorePath, truststorePassword,
crlPath, trustManagerFactoryPlugin, trustAll);
private SSLEngine loadJdkSslEngine(final SSLContextConfig sslContextConfig) throws Exception {
final SSLContext context = SSLContextFactoryProvider.getSSLContextFactory()
.getSSLContext(sslContextConfig, configuration);
Subject subject = null;
if (kerb5Config != null) {
LoginContext loginContext = new LoginContext(kerb5Config);
@ -741,26 +748,9 @@ public class NettyConnector extends AbstractConnector {
return engine;
}
private SSLEngine loadOpenSslEngine(ByteBufAllocator alloc,
String keystoreProvider,
String keystorePath,
String keystorePassword,
String truststoreProvider,
String truststorePath,
String truststorePassword) throws Exception {
SslContext context = new SSLSupport()
.setKeystoreProvider(keystoreProvider)
.setKeystorePath(keystorePath)
.setKeystorePassword(keystorePassword)
.setTruststoreProvider(truststoreProvider)
.setTruststorePath(truststorePath)
.setTruststorePassword(truststorePassword)
.setSslProvider(sslProvider)
.setTrustAll(trustAll)
.setTrustManagerFactoryPlugin(trustManagerFactoryPlugin)
.createNettyClientContext();
private SSLEngine loadOpenSslEngine(final ByteBufAllocator alloc, final SSLContextConfig sslContextConfig) throws Exception {
final SslContext context = OpenSSLContextFactoryProvider.getOpenSSLContextFactory()
.getClientSslContext(sslContextConfig, configuration);
Subject subject = null;
if (kerb5Config != null) {

View File

@ -0,0 +1,75 @@
/*
* Copyright 2021 The Apache Software Foundation.
*
* Licensed 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.remoting.impl.ssl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import io.netty.handler.ssl.SslContext;
import org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactory;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
/**
* {@link OpenSSLContextFactory} providing a cache of {@link SslContext}.
* Since {@link SslContext} should be reused instead of recreated and are thread safe.
* To activate it you need to allow this Service to be discovered by having a
* <code>META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactory</code>
* file with <code>org.apache.activemq.artemis.core.remoting.impl.ssl.CachingOpenSSLContextFactory</code>
* as value.
*/
public class CachingOpenSSLContextFactory extends DefaultOpenSSLContextFactory {
private final ConcurrentMap<SSLContextConfig, SslContext> clientSslContextCache = new ConcurrentHashMap<>(2);
private final ConcurrentMap<SSLContextConfig, SslContext> serversSslContextCache = new ConcurrentHashMap<>(2);
@Override
public void clearSslContexts() {
clientSslContextCache.clear();
serversSslContextCache.clear();
}
@Override
public SslContext getClientSslContext(final SSLContextConfig config, final Map<String, Object> additionalOpts) throws Exception {
return clientSslContextCache.computeIfAbsent(config, this::getClientSslContext);
}
private SslContext getClientSslContext(final SSLContextConfig config) {
try {
return super.getClientSslContext(config, null);
} catch (final Exception ex) {
throw new RuntimeException("An unexpected exception occured while creating Client OpenSSL Context with " + config, ex);
}
}
@Override
public SslContext getServerSslContext(final SSLContextConfig config, final Map<String, Object> additionalOpts) throws Exception {
return clientSslContextCache.computeIfAbsent(config, this::getServerSslContext);
}
private SslContext getServerSslContext(final SSLContextConfig config) {
try {
return super.getServerSslContext(config, null);
} catch (final Exception ex) {
throw new RuntimeException("An unexpected exception occured while creating Server OpenSSL Context " + config, ex);
}
}
@Override
public int getPriority() {
return 10;
}
}

View File

@ -15,66 +15,61 @@
*/
package org.apache.activemq.artemis.core.remoting.impl.ssl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.net.ssl.SSLContext;
import io.netty.handler.ssl.SslContext;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
/**
* SSLContextFactory providing a cache of SSLContext.
* Since SSLContext should be reused instead of recreated and are thread safe.
* To activate it uou need to allow this Service to be discovered by having a
* {@link SSLContextFactory} providing a cache of {@link SSLContext}.
* Since {@link SSLContext} should be reused instead of recreated and are thread safe.
* To activate it you need to allow this Service to be discovered by having a
* <code>META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory</code>
* file with <code> org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory</code>
* file with <code>org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory</code>
* as value.
*/
public class CachingSSLContextFactory extends DefaultSSLContextFactory {
private static final Map<String, SSLContext> SSL_CONTEXTS = Collections.synchronizedMap(new HashMap<>());
private static final ConcurrentMap<Object, SSLContext> sslContextCache = new ConcurrentHashMap<>(2);
@Override
public void clearSSLContexts() {
SSL_CONTEXTS.clear();
sslContextCache.clear();
}
@Override
public SSLContext getSSLContext(Map<String, Object> configuration,
String keystoreProvider, String keystorePath, String keystorePassword,
String truststoreProvider, String truststorePath, String truststorePassword,
String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception {
String sslContextName = getSSLContextName(configuration, keystorePath, keystoreProvider, truststorePath, truststoreProvider);
if (!SSL_CONTEXTS.containsKey(sslContextName)) {
SSL_CONTEXTS.put(sslContextName, createSSLContext(configuration,
keystoreProvider, keystorePath, keystorePassword,
truststoreProvider, truststorePath, truststorePassword,
crlPath, trustManagerFactoryPlugin, trustAll));
}
return SSL_CONTEXTS.get(sslContextName);
public SSLContext getSSLContext(final SSLContextConfig config, final Map<String, Object> additionalOpts) throws Exception {
final Object cacheKey = getCacheKey(config, additionalOpts);
return sslContextCache.computeIfAbsent(cacheKey, key -> {
try {
return CachingSSLContextFactory.super.getSSLContext(config, additionalOpts);
} catch (final Exception ex) {
throw new RuntimeException("An unexpected exception occured while creating JDK SSLContext with " + config, ex);
}
});
}
/**
* Obtain the sslContextName :
* - if available the 'sslContext' from the configuration
* - otherwise if available the keyStorePath + '_' + keystoreProvider
* - otherwise the truststorePath + '_' + truststoreProvider.
* @param configuration
* @param keyStorePath
* @param keystoreProvider
* @param truststorePath
* @param truststoreProvider
* @return the ley associated to the SSLContext.
* Obtains/calculates a cache key for the corresponding {@link SslContext}.
* <ol>
* <li>If <code>config</code> contains an entry with key "sslContext", the associated value is returned
* <li>Otherwise, the provided {@link SSLContextConfig} is used as cache key.
* </ol>
*
* @return the SSL context name to cache/retrieve the {@link SslContext}.
*/
protected String getSSLContextName(Map<String, Object> configuration, String keyStorePath, String keystoreProvider, String truststorePath, String truststoreProvider) {
String sslContextName = ConfigurationHelper.getStringProperty(TransportConstants.SSL_CONTEXT_PROP_NAME, null, configuration);
if (sslContextName == null) {
if (keyStorePath != null) {
return keyStorePath + '_' + keystoreProvider;
}
return truststorePath + '_' + truststoreProvider;
}
return sslContextName;
protected Object getCacheKey(final SSLContextConfig config, final Map<String, Object> additionalOpts) {
final Object cacheKey = ConfigurationHelper.getStringProperty(TransportConstants.SSL_CONTEXT_PROP_NAME, null, additionalOpts);
if (cacheKey != null)
return cacheKey;
return config;
}
@Override

View File

@ -0,0 +1,61 @@
/*
* Copyright 2021 The Apache Software Foundation.
*
* Licensed 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.remoting.impl.ssl;
import java.util.Map;
import io.netty.handler.ssl.SslContext;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactory;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
/**
* Default {@link OpenSSLContextFactory} for use in {@link NettyConnector} and NettyAcceptor.
*/
public class DefaultOpenSSLContextFactory implements OpenSSLContextFactory {
/**
* @param additionalOpts not used by this implementation
*
* @return an {@link SslContext} instance for the given configuration.
*/
@Override
public SslContext getClientSslContext(final SSLContextConfig config, final Map<String, Object> additionalOpts) throws Exception {
log.debugf("Creating Client OpenSSL Context with %s", config);
return new SSLSupport(config)
.setSslProvider(TransportConstants.OPENSSL_PROVIDER)
.createNettyClientContext();
}
/**
* @param additionalOpts not used by this implementation
*
* @return an {@link SslContext} instance for the given configuration.
*/
@Override
public SslContext getServerSslContext(final SSLContextConfig config, final Map<String, Object> additionalOpts) throws Exception {
log.debugf("Creating Server OpenSSL Context with %s", config);
return new SSLSupport(config)
.setSslProvider(TransportConstants.OPENSSL_PROVIDER)
.createNettyContext();
}
@Override
public int getPriority() {
return 5;
}
}

View File

@ -18,6 +18,7 @@ package org.apache.activemq.artemis.core.remoting.impl.ssl;
import java.util.Map;
import javax.net.ssl.SSLContext;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
@ -26,43 +27,23 @@ import org.apache.activemq.artemis.utils.ConfigurationHelper;
*/
public class DefaultSSLContextFactory implements SSLContextFactory {
@Override
public SSLContext getSSLContext(Map<String, Object> configuration,
String keystoreProvider, String keystorePath, String keystorePassword,
String truststoreProvider, String truststorePath, String truststorePassword,
String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception {
return createSSLContext(configuration,
keystoreProvider, keystorePath, keystorePassword,
truststoreProvider, truststorePath, truststorePassword,
crlPath, trustManagerFactoryPlugin, trustAll);
}
public SSLContext getSSLContext(final SSLContextConfig config, final Map<String, Object> additionalOpts) throws Exception {
final boolean useDefaultSslContext = ConfigurationHelper.getBooleanProperty(
TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME,
TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT,
additionalOpts
);
protected SSLContext createSSLContext(Map<String, Object> configuration,
String keystoreProvider, String keystorePath, String keystorePassword,
String truststoreProvider, String truststorePath, String truststorePassword,
String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception {
if (log.isDebugEnabled()) {
final StringBuilder builder = new StringBuilder();
configuration.forEach((k, v) -> builder.append("\r\n").append(k).append("=").append(k.toLowerCase().contains("password") ? "****" : v));
log.debugf("Creating SSL context with configuration %s", builder.toString());
}
boolean useDefaultSslContext = ConfigurationHelper.getBooleanProperty(TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME, TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT, configuration);
if (useDefaultSslContext) {
log.debug("Using the Default JDK SSLContext.");
return SSLContext.getDefault();
}
return new SSLSupport()
.setKeystoreProvider(keystoreProvider)
.setKeystorePath(keystorePath)
.setKeystorePassword(keystorePassword)
.setTruststoreProvider(truststoreProvider)
.setTruststorePath(truststorePath)
.setTruststorePassword(truststorePassword)
.setTrustAll(trustAll)
.setCrlPath(crlPath)
.setTrustManagerFactoryPlugin(trustManagerFactoryPlugin)
.createContext();
log.debugf("Creating JDK SSLContext with %s", config);
return new SSLSupport(config).createContext();
}
@Override
public int getPriority() {
return 5;

View File

@ -46,6 +46,7 @@ import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.apache.activemq.artemis.api.core.TrustManagerFactoryPlugin;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
import org.apache.activemq.artemis.utils.ClassloadingUtil;
/**
@ -66,6 +67,21 @@ public class SSLSupport {
private boolean trustAll = TransportConstants.DEFAULT_TRUST_ALL;
private String trustManagerFactoryPlugin = TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN;
public SSLSupport() {
}
public SSLSupport(final SSLContextConfig config) {
keystoreProvider = config.getKeystoreProvider();
keystorePath = config.getKeystorePath();
keystorePassword = config.getKeystorePassword();
truststoreProvider = config.getTruststoreProvider();
truststorePath = config.getTruststorePath();
truststorePassword = config.getTruststorePassword();
crlPath = config.getCrlPath();
trustAll = config.isTrustAll();
trustManagerFactoryPlugin = config.getTrustManagerFactoryPlugin();
}
public String getKeystoreProvider() {
return keystoreProvider;
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2021 The Apache Software Foundation.
*
* Licensed 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.spi.core.remoting.ssl;
import java.util.Map;
import io.netty.handler.ssl.SslContext;
import org.jboss.logging.Logger;
/**
* Service interface to create an {@link SslContext} for a configuration.
* This is ONLY used with OpenSSL.
* To create and use your own implementation you need to create a file
* <code>META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactory</code>
* in your jar and fill it with the full qualified name of your implementation.
*/
public interface OpenSSLContextFactory extends Comparable<OpenSSLContextFactory> {
Logger log = Logger.getLogger(OpenSSLContextFactory.class);
/**
* Release any cached {@link SslContext} instances.
*/
default void clearSslContexts() {
}
@Override
default int compareTo(final OpenSSLContextFactory other) {
return this.getPriority() - other.getPriority();
}
/**
* @param additionalOpts implementation specific additional options.
*
* @return an {@link SslContext} instance for the given configuration.
*/
SslContext getClientSslContext(SSLContextConfig config, Map<String, Object> additionalOpts) throws Exception;
/**
* @param additionalOpts implementation specific additional options.
*
* @return an {@link SslContext} instance for the given configuration.
*/
SslContext getServerSslContext(SSLContextConfig config, Map<String, Object> additionalOpts) throws Exception;
/**
* The priority for the {@link OpenSSLContextFactory} when resolving the service to get the implementation.
* This is used when selecting the implementation when several implementations are loaded.
* The highest priority implementation will be used.
*/
int getPriority();
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2021 The Apache Software Foundation.
*
* Licensed 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.spi.core.remoting.ssl;
import java.util.ServiceLoader;
/**
* Provider that loads all registered {@link OpenSSLContextFactory} services and returns the one with the highest priority.
*/
public class OpenSSLContextFactoryProvider {
private static final OpenSSLContextFactory FACTORY;
static {
OpenSSLContextFactory factoryWithHighestPrio = null;
for (OpenSSLContextFactory factory : ServiceLoader.load(OpenSSLContextFactory.class)) {
if (factoryWithHighestPrio == null || factory.getPriority() > factoryWithHighestPrio.getPriority()) {
factoryWithHighestPrio = factory;
}
}
if (factoryWithHighestPrio == null)
throw new IllegalStateException("No OpenSSLContextFactory registered!");
FACTORY = factoryWithHighestPrio;
}
/**
* @return the {@link OpenSSLContextFactory} with the higher priority.
*/
public static OpenSSLContextFactory getOpenSSLContextFactory() {
return FACTORY;
}
}

View File

@ -0,0 +1,220 @@
/*
* Copyright 2021 The Apache Software Foundation.
*
* Licensed 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.spi.core.remoting.ssl;
import java.util.Objects;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
/**
* This class holds configuration parameters for SSL context initialization.
* To be used with {@link SSLContextFactory} and {@link OpenSSLContextFactory}.
* <br>
* Use {@link SSLContextConfig#builder()} to create new immutable instances.
*/
public final class SSLContextConfig {
public static final class Builder {
private String keystorePath = TransportConstants.DEFAULT_KEYSTORE_PATH;
private String keystorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD;
private String keystoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER;
private String truststorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH;
private String truststorePassword = TransportConstants.DEFAULT_TRUSTSTORE_PASSWORD;
private String truststoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER;
private String crlPath = TransportConstants.DEFAULT_CRL_PATH;
private String trustManagerFactoryPlugin = TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN;
private boolean trustAll = TransportConstants.DEFAULT_TRUST_ALL;
private Builder() {
}
public Builder from(final SSLContextConfig config) {
if (config == null)
return this;
keystorePath = config.getKeystorePath();
keystorePassword = config.getKeystorePassword();
keystoreProvider = config.getKeystoreProvider();
truststorePath = config.getTruststorePath();
truststorePassword = config.getTruststorePassword();
crlPath = config.getCrlPath();
truststoreProvider = config.getTruststoreProvider();
trustAll = config.trustAll;
return this;
}
public SSLContextConfig build() {
return new SSLContextConfig(
keystoreProvider, keystorePath, keystorePassword,
truststoreProvider, truststorePath, truststorePassword,
crlPath, trustManagerFactoryPlugin, trustAll
);
}
public Builder keystorePath(final String keystorePath) {
this.keystorePath = keystorePath;
return this;
}
public Builder keystorePassword(final String keystorePassword) {
this.keystorePassword = keystorePassword;
return this;
}
public Builder keystoreProvider(final String keystoreProvider) {
this.keystoreProvider = keystoreProvider;
return this;
}
public Builder truststorePath(final String truststorePath) {
this.truststorePath = truststorePath;
return this;
}
public Builder truststorePassword(final String truststorePassword) {
this.truststorePassword = truststorePassword;
return this;
}
public Builder truststoreProvider(final String truststoreProvider) {
this.truststoreProvider = truststoreProvider;
return this;
}
public Builder crlPath(final String crlPath) {
this.crlPath = crlPath;
return this;
}
public Builder trustAll(final boolean trustAll) {
this.trustAll = trustAll;
return this;
}
public Builder trustManagerFactoryPlugin(final String trustManagerFactoryPlugin) {
this.trustManagerFactoryPlugin = trustManagerFactoryPlugin;
return this;
}
}
public static Builder builder() {
return new Builder();
}
private final String keystorePath;
private final String keystorePassword;
private final String keystoreProvider;
private final String truststorePath;
private final String truststorePassword;
private final String truststoreProvider;
private final String trustManagerFactoryPlugin;
private final String crlPath;
private final boolean trustAll;
private final int hashCode;
private SSLContextConfig(
final String keystoreProvider, final String keystorePath, final String keystorePassword,
final String truststoreProvider, final String truststorePath, final String truststorePassword,
final String crlPath, final String trustManagerFactoryPlugin, final boolean trustAll
) {
this.keystorePath = keystorePath;
this.keystoreProvider = keystoreProvider;
this.keystorePassword = keystorePassword;
this.truststorePath = truststorePath;
this.truststorePassword = truststorePassword;
this.truststoreProvider = truststoreProvider;
this.trustManagerFactoryPlugin = trustManagerFactoryPlugin;
this.crlPath = crlPath;
this.trustAll = trustAll;
hashCode = Objects.hash(
keystorePath, keystoreProvider,
truststorePath, truststoreProvider,
crlPath, trustManagerFactoryPlugin, trustAll
);
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
final SSLContextConfig other = (SSLContextConfig) obj;
return Objects.equals(keystorePath, other.keystorePath)
&& Objects.equals(keystoreProvider, other.keystoreProvider)
&& Objects.equals(truststorePath, other.truststorePath)
&& Objects.equals(truststoreProvider, other.truststoreProvider)
&& Objects.equals(crlPath, other.crlPath)
&& Objects.equals(trustManagerFactoryPlugin, other.trustManagerFactoryPlugin)
&& trustAll == other.trustAll;
}
public String getCrlPath() {
return crlPath;
}
public String getKeystorePassword() {
return keystorePassword;
}
public String getKeystorePath() {
return keystorePath;
}
public String getKeystoreProvider() {
return keystoreProvider;
}
public String getTrustManagerFactoryPlugin() {
return trustManagerFactoryPlugin;
}
public String getTruststorePassword() {
return truststorePassword;
}
public String getTruststorePath() {
return truststorePath;
}
public String getTruststoreProvider() {
return truststoreProvider;
}
@Override
public int hashCode() {
return hashCode;
}
public boolean isTrustAll() {
return trustAll;
}
@Override
public String toString() {
return "SSLSupport [" +
"keystoreProvider=" + keystoreProvider +
", keystorePath=" + keystorePath +
", keystorePassword=" + (keystorePassword == null ? null : "******") +
", truststoreProvider=" + truststoreProvider +
", truststorePath=" + truststorePath +
", truststorePassword=" + (truststorePassword == null ? null : "******") +
", crlPath=" + crlPath +
", trustAll=" + trustAll +
", trustManagerFactoryPlugin=" + trustManagerFactoryPlugin +
"]";
}
}

View File

@ -30,24 +30,43 @@ public interface SSLContextFactory extends Comparable<SSLContextFactory> {
Logger log = Logger.getLogger(SSLContextFactory.class);
/**
* Obtain a SSLContext from the configuration.
* @param configuration
* @param keystoreProvider
* @param keystorePath
* @param keystorePassword
* @param truststoreProvider
* @param truststorePath
* @param truststorePassword
* @param crlPath
* @param trustManagerFactoryPlugin
* @param trustAll
* @return a SSLContext instance.
* @throws Exception
* @return an {@link SSLContext} for the given configuration.
*
* @deprecated use {@link #getSSLContext(SSLContextConfig, Map)} instead
*/
SSLContext getSSLContext(Map<String, Object> configuration,
@SuppressWarnings("unused")
@Deprecated
default SSLContext getSSLContext(Map<String, Object> configuration,
String keystoreProvider, String keystorePath, String keystorePassword,
String truststoreProvider, String truststorePath, String truststorePassword,
String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception;
String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception {
final SSLContextConfig sslContextConfig = SSLContextConfig.builder()
.keystoreProvider(keystoreProvider)
.keystorePath(keystorePath)
.keystorePassword(keystorePassword)
.truststoreProvider(truststoreProvider)
.truststorePath(truststorePath)
.truststorePassword(truststorePassword)
.trustManagerFactoryPlugin(trustManagerFactoryPlugin)
.crlPath(crlPath)
.build();
return getSSLContext(sslContextConfig, configuration);
}
/**
* @param additionalOpts implementation specific additional options.
*
* @return an {@link SSLContext} for the given configuration.
*/
default SSLContext getSSLContext(SSLContextConfig config, Map<String, Object> additionalOpts) throws Exception {
return getSSLContext(additionalOpts,
config.getKeystoreProvider(), config.getKeystorePath(), config.getKeystorePassword(),
config.getTruststoreProvider(), config.getTruststorePath(), config.getTruststorePassword(),
config.getCrlPath(), config.getTrustManagerFactoryPlugin(), config.isTrustAll()
);
}
default void clearSSLContexts() {
}

View File

@ -0,0 +1 @@
org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultOpenSSLContextFactory

View File

@ -0,0 +1,31 @@
/*
* Copyright 2021 The Apache Software Foundation.
*
* Licensed 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.spi.core.remoting.ssl;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
public class OpenSSLContextFactoryProviderTest {
/**
* Test to retrieve a {@link OpenSSLContextFactory} registered via META-INF/services.
*/
@Test
public void testGetOpenSSLContextFactory() {
assertNotNull(OpenSSLContextFactoryProvider.getOpenSSLContextFactory());
}
}

View File

@ -92,6 +92,8 @@ import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
import org.apache.activemq.artemis.spi.core.remoting.BufferHandler;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
import org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactoryProvider;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
@ -167,6 +169,8 @@ public class NettyAcceptor extends AbstractAcceptor {
private final String crlPath;
private SSLContextConfig sslContextConfig;
private final String enabledCipherSuites;
private final String enabledProtocols;
@ -229,7 +233,6 @@ public class NettyAcceptor extends AbstractAcceptor {
private final boolean autoStart;
final AtomicBoolean warningPrinted = new AtomicBoolean(false);
final Executor failureExecutor;
@ -309,6 +312,17 @@ public class NettyAcceptor extends AbstractAcceptor {
sniHost = ConfigurationHelper.getStringProperty(TransportConstants.SNIHOST_PROP_NAME, TransportConstants.DEFAULT_SNIHOST_CONFIG, configuration);
trustManagerFactoryPlugin = ConfigurationHelper.getStringProperty(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME, TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN, configuration);
sslContextConfig = SSLContextConfig.builder()
.keystoreProvider(keyStoreProvider)
.keystorePath(keyStorePath)
.keystorePassword(keyStorePassword)
.truststoreProvider(trustStoreProvider)
.truststorePath(trustStorePath)
.truststorePassword(trustStorePassword)
.trustManagerFactoryPlugin(trustManagerFactoryPlugin)
.crlPath(crlPath)
.build();
} else {
keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER;
keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH;
@ -498,6 +512,10 @@ public class NettyAcceptor extends AbstractAcceptor {
public void setKeyStorePath(String keyStorePath) {
this.keyStorePath = keyStorePath;
this.configuration.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, keyStorePath);
sslContextConfig = SSLContextConfig.builder()
.from(sslContextConfig)
.keystorePath(keyStorePath)
.build();
}
/**
@ -604,10 +622,7 @@ public class NettyAcceptor extends AbstractAcceptor {
final SSLContext context;
try {
checkSSLConfiguration();
context = SSLContextFactoryProvider.getSSLContextFactory().getSSLContext(configuration,
keyStoreProvider, keyStorePath, keyStorePassword,
trustStoreProvider, trustStorePath, trustStorePassword,
crlPath, trustManagerFactoryPlugin, TransportConstants.DEFAULT_TRUST_ALL);
context = SSLContextFactoryProvider.getSSLContextFactory().getSSLContext(sslContextConfig, configuration);
} catch (Exception e) {
IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port, e);
throw ise;
@ -645,16 +660,7 @@ public class NettyAcceptor extends AbstractAcceptor {
final SslContext context;
try {
checkSSLConfiguration();
context = new SSLSupport()
.setKeystoreProvider(keyStoreProvider)
.setKeystorePath(keyStorePath)
.setKeystorePassword(keyStorePassword)
.setTruststoreProvider(trustStoreProvider)
.setTruststorePath(trustStorePath)
.setTruststorePassword(trustStorePassword)
.setSslProvider(sslProvider)
.setTrustManagerFactoryPlugin(trustManagerFactoryPlugin)
.createNettyContext();
context = OpenSSLContextFactoryProvider.getOpenSSLContextFactory().getServerSslContext(sslContextConfig, configuration);
} catch (Exception e) {
IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port, e);
throw ise;

View File

@ -71,6 +71,7 @@ import org.apache.activemq.artemis.spi.core.remoting.AcceptorFactory;
import org.apache.activemq.artemis.spi.core.remoting.BufferHandler;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener;
import org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactoryProvider;
import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
@ -386,6 +387,7 @@ public class RemotingServiceImpl implements RemotingService, ServerConnectionLif
return;
}
SSLContextFactoryProvider.getSSLContextFactory().clearSSLContexts();
OpenSSLContextFactoryProvider.getOpenSSLContextFactory().clearSslContexts();
failureCheckAndFlushThread.close(criticalError);

View File

@ -318,9 +318,12 @@ additional properties:
- `sslContext`
A key that can be used in conjunction with `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory`
to cache created SSLContext and avoid recreating. Look [Configuring a SSLContextFactory](#Configuring a SSLContextFactory)
for more details.
An optional cache key only evaluated if `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory`
is active, to cache the initial created SSL context and reuse it. If not
specified CachingSSLContextFactory will automatically calculate a cache key based on
the given keystore/truststore parameters.
See [Configuring an SSLContextFactory](#Configuring an SSLContextFactory)
for more details.
- `sslEnabled`
@ -499,22 +502,34 @@ for more details.
[broker's classpath](using-server.md#adding-runtime-dependencies).
#### Configuring a SSLContextFactory
#### Configuring an SSLContextFactory
If you have a `JDK` provider you can configure which SSLContextFactory to use.
Currently we provide two implementations:
If you use `JDK` as SSL provider (the default), you can configure which
SSLContextFactory to use.
Currently the following two implementations are provided:
- `org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultSSLContextFactory`
(registered by the default)
- `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory`
but you can also add your own implementation of `org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory`.
The implementations are loaded by a ServiceLoader, thus you need to declare your implementation in
You may also create your own implementation of
`org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory`.
The implementations are loaded by a `java.util.ServiceLoader`, thus you need to declare your implementation in
a `META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory` file.
If several implementations are available, the one with the highest `priority` will be selected.
So for example, if you want to use `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory`
you need to add a `META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory` file
to your classpath with the content `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory`.
**Note:** This mechanism doesn't work if you have selected `OPENSSL` as provider.
A similar mechanism exists for the `OPENSSL` SSL provider in which case you can configure an OpenSSLContextFactory.
Currently the following two implementations are provided:
- `org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultOpenSSLContextFactory`
(registered by the default)
- `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingOpenSSLContextFactory`
You may also create your own implementation of
`org.apache.activemq.artemis.spi.core.remoting.ssl.OpenSSLContextFactory`.
### Configuring Netty HTTP