diff --git a/changelog/unreleased/pr-20842.toml b/changelog/unreleased/pr-20842.toml new file mode 100644 index 000000000000..dfc17e00883a --- /dev/null +++ b/changelog/unreleased/pr-20842.toml @@ -0,0 +1,5 @@ +type = "c" +message = "Replace datanode insecure_startup configuration with selfsigned_startup, providing full selfsigned SSL setup" + +issues = ["18911"] +pulls = ["20842"] diff --git a/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeCertRenewalPeriodical.java b/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeCertRenewalPeriodical.java index 75b4ee84199d..c247ab7f3e53 100644 --- a/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeCertRenewalPeriodical.java +++ b/data-node/src/main/java/org/graylog/datanode/bootstrap/preflight/DataNodeCertRenewalPeriodical.java @@ -37,7 +37,8 @@ @Singleton public class DataNodeCertRenewalPeriodical extends Periodical { private static final Logger LOG = LoggerFactory.getLogger(DataNodeCertRenewalPeriodical.class); - public static final Duration PERIODICAL_DURATION = Duration.ofMinutes(30); + public static final Duration PERIODICAL_DURATION = Duration.ofSeconds(2); + public static final Duration CSR_TRIGGER_PERIOD_LIMIT = Duration.ofMinutes(5); private final DatanodeKeystore datanodeKeystore; private final Supplier renewalPolicySupplier; @@ -46,15 +47,13 @@ public class DataNodeCertRenewalPeriodical extends Periodical { private final Supplier isServerInPreflightMode; + private Instant lastCsrRequest; + @Inject public DataNodeCertRenewalPeriodical(DatanodeKeystore datanodeKeystore, ClusterConfigService clusterConfigService, CsrRequester csrRequester, PreflightConfigService preflightConfigService) { this(datanodeKeystore, () -> clusterConfigService.get(RenewalPolicy.class), csrRequester, () -> isInPreflight(preflightConfigService)); } - private static boolean isInPreflight(PreflightConfigService preflightConfigService) { - return preflightConfigService.getPreflightConfigResult() != PreflightConfigResult.FINISHED; - } - protected DataNodeCertRenewalPeriodical(DatanodeKeystore datanodeKeystore, Supplier renewalPolicySupplier, CsrRequester csrRequester, Supplier isServerInPreflightMode) { this.datanodeKeystore = datanodeKeystore; this.renewalPolicySupplier = renewalPolicySupplier; @@ -62,7 +61,6 @@ protected DataNodeCertRenewalPeriodical(DatanodeKeystore datanodeKeystore, Suppl this.isServerInPreflightMode = isServerInPreflightMode; } - @Override public void doRun() { if (isServerInPreflightMode.get()) { @@ -80,7 +78,10 @@ public void doRun() { case MANUAL -> manualRenewal(); } }); + } + private static boolean isInPreflight(PreflightConfigService preflightConfigService) { + return preflightConfigService.getPreflightConfigResult() != PreflightConfigResult.FINISHED; } private void manualRenewal() { @@ -88,7 +89,11 @@ private void manualRenewal() { } private void automaticRenewal() { - csrRequester.triggerCertificateSigningRequest(); + final Instant now = Instant.now(); + if (lastCsrRequest == null || now.minus(CSR_TRIGGER_PERIOD_LIMIT).isAfter(lastCsrRequest)) { + lastCsrRequest = now; + csrRequester.triggerCertificateSigningRequest(); + } } private boolean needsNewCertificate(RenewalPolicy renewalPolicy) { diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/variants/InSecureConfiguration.java b/data-node/src/main/java/org/graylog/datanode/configuration/variants/InSecureConfiguration.java index b56d76b9f711..a6951471e41b 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/variants/InSecureConfiguration.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/variants/InSecureConfiguration.java @@ -18,9 +18,14 @@ import org.graylog.datanode.Configuration; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +@Deprecated public class InSecureConfiguration implements SecurityConfigurationVariant { + private static final Logger LOG = LoggerFactory.getLogger(InSecureConfiguration.class); + @Override public boolean isConfigured(final Configuration localConfiguration) { return localConfiguration.isInsecureStartup(); @@ -28,6 +33,7 @@ public boolean isConfigured(final Configuration localConfiguration) { @Override public OpensearchSecurityConfiguration build() throws KeyStoreStorageException { + LOG.warn("Insecure configuration is deprecated. Please use selfsigned_setup to create fully encrypted setups."); return OpensearchSecurityConfiguration.disabled(); } } diff --git a/full-backend-tests/src/test/java/org/graylog/datanode/DatanodeSelfsignedStartupIT.java b/full-backend-tests/src/test/java/org/graylog/datanode/DatanodeSelfsignedStartupIT.java new file mode 100644 index 000000000000..a48220dbdfc8 --- /dev/null +++ b/full-backend-tests/src/test/java/org/graylog/datanode/DatanodeSelfsignedStartupIT.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog.datanode; + +import com.github.joschi.jadconfig.util.Duration; +import com.github.rholder.retry.RetryException; +import io.restassured.response.ValidatableResponse; +import org.graylog.testing.completebackend.ContainerizedGraylogBackend; +import org.graylog.testing.completebackend.Lifecycle; +import org.graylog.testing.completebackend.apis.GraylogApis; +import org.graylog.testing.containermatrix.SearchServer; +import org.graylog.testing.containermatrix.annotations.ContainerMatrixTest; +import org.graylog.testing.containermatrix.annotations.ContainerMatrixTestsConfiguration; +import org.graylog.testing.restoperations.DatanodeOpensearchWait; +import org.graylog.testing.restoperations.RestOperationParameters; +import org.graylog2.security.IndexerJwtAuthTokenProvider; +import org.graylog2.security.JwtSecret; +import org.hamcrest.Matchers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.concurrent.ExecutionException; + +@ContainerMatrixTestsConfiguration(serverLifecycle = Lifecycle.CLASS, searchVersions = SearchServer.DATANODE_DEV, + additionalConfigurationParameters = { + @ContainerMatrixTestsConfiguration.ConfigurationParameter(key = "GRAYLOG_DATANODE_INSECURE_STARTUP", value = "false"), + @ContainerMatrixTestsConfiguration.ConfigurationParameter(key = "GRAYLOG_SELFSIGNED_STARTUP", value = "true"), + @ContainerMatrixTestsConfiguration.ConfigurationParameter(key = "GRAYLOG_ELASTICSEARCH_HOSTS", value = ""), + }) +public class DatanodeSelfsignedStartupIT { + + + private final Logger log = LoggerFactory.getLogger(DatanodeProvisioningIT.class); + + private final GraylogApis apis; + + public DatanodeSelfsignedStartupIT(GraylogApis apis) { + this.apis = apis; + } + + @ContainerMatrixTest + public void testSelfsignedStartup() throws ExecutionException, RetryException { + testEncryptedConnectionToOpensearch(); + } + + + private int getOpensearchPort() { + final String indexerHostAddress = apis.backend().searchServerInstance().getHttpHostAddress(); + return Integer.parseInt(indexerHostAddress.split(":")[1]); + } + + private void testEncryptedConnectionToOpensearch() throws ExecutionException, RetryException { + try { + final ValidatableResponse response = new DatanodeOpensearchWait(RestOperationParameters.builder() + .port(getOpensearchPort()) + .relaxedHTTPSValidation(true) + .jwtTokenProvider(new IndexerJwtAuthTokenProvider(new JwtSecret(ContainerizedGraylogBackend.PASSWORD_SECRET), Duration.seconds(120), Duration.seconds(60))) + .build()) + .waitForNodesCount(1); + + response.assertThat().body("status", Matchers.equalTo("green")); + } catch (Exception e) { + log.error("Could not connect to Opensearch\n" + apis.backend().getSearchLogs()); + throw e; + } + } +} diff --git a/graylog2-server/src/main/java/org/graylog2/Configuration.java b/graylog2-server/src/main/java/org/graylog2/Configuration.java index d4279b8b0348..cc01d2b40b09 100644 --- a/graylog2-server/src/main/java/org/graylog2/Configuration.java +++ b/graylog2-server/src/main/java/org/graylog2/Configuration.java @@ -37,6 +37,7 @@ import org.graylog2.cluster.leader.LeaderElectionMode; import org.graylog2.cluster.leader.LeaderElectionService; import org.graylog2.cluster.lock.MongoLockService; +import org.graylog2.configuration.Documentation; import org.graylog2.configuration.converters.JavaDurationConverter; import org.graylog2.notifications.Notification; import org.graylog2.outputs.BatchSizeConfig; @@ -249,6 +250,14 @@ public class Configuration extends CaConfiguration { @Parameter(value = "field_value_suggestion_mode", required = true, converter = FieldValueSuggestionModeConverter.class) private FieldValueSuggestionMode fieldValueSuggestionMode = FieldValueSuggestionMode.ON; + @Documentation(""" + Enabling this parameter will activate automatic security configuration. Graylog server will + set a default 30-day automatic certificate renewal policy and create a self-signed CA. This CA + will be used to sign certificates for SSL communication between the server and datanodes. + """) + @Parameter(value = "selfsigned_startup") + private boolean selfsignedStartup = false; + public static final String INSTALL_HTTP_CONNECTION_TIMEOUT = "install_http_connection_timeout"; public static final String INSTALL_OUTPUT_BUFFER_DRAINING_INTERVAL = "install_output_buffer_drain_interval"; public static final String INSTALL_OUTPUT_BUFFER_DRAINING_MAX_RETRIES = "install_output_buffer_max_retries"; @@ -559,6 +568,10 @@ public int getQueryLatencyMonitoringWindowSize() { return queryLatencyMonitoringWindowSize; } + public boolean selfsignedStartupEnabled() { + return selfsignedStartup; + } + public static class NodeIdFileValidator implements Validator { @Override public void validate(String name, String path) throws ValidationException { diff --git a/graylog2-server/src/main/java/org/graylog2/cluster/preflight/GraylogServerProvisioningBindings.java b/graylog2-server/src/main/java/org/graylog2/cluster/preflight/GraylogServerProvisioningBindings.java index ae717de032b2..e257e95e5646 100644 --- a/graylog2-server/src/main/java/org/graylog2/cluster/preflight/GraylogServerProvisioningBindings.java +++ b/graylog2-server/src/main/java/org/graylog2/cluster/preflight/GraylogServerProvisioningBindings.java @@ -21,6 +21,7 @@ import org.graylog2.bootstrap.preflight.GraylogCertificateProvisionerImpl; import org.graylog2.cluster.certificates.CertificateExchange; import org.graylog2.cluster.certificates.CertificateExchangeImpl; +import org.graylog2.configuration.IndexerDiscoverySecurityAutoconfig; public class GraylogServerProvisioningBindings extends AbstractModule { @@ -28,5 +29,6 @@ public class GraylogServerProvisioningBindings extends AbstractModule { protected void configure() { bind(CertificateExchange.class).to(CertificateExchangeImpl.class); bind(GraylogCertificateProvisioner.class).to(GraylogCertificateProvisionerImpl.class); + bind(IndexerDiscoverySecurityAutoconfig.class); } } diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryCertProvisioning.java b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryCertProvisioning.java new file mode 100644 index 000000000000..8137ffa94ec5 --- /dev/null +++ b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryCertProvisioning.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog2.configuration; + +import jakarta.inject.Inject; +import org.graylog2.bootstrap.preflight.GraylogCertificateProvisioner; + +public class IndexerDiscoveryCertProvisioning implements IndexerDiscoveryListener { + + private final GraylogCertificateProvisioner graylogCertificateProvisioner; + + @Inject + public IndexerDiscoveryCertProvisioning(GraylogCertificateProvisioner graylogCertificateProvisioner) { + this.graylogCertificateProvisioner = graylogCertificateProvisioner; + } + + @Override + public void beforeIndexerDiscovery() { + + } + + @Override + public void onDiscoveryRetry() { + // let's try to provision certificates, maybe there are datanodes waiting for these + graylogCertificateProvisioner.runProvisioning(); + } +} diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryListener.java b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryListener.java new file mode 100644 index 000000000000..10d7596f8ce9 --- /dev/null +++ b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryListener.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog2.configuration; + +public interface IndexerDiscoveryListener { + /** + * Triggered before we start with indexer discovery. Won't be triggered if there are any indexers + * explicitly defined in the configuration. + */ + void beforeIndexerDiscovery(); + + /** + * Triggered after each unsuccessful retry during indexer discovery + */ + void onDiscoveryRetry(); +} diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryModule.java b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryModule.java index 55272ed3f5f8..af5e178af74c 100644 --- a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryModule.java +++ b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryModule.java @@ -18,6 +18,7 @@ import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.Multibinder; import org.graylog2.bindings.providers.MongoConnectionProvider; import org.graylog2.bootstrap.preflight.PreflightConfigService; import org.graylog2.bootstrap.preflight.PreflightConfigServiceImpl; @@ -34,6 +35,9 @@ public class IndexerDiscoveryModule extends AbstractModule { @Override protected void configure() { + registerIndexerDiscoveryListener(IndexerDiscoveryCertProvisioning.class); + registerIndexerDiscoveryListener(IndexerDiscoverySecurityAutoconfig.class); + bind(new TypeLiteral>() {}).annotatedWith(IndexerHosts.class).toProvider(IndexerDiscoveryProvider.class).asEagerSingleton(); bind(Boolean.class).annotatedWith(RunsWithDataNode.class).toProvider(RunsWithDataNodeDiscoveryProvider.class).asEagerSingleton(); bind(new TypeLiteral>() {}).to(DataNodeClusterService.class); @@ -41,4 +45,12 @@ protected void configure() { bind(MongoConnection.class).toProvider(MongoConnectionProvider.class); bind(JwtSecret.class).toProvider(JwtSecretProvider.class).asEagerSingleton(); } + + protected void registerIndexerDiscoveryListener(Class listener) { + indexerDiscoveryListerers().addBinding().to(listener); + } + + protected Multibinder indexerDiscoveryListerers() { + return Multibinder.newSetBinder(binder(), IndexerDiscoveryListener.class); + } } diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryProvider.java b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryProvider.java index 916fc03b307c..6289b249c798 100644 --- a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryProvider.java +++ b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryProvider.java @@ -27,7 +27,6 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Provider; -import org.graylog2.bootstrap.preflight.GraylogCertificateProvisioner; import org.graylog2.bootstrap.preflight.PreflightConfigResult; import org.graylog2.bootstrap.preflight.PreflightConfigService; import org.graylog2.cluster.Node; @@ -39,6 +38,7 @@ import java.net.URI; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -53,7 +53,8 @@ public class IndexerDiscoveryProvider implements Provider> { private final List hosts; private final PreflightConfigService preflightConfigService; private final NodeService nodeService; - private final GraylogCertificateProvisioner graylogCertificateProvisioner; + + private final Set indexerDiscoveryListeners; private final Supplier> resultsCachingSupplier; @@ -68,16 +69,17 @@ public IndexerDiscoveryProvider( @Named("datanode_startup_connection_delay") Duration delayBetweenAttempts, PreflightConfigService preflightConfigService, NodeService nodeService, - GraylogCertificateProvisioner graylogCertificateProvisioner) { + Set indexerDiscoveryListeners) { this.hosts = hosts; this.connectionAttempts = connectionAttempts; this.delayBetweenAttempts = delayBetweenAttempts; this.preflightConfigService = preflightConfigService; this.nodeService = nodeService; - this.graylogCertificateProvisioner = graylogCertificateProvisioner; + this.indexerDiscoveryListeners = indexerDiscoveryListeners; this.resultsCachingSupplier = Suppliers.memoize(this::doGet); } + @Override public List get() { return resultsCachingSupplier.get(); @@ -90,6 +92,8 @@ private List doGet() { return hosts; } + indexerDiscoveryListeners.forEach(IndexerDiscoveryListener::beforeIndexerDiscovery); + final PreflightConfigResult preflightResult = preflightConfigService.getPreflightConfigResult(); // if preflight is finished, we assume that there will be some datanode registered via node-service. @@ -109,8 +113,7 @@ public void onRetry(Attempt attempt) { } } - // let's try to provision certificates, maybe there are datanodes waiting for these - graylogCertificateProvisioner.runProvisioning(); + indexerDiscoveryListeners.forEach(IndexerDiscoveryListener::onDiscoveryRetry); } }) .withWaitStrategy(WaitStrategies.fixedWait(delayBetweenAttempts.getQuantity(), delayBetweenAttempts.getUnit())) diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoverySecurityAutoconfig.java b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoverySecurityAutoconfig.java new file mode 100644 index 000000000000..8f55dfa0dc9d --- /dev/null +++ b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoverySecurityAutoconfig.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog2.configuration; + +import jakarta.inject.Inject; +import org.graylog.security.certutil.CaKeystore; +import org.graylog2.Configuration; +import org.graylog2.bootstrap.preflight.PreflightConfigResult; +import org.graylog2.bootstrap.preflight.PreflightConfigService; +import org.graylog2.plugin.certificates.RenewalPolicy; +import org.graylog2.plugin.cluster.ClusterConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public class IndexerDiscoverySecurityAutoconfig implements IndexerDiscoveryListener { + + private static final Logger LOG = LoggerFactory.getLogger(IndexerDiscoverySecurityAutoconfig.class); + private static final RenewalPolicy DEFAULT_CERT_RENEWAL_POLICY = new RenewalPolicy(RenewalPolicy.Mode.AUTOMATIC, "P30D"); + + private final ClusterConfigService clusterConfigService; + private final CaKeystore caKeystore; + private final Configuration configuration; + private final PreflightConfigService preflightConfigService; + + @Inject + public IndexerDiscoverySecurityAutoconfig(Configuration configuration, PreflightConfigService preflightConfigService, ClusterConfigService clusterConfigService, CaKeystore caKeystore) { + this.configuration = configuration; + this.preflightConfigService = preflightConfigService; + this.clusterConfigService = clusterConfigService; + this.caKeystore = caKeystore; + } + + public void configureSelfsignedStartup() { + final PreflightConfigResult preflightConfigResult = preflightConfigService.getPreflightConfigResult(); + if (preflightConfigResult == PreflightConfigResult.UNKNOWN) { + LOG.info("Setting preflight config to FINISHED"); + preflightConfigService.setConfigResult(PreflightConfigResult.FINISHED); + } + + if (getRenewalPolicy().isEmpty()) { + LOG.info("Setting renewal policy to " + DEFAULT_CERT_RENEWAL_POLICY); + clusterConfigService.write(DEFAULT_CERT_RENEWAL_POLICY); + } + + if (!caKeystore.exists()) { + LOG.info("Creating new self-signed Graylog CA"); + caKeystore.createSelfSigned("Graylog CA"); + } + } + + private Optional getRenewalPolicy() { + return Optional.ofNullable(this.clusterConfigService.get(RenewalPolicy.class)); + } + + @Override + public void beforeIndexerDiscovery() { + if (configuration.selfsignedStartupEnabled()) { + LOG.info("Self-signed security startup enabled, configuring renewal policy and CA if needed"); + configureSelfsignedStartup(); + } + } + + @Override + public void onDiscoveryRetry() { + + } +} diff --git a/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperation.java b/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperation.java index b7724a6cb5a3..e64492ca614e 100644 --- a/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperation.java +++ b/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperation.java @@ -91,7 +91,7 @@ String serializeResponse(ExtractableResponse extract) { } String formatUrl(RestOperationParameters params, String url) { - final boolean securedConnection = params.truststore() != null; + final boolean securedConnection = params.truststore() != null || params.relaxedHTTPSValidation(); String procotol = securedConnection ? "https" : "http"; return procotol + "://localhost:" + params.port() + url; } diff --git a/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperationParameters.java b/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperationParameters.java index c94829848f02..8255edbad2f1 100644 --- a/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperationParameters.java +++ b/graylog2-server/src/test/java/org/graylog/testing/restoperations/RestOperationParameters.java @@ -34,6 +34,8 @@ public abstract class RestOperationParameters { @Nullable abstract Provider jwtTokenProvider(); + abstract boolean relaxedHTTPSValidation(); + private static final int DEFAULT_ATTEMPTS_COUNT = 160; abstract int attempts_count(); @@ -53,7 +55,8 @@ public String formatCurlAuthentication() { public static Builder builder() { return new AutoValue_RestOperationParameters.Builder() - .attempts_count(DEFAULT_ATTEMPTS_COUNT); + .attempts_count(DEFAULT_ATTEMPTS_COUNT) + .relaxedHTTPSValidation(false); } abstract Builder toBuilder(); @@ -69,5 +72,7 @@ public abstract static class Builder { public abstract Builder attempts_count(int attempts_count); public abstract RestOperationParameters build(); + + public abstract Builder relaxedHTTPSValidation(boolean relaxedHttpsValidation); } } diff --git a/graylog2-server/src/test/java/org/graylog/testing/restoperations/WaitingRestOperation.java b/graylog2-server/src/test/java/org/graylog/testing/restoperations/WaitingRestOperation.java index 03674efdca4b..e85bd807f561 100644 --- a/graylog2-server/src/test/java/org/graylog/testing/restoperations/WaitingRestOperation.java +++ b/graylog2-server/src/test/java/org/graylog/testing/restoperations/WaitingRestOperation.java @@ -84,6 +84,10 @@ public void onRetry(Attempt attempt) { final RequestSpecification req = RestAssured.given() .accept(ContentType.JSON); + if(parameters.relaxedHTTPSValidation()) { + req.relaxedHTTPSValidation(); + } + Optional.ofNullable(parameters.truststore()).ifPresent(ts -> req.trustStore(parameters.truststore())); parameters.addAuthorizationHeaders(req); diff --git a/graylog2-server/src/test/java/org/graylog2/configuration/IndexerDiscoveryProviderTest.java b/graylog2-server/src/test/java/org/graylog2/configuration/IndexerDiscoveryProviderTest.java index c3d243f1c52e..1423187750ac 100644 --- a/graylog2-server/src/test/java/org/graylog2/configuration/IndexerDiscoveryProviderTest.java +++ b/graylog2-server/src/test/java/org/graylog2/configuration/IndexerDiscoveryProviderTest.java @@ -30,7 +30,9 @@ import javax.annotation.Nullable; import java.net.URI; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.UUID; @@ -40,7 +42,6 @@ class IndexerDiscoveryProviderTest { - public static final GraylogCertificateProvisioner NOOP_CERT_PROVISIONER = () -> {}; @Test void testAutomaticDiscovery() { @@ -50,7 +51,7 @@ void testAutomaticDiscovery() { Duration.seconds(1), preflightConfig(PreflightConfigResult.FINISHED), nodes("http://localhost:9200", "http://other:9201"), - NOOP_CERT_PROVISIONER + Collections.emptySet() ); Assertions.assertThat(provider.get()) @@ -59,6 +60,29 @@ void testAutomaticDiscovery() { .contains("http://localhost:9200", "http://other:9201"); } + @Test + void testListeners() { + final IndexerDiscoveryListener listener1 = Mockito.mock(IndexerDiscoveryListener.class); + final IndexerDiscoveryListener listener2 = Mockito.mock(IndexerDiscoveryListener.class); + + final IndexerDiscoveryProvider provider = new IndexerDiscoveryProvider( + Collections.emptyList(), + 10, + Duration.milliseconds(1), + preflightConfig(PreflightConfigResult.FINISHED), + nodes(), + new HashSet<>(List.of(listener1, listener2)) + ); + + Assertions.assertThatThrownBy(provider::get) + .hasMessageContaining("Unable to retrieve Datanode connection"); + + Mockito.verify(listener1, Mockito.times(1)).beforeIndexerDiscovery(); + Mockito.verify(listener1, Mockito.times(10)).onDiscoveryRetry(); + Mockito.verify(listener2, Mockito.times(1)).beforeIndexerDiscovery(); + Mockito.verify(listener2, Mockito.times(10)).onDiscoveryRetry(); + + } @Test void testAutomaticDiscoveryOneUnconfigured() { @@ -68,7 +92,7 @@ void testAutomaticDiscoveryOneUnconfigured() { Duration.seconds(1), preflightConfig(PreflightConfigResult.FINISHED), nodes("http://localhost:9200", ""), // the second node is not configured yet, has no transport address - NOOP_CERT_PROVISIONER + Collections.emptySet() ); Assertions.assertThat(provider.get()) @@ -86,7 +110,7 @@ void testPreconfiguredIndexers() { Duration.seconds(1), preflightConfig(null), nodes(), - NOOP_CERT_PROVISIONER + Collections.emptySet() ); Assertions.assertThat(provider.get()) @@ -103,7 +127,7 @@ void testSkippedConfigWithDefaultIndexer() { Duration.seconds(1), preflightConfig(PreflightConfigResult.SKIPPED), nodes(), - NOOP_CERT_PROVISIONER + Collections.emptySet() ); Assertions.assertThat(provider.get()) @@ -120,7 +144,7 @@ void testFailedAutodiscovery() { Duration.seconds(1), preflightConfig(PreflightConfigResult.FINISHED), // preflight correctly finished nodes(), // but still no nodes discovered - NOOP_CERT_PROVISIONER + Collections.emptySet() ); Assertions.assertThatThrownBy(provider::get) @@ -131,13 +155,26 @@ void testFailedAutodiscovery() { @Test void testProvisioningWillBeTriggered() { final GraylogCertificateProvisioner provisioner = Mockito.mock(GraylogCertificateProvisioner.class); + final org.graylog2.configuration.IndexerDiscoveryListener indexerDiscoveryListener = new IndexerDiscoveryListener() { + + @Override + public void beforeIndexerDiscovery() { + + } + + @Override + public void onDiscoveryRetry() { + provisioner.runProvisioning(); + } + }; + final IndexerDiscoveryProvider provider = new IndexerDiscoveryProvider( Collections.emptyList(), 10, Duration.milliseconds(1), preflightConfig(PreflightConfigResult.FINISHED), nodes(), - provisioner + Collections.singleton(indexerDiscoveryListener) ); Assertions.assertThatThrownBy(provider::get)