ARTEMIS-4164: add acceptor sslAutoReload option to have the broker watch key and trust store paths for modifications and automatically call reload

This commit is contained in:
Gary Tully 2023-11-23 10:52:08 +00:00 committed by clebertsuconic
parent 8195394eca
commit 348763e14a
4 changed files with 141 additions and 0 deletions

View File

@ -36,6 +36,10 @@ public class TransportConstants {
public static final String SSL_ENABLED_PROP_NAME = "sslEnabled"; public static final String SSL_ENABLED_PROP_NAME = "sslEnabled";
public static final String SSL_AUTO_RELOAD_PROP_NAME = "sslAutoReload";
public static final boolean DEFAULT_SSL_AUTO_RELOAD = false;
public static final String HTTP_ENABLED_PROP_NAME = "httpEnabled"; public static final String HTTP_ENABLED_PROP_NAME = "httpEnabled";
public static final String HTTP_CLIENT_IDLE_PROP_NAME = "httpClientIdleTime"; public static final String HTTP_CLIENT_IDLE_PROP_NAME = "httpClientIdleTime";
@ -396,6 +400,7 @@ public class TransportConstants {
static { static {
Set<String> allowableAcceptorKeys = new HashSet<>(); Set<String> allowableAcceptorKeys = new HashSet<>();
allowableAcceptorKeys.add(TransportConstants.SSL_ENABLED_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.SSL_ENABLED_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.SSL_AUTO_RELOAD_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.HTTP_RESPONSE_TIME_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.HTTP_RESPONSE_TIME_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.HTTP_SERVER_SCAN_PERIOD_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.HTTP_SERVER_SCAN_PERIOD_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.HTTP_UPGRADE_ENABLED_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.HTTP_UPGRADE_ENABLED_PROP_NAME);

View File

@ -23,6 +23,7 @@ import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
@ -63,6 +64,7 @@ import org.apache.activemq.artemis.api.core.Pair;
import org.apache.activemq.artemis.api.core.QueueConfiguration; import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.management.AcceptorControl;
import org.apache.activemq.artemis.api.core.management.ResourceNames; import org.apache.activemq.artemis.api.core.management.ResourceNames;
import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl; import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl;
import org.apache.activemq.artemis.core.config.BridgeConfiguration; import org.apache.activemq.artemis.core.config.BridgeConfiguration;
@ -202,6 +204,7 @@ import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoader;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.ActiveMQThreadPoolExecutor; import org.apache.activemq.artemis.utils.ActiveMQThreadPoolExecutor;
import org.apache.activemq.artemis.utils.CompositeAddress; import org.apache.activemq.artemis.utils.CompositeAddress;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
import org.apache.activemq.artemis.utils.ExecutorFactory; import org.apache.activemq.artemis.utils.ExecutorFactory;
import org.apache.activemq.artemis.utils.ReusableLatch; import org.apache.activemq.artemis.utils.ReusableLatch;
import org.apache.activemq.artemis.utils.SecurityFormatter; import org.apache.activemq.artemis.utils.SecurityFormatter;
@ -220,6 +223,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.groupingBy;
import static org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.DEFAULT_SSL_AUTO_RELOAD;
import static org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.KEYSTORE_PATH_PROP_NAME;
import static org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.SSL_AUTO_RELOAD_PROP_NAME;
import static org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants.TRUSTSTORE_PATH_PROP_NAME;
import static org.apache.activemq.artemis.utils.collections.IterableStream.iterableOf; import static org.apache.activemq.artemis.utils.collections.IterableStream.iterableOf;
/** /**
@ -3361,6 +3368,25 @@ public class ActiveMQServerImpl implements ActiveMQServer {
PropertiesLoader.reload(); PropertiesLoader.reload();
}); });
} }
// track tls resources on acceptors and reload via remoting server
configuration.getAcceptorConfigurations().forEach((acceptorConfig) -> {
Map<String, Object> config = acceptorConfig.getCombinedParams();
if (ConfigurationHelper.getBooleanProperty(SSL_AUTO_RELOAD_PROP_NAME, DEFAULT_SSL_AUTO_RELOAD, config)) {
URL pathUrl = fileUrlFrom(config.get(KEYSTORE_PATH_PROP_NAME));
if (pathUrl != null) {
reloadManager.addCallback(pathUrl, (uri) -> {
reloadNamedAcceptor(acceptorConfig.getName());
});
}
pathUrl = fileUrlFrom(config.get(TRUSTSTORE_PATH_PROP_NAME));
if (pathUrl != null) {
reloadManager.addCallback(pathUrl, (uri) -> {
reloadNamedAcceptor(acceptorConfig.getName());
});
}
}
});
} }
if (hasBrokerPlugins()) { if (hasBrokerPlugins()) {
@ -3374,6 +3400,26 @@ public class ActiveMQServerImpl implements ActiveMQServer {
return true; return true;
} }
private void reloadNamedAcceptor(String name) {
// preference for Control to capture consistent audit logging
if (managementService != null) {
Object targetControl = managementService.getResource(ResourceNames.ACCEPTOR + name);
if (targetControl instanceof AcceptorControl) {
((AcceptorControl) targetControl).reload();
}
}
}
private URL fileUrlFrom(Object o) {
if (o instanceof String) {
try {
return new File((String) o).toURI().toURL();
} catch (MalformedURLException ignored) {
}
}
return null;
}
@Override @Override
public void installMirrorController(MirrorController mirrorController) { public void installMirrorController(MirrorController mirrorController) {
logger.debug("Mirror controller is being installed"); logger.debug("Mirror controller is being installed");

View File

@ -291,6 +291,11 @@ sslEnabled::
Must be `true` to enable SSL. Must be `true` to enable SSL.
Default is `false`. Default is `false`.
sslAutoReload::
Must be `true` to have the broker 'watch' an acceptors keyStorePath and/or trustStorePath and invoke reload() on update.
The watch period is controlled by xref:config-reload.adoc#configuration-reload[the configuration reload feature].
Default is `false`.
keyStorePath:: keyStorePath::
When used on an `acceptor` this is the path to the SSL key store on the server which holds the server's certificates (whether self-signed or signed by an authority). When used on an `acceptor` this is the path to the SSL key store on the server which holds the server's certificates (whether self-signed or signed by an authority).
+ +

View File

@ -0,0 +1,85 @@
/*
* 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.ssl;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.Wait;
import org.junit.Test;
/**
* See the tests/security-resources/build.sh script for details on the security resources used.
*/
public class SSLAutoReloadTest extends ActiveMQTestBase {
private final String PASSWORD = "securepass";
@Test
public void testOneWaySSLWithAutoReload() throws Exception {
File parentDir = new File(temporaryFolder.getRoot(), "sub");
parentDir.mkdirs();
// reference keystore from temp location that we can update
final File keyStoreToReload = new File(parentDir, "server-ks.p12");
copyRecursive(new File(this.getClass().getClassLoader().getResource("unknown-server-keystore.p12").getFile()), keyStoreToReload);
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_AUTO_RELOAD_PROP_NAME, true);
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, keyStoreToReload.getAbsolutePath());
params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, PASSWORD);
params.put(TransportConstants.HOST_PROP_NAME, "localhost");
ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "nettySSL"));
ActiveMQServer server = createServer(false, config);
server.getConfiguration().setConfigurationFileRefreshPeriod(50);
server.start();
waitForServerToStart(server);
String url = "tcp://127.0.0.1:61616?sslEnabled=true;trustStorePath=server-ca-truststore.p12;trustStorePassword=" + PASSWORD;
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocator(url)).setCallTimeout(3000);
try {
createSessionFactory(locator);
fail("Creating session here should fail due to SSL handshake problems.");
} catch (Exception ignored) {
}
// update the server side keystore
copyRecursive(new File(this.getClass().getClassLoader().getResource("server-keystore.p12").getFile()), keyStoreToReload);
// expect success after auto reload, which we wait for
Wait.waitFor(() -> {
try {
addSessionFactory(createSessionFactory(locator));
return true;
} catch (Throwable ignored) {
}
return false;
}, 5000, 100);
}
}