From 4f4edd9144c090e915f3d15e0ad98e5c685e68a7 Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Mon, 25 Nov 2024 18:29:16 +0100 Subject: [PATCH] Add support for certificates hot reload (#4880) Signed-off-by: Andrey Pleskach (cherry picked from commit c22002a5313fa2cc265b43c2177ecc2357ad0701) --- .../security/OpenSearchSecurityPlugin.java | 23 +- .../security/ssl/SslContextHandler.java | 10 +- .../security/ssl/SslSettingsManager.java | 57 +++- .../security/support/ConfigConstants.java | 1 + .../security/ssl/CertificatesRule.java | 22 +- .../SslSettingsManagerReloadListenerTest.java | 256 ++++++++++++++++++ .../config/JdkSslCertificatesLoaderTest.java | 2 +- src/test/resources/log4j2-test.properties | 3 + 8 files changed, 361 insertions(+), 13 deletions(-) create mode 100644 src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 80df34cb83..cc5623ca62 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -221,6 +221,8 @@ import static org.opensearch.security.setting.DeprecatedSettings.checkForDeprecatedSetting; import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX; import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE; +import static org.opensearch.security.support.ConfigConstants.SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED; +import static org.opensearch.security.support.ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED; import static org.opensearch.security.support.ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION; public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin @@ -310,7 +312,11 @@ private static boolean useClusterStateToInitSecurityConfig(final Settings settin * @return true if ssl cert reload is enabled else false */ private static boolean isSslCertReloadEnabled(final Settings settings) { - return settings.getAsBoolean(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, false); + return settings.getAsBoolean(SECURITY_SSL_CERT_RELOAD_ENABLED, false); + } + + private boolean sslCertificatesHotReloadEnabled(final Settings settings) { + return settings.getAsBoolean(SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED, false); } @SuppressWarnings("removal") @@ -1204,9 +1210,20 @@ public Collection createComponents( components.add(dcf); components.add(userService); components.add(passwordHasher); - components.add(sslSettingsManager); + if (isSslCertReloadEnabled(settings) && sslCertificatesHotReloadEnabled(settings)) { + throw new OpenSearchException( + "Either " + + SECURITY_SSL_CERT_RELOAD_ENABLED + + " or " + + SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED + + " can be set to true, but not both." + ); + } + if (sslCertificatesHotReloadEnabled(settings) && !isSslCertReloadEnabled(settings)) { + sslSettingsManager.addSslConfigurationsChangeListener(resourceWatcherService); + } final var allowDefaultInit = settings.getAsBoolean(SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, false); final var useClusterState = useClusterStateToInitSecurityConfig(settings); if (!SSLConfig.isSslOnlyMode() && !isDisabled(settings) && allowDefaultInit && useClusterState) { @@ -2024,7 +2041,7 @@ public List> getSettings() { Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true, Property.NodeScope, Property.Filtered) ); settings.add( - Setting.boolSetting(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered) + Setting.boolSetting(SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered) ); settings.add( Setting.boolSetting( diff --git a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java index fae9cb27ba..c5101da0dd 100644 --- a/src/main/java/org/opensearch/security/ssl/SslContextHandler.java +++ b/src/main/java/org/opensearch/security/ssl/SslContextHandler.java @@ -21,6 +21,9 @@ import java.util.stream.Stream; import javax.net.ssl.SSLEngine; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import org.opensearch.security.ssl.config.Certificate; import org.opensearch.transport.NettyAllocator; @@ -30,6 +33,8 @@ public class SslContextHandler { + private final static Logger LOGGER = LogManager.getLogger(SslContextHandler.class); + private SslContext sslContext; private final SslConfiguration sslConfiguration; @@ -78,7 +83,7 @@ Stream keyMaterialCertificates(final List certificates return certificates.stream().filter(Certificate::hasKey); } - void reloadSslContext() throws CertificateException { + boolean reloadSslContext() throws CertificateException { final var newCertificates = sslConfiguration.certificates(); boolean hasChanges = false; @@ -89,11 +94,13 @@ void reloadSslContext() throws CertificateException { final var newKeyMaterialCertificates = keyMaterialCertificates(newCertificates).collect(Collectors.toList()); if (notSameCertificates(loadedAuthorityCertificates, newAuthorityCertificates)) { + LOGGER.debug("Certification authority has changed"); hasChanges = true; validateDates(newAuthorityCertificates); } if (notSameCertificates(loadedKeyMaterialCertificates, newKeyMaterialCertificates)) { + LOGGER.debug("Key material and access certificate has changed"); hasChanges = true; validateNewKeyMaterialCertificates( loadedKeyMaterialCertificates, @@ -111,6 +118,7 @@ void reloadSslContext() throws CertificateException { loadedCertificates.clear(); loadedCertificates.addAll(newCertificates); } + return hasChanges; } private boolean notSameCertificates(final List loadedCertificates, final List newCertificates) { diff --git a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java index 16a05b2f55..137eb38861 100644 --- a/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java +++ b/src/main/java/org/opensearch/security/ssl/SslSettingsManager.java @@ -11,11 +11,15 @@ package org.opensearch.security.ssl; +import java.io.IOException; +import java.nio.file.Path; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import javax.crypto.Cipher; import com.google.common.collect.ImmutableMap; @@ -29,6 +33,9 @@ import org.opensearch.security.ssl.config.CertType; import org.opensearch.security.ssl.config.SslCertificatesLoader; import org.opensearch.security.ssl.config.SslParameters; +import org.opensearch.watcher.FileChangesListener; +import org.opensearch.watcher.FileWatcher; +import org.opensearch.watcher.ResourceWatcherService; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; @@ -108,13 +115,13 @@ private Map buildSslContexts(final Environment envi public synchronized void reloadSslContext(final CertType certType) { sslContextHandler(certType).ifPresentOrElse(sscContextHandler -> { - LOGGER.info("Reloading {} SSL context", certType.name()); try { - sscContextHandler.reloadSslContext(); + if (sscContextHandler.reloadSslContext()) { + LOGGER.info("{} SSL context reloaded", certType.name()); + } } catch (CertificateException e) { throw new OpenSearchException(e); } - LOGGER.info("{} SSL context reloaded", certType.name()); }, () -> LOGGER.error("Missing SSL Context for {}", certType.name())); } @@ -180,6 +187,50 @@ private Map loadConfigurations(final Environment env return configurationBuilder.build(); } + public void addSslConfigurationsChangeListener(final ResourceWatcherService resourceWatcherService) { + for (final var directoryToMonitor : directoriesToMonitor()) { + final var fileWatcher = new FileWatcher(directoryToMonitor); + fileWatcher.addListener(new FileChangesListener() { + @Override + public void onFileCreated(final Path file) { + onFileChanged(file); + } + + @Override + public void onFileDeleted(final Path file) { + onFileChanged(file); + } + + @Override + public void onFileChanged(final Path file) { + for (final var e : sslSettingsContexts.entrySet()) { + final var certType = e.getKey(); + final var sslConfiguration = e.getValue().sslConfiguration(); + if (sslConfiguration.dependentFiles().contains(file)) { + SslSettingsManager.this.reloadSslContext(certType); + } + } + } + }); + try { + resourceWatcherService.add(fileWatcher, ResourceWatcherService.Frequency.HIGH); + LOGGER.info("Added SSL configuration change listener for: {}", directoryToMonitor); + } catch (IOException e) { + // TODO: should we fail here, or are error logs sufficient? + throw new OpenSearchException("Couldn't add SSL configurations change listener", e); + } + } + } + + private Set directoriesToMonitor() { + return sslSettingsContexts.values() + .stream() + .map(SslContextHandler::sslConfiguration) + .flatMap(c -> c.dependentFiles().stream()) + .map(Path::getParent) + .collect(Collectors.toSet()); + } + private boolean clientNode(final Settings settings) { return !"node".equals(settings.get(OpenSearchSecuritySSLPlugin.CLIENT_TYPE)); } diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 2fe14b8404..174ef09acc 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -285,6 +285,7 @@ public class ConfigConstants { public static final String SECURITY_SSL_DUAL_MODE_SKIP_SECURITY = OPENDISTRO_SECURITY_CONFIG_PREFIX + "passive_security"; public static final String LEGACY_OPENDISTRO_SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED = "opendistro_security_config.ssl_dual_mode_enabled"; public static final String SECURITY_SSL_CERT_RELOAD_ENABLED = "plugins.security.ssl_cert_reload_enabled"; + public static final String SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED = "plugins.security.ssl.certificates_hot_reload.enabled"; public static final String SECURITY_DISABLE_ENVVAR_REPLACEMENT = "plugins.security.disable_envvar_replacement"; public static final String SECURITY_DFM_EMPTY_OVERRIDES_ALL = "plugins.security.dfm_empty_overrides_all"; diff --git a/src/test/java/org/opensearch/security/ssl/CertificatesRule.java b/src/test/java/org/opensearch/security/ssl/CertificatesRule.java index b5a397d7c8..429768fdac 100644 --- a/src/test/java/org/opensearch/security/ssl/CertificatesRule.java +++ b/src/test/java/org/opensearch/security/ssl/CertificatesRule.java @@ -69,16 +69,28 @@ public class CertificatesRule extends ExternalResource { private PrivateKey accessCertificatePrivateKey; + private final boolean generateDefaultCertificates; + + public CertificatesRule() { + this(true); + } + + public CertificatesRule(final boolean generateDefaultCertificates) { + this.generateDefaultCertificates = generateDefaultCertificates; + } + @Override protected void before() throws Throwable { super.before(); temporaryFolder.create(); configRootFolder = temporaryFolder.newFolder("esHome").toPath(); - final var keyPair = generateKeyPair(); - caCertificateHolder = generateCaCertificate(keyPair); - final var keyAndCertificate = generateAccessCertificate(keyPair); - accessCertificatePrivateKey = keyAndCertificate.v1(); - accessCertificateHolder = keyAndCertificate.v2(); + if (generateDefaultCertificates) { + final var keyPair = generateKeyPair(); + caCertificateHolder = generateCaCertificate(keyPair); + final var keyAndCertificate = generateAccessCertificate(keyPair); + accessCertificatePrivateKey = keyAndCertificate.v1(); + accessCertificateHolder = keyAndCertificate.v2(); + } } @Override diff --git a/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java new file mode 100644 index 0000000000..64308d0abb --- /dev/null +++ b/src/test/java/org/opensearch/security/ssl/SslSettingsManagerReloadListenerTest.java @@ -0,0 +1,256 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.ssl; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +import com.carrotsearch.randomizedtesting.RandomizedTest; +import org.awaitility.Awaitility; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.bouncycastle.cert.X509CertificateHolder; + +import org.opensearch.common.collect.Tuple; +import org.opensearch.common.settings.MockSecureSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.env.Environment; +import org.opensearch.env.TestEnvironment; +import org.opensearch.security.ssl.config.CertType; +import org.opensearch.threadpool.TestThreadPool; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +import static org.opensearch.security.ssl.CertificatesUtils.privateKeyToPemObject; +import static org.opensearch.security.ssl.CertificatesUtils.writePemContent; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_ENABLED; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_KEYSTORE_TYPE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMCERT_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMKEY_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_HTTP_TRUSTSTORE_TYPE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_HTTP_PREFIX; +import static org.opensearch.security.ssl.util.SSLConfigConstants.SSL_TRANSPORT_PREFIX; + +public class SslSettingsManagerReloadListenerTest extends RandomizedTest { + + @ClassRule + public static CertificatesRule certificatesRule = new CertificatesRule(false); + + ThreadPool threadPool; + + ResourceWatcherService resourceWatcherService; + + @FunctionalInterface + interface CertificatesWriter { + void write( + final String filePrefix, + final X509CertificateHolder caCertificate, + final Tuple accessKeyAndCertificate + ) throws Exception; + } + + @Before + public void setUp() throws Exception { + threadPool = new TestThreadPool("reload tests"); + resourceWatcherService = new ResourceWatcherService( + Settings.builder().put("resource.reload.interval.high", "1s").build(), + threadPool + ); + } + + static Path path(final String fileName) { + return certificatesRule.configRootFolder().resolve(fileName); + } + + @After + public void cleanUp() { + if (resourceWatcherService != null) { + resourceWatcherService.close(); + } + if (threadPool != null) { + ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS); + } + } + + @Test + public void reloadsSslContextOnPemFilesChanged() throws Exception { + final var securitySettings = new MockSecureSettings(); + securitySettings.setString(SSL_HTTP_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); + securitySettings.setString(SSL_TRANSPORT_PREFIX + "pemkey_password_secure", certificatesRule.privateKeyPassword()); + reloadSslContextOnFilesChanged( + defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) + .put(SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH, path("http_ca_certificate.pem")) + .put(SECURITY_SSL_HTTP_PEMCERT_FILEPATH, path("http_access_certificate.pem")) + .put(SECURITY_SSL_HTTP_PEMKEY_FILEPATH, path("http_access_certificate_pk.pem")) + .put(SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, path("transport_ca_certificate.pem")) + .put(SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, path("transport_access_certificate.pem")) + .put(SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, path("transport_access_certificate_pk.pem")) + .setSecureSettings(securitySettings) + .build(), + (filePrefix, caCertificate, accessKeyAndCertificate) -> { + writePemContent(path(String.format("%s_ca_certificate.pem", filePrefix)), caCertificate); + writePemContent(path(String.format("%s_access_certificate.pem", filePrefix)), accessKeyAndCertificate.v2()); + writePemContent( + path(String.format("%s_access_certificate_pk.pem", filePrefix)), + privateKeyToPemObject(accessKeyAndCertificate.v1(), certificatesRule.privateKeyPassword()) + ); + } + ); + } + + @Test + public void reloadsSslContextOnJdkStoreFilesChanged() throws Exception { + final var keyStorePassword = randomAsciiAlphanumOfLength(10); + final var secureSettings = new MockSecureSettings(); + secureSettings.setString(SSL_HTTP_PREFIX + "truststore_password_secure", keyStorePassword); + secureSettings.setString(SSL_HTTP_PREFIX + "keystore_password_secure", keyStorePassword); + secureSettings.setString(SSL_HTTP_PREFIX + "keystore_keypassword_secure", certificatesRule.privateKeyPassword()); + + secureSettings.setString(SSL_TRANSPORT_PREFIX + "truststore_password_secure", keyStorePassword); + secureSettings.setString(SSL_TRANSPORT_PREFIX + "keystore_password_secure", keyStorePassword); + secureSettings.setString(SSL_TRANSPORT_PREFIX + "keystore_keypassword_secure", certificatesRule.privateKeyPassword()); + reloadSslContextOnFilesChanged( + defaultSettingsBuilder().put(SECURITY_SSL_HTTP_ENABLED, true) + .put(SECURITY_SSL_HTTP_TRUSTSTORE_FILEPATH, path("http_truststore.jks")) + .put(SECURITY_SSL_HTTP_TRUSTSTORE_TYPE, "jks") + .put(SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, path("http_keystore.p12")) + .put(SECURITY_SSL_HTTP_KEYSTORE_TYPE, "pkcs12") + .put(SECURITY_SSL_TRANSPORT_ENABLED, true) + .put(SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, path("transport_truststore.jks")) + .put(SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE, "jks") + .put(SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, path("transport_keystore.p12")) + .put(SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, "pkcs12") + .setSecureSettings(secureSettings) + .build(), + (filePrefix, caCertificate, accessKeyAndCertificate) -> { + final var trustStore = KeyStore.getInstance("jks"); + trustStore.load(null, null); + trustStore.setCertificateEntry("ca", certificatesRule.toX509Certificate(caCertificate)); + writeStore(trustStore, path(String.format("%s_truststore.jks", filePrefix)), keyStorePassword); + + final var keyStore = KeyStore.getInstance("pkcs12"); + keyStore.load(null, null); + keyStore.setKeyEntry( + "pk", + accessKeyAndCertificate.v1(), + certificatesRule.privateKeyPassword().toCharArray(), + new X509Certificate[] { certificatesRule.toX509Certificate(accessKeyAndCertificate.v2()) } + ); + writeStore(keyStore, path(String.format("%s_keystore.p12", filePrefix)), keyStorePassword); + } + ); + } + + void reloadSslContextOnFilesChanged(final Settings settings, final CertificatesWriter certificatesWriter) throws Exception { + final var defaultHttpCertificates = generateCertificates(); + final var defaultHttpKeyPair = defaultHttpCertificates.v1(); + final var httpCaCertificate = defaultHttpCertificates.v2().v1(); + final var httpAccessKeyAndCertificate = defaultHttpCertificates.v2().v2(); + + final var defaultTransportCertificates = generateCertificates(); + final var defaultTransportKeyPair = defaultTransportCertificates.v1(); + final var transportCaCertificate = defaultTransportCertificates.v2().v1(); + final var transportAccessKeyAndCertificate = defaultTransportCertificates.v2().v2(); + + final var reloadHttpCertificates = randomBoolean(); + + certificatesWriter.write("http", httpCaCertificate, httpAccessKeyAndCertificate); + certificatesWriter.write("transport", transportCaCertificate, transportAccessKeyAndCertificate); + + final var sslSettingsManager = new SslSettingsManager(TestEnvironment.newEnvironment(settings)); + sslSettingsManager.addSslConfigurationsChangeListener(resourceWatcherService); + + final var httpSslContextBefore = sslSettingsManager.sslContextHandler(CertType.HTTP).orElseThrow().sslContext(); + final var transportSslContextBefore = sslSettingsManager.sslContextHandler(CertType.TRANSPORT).orElseThrow().sslContext(); + final var transportClientSslContextBefore = sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT) + .orElseThrow() + .sslContext(); + + final var filePrefix = reloadHttpCertificates ? "http" : "transport"; + final var keyPair = reloadHttpCertificates ? defaultHttpKeyPair : defaultTransportKeyPair; + var caCertificate = reloadHttpCertificates ? httpCaCertificate : transportCaCertificate; + var keyAndCertificate = reloadHttpCertificates ? httpAccessKeyAndCertificate : transportAccessKeyAndCertificate; + + if (randomBoolean()) { + caCertificate = certificatesRule.generateCaCertificate( + keyPair, + caCertificate.getNotBefore().toInstant(), + caCertificate.getNotAfter().toInstant().plus(365, ChronoUnit.DAYS) + ); + } else { + keyAndCertificate = certificatesRule.generateAccessCertificate( + keyPair, + keyAndCertificate.v2().getNotBefore().toInstant(), + keyAndCertificate.v2().getNotAfter().toInstant().plus(365, ChronoUnit.DAYS) + ); + } + certificatesWriter.write(filePrefix, caCertificate, keyAndCertificate); + Awaitility.await("Wait for reloading SSL context").until(() -> { + final var httpSslContextAfter = sslSettingsManager.sslContextHandler(CertType.HTTP).orElseThrow().sslContext(); + final var transportSslContextAfter = sslSettingsManager.sslContextHandler(CertType.TRANSPORT).orElseThrow().sslContext(); + final var transportClientSslContextAfter = sslSettingsManager.sslContextHandler(CertType.TRANSPORT_CLIENT) + .orElseThrow() + .sslContext(); + + if (reloadHttpCertificates) { + return !httpSslContextAfter.equals(httpSslContextBefore) + && transportSslContextBefore.equals(transportSslContextAfter) + && transportClientSslContextBefore.equals(transportClientSslContextAfter); + } else { + return httpSslContextAfter.equals(httpSslContextBefore) + && !transportSslContextBefore.equals(transportSslContextAfter) + && !transportClientSslContextBefore.equals(transportClientSslContextAfter); + } + }); + } + + private Tuple>> generateCertificates() throws Exception { + final var defaultKeyPair = certificatesRule.generateKeyPair(); + return Tuple.tuple( + defaultKeyPair, + Tuple.tuple(certificatesRule.generateCaCertificate(defaultKeyPair), certificatesRule.generateAccessCertificate(defaultKeyPair)) + ); + } + + Settings.Builder defaultSettingsBuilder() { + return Settings.builder() + .put(Environment.PATH_HOME_SETTING.getKey(), certificatesRule.configRootFolder().toString()) + .put("client.type", "node"); + } + + void writeStore(final KeyStore keyStore, final Path path, final String password) throws Exception { + try (final var out = Files.newOutputStream(path)) { + keyStore.store(out, password.toCharArray()); + } + } + +} diff --git a/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java b/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java index 174f6c0fd5..829fc6a386 100644 --- a/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java +++ b/src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java @@ -289,7 +289,7 @@ Path createTrustStore(final String type, final String password, Map> keysAndCertificates) throws Exception { final var keyStore = keyStore(type); - final var keyStorePath = path(String.format("keystore.%s", isNull(type) ? "jsk" : type)); + final var keyStorePath = path(String.format("keystore.%s", isNull(type) ? "jks" : type)); for (final var alias : keysAndCertificates.keySet()) { final var keyAndCertificate = keysAndCertificates.get(alias); keyStore.setKeyEntry( diff --git a/src/test/resources/log4j2-test.properties b/src/test/resources/log4j2-test.properties index 866b68325c..78871d8395 100644 --- a/src/test/resources/log4j2-test.properties +++ b/src/test/resources/log4j2-test.properties @@ -23,6 +23,9 @@ logger.ldapServerLogger.level = info logger.ldapAuthBackend.name = com.amazon.dlic.auth.ldap.backend.LDAPAuthorizationBackend logger.ldapAuthBackend.level = debug +logger.sslConfig.name = org.opensearch.security.ssl +logger.sslConfig.level = info + #logger.resolver.name = org.opensearch.security.resolver #logger.resolver.level = trace