From 129d1a499e4130762856806a8acc84ecb344b285 Mon Sep 17 00:00:00 2001 From: Clement de Groc Date: Wed, 26 Mar 2025 16:03:41 +0100 Subject: [PATCH 1/3] [TINKERPOP-3146] Support hot reloading of SSL certificates --- CHANGELOG.asciidoc | 1 + gremlin-server/pom.xml | 10 ++ .../gremlin/server/AbstractChannelizer.java | 93 +++++++------ .../SSLStoreFilesModificationWatcher.java | 124 ++++++++++++++++++ .../SSLStoreFilesModificationWatcherTest.java | 65 +++++++++ pom.xml | 6 + 6 files changed, 256 insertions(+), 43 deletions(-) create mode 100644 gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java create mode 100644 gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcherTest.java diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 8e0d126c102..97f29200ff3 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -45,6 +45,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima * Changed `gremlin-go` Client `ReadBufferSize` and `WriteBufferSize` defaults to 1048576 (1MB) to align with DriverRemoteConnection. * Fixed bug in `IndexStep` which prevented Java serialization due to non-serializable lambda usage by creating serializable function classes. * Fixed bug in `Operator` which was caused only a single method parameter to be Collection type checked instead of all parameters. +* Support hot reloading of SSL certificates. [[release-3-7-3]] === TinkerPop 3.7.3 (October 23, 2024) diff --git a/gremlin-server/pom.xml b/gremlin-server/pom.xml index 80fc2cb60ec..97ed90a1d73 100644 --- a/gremlin-server/pom.xml +++ b/gremlin-server/pom.xml @@ -59,6 +59,16 @@ limitations under the License. logback-classic true + + io.github.hakky54 + sslcontext-kickstart-for-netty + + + org.slf4j + slf4j-api + + + com.codahale.metrics diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java index c133f686529..8374a8cbb9f 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java @@ -21,9 +21,13 @@ import io.netty.channel.group.ChannelGroup; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.netty.handler.timeout.IdleStateHandler; +import nl.altindag.ssl.SSLFactory; +import nl.altindag.ssl.exception.GenericSecurityException; +import nl.altindag.ssl.netty.util.NettySslUtils; +import nl.altindag.ssl.util.SSLFactoryUtils; +import org.apache.tinkerpop.gremlin.server.util.SSLStoreFilesModificationWatcher; import org.apache.tinkerpop.gremlin.util.MessageSerializer; import org.apache.tinkerpop.gremlin.util.message.RequestMessage; import org.apache.tinkerpop.gremlin.util.message.ResponseMessage; @@ -44,19 +48,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLException; -import javax.net.ssl.TrustManagerFactory; - import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -65,6 +62,7 @@ import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.stream.Stream; /** @@ -148,8 +146,32 @@ public void init(final ServerGremlinExecutor serverGremlinExecutor) { configureSerializers(); // configure ssl if present - sslContext = settings.optionalSsl().isPresent() && settings.ssl.enabled ? - Optional.ofNullable(createSSLContext(settings)) : Optional.empty(); + if (settings.optionalSsl().isPresent() && settings.ssl.enabled) { + if (settings.ssl.getSslContext().isPresent()) { + logger.info("Using the SslContext override"); + this.sslContext = settings.ssl.getSslContext(); + } else { + final SSLFactory sslFactory = createSSLFactoryBuilder(settings).withSwappableTrustMaterial().withSwappableIdentityMaterial().build(); + this.sslContext = Optional.of(createSSLContext(sslFactory)); + + // Every minute, check if keyStore/trustStore were modified, and if they were, + // reload the SSLFactory which will reload the underlying KeyManager/TrustManager that Netty SSLHandler uses. + scheduledExecutorService.scheduleAtFixedRate( + new SSLStoreFilesModificationWatcher(settings.ssl.keyStore, settings.ssl.trustStore, () -> { + SSLFactory newSslFactory = createSSLFactoryBuilder(settings).build(); + try { + SSLFactoryUtils.reload(sslFactory, newSslFactory); + } catch (RuntimeException e) { + logger.error("Failed to reload SSLFactory", e); + } + }), + 1L, 1L, TimeUnit.MINUTES + ); + } + } else { + this.sslContext = Optional.empty(); + } + if (sslContext.isPresent()) logger.info("SSL enabled"); authenticator = createAuthenticator(settings.authentication); @@ -307,74 +329,59 @@ private void configureSerializers() { } } - private SslContext createSSLContext(final Settings settings) { + private SSLFactory.Builder createSSLFactoryBuilder(final Settings settings) { final Settings.SslSettings sslSettings = settings.ssl; - if (sslSettings.getSslContext().isPresent()) { - logger.info("Using the SslContext override"); - return sslSettings.getSslContext().get(); - } - - final SslProvider provider = SslProvider.JDK; - - final SslContextBuilder builder; - - // Build JSSE SSLContext + final SSLFactory.Builder builder = SSLFactory.builder(); try { - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - - // Load private key and signed cert if (null != sslSettings.keyStore) { final String keyStoreType = null == sslSettings.keyStoreType ? KeyStore.getDefaultType() : sslSettings.keyStoreType; - final KeyStore keystore = KeyStore.getInstance(keyStoreType); final char[] password = null == sslSettings.keyStorePassword ? null : sslSettings.keyStorePassword.toCharArray(); try (final InputStream in = new FileInputStream(sslSettings.keyStore)) { - keystore.load(in, password); + builder.withIdentityMaterial(in, password, keyStoreType); } - kmf.init(keystore, password); } else { throw new IllegalStateException("keyStore must be configured when SSL is enabled."); } - builder = SslContextBuilder.forServer(kmf); - // Load custom truststore for client auth certs if (null != sslSettings.trustStore) { final String trustStoreType = null != sslSettings.trustStoreType ? sslSettings.trustStoreType - : sslSettings.keyStoreType != null ? sslSettings.keyStoreType : KeyStore.getDefaultType(); - - final KeyStore truststore = KeyStore.getInstance(trustStoreType); + : sslSettings.keyStoreType != null ? sslSettings.keyStoreType : KeyStore.getDefaultType(); final char[] password = null == sslSettings.trustStorePassword ? null : sslSettings.trustStorePassword.toCharArray(); try (final InputStream in = new FileInputStream(sslSettings.trustStore)) { - truststore.load(in, password); + builder.withTrustMaterial(in, password, trustStoreType); } - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(truststore); - builder.trustManager(tmf); } - - } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException e) { + } catch (GenericSecurityException | IOException e) { logger.error(e.getMessage()); throw new RuntimeException("There was an error enabling SSL.", e); } if (null != sslSettings.sslCipherSuites && !sslSettings.sslCipherSuites.isEmpty()) { - builder.ciphers(sslSettings.sslCipherSuites); + builder.withCiphers(sslSettings.sslCipherSuites.toArray(new String[] {})); } if (null != sslSettings.sslEnabledProtocols && !sslSettings.sslEnabledProtocols.isEmpty()) { - builder.protocols(sslSettings.sslEnabledProtocols.toArray(new String[] {})); + builder.withProtocols(sslSettings.sslEnabledProtocols.toArray(new String[] {})); } - + if (null != sslSettings.needClientAuth && ClientAuth.OPTIONAL == sslSettings.needClientAuth) { logger.warn("needClientAuth = OPTIONAL is not a secure configuration. Setting to REQUIRE."); sslSettings.needClientAuth = ClientAuth.REQUIRE; } - builder.clientAuth(sslSettings.needClientAuth).sslProvider(provider); + if (sslSettings.needClientAuth == ClientAuth.REQUIRE) { + builder.withNeedClientAuthentication(true); + } + + return builder; + } + private static SslContext createSSLContext(final SSLFactory sslFactory) { try { - return builder.build(); + final SslProvider provider = SslProvider.JDK; + return NettySslUtils.forServer(sslFactory).sslProvider(provider).build(); } catch (SSLException ssle) { logger.error(ssle.getMessage()); throw new RuntimeException("There was an error enabling SSL.", ssle); diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java new file mode 100644 index 00000000000..b139a405055 --- /dev/null +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java @@ -0,0 +1,124 @@ +/* + * 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.tinkerpop.gremlin.server.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +/** + * FileWatcher monitoring changes to SSL keyStore/trustStore files. + * If a keyStore/trustStore file is set to null, it will be ignored. + * If a keyStore/trustStore file is deleted, it will be considered not modified. + */ +public class SSLStoreFilesModificationWatcher implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(SSLStoreFilesModificationWatcher.class); + + private final Path keyStore; + private final Path trustStore; + private final Runnable onModificationRunnable; + + private ZonedDateTime lastModifiedTimeKeyStore = null; + private ZonedDateTime lastModifiedTimeTrustStore = null; + + /** + * Create a FileWatcher on keyStore/trustStore + * + * @param keyStore path to the keyStore file or null to ignore + * @param trustStore path to the trustStore file or null to ignore + * @param onModificationRunnable function to run when a modification to the keyStore or trustStore is detected + */ + public SSLStoreFilesModificationWatcher(String keyStore, String trustStore, Runnable onModificationRunnable) { + // keyStore/trustStore can be null when not specified in gremlin-server Settings + this.keyStore = keyStore != null ? Paths.get(keyStore) : null; + this.trustStore = trustStore != null ? Paths.get(trustStore) : null; + this.onModificationRunnable = onModificationRunnable; + + // Initialize lastModifiedTime + try { + if (this.keyStore != null) { + lastModifiedTimeKeyStore = getLastModifiedTime(this.keyStore); + } + if (this.trustStore != null) { + lastModifiedTimeTrustStore = getLastModifiedTime(this.trustStore); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + logger.info("Started listening to modifications to the KeyStore and TrustStore files"); + } + + @Override + public void run() { + try { + boolean keyStoreUpdated = false; + boolean trustStoreUpdated = false; + ZonedDateTime keyStoreModificationDateTime = null; + ZonedDateTime trustStoreModificationDateTime = null; + + // Check if the keyStore file still exists and compare its last_modified_time + if (keyStore != null && Files.exists(keyStore)) { + keyStoreModificationDateTime = getLastModifiedTime(keyStore); + keyStoreUpdated = lastModifiedTimeKeyStore.isBefore(keyStoreModificationDateTime); + if (keyStoreUpdated) { + logger.info("KeyStore file has been modified."); + } + } + + // Check if the trustStore file still exists and compare its last_modified_time + if (trustStore != null && Files.exists(trustStore)) { + trustStoreModificationDateTime = getLastModifiedTime(trustStore); + trustStoreUpdated = lastModifiedTimeTrustStore.isBefore(trustStoreModificationDateTime); + if (trustStoreUpdated) { + logger.info("TrustStore file has been modified."); + } + } + + // If one of the files was updated, execute + if (keyStoreUpdated || trustStoreUpdated) { + onModificationRunnable.run(); + + if (keyStoreUpdated) { + lastModifiedTimeKeyStore = keyStoreModificationDateTime; + logger.info("Updated KeyStore configuration"); + } + if (trustStoreUpdated) { + lastModifiedTimeTrustStore = trustStoreModificationDateTime; + logger.info("Updated TrustStore configuration"); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static ZonedDateTime getLastModifiedTime(Path filepath) throws IOException { + BasicFileAttributes attributes = Files.readAttributes(filepath, BasicFileAttributes.class); + return ZonedDateTime.ofInstant(attributes.lastModifiedTime().toInstant(), ZoneOffset.UTC); + } +} \ No newline at end of file diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcherTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcherTest.java new file mode 100644 index 00000000000..46fe502d4d9 --- /dev/null +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcherTest.java @@ -0,0 +1,65 @@ +/* + * 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.tinkerpop.gremlin.server.util; + +import io.cucumber.messages.internal.com.google.common.io.Files; +import org.apache.tinkerpop.gremlin.TestHelper; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SSLStoreFilesModificationWatcherTest { + @Test + public void shouldDetectFileChange() throws IOException { + File keyStoreFile = TestHelper.generateTempFileFromResource(SSLStoreFilesModificationWatcherTest.class, "/server-key.jks", ""); + File trustStoreFile = TestHelper.generateTempFileFromResource(SSLStoreFilesModificationWatcherTest.class, "/server-trust.jks", ""); + + AtomicBoolean modified = new AtomicBoolean(false); + SSLStoreFilesModificationWatcher watcher = new SSLStoreFilesModificationWatcher(keyStoreFile.getAbsolutePath(), trustStoreFile.getAbsolutePath(), () -> modified.set(true)); + + // No modification yet + watcher.run(); + assertFalse(modified.get()); + + // KeyStore file modified + Files.touch(keyStoreFile); + watcher.run(); + assertTrue(modified.get()); + modified.set(false); + + // No modification + watcher.run(); + assertFalse(modified.get()); + + // TrustStore file modified + Files.touch(trustStoreFile); + watcher.run(); + assertTrue(modified.get()); + modified.set(false); + + // No modification + watcher.run(); + assertFalse(modified.get()); + } +} diff --git a/pom.xml b/pom.xml index 02f4e96a793..50aad5a6e84 100644 --- a/pom.xml +++ b/pom.xml @@ -178,6 +178,7 @@ limitations under the License. 1.7.25 2.0 3.3.2 + 9.1.0 UTF-8 UTF-8 @@ -781,6 +782,11 @@ limitations under the License. commons-lang3 ${commons.lang3.version} + + io.github.hakky54 + sslcontext-kickstart-for-netty + ${sslcontext.kickstart.version} + com.codahale.metrics metrics-core From d144f576146e2c81ab61a774b2899f6a1de8ca01 Mon Sep 17 00:00:00 2001 From: Clement de Groc Date: Fri, 18 Apr 2025 15:59:06 +0200 Subject: [PATCH 2/3] [TINKERPOP-3146] Address PR comments --- .../tinkerpop/gremlin/server/AbstractChannelizer.java | 2 +- .../java/org/apache/tinkerpop/gremlin/server/Settings.java | 6 ++++++ .../server/util/SSLStoreFilesModificationWatcher.java | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java index 8374a8cbb9f..a2504583bbf 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java @@ -165,7 +165,7 @@ public void init(final ServerGremlinExecutor serverGremlinExecutor) { logger.error("Failed to reload SSLFactory", e); } }), - 1L, 1L, TimeUnit.MINUTES + settings.ssl.refreshInterval, settings.ssl.refreshInterval, TimeUnit.MILLISECONDS ); } } else { diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java index 7a21afcf0d1..8968fa2daa4 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java @@ -581,6 +581,12 @@ public static class SslSettings { */ public ClientAuth needClientAuth = ClientAuth.NONE; + /** + * The interval, in milliseconds, at which the trustStore and keyStore files are checked for updates. + * The default interval is 60 seconds. + */ + public long refreshInterval = 60000L; + private SslContext sslContext; /** diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java index b139a405055..fc80e151a96 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/SSLStoreFilesModificationWatcher.java @@ -53,7 +53,7 @@ public class SSLStoreFilesModificationWatcher implements Runnable { * @param trustStore path to the trustStore file or null to ignore * @param onModificationRunnable function to run when a modification to the keyStore or trustStore is detected */ - public SSLStoreFilesModificationWatcher(String keyStore, String trustStore, Runnable onModificationRunnable) { + public SSLStoreFilesModificationWatcher(final String keyStore, final String trustStore, final Runnable onModificationRunnable) { // keyStore/trustStore can be null when not specified in gremlin-server Settings this.keyStore = keyStore != null ? Paths.get(keyStore) : null; this.trustStore = trustStore != null ? Paths.get(trustStore) : null; @@ -117,8 +117,8 @@ public void run() { } } - private static ZonedDateTime getLastModifiedTime(Path filepath) throws IOException { + private static ZonedDateTime getLastModifiedTime(final Path filepath) throws IOException { BasicFileAttributes attributes = Files.readAttributes(filepath, BasicFileAttributes.class); return ZonedDateTime.ofInstant(attributes.lastModifiedTime().toInstant(), ZoneOffset.UTC); } -} \ No newline at end of file +} From 0537cbefddad9d0ac1e052512bc2a7b83a7d8766 Mon Sep 17 00:00:00 2001 From: Clement de Groc Date: Wed, 30 Apr 2025 09:19:25 +0200 Subject: [PATCH 3/3] [TINKERPOP-3146] Allow disabling hot reloading Set refreshInterval to <= 0 to disable hot reloading (previous behavior) --- .../gremlin/server/AbstractChannelizer.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java index a2504583bbf..9af52699dc7 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/AbstractChannelizer.java @@ -154,19 +154,21 @@ public void init(final ServerGremlinExecutor serverGremlinExecutor) { final SSLFactory sslFactory = createSSLFactoryBuilder(settings).withSwappableTrustMaterial().withSwappableIdentityMaterial().build(); this.sslContext = Optional.of(createSSLContext(sslFactory)); - // Every minute, check if keyStore/trustStore were modified, and if they were, - // reload the SSLFactory which will reload the underlying KeyManager/TrustManager that Netty SSLHandler uses. - scheduledExecutorService.scheduleAtFixedRate( - new SSLStoreFilesModificationWatcher(settings.ssl.keyStore, settings.ssl.trustStore, () -> { - SSLFactory newSslFactory = createSSLFactoryBuilder(settings).build(); - try { - SSLFactoryUtils.reload(sslFactory, newSslFactory); - } catch (RuntimeException e) { - logger.error("Failed to reload SSLFactory", e); - } - }), - settings.ssl.refreshInterval, settings.ssl.refreshInterval, TimeUnit.MILLISECONDS - ); + if (settings.ssl.refreshInterval > 0) { + // At the scheduled refreshInterval, check whether the keyStore or trustStore has been modified. If they were, + // reload the SSLFactory which will reload the underlying KeyManager/TrustManager that Netty SSLHandler uses. + scheduledExecutorService.scheduleAtFixedRate( + new SSLStoreFilesModificationWatcher(settings.ssl.keyStore, settings.ssl.trustStore, () -> { + SSLFactory newSslFactory = createSSLFactoryBuilder(settings).build(); + try { + SSLFactoryUtils.reload(sslFactory, newSslFactory); + } catch (RuntimeException e) { + logger.error("Failed to reload SSLFactory", e); + } + }), + settings.ssl.refreshInterval, settings.ssl.refreshInterval, TimeUnit.MILLISECONDS + ); + } } } else { this.sslContext = Optional.empty();