diff --git a/UPGRADING.md b/UPGRADING.md
index 1049ffea31..dc8b1290a1 100644
--- a/UPGRADING.md
+++ b/UPGRADING.md
@@ -6,6 +6,7 @@ Upgrading to Graylog 5.2.x
- New pipeline rule functions for manipulating maps: `map_set` and `map_remove`.
## Breaking Changes
+- If you use the DataNode, the system clocks of the nodes have to be synchronized with an external source for JWT usage (within a margin of a couple of seconds).
### Migrating from legacy index templates to composable index templates
diff --git a/changelog/unreleased/pr-16334.toml b/changelog/unreleased/pr-16334.toml
new file mode 100644
index 0000000000..d757102df5
--- /dev/null
+++ b/changelog/unreleased/pr-16334.toml
@@ -0,0 +1,9 @@
+type = "added"
+message = "DataNode: if you use the DataNode, the nodes system clocks have to be synchronized for JWT usage."
+
+pulls = ["16334"]
+
+details.user = """
+JWT usage over several systems/nodes requires clock synchronization to prevent errors with expiration time and
+clock skew between client/server.
+"""
diff --git a/data-node/pom.xml b/data-node/pom.xml
index 4b2de58e4a..5eb8847266 100644
--- a/data-node/pom.xml
+++ b/data-node/pom.xml
@@ -195,6 +195,19 @@
netty-tcnative-boringssl-static
linux-x86_64
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+
org.glassfish.jersey.inject
diff --git a/data-node/src/main/java/org/graylog/datanode/Configuration.java b/data-node/src/main/java/org/graylog/datanode/Configuration.java
index 91fe38f123..21347106a3 100644
--- a/data-node/src/main/java/org/graylog/datanode/Configuration.java
+++ b/data-node/src/main/java/org/graylog/datanode/Configuration.java
@@ -23,6 +23,7 @@ import com.github.joschi.jadconfig.Validator;
import com.github.joschi.jadconfig.ValidatorMethod;
import com.github.joschi.jadconfig.converters.IntegerConverter;
import com.github.joschi.jadconfig.converters.StringListConverter;
+import com.github.joschi.jadconfig.util.Duration;
import com.github.joschi.jadconfig.validators.PositiveIntegerValidator;
import com.github.joschi.jadconfig.validators.StringNotBlankValidator;
import com.github.joschi.jadconfig.validators.URIAbsoluteValidator;
@@ -190,12 +191,27 @@ public class Configuration extends BaseConfiguration {
return opensearchProcessLogsBufferSize;
}
- @Parameter(value = "rest_api_username")
- private String restApiUsername;
-
@Parameter(value = "password_secret", required = true, validators = StringNotBlankValidator.class)
private String passwordSecret;
+ public String getPasswordSecret() {
+ return passwordSecret;
+ }
+
+ @Parameter(value = "indexer_jwt_auth_token_caching_duration")
+ Duration indexerJwtAuthTokenCachingDuration = Duration.seconds(60);
+
+ public Duration getIndexerJwtAuthTokenCachingDuration() {
+ return indexerJwtAuthTokenCachingDuration;
+ }
+
+ @Parameter(value = "indexer_jwt_auth_token_expiration_duration")
+ Duration indexerJwtAuthTokenExpirationDuration = Duration.seconds(180);
+
+ public Duration getIndexerJwtAuthTokenExpirationDuration() {
+ return indexerJwtAuthTokenExpirationDuration;
+ }
+
@ValidatorMethod
@SuppressWarnings("unused")
public void validatePasswordSecret() throws ValidationException {
@@ -204,9 +220,6 @@ public class Configuration extends BaseConfiguration {
}
}
- @Parameter(value = "rest_api_password")
- private String restApiPassword;
-
@Parameter(value = "node_id_file", validators = NodeIdFileValidator.class)
private String nodeIdFile = "data/node-id";
@@ -274,16 +287,6 @@ public class Configuration extends BaseConfiguration {
return datanodeHttpCertificatePassword;
}
-
- public String getRestApiUsername() {
- return restApiUsername;
- }
-
- public String getRestApiPassword() {
- return restApiPassword;
- }
-
-
public Optional getOpensearchNetworkHostHost() {
return Optional.ofNullable(opensearchNetworkHostHost);
}
diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfiguration.java b/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfiguration.java
index b570005be6..2744fe5d5c 100644
--- a/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfiguration.java
+++ b/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfiguration.java
@@ -17,6 +17,7 @@
package org.graylog.datanode.configuration;
import org.graylog.datanode.OpensearchDistribution;
+import org.graylog2.security.IndexerJwtAuthTokenProvider;
/**
* DatanodeConfiguration holds the static configuration as parsed during the datanode startup, either from the
@@ -25,6 +26,7 @@ import org.graylog.datanode.OpensearchDistribution;
public record DatanodeConfiguration(
OpensearchDistribution opensearchDistribution,
String nodeName,
- int processLogsBufferSize
+ int processLogsBufferSize,
+ IndexerJwtAuthTokenProvider indexerJwtAuthTokenProvider
) {
}
diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfigurationProvider.java b/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfigurationProvider.java
index 7964735919..1d98e1090d 100644
--- a/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfigurationProvider.java
+++ b/data-node/src/main/java/org/graylog/datanode/configuration/DatanodeConfigurationProvider.java
@@ -18,6 +18,7 @@ package org.graylog.datanode.configuration;
import org.graylog.datanode.Configuration;
import org.graylog.datanode.OpensearchDistribution;
+import org.graylog2.security.IndexerJwtAuthTokenProvider;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -31,12 +32,13 @@ public class DatanodeConfigurationProvider implements Provider {
-
private final Configuration localConfiguration;
private final UploadedCertFilesSecureConfiguration uploadedCertFilesSecureConfiguration;
private final MongoCertSecureConfiguration mongoCertSecureConfiguration;
private final InSecureConfiguration inSecureConfiguration;
private final DatanodeConfiguration datanodeConfiguration;
+ private final byte[] signingKey;
@Inject
public OpensearchConfigurationProvider(Configuration localConfiguration,
DatanodeConfiguration datanodeConfiguration,
UploadedCertFilesSecureConfiguration uploadedCertFilesSecureConfiguration,
MongoCertSecureConfiguration mongoCertSecureConfiguration,
- InSecureConfiguration inSecureConfiguration) {
+ InSecureConfiguration inSecureConfiguration,
+ final @Named("password_secret") String passwordSecret) {
this.localConfiguration = localConfiguration;
this.datanodeConfiguration = datanodeConfiguration;
this.uploadedCertFilesSecureConfiguration = uploadedCertFilesSecureConfiguration;
this.mongoCertSecureConfiguration = mongoCertSecureConfiguration;
this.inSecureConfiguration = inSecureConfiguration;
+ this.signingKey = passwordSecret.getBytes(StandardCharsets.UTF_8);
}
@Override
@@ -82,7 +86,7 @@ public class OpensearchConfigurationProvider implements Provider filterConfigurationMap(final Map map, final String... keys) {
+ Map result = map;
+ for(final String key: List.of(keys)) {
+ result = (Map)result.get(key);
+ }
+ return result;
+ }
+
+ private void enableJwtAuthenticationInConfig(final Path opensearchConfigDir, final byte[] signingKey) throws IOException {
+ final ObjectMapper objectMapper = new YAMLMapper();
+ final File file = opensearchConfigDir.resolve(Path.of("opensearch-security", "config.yml")).toFile();
+ Map contents = objectMapper.readValue(file, new TypeReference<>() {});
+
+ /* TODO: the following two lines should be active, once #16334 is merged and there are nightly builds of master available to build a compatible 5.2-dev DataNode Docker image
+ Map config = filterConfigurationMap(contents, "config", "dynamic", "authc", "jwt_auth_domain", "http_authenticator", "config");
+ config.put("signing_key", Base64.getEncoder().encodeToString(signingKey));
+ */
+
+ // TODO: remove the following block, once #16334 is merged and there are nightly builds of master available to build a compatible 5.2-dev DataNode Docker image
+ Map jwt_auth_domain = filterConfigurationMap(contents, "config", "dynamic", "authc", "jwt_auth_domain");
+ jwt_auth_domain.put("http_enabled", true);
+ jwt_auth_domain.put("transport_enabled", true);
+
+ Map config = filterConfigurationMap(jwt_auth_domain, "http_authenticator", "config");
+ config.put("signing_key", Base64.getEncoder().encodeToString(signingKey));
+ config.put("roles_key", "os_roles");
+ // TODO: remove up until here
+
+ objectMapper.writeValue(file, contents);
+ }
+
public boolean securityEnabled() {
return !Objects.isNull(httpCertificate) && !Objects.isNull(transportCertificate);
}
@@ -169,37 +200,6 @@ public class OpensearchSecurityConfiguration {
return config.build();
}
- protected void configureInitialAdmin(final Configuration localConfiguration, final Path opensearchConfigDir,
- final String adminUsername,
- final String adminPassword) throws IOException {
- final Path internalUsersFile = opensearchConfigDir.resolve("opensearch-security").resolve("internal_users.yml");
-
- Objects.requireNonNull(localConfiguration.getRestApiUsername(),
- "rest_api_username has to be configured the usage of secured Opensearch REST api"
- );
-
- Objects.requireNonNull(localConfiguration.getRestApiPassword(),
- "rest_api_password has to be configured the usage of secured Opensearch REST api"
- );
-
- final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
- final Map map = mapper.readValue(new FileInputStream(internalUsersFile.toFile()), TypeReferences.MAP_STRING_OBJECT);
- final Map adminUserConfig = (Map) map.get("admin");
-
- map.remove("admin");
- map.put(adminUsername, adminUserConfig);
-
- final BCryptPasswordAlgorithm passwordAlgorithm = new BCryptPasswordAlgorithm(12);
- final String hashWithPrefix = passwordAlgorithm.hash(adminPassword);
-
- // remove the prefix and suffix, we need just the hash itself
- final String hash = hashWithPrefix.substring("{bcrypt}".length(), hashWithPrefix.indexOf("{salt}"));
- adminUserConfig.put("hash", hash);
-
- final FileOutputStream fos = new FileOutputStream(internalUsersFile.toFile());
- mapper.writeValue(fos, map);
- }
-
private void logCertificateInformation(String certificateType, KeystoreInformation keystore) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
final KeyStore instance = KeyStore.getInstance(KEYSTORE_FORMAT);
try (final FileInputStream is = new FileInputStream(keystore.location().toFile())) {
diff --git a/data-node/src/main/java/org/graylog/datanode/management/OpensearchProcessImpl.java b/data-node/src/main/java/org/graylog/datanode/management/OpensearchProcessImpl.java
index f36d9d19d4..342069a2ae 100644
--- a/data-node/src/main/java/org/graylog/datanode/management/OpensearchProcessImpl.java
+++ b/data-node/src/main/java/org/graylog/datanode/management/OpensearchProcessImpl.java
@@ -71,7 +71,7 @@ class OpensearchProcessImpl implements OpensearchProcess, ProcessListener {
}
private RestHighLevelClient createRestClient(OpensearchConfiguration configuration) {
- return OpensearchRestClient.build(configuration, trustManager);
+ return OpensearchRestClient.build(configuration, datanodeConfiguration, trustManager);
}
@Override
@@ -98,8 +98,7 @@ class OpensearchProcessImpl implements OpensearchProcess, ProcessListener {
.map(httpHost -> new URIBuilder()
.setHost(httpHost.getHostName())
.setPort(httpHost.getPort())
- .setScheme(httpHost.getSchemeName())
- .setUserInfo(configuration.get().authUsername(), configuration.get().authPassword()).toString())
+ .setScheme(httpHost.getSchemeName()).toString())
.orElse(""); // TODO: will this cause problems in the nodeservice?
return URI.create(baseUrl);
}
diff --git a/data-node/src/main/java/org/graylog/datanode/management/OpensearchRestClient.java b/data-node/src/main/java/org/graylog/datanode/management/OpensearchRestClient.java
index 5014b3ea2a..ba75092d25 100644
--- a/data-node/src/main/java/org/graylog/datanode/management/OpensearchRestClient.java
+++ b/data-node/src/main/java/org/graylog/datanode/management/OpensearchRestClient.java
@@ -16,19 +16,15 @@
*/
package org.graylog.datanode.management;
+import org.graylog.datanode.configuration.DatanodeConfiguration;
import org.graylog.datanode.process.OpensearchConfiguration;
-import org.graylog.security.certutil.CaService;
import org.graylog.shaded.opensearch2.org.apache.http.HttpHost;
-import org.graylog.shaded.opensearch2.org.apache.http.auth.AuthScope;
-import org.graylog.shaded.opensearch2.org.apache.http.auth.UsernamePasswordCredentials;
-import org.graylog.shaded.opensearch2.org.apache.http.client.CredentialsProvider;
-import org.graylog.shaded.opensearch2.org.apache.http.impl.client.BasicCredentialsProvider;
+import org.graylog.shaded.opensearch2.org.apache.http.HttpRequestInterceptor;
import org.graylog.shaded.opensearch2.org.opensearch.client.RestHighLevelClient;
import org.graylog2.security.CustomCAX509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.security.KeyManagementException;
@@ -38,23 +34,21 @@ import java.security.SecureRandom;
public class OpensearchRestClient {
private static final Logger LOG = LoggerFactory.getLogger(OpensearchRestClient.class);
- public static RestHighLevelClient build(final OpensearchConfiguration configuration, final CustomCAX509TrustManager tm) {
- final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
-
+ public static RestHighLevelClient build(final OpensearchConfiguration configuration, final DatanodeConfiguration datanodeConfiguration, final CustomCAX509TrustManager tm) {
final HttpHost host = configuration.getRestBaseUrl();
org.graylog.shaded.opensearch2.org.opensearch.client.RestClientBuilder builder = org.graylog.shaded.opensearch2.org.opensearch.client.RestClient.builder(host);
if ("https".equals(host.getSchemeName())) {
- if (configuration.authUsername() != null && configuration.authPassword() != null) {
- credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(configuration.authUsername(), configuration.authPassword()));
- }
try {
final var sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{tm}, new SecureRandom());
builder.setHttpClientConfigCallback(httpClientBuilder -> {
- httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
+ httpClientBuilder.addInterceptorLast((HttpRequestInterceptor) (request, context) -> {
+ final String jwtToken = datanodeConfiguration.indexerJwtAuthTokenProvider().get();
+ request.addHeader("Authorization", jwtToken);
+ });
httpClientBuilder.setSSLContext(sslContext);
return httpClientBuilder;
});
diff --git a/data-node/src/main/java/org/graylog/datanode/process/OpensearchConfiguration.java b/data-node/src/main/java/org/graylog/datanode/process/OpensearchConfiguration.java
index d99dbe2df3..a1510832fc 100644
--- a/data-node/src/main/java/org/graylog/datanode/process/OpensearchConfiguration.java
+++ b/data-node/src/main/java/org/graylog/datanode/process/OpensearchConfiguration.java
@@ -33,8 +33,6 @@ public record OpensearchConfiguration(
String hostname,
int httpPort,
int transportPort,
- String authUsername,
- String authPassword,
String clusterName, String nodeName, List nodeRoles,
List discoverySeedHosts,
OpensearchSecurityConfiguration opensearchSecurityConfiguration,
diff --git a/data-node/src/main/resources/opensearch/config/opensearch-security/config.yml b/data-node/src/main/resources/opensearch/config/opensearch-security/config.yml
index 59f5f05352..b9958375af 100644
--- a/data-node/src/main/resources/opensearch/config/opensearch-security/config.yml
+++ b/data-node/src/main/resources/opensearch/config/opensearch-security/config.yml
@@ -121,8 +121,8 @@ config:
type: noop
jwt_auth_domain:
description: "Authenticate via Json Web Token"
- http_enabled: false
- transport_enabled: false
+ http_enabled: true
+ transport_enabled: true
order: 0
http_authenticator:
type: jwt
@@ -131,7 +131,7 @@ config:
signing_key: "base64 encoded HMAC key or public RSA/ECDSA pem key"
jwt_header: "Authorization"
jwt_url_parameter: null
- roles_key: null
+ roles_key: "os_roles"
subject_key: null
authentication_backend:
type: noop
diff --git a/data-node/src/main/resources/opensearch/config/opensearch-security/internal_users.yml b/data-node/src/main/resources/opensearch/config/opensearch-security/internal_users.yml
index f4d31e52c6..68838f482f 100644
--- a/data-node/src/main/resources/opensearch/config/opensearch-security/internal_users.yml
+++ b/data-node/src/main/resources/opensearch/config/opensearch-security/internal_users.yml
@@ -10,13 +10,6 @@ _meta:
## Demo users
-admin:
- hash: "$2a$12$VcCDgh2NDk07JGN0rjGbM.Ad41qVR/YFJcgHp0UGns5JDymv..TOG"
- reserved: true
- backend_roles:
- - "admin"
- description: "Demo admin user"
-
anomalyadmin:
hash: "$2y$12$TRwAAJgnNo67w3rVUz4FIeLx9Dy/llB79zf9I15CKJ9vkM4ZzAd3."
reserved: false
diff --git a/data-node/src/test/java/org/graylog/datanode/DatanodeOpensearchWait.java b/data-node/src/test/java/org/graylog/datanode/DatanodeOpensearchWait.java
index a8a37f7462..8eb233f25c 100644
--- a/data-node/src/test/java/org/graylog/datanode/DatanodeOpensearchWait.java
+++ b/data-node/src/test/java/org/graylog/datanode/DatanodeOpensearchWait.java
@@ -38,6 +38,8 @@ import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLHandshakeException;
import java.net.SocketException;
import java.security.KeyStore;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@@ -46,13 +48,12 @@ import java.util.function.Predicate;
public class DatanodeOpensearchWait {
- private static final Logger LOG = LoggerFactory.getLogger(DatanodeSecuritySetupIT.class);
+ private static final Logger LOG = LoggerFactory.getLogger(DatanodeOpensearchWait.class);
private static final int ATTEMPTS_COUNT = 160;
private final int opensearchPort;
private KeyStore truststore;
- private String username;
- private String password;
+ private String jwtToken;
private String lastRecordedResponse;
private DatanodeOpensearchWait(int opensearchPort) {
@@ -68,9 +69,8 @@ public class DatanodeOpensearchWait {
return this;
}
- public DatanodeOpensearchWait withBasicAuth(String username, String password) {
- this.username = username;
- this.password = password;
+ public DatanodeOpensearchWait withJwtAuth(String jwtToken) {
+ this.jwtToken = jwtToken;
return this;
}
@@ -90,6 +90,11 @@ public class DatanodeOpensearchWait {
@SafeVarargs
private ValidatableResponse waitForOpensearch(Predicate... predicates) throws ExecutionException, RetryException {
+
+ if(LOG.isInfoEnabled()) {
+ LOG.info("cURL request: " + formatCurlConnectionInfo());
+ }
+
//noinspection UnstableApiUsage
final RetryerBuilder builder = RetryerBuilder.newBuilder()
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
@@ -122,8 +127,8 @@ public class DatanodeOpensearchWait {
Optional.ofNullable(truststore).ifPresent(ts -> req.trustStore(truststore));
- if (username != null && password != null) {
- req.auth().basic(username, password);
+ if (jwtToken != null) {
+ req.header( "Authorization", "Bearer " + jwtToken);
}
return req.get(formatClusterHeathUrl())
@@ -138,6 +143,16 @@ public class DatanodeOpensearchWait {
}
}
+ private String formatCurlConnectionInfo() {
+ List builder = new LinkedList<>();
+ builder.add("curl -k"); // trust self-signed certs
+ if(jwtToken != null) {
+ builder.add("-H \"Authorization: Bearer " + jwtToken + "\"");
+ }
+ builder.add(formatClusterHeathUrl());
+ return String.join(" ", builder);
+ }
+
private void printShardsInfo() {
try {
final RequestSpecification req = RestAssured.given()
@@ -145,8 +160,8 @@ public class DatanodeOpensearchWait {
Optional.ofNullable(truststore).ifPresent(ts -> req.trustStore(truststore));
- if (username != null && password != null) {
- req.auth().basic(username, password);
+ if (jwtToken != null) {
+ req.header( "Authorization", "Bearer " + jwtToken);
}
final String shardsResponse = req.get("https://localhost:" + opensearchPort + "/_cat/shards?v=true&h=index,shard,prirep,state,node,unassigned.reason&s=state")
diff --git a/data-node/src/test/java/org/graylog/datanode/integration/DatanodeClusterIT.java b/data-node/src/test/java/org/graylog/datanode/integration/DatanodeClusterIT.java
index 1768282f46..5a704bf4e9 100644
--- a/data-node/src/test/java/org/graylog/datanode/integration/DatanodeClusterIT.java
+++ b/data-node/src/test/java/org/graylog/datanode/integration/DatanodeClusterIT.java
@@ -16,11 +16,14 @@
*/
package org.graylog.datanode.integration;
+import com.github.joschi.jadconfig.util.Duration;
import com.github.rholder.retry.RetryException;
import org.apache.commons.lang3.RandomStringUtils;
import org.graylog.datanode.DatanodeOpensearchWait;
import org.graylog.datanode.configuration.variants.KeystoreInformation;
import org.graylog.datanode.testinfra.DatanodeContainerizedBackend;
+import org.graylog2.security.IndexerJwtAuthTokenProvider;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -30,8 +33,11 @@ import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
+import javax.net.ssl.SSLHandshakeException;
import javax.validation.constraints.NotNull;
import java.io.IOException;
+import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
@@ -144,6 +150,7 @@ public class DatanodeClusterIT {
mongodb,
hostname,
datanodeContainer -> {
+ datanodeContainer.withEnv("GRAYLOG_DATANODE_PASSWORD_SECRET", DatanodeContainerizedBackend.SIGNING_SECRET);
datanodeContainer.withEnv("GRAYLOG_DATANODE_CLUSTER_INITIAL_MANAGER_NODES", hostnameNodeA);
datanodeContainer.withEnv("GRAYLOG_DATANODE_OPENSEARCH_DISCOVERY_SEED_HOSTS", hostnameNodeA + ":9300");
@@ -174,11 +181,13 @@ public class DatanodeClusterIT {
});
}
- private void waitForNodesCount(int countOfNodes) throws ExecutionException, RetryException {
+ private void waitForNodesCount(final int countOfNodes) throws ExecutionException, RetryException {
+ final String jwtToken = IndexerJwtAuthTokenProvider.createToken(DatanodeContainerizedBackend.SIGNING_SECRET.getBytes(StandardCharsets.UTF_8), Duration.seconds(120));
+
try {
DatanodeOpensearchWait.onPort(nodeA.getOpensearchRestPort())
.withTruststore(trustStore)
- .withBasicAuth(initialAdminUsername, initialAdminPassword)
+ .withJwtAuth(jwtToken)
.waitForNodesCount(countOfNodes);
} catch (Exception retryException) {
LOG.error("DataNode Container logs from nodeA follow:\n" + nodeA.getLogs());
diff --git a/data-node/src/test/java/org/graylog/datanode/integration/DatanodeSecuritySetupIT.java b/data-node/src/test/java/org/graylog/datanode/integration/DatanodeSecuritySetupIT.java
index f698124e9b..769a8de27d 100644
--- a/data-node/src/test/java/org/graylog/datanode/integration/DatanodeSecuritySetupIT.java
+++ b/data-node/src/test/java/org/graylog/datanode/integration/DatanodeSecuritySetupIT.java
@@ -16,6 +16,7 @@
*/
package org.graylog.datanode.integration;
+import com.github.joschi.jadconfig.util.Duration;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
@@ -28,6 +29,7 @@ import org.apache.http.NoHttpResponseException;
import org.graylog.datanode.configuration.variants.KeystoreInformation;
import org.graylog.datanode.testinfra.DatanodeContainerizedBackend;
import org.graylog2.plugin.Tools;
+import org.graylog2.security.IndexerJwtAuthTokenProvider;
import org.graylog2.shared.utilities.StringUtils;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
@@ -40,6 +42,7 @@ import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
@@ -86,6 +89,7 @@ public class DatanodeSecuritySetupIT {
datanodeContainer.withEnv("GRAYLOG_DATANODE_TRANSPORT_CERTIFICATE", "datanode-transport-certificates.p12");
datanodeContainer.withEnv("GRAYLOG_DATANODE_TRANSPORT_CERTIFICATE_PASSWORD", transportCert.passwordAsString());
datanodeContainer.withEnv("GRAYLOG_DATANODE_INSECURE_STARTUP", "false");
+ datanodeContainer.withEnv("GRAYLOG_DATANODE_PASSWORD_SECRET", DatanodeContainerizedBackend.SIGNING_SECRET);
// configure http security
datanodeContainer.withEnv("GRAYLOG_DATANODE_HTTP_CERTIFICATE", "datanode-https-certificates.p12");
@@ -115,24 +119,24 @@ public class DatanodeSecuritySetupIT {
@Test
void testSecuredSetup() throws ExecutionException, RetryException {
+ final String jwtAuthToken = IndexerJwtAuthTokenProvider.createToken(DatanodeContainerizedBackend.SIGNING_SECRET.getBytes(StandardCharsets.UTF_8), Duration.seconds(120));
- waitForOpensearchAvailableStatus(backend.getDatanodeRestPort(), trustStore);
+ waitForOpensearchAvailableStatus(backend.getDatanodeRestPort(), backend.getOpensearchRestPort(), trustStore);
try {
- given()
- .auth().basic(httpUsername, httpPassword)
- .trustStore(trustStore)
- .get("https://localhost:" + backend.getOpensearchRestPort())
- .then().assertThat()
- .body("name", Matchers.equalTo(containerHostname))
- .body("cluster_name", Matchers.equalTo("datanode-cluster"));
+ given().header( "Authorization", "Bearer " + jwtAuthToken)
+ .trustStore(trustStore)
+ .get("https://localhost:" + backend.getOpensearchRestPort())
+ .then().assertThat()
+ .body("name", Matchers.equalTo(containerHostname))
+ .body("cluster_name", Matchers.equalTo("datanode-cluster"));
} catch (Exception ex) {
LOG.error("Error connecting to OpenSearch in the DataNode, showing logs:\n{}", backend.getLogs());
throw ex;
}
}
- private void waitForOpensearchAvailableStatus(Integer datanodeRestPort, KeyStore trustStore) throws ExecutionException, RetryException {
+ private void waitForOpensearchAvailableStatus(final Integer datanodeRestPort, final Integer opensearchPort, final KeyStore trustStore) throws ExecutionException, RetryException {
final Retryer retryer = RetryerBuilder.newBuilder()
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(120))
@@ -145,11 +149,13 @@ public class DatanodeSecuritySetupIT {
try {
var hostname = Tools.getLocalCanonicalHostname();
- var url = StringUtils.f("https://%s:%d", hostname, datanodeRestPort);
- LOG.info("Trying to connect to: {}", url);
+ var rest = StringUtils.f("https://%s:%d", hostname, datanodeRestPort);
+ var os = StringUtils.f("https://%s:%d", hostname, opensearchPort);
+ LOG.info("Trying to connect to REST API at: {}, OpenSearch is at: {}", rest, os);
retryer.call(() -> RestAssured.given()
+ .auth().basic(httpUsername, httpPassword)
.trustStore(trustStore)
- .get(url)
+ .get(rest)
.then());
} catch (Exception ex) {
LOG.error("Error starting the DataNode, showing logs:\n" + backend.getLogs());
diff --git a/data-node/src/test/java/org/graylog/datanode/testinfra/DatanodeContainerizedBackend.java b/data-node/src/test/java/org/graylog/datanode/testinfra/DatanodeContainerizedBackend.java
index 45212642f6..ffad624175 100644
--- a/data-node/src/test/java/org/graylog/datanode/testinfra/DatanodeContainerizedBackend.java
+++ b/data-node/src/test/java/org/graylog/datanode/testinfra/DatanodeContainerizedBackend.java
@@ -38,6 +38,7 @@ import java.util.Properties;
import java.util.function.Supplier;
public class DatanodeContainerizedBackend {
+ static public final String SIGNING_SECRET = "0123456789012345678901234567890123456789012345678901234567890987654321098765432109876543211";
public static final int DATANODE_REST_PORT = 8999;
public static final int DATANODE_OPENSEARCH_PORT = 9200;
@@ -106,7 +107,7 @@ public class DatanodeContainerizedBackend {
.withEnv("GRAYLOG_DATANODE_REST_API_USERNAME", "admin")
.withEnv("GRAYLOG_DATANODE_REST_API_PASSWORD", "admin")
- .withEnv("GRAYLOG_DATANODE_PASSWORD_SECRET", "this_is_not_used_but_required")
+ .withEnv("GRAYLOG_DATANODE_PASSWORD_SECRET", SIGNING_SECRET)
.withEnv("GRAYLOG_DATANODE_NODE_ID_FILE", "./node-id")
.withEnv("GRAYLOG_DATANODE_HTTP_BIND_ADDRESS", "0.0.0.0:" + DATANODE_REST_PORT)
diff --git a/graylog-storage-opensearch2/pom.xml b/graylog-storage-opensearch2/pom.xml
index 66e14b8886..c610845658 100644
--- a/graylog-storage-opensearch2/pom.xml
+++ b/graylog-storage-opensearch2/pom.xml
@@ -73,6 +73,19 @@
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+
org.opensearch
opensearch-testcontainers
diff --git a/graylog-storage-opensearch2/src/main/java/org/graylog/storage/opensearch2/RestHighLevelClientProvider.java b/graylog-storage-opensearch2/src/main/java/org/graylog/storage/opensearch2/RestHighLevelClientProvider.java
index ff68a99912..41880c307d 100644
--- a/graylog-storage-opensearch2/src/main/java/org/graylog/storage/opensearch2/RestHighLevelClientProvider.java
+++ b/graylog-storage-opensearch2/src/main/java/org/graylog/storage/opensearch2/RestHighLevelClientProvider.java
@@ -19,12 +19,15 @@ package org.graylog.storage.opensearch2;
import com.github.joschi.jadconfig.util.Duration;
import com.google.common.base.Suppliers;
import org.graylog.shaded.opensearch2.org.apache.http.HttpHost;
+import org.graylog.shaded.opensearch2.org.apache.http.HttpRequestInterceptor;
import org.graylog.shaded.opensearch2.org.apache.http.client.CredentialsProvider;
import org.graylog.shaded.opensearch2.org.opensearch.client.RestClient;
import org.graylog.shaded.opensearch2.org.opensearch.client.RestClientBuilder;
import org.graylog.shaded.opensearch2.org.opensearch.client.RestHighLevelClient;
import org.graylog.shaded.opensearch2.org.opensearch.client.sniff.OpenSearchNodesSniffer;
import org.graylog2.configuration.IndexerHosts;
+import org.graylog2.configuration.RunsWithDataNode;
+import org.graylog2.security.IndexerJwtAuthToken;
import org.graylog2.security.TrustManagerAndSocketFactoryProvider;
import org.graylog2.system.shutdown.GracefulShutdownService;
import org.slf4j.Logger;
@@ -66,7 +69,10 @@ public class RestHighLevelClientProvider implements Provider new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme())).toArray(HttpHost[]::new);
final RestClientBuilder restClientBuilder = RestClient.builder(esHosts)
- .setRequestConfigCallback(requestConfig -> requestConfig
- .setConnectTimeout(Math.toIntExact(connectTimeout.toMilliseconds()))
- .setSocketTimeout(Math.toIntExact(socketTimeout.toMilliseconds()))
- .setExpectContinueEnabled(useExpectContinue)
- .setAuthenticationEnabled(true)
+ .setRequestConfigCallback(requestConfig -> {
+ requestConfig
+ .setConnectTimeout(Math.toIntExact(connectTimeout.toMilliseconds()))
+ .setSocketTimeout(Math.toIntExact(socketTimeout.toMilliseconds()))
+ .setExpectContinueEnabled(useExpectContinue);
+ // manually handle Auth if we use JWT
+ if (!isJwtAuthentication) {
+ requestConfig.setAuthenticationEnabled(true);
+ }
+ return requestConfig;
+ }
)
.setHttpClientConfigCallback(httpClientConfig -> {
httpClientConfig
.setMaxConnTotal(maxTotalConnections)
- .setMaxConnPerRoute(maxTotalConnectionsPerRoute)
- .setDefaultCredentialsProvider(credentialsProvider);
+ .setMaxConnPerRoute(maxTotalConnectionsPerRoute);
+
+ if(isJwtAuthentication) {
+ httpClientConfig.addInterceptorLast((HttpRequestInterceptor) (request, context) -> request.addHeader("Authorization", jwtBearerToken));
+ } else {
+ httpClientConfig.setDefaultCredentialsProvider(credentialsProvider);
+ }
if(muteElasticsearchDeprecationWarnings) {
httpClientConfig.addInterceptorFirst(new OpenSearchFilterDeprecationWarningsInterceptor());
diff --git a/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/DatanodeInstance.java b/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/DatanodeInstance.java
index 3558cbfcd0..57c4f7a5fd 100644
--- a/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/DatanodeInstance.java
+++ b/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/DatanodeInstance.java
@@ -16,54 +16,32 @@
*/
package org.graylog.storage.opensearch2.testing;
-import com.github.joschi.jadconfig.util.Duration;
-import com.google.common.collect.ImmutableList;
-import org.graylog.shaded.opensearch2.org.apache.http.impl.client.BasicCredentialsProvider;
-import org.graylog.shaded.opensearch2.org.opensearch.client.RestHighLevelClient;
-import org.graylog.storage.opensearch2.OpenSearchClient;
-import org.graylog.storage.opensearch2.RestHighLevelClientProvider;
import org.graylog.testing.containermatrix.SearchServer;
-import org.graylog.testing.elasticsearch.Adapters;
-import org.graylog.testing.elasticsearch.Client;
-import org.graylog.testing.elasticsearch.FixtureImporter;
-import org.graylog.testing.elasticsearch.TestableSearchServerInstance;
-import org.graylog2.shared.bindings.providers.ObjectMapperProvider;
import org.graylog2.storage.SearchVersion;
-import org.graylog2.system.shutdown.GracefulShutdownService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.DockerImageName;
-import java.net.URI;
import java.util.List;
import java.util.Locale;
import static java.util.Objects.isNull;
-public class DatanodeInstance extends TestableSearchServerInstance {
+public class DatanodeInstance extends OpenSearchInstance {
+ private static final Logger LOG = LoggerFactory.getLogger(DatanodeInstance.class);
public static final SearchServer DATANODE_VERSION = SearchServer.DATANODE_DEV;
- private final OpenSearchClient openSearchClient;
- private final Client client;
- private final FixtureImporter fixtureImporter;
- private final Adapters adapters;
-
public DatanodeInstance(final SearchVersion version, final Network network, final String heapSize, final List featureFlags) {
- super(version, network, heapSize);
-
- RestHighLevelClient restHighLevelClient = buildRestClient();
- this.openSearchClient = new OpenSearchClient(restHighLevelClient, false, new ObjectMapperProvider().get());
- this.client = new ClientOS2(this.openSearchClient, featureFlags);
- this.fixtureImporter = new FixtureImporterOS2(this.openSearchClient);
- adapters = new AdaptersOS2(openSearchClient);
- Runtime.getRuntime().addShutdownHook(new Thread(this::close));
+ super(version, network, heapSize, featureFlags);
}
@Override
protected String imageName() {
- return String.format(Locale.ROOT, "graylog/graylog-datanode:%s", "dev");
+ return String.format(Locale.ROOT, "graylog/graylog-datanode:%s", "5.2-dev");
}
@Override
@@ -71,42 +49,6 @@ public class DatanodeInstance extends TestableSearchServerInstance {
return DATANODE_VERSION;
}
- private RestHighLevelClient buildRestClient() {
- return new RestHighLevelClientProvider(
- new GracefulShutdownService(),
- ImmutableList.of(URI.create("http://" + this.getHttpHostAddress())),
- Duration.seconds(60),
- Duration.seconds(60),
- Duration.seconds(60),
- 1,
- 1,
- 1,
- false,
- false,
- null,
- Duration.seconds(60),
- "http",
- false,
- false,
- new BasicCredentialsProvider(),
- null)
- .get();
- }
-
- @Override
- public Client client() {
- return this.client;
- }
-
- @Override
- public FixtureImporter fixtureImporter() {
- return this.fixtureImporter;
- }
-
- public OpenSearchClient openSearchClient() {
- return this.openSearchClient;
- }
-
@Override
public GenericContainer> buildContainer(String image, Network network) {
return new GenericContainer<>(DockerImageName.parse(image))
@@ -118,19 +60,20 @@ public class DatanodeInstance extends TestableSearchServerInstance {
.withEnv("GRAYLOG_DATANODE_ROOT_PASSWORD_SHA2", "")
.withEnv("GRAYLOG_DATANODE_MONGODB_URI", "mongodb://mongodb:27017/graylog")
.withEnv("GRAYLOG_DATANODE_SINGLE_NODE_ONLY", "true")
- .withExposedPorts(9200, 9300)
+ .withEnv("GRAYLOG_DATANODE_INSECURE_STARTUP", "true")
+ .withExposedPorts(8999, 9200, 9300)
.withNetwork(network)
.withNetworkAliases(NETWORK_ALIAS)
- .waitingFor(Wait.forHttp("/_cluster/health").forPort(OPENSEARCH_PORT).forStatusCode(200).forResponsePredicate(s -> s.contains("\"status\":\"green\"")));
- }
-
- @Override
- public Adapters adapters() {
- return this.adapters;
- }
-
- @Override
- public String getLogs() {
- return this.container.getLogs();
+ .waitingFor(
+ Wait.forHttp("/_cluster/health")
+ .forPort(OPENSEARCH_PORT)
+ .forStatusCode(200)
+ .forResponsePredicate(s -> {
+ LOG.info("Response while waiting: {}", s);
+ // allow yellow for fixing indices later
+ return s.contains("\"status\":\"green\"") || s.contains("\"status\":\"yellow\"");
+ })
+ .withStartupTimeout(java.time.Duration.ofSeconds(180))
+ );
}
}
diff --git a/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/OpenSearchInstance.java b/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/OpenSearchInstance.java
index 61d437b5b7..3bad829693 100644
--- a/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/OpenSearchInstance.java
+++ b/graylog-storage-opensearch2/src/test/java/org/graylog/storage/opensearch2/testing/OpenSearchInstance.java
@@ -155,7 +155,10 @@ public class OpenSearchInstance extends TestableSearchServerInstance {
false,
false,
new BasicCredentialsProvider(),
- null)
+ null,
+ false,
+ false,
+ "maybe_want_jwt_here")
.get();
}
diff --git a/graylog2-server/pom.xml b/graylog2-server/pom.xml
index 593897674a..5a41d70c1d 100644
--- a/graylog2-server/pom.xml
+++ b/graylog2-server/pom.xml
@@ -104,6 +104,19 @@
org.apache.directory.api
api-all
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+
com.google.inject
diff --git a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/GraylogCertificateProvisioningPeriodical.java b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/GraylogCertificateProvisioningPeriodical.java
index 8a877d717e..5e318dcd51 100644
--- a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/GraylogCertificateProvisioningPeriodical.java
+++ b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/GraylogCertificateProvisioningPeriodical.java
@@ -16,9 +16,7 @@
*/
package org.graylog2.bootstrap.preflight;
-import com.google.common.base.Splitter;
import okhttp3.Call;
-import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@@ -39,6 +37,7 @@ import org.graylog2.plugin.certificates.RenewalPolicy;
import org.graylog2.plugin.cluster.ClusterConfigService;
import org.graylog2.plugin.periodical.Periodical;
import org.graylog2.security.CustomCAX509TrustManager;
+import org.graylog2.security.IndexerJwtAuthToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,8 +47,6 @@ import javax.inject.Singleton;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -60,8 +57,6 @@ import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;
-import static com.google.common.base.Strings.isNullOrEmpty;
-
@Singleton
public class GraylogCertificateProvisioningPeriodical extends Periodical {
private static final Logger LOG = LoggerFactory.getLogger(GraylogCertificateProvisioningPeriodical.class);
@@ -78,6 +73,7 @@ public class GraylogCertificateProvisioningPeriodical extends Periodical {
private final String passwordSecret;
private final ClusterEventBus clusterEventBus;
private Optional okHttpClient = Optional.empty();
+ private final String jwtBearerToken;
@Inject
public GraylogCertificateProvisioningPeriodical(final DataNodeProvisioningService dataNodeProvisioningService,
@@ -88,7 +84,9 @@ public class GraylogCertificateProvisioningPeriodical extends Periodical {
final NodeService nodeService,
final CsrSigner csrSigner,
final ClusterConfigService clusterConfigService,
- final @Named("password_secret") String passwordSecret, ClusterEventBus clusterEventBus) {
+ final @Named("password_secret") String passwordSecret,
+ @IndexerJwtAuthToken String jwtBearerToken,
+ ClusterEventBus clusterEventBus) {
this.dataNodeProvisioningService = dataNodeProvisioningService;
this.csrStorage = csrStorage;
this.certMongoStorage = certMongoStorage;
@@ -99,10 +97,11 @@ public class GraylogCertificateProvisioningPeriodical extends Periodical {
this.csrSigner = csrSigner;
this.clusterConfigService = clusterConfigService;
this.clusterEventBus = clusterEventBus;
+ this.jwtBearerToken = jwtBearerToken;
}
// building a httpclient to check the connectivity to OpenSearch - TODO: maybe replace it with a VersionProbe already?
- private Optional buildTempHttpClient() {
+ private Optional buildConnectivityCheckOkHttpClient() {
try {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
try {
@@ -147,7 +146,7 @@ public class GraylogCertificateProvisioningPeriodical extends Periodical {
}
if (okHttpClient.isEmpty()) {
- okHttpClient = buildTempHttpClient();
+ okHttpClient = buildConnectivityCheckOkHttpClient();
}
KeyStore caKeystore = optKey.get();
@@ -203,18 +202,7 @@ public class GraylogCertificateProvisioningPeriodical extends Periodical {
Request request = new Request.Builder().url(node.getTransportAddress()).build();
if(okHttpClient.isPresent()) {
OkHttpClient.Builder builder = okHttpClient.get().newBuilder();
- try {
- URI uri = new URI(node.getTransportAddress());
- if (!isNullOrEmpty(uri.getUserInfo())) {
- var list = Splitter.on(":").limit(2).splitToList(uri.getUserInfo());
- builder.authenticator((route, response) -> {
- String credential = Credentials.basic(list.get(0), list.get(1));
- return response.request().newBuilder().header("Authorization", credential).build();
- });
- }
- } catch (URISyntaxException ex) {
- return false;
- }
+ builder.authenticator((route, response) -> response.request().newBuilder().header("Authorization", jwtBearerToken).build());
Call call = builder.build().newCall(request);
try(Response response = call.execute()) {
return response.isSuccessful();
diff --git a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java
index 2e174ceaa4..0b0c83dbd0 100644
--- a/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java
+++ b/graylog2-server/src/main/java/org/graylog2/bootstrap/preflight/ServerPreflightChecksModule.java
@@ -28,6 +28,8 @@ import org.graylog2.plugin.inject.Graylog2Module;
import org.graylog2.plugin.system.FilePersistedNodeIdProvider;
import org.graylog2.plugin.system.NodeId;
import org.graylog2.security.CustomCAX509TrustManager;
+import org.graylog2.security.IndexerJwtAuthToken;
+import org.graylog2.security.IndexerJwtAuthTokenProvider;
import org.graylog2.security.TrustManagerProvider;
import org.graylog2.shared.bindings.providers.OkHttpClientProvider;
import org.graylog2.storage.providers.ElasticsearchVersionProvider;
@@ -44,8 +46,7 @@ public class ServerPreflightChecksModule extends Graylog2Module {
.implement(TrustManager.class, CustomCAX509TrustManager.class)
.build(TrustManagerProvider.class));
- bind(CustomCAX509TrustManager.class).asEagerSingleton();
-
+ bind(CustomCAX509TrustManager.class).asEagerSingleton();
bind(KeystoreContentMover.class).to(SinglePasswordKeystoreContentMover.class).asEagerSingleton();
bind(OkHttpClient.class).toProvider(OkHttpClientProvider.class).asEagerSingleton();
@@ -53,6 +54,7 @@ public class ServerPreflightChecksModule extends Graylog2Module {
bind(NodeId.class).toProvider(FilePersistedNodeIdProvider.class).asEagerSingleton();
bind(AuditEventSender.class).to(NullAuditEventSender.class);
+ bind(String.class).annotatedWith(IndexerJwtAuthToken.class).toProvider(IndexerJwtAuthTokenProvider.class).asEagerSingleton();
// The MongoDBPreflightCheck is not registered here, because it is called separately from ServerBootstrap
addPreflightCheck(SearchDbPreflightCheck.class);
addPreflightCheck(DiskJournalPreflightCheck.class);
diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/ElasticsearchClientConfiguration.java b/graylog2-server/src/main/java/org/graylog2/configuration/ElasticsearchClientConfiguration.java
index 2988a37038..fa7b7a5ca6 100644
--- a/graylog2-server/src/main/java/org/graylog2/configuration/ElasticsearchClientConfiguration.java
+++ b/graylog2-server/src/main/java/org/graylog2/configuration/ElasticsearchClientConfiguration.java
@@ -25,12 +25,10 @@ import org.graylog2.configuration.converters.URIListConverter;
import org.graylog2.configuration.validators.ElasticsearchVersionValidator;
import org.graylog2.configuration.validators.HttpOrHttpsSchemeValidator;
import org.graylog2.configuration.validators.ListOfURIsWithHostAndSchemeValidator;
-import org.graylog2.configuration.validators.NonEmptyListValidator;
import org.graylog2.storage.SearchVersion;
import java.net.URI;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
public class ElasticsearchClientConfiguration {
@@ -93,4 +91,13 @@ public class ElasticsearchClientConfiguration {
@Parameter(value = "elasticsearch_mute_deprecation_warnings")
private boolean muteDeprecationWarnings = false;
+
+ @Parameter(value = "indexer_use_jwt_authentication")
+ boolean indexerUseJwtAuthentication = false;
+
+ @Parameter(value = "indexer_jwt_auth_token_caching_duration")
+ Duration indexerJwtAuthTokenCachingDuration = Duration.seconds(60);
+
+ @Parameter(value = "indexer_jwt_auth_token_expiration_duration")
+ Duration indexerJwtAuthTokenExpirationDuration = Duration.seconds(180);
}
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 8635e1bc2a..aff100da3b 100644
--- a/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryModule.java
+++ b/graylog2-server/src/main/java/org/graylog2/configuration/IndexerDiscoveryModule.java
@@ -32,6 +32,7 @@ public class IndexerDiscoveryModule extends AbstractModule {
@Override
protected void configure() {
bind(new TypeLiteral>() {}).annotatedWith(IndexerHosts.class).toProvider(IndexerDiscoveryProvider.class).asEagerSingleton();
+ bind(Boolean.class).annotatedWith(RunsWithDataNode.class).toProvider(RunsWithDataNodeDiscoveryProvider.class).asEagerSingleton();
bind(NodeService.class).to(NodeServiceImpl.class);
bind(PreflightConfigService.class).to(PreflightConfigServiceImpl.class);
bind(MongoConnection.class).toProvider(MongoConnectionProvider.class);
diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/RunsWithDataNode.java b/graylog2-server/src/main/java/org/graylog2/configuration/RunsWithDataNode.java
new file mode 100644
index 0000000000..40447cd829
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog2/configuration/RunsWithDataNode.java
@@ -0,0 +1,29 @@
+/*
+ * 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 javax.inject.Qualifier;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.PARAMETER, ElementType.TYPE })
+@Qualifier
+public @interface RunsWithDataNode {
+}
diff --git a/graylog2-server/src/main/java/org/graylog2/configuration/RunsWithDataNodeDiscoveryProvider.java b/graylog2-server/src/main/java/org/graylog2/configuration/RunsWithDataNodeDiscoveryProvider.java
new file mode 100644
index 0000000000..b354a678d9
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog2/configuration/RunsWithDataNodeDiscoveryProvider.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 com.google.common.base.Suppliers;
+import org.graylog2.bootstrap.preflight.PreflightConfig;
+import org.graylog2.bootstrap.preflight.PreflightConfigResult;
+import org.graylog2.bootstrap.preflight.PreflightConfigService;
+import org.graylog2.cluster.Node;
+import org.graylog2.cluster.NodeService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+
+public class RunsWithDataNodeDiscoveryProvider implements Provider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RunsWithDataNodeDiscoveryProvider.class);
+ private final PreflightConfigService preflightConfigService;
+ private final NodeService nodeService;
+
+ private final Supplier resultsCachingSupplier;
+
+ @Inject
+ public RunsWithDataNodeDiscoveryProvider(
+ PreflightConfigService preflightConfigService,
+ NodeService nodeService) {
+ this.preflightConfigService = preflightConfigService;
+ this.nodeService = nodeService;
+ this.resultsCachingSupplier = Suppliers.memoize(this::doGet);
+ }
+
+ @Override
+ public Boolean get() {
+ return resultsCachingSupplier.get();
+ }
+
+ private Boolean doGet() {
+ final PreflightConfigResult preflightResult = preflightConfigService.getPersistedConfig()
+ .map(PreflightConfig::result)
+ .orElse(PreflightConfigResult.UNKNOWN);
+
+ // if preflight is finished, we assume that there will be some datanode registered via node-service.
+ if (preflightResult == PreflightConfigResult.FINISHED) {
+ final List discovered = discover();
+ if (!discovered.isEmpty()) {
+ LOG.debug("Running with DataNode(s).");
+ return Boolean.TRUE;
+ }
+ }
+ LOG.debug("Not running with DataNode(s).");
+ return Boolean.FALSE;
+ }
+
+ private List discover() {
+ return nodeService.allActive(Node.Type.DATANODE).values().stream()
+ .map(Node::getTransportAddress)
+ .map(URI::create)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog2/security/IndexerJwtAuthToken.java b/graylog2-server/src/main/java/org/graylog2/security/IndexerJwtAuthToken.java
new file mode 100644
index 0000000000..1361868f10
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog2/security/IndexerJwtAuthToken.java
@@ -0,0 +1,29 @@
+/*
+ * 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.security;
+
+import javax.inject.Qualifier;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.PARAMETER, ElementType.TYPE })
+@Qualifier
+public @interface IndexerJwtAuthToken {
+}
diff --git a/graylog2-server/src/main/java/org/graylog2/security/IndexerJwtAuthTokenProvider.java b/graylog2-server/src/main/java/org/graylog2/security/IndexerJwtAuthTokenProvider.java
new file mode 100644
index 0000000000..ee54b8705d
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog2/security/IndexerJwtAuthTokenProvider.java
@@ -0,0 +1,80 @@
+/*
+ * 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.security;
+
+import com.google.common.base.Suppliers;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import javax.crypto.spec.SecretKeySpec;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import com.github.joschi.jadconfig.util.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+@Singleton
+public class IndexerJwtAuthTokenProvider implements Provider {
+ private final Supplier authHeaderBearerString;
+
+ private static final Logger LOG = LoggerFactory.getLogger(IndexerJwtAuthTokenProvider.class);
+
+ @Inject
+ public IndexerJwtAuthTokenProvider(@Named("password_secret") String signingKey,
+ @Named("indexer_jwt_auth_token_expiration_duration") final Duration tokenExpirationDuration,
+ @Named("indexer_jwt_auth_token_caching_duration") final Duration cachingDuration) {
+ authHeaderBearerString = Suppliers.memoizeWithExpiration(() -> {
+ LOG.debug("Creating new JWT token, expiration set to {}", tokenExpirationDuration);
+ return "Bearer " + createToken(signingKey.getBytes(StandardCharsets.UTF_8), tokenExpirationDuration);
+ }, cachingDuration.toSeconds(), TimeUnit.SECONDS);
+ }
+
+ public static String createToken(final byte[] apiKeySecretBytes, final Duration tokenExpirationDuration) {
+ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+
+ long nowMillis = System.currentTimeMillis();
+ Date now = new Date(nowMillis);
+
+ Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
+
+ JwtBuilder builder = Jwts.builder().setId("graylog datanode connect " + nowMillis)
+ .addClaims(Map.of("os_roles", "admin"))
+ .setIssuedAt(now)
+ .setSubject("admin")
+ .setIssuer("graylog")
+ .setNotBefore(now)
+ .setExpiration(new Date(nowMillis + tokenExpirationDuration.toMilliseconds()))
+ .signWith(signingKey, signatureAlgorithm);
+
+ return builder.compact();
+ }
+
+ @Override
+ public String get() {
+ return authHeaderBearerString.get();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java b/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java
index 8fa1c85a81..d76d41ebfb 100644
--- a/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java
+++ b/graylog2-server/src/main/java/org/graylog2/shared/security/SecurityBindings.java
@@ -26,6 +26,8 @@ import org.graylog2.rest.models.system.sessions.responses.DefaultSessionResponse
import org.graylog2.rest.models.system.sessions.responses.SessionResponseFactory;
import org.graylog2.security.CustomCAX509TrustManager;
import org.graylog2.security.DefaultX509TrustManager;
+import org.graylog2.security.IndexerJwtAuthToken;
+import org.graylog2.security.IndexerJwtAuthTokenProvider;
import org.graylog2.security.TrustManagerProvider;
import org.graylog2.security.UserSessionTerminationService;
import org.graylog2.security.encryption.EncryptedValueService;
@@ -46,6 +48,7 @@ public class SecurityBindings extends PluginModule {
.implement(TrustManager.class, DefaultX509TrustManager.class)
.build(TrustManagerProvider.class));
bind(CustomCAX509TrustManager.class).asEagerSingleton();
+ bind(String.class).annotatedWith(IndexerJwtAuthToken.class).toProvider(IndexerJwtAuthTokenProvider.class).asEagerSingleton();
OptionalBinder.newOptionalBinder(binder(), ActorAwareAuthenticationTokenFactory.class)
.setDefault().to(ActorAwareUsernamePasswordTokenFactory.class);
diff --git a/graylog2-server/src/main/java/org/graylog2/storage/versionprobe/VersionProbe.java b/graylog2-server/src/main/java/org/graylog2/storage/versionprobe/VersionProbe.java
index 2b0ab1e95e..2bc85ea694 100644
--- a/graylog2-server/src/main/java/org/graylog2/storage/versionprobe/VersionProbe.java
+++ b/graylog2-server/src/main/java/org/graylog2/storage/versionprobe/VersionProbe.java
@@ -29,6 +29,8 @@ import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
+import org.graylog2.configuration.RunsWithDataNode;
+import org.graylog2.security.IndexerJwtAuthToken;
import org.graylog2.shared.utilities.ExceptionUtils;
import org.graylog2.storage.SearchVersion;
import org.slf4j.Logger;
@@ -55,16 +57,23 @@ public class VersionProbe {
private final OkHttpClient okHttpClient;
private final int connectionAttempts;
private final Duration delayBetweenAttempts;
+ private final boolean isJwtAuthentication;
+ private final String jwtBearerToken;
@Inject
public VersionProbe(ObjectMapper objectMapper,
OkHttpClient okHttpClient,
@Named("elasticsearch_version_probe_attempts") int elasticsearchVersionProbeAttempts,
- @Named("elasticsearch_version_probe_delay") Duration elasticsearchVersionProbeDelay) {
+ @Named("elasticsearch_version_probe_delay") Duration elasticsearchVersionProbeDelay,
+ @RunsWithDataNode Boolean runsWithDataNode,
+ @Named("indexer_use_jwt_authentication") boolean opensearchUseJwtAuthentication,
+ @IndexerJwtAuthToken String jwtBearerToken) {
this.objectMapper = objectMapper;
this.okHttpClient = okHttpClient;
this.connectionAttempts = elasticsearchVersionProbeAttempts;
this.delayBetweenAttempts = elasticsearchVersionProbeDelay;
+ this.isJwtAuthentication = runsWithDataNode || opensearchUseJwtAuthentication;
+ this.jwtBearerToken = jwtBearerToken;
}
public Optional probe(final Collection hosts) {
@@ -83,9 +92,9 @@ public class VersionProbe {
}
}
if (connectionAttempts == 0) {
- LOG.info("Elasticsearch is not available. Retry #{}", attempt.getAttemptNumber());
+ LOG.info("OpenSearch/Elasticsearch is not available. Retry #{}", attempt.getAttemptNumber());
} else {
- LOG.info("Elasticsearch is not available. Retry #{}/{}", attempt.getAttemptNumber(), connectionAttempts);
+ LOG.info("OpenSearch/Elasticsearch is not available. Retry #{}/{}", attempt.getAttemptNumber(), connectionAttempts);
}
}
})
@@ -93,7 +102,7 @@ public class VersionProbe {
.withStopStrategy((connectionAttempts == 0) ? StopStrategies.neverStop() : StopStrategies.stopAfterAttempt(connectionAttempts))
.build().call(() -> this.probeAllHosts(hosts));
} catch (ExecutionException | RetryException e) {
- LOG.error("Unable to retrieve version from Elasticsearch node: ", e);
+ LOG.error("Unable to retrieve version from OpenSearch/Elasticsearch node: ", e);
}
return Optional.empty();
}
@@ -126,9 +135,9 @@ public class VersionProbe {
final Consumer errorLogger = (responseBody) -> {
try {
final ErrorResponse errorResponse = errorResponseConverter.convert(responseBody);
- LOG.error("Unable to retrieve version from Elasticsearch node {}:{}: {}", host.getHost(), host.getPort(), errorResponse);
+ LOG.error("Unable to retrieve version from OpenSearch/Elasticsearch node {}:{}: {}", host.getHost(), host.getPort(), errorResponse);
} catch (IOException e) {
- LOG.error("Unable to retrieve version from Elasticsearch node {}:{}: unknown error - an exception occurred while deserializing error response: {}", host.getHost(), host.getPort(), e);
+ LOG.error("Unable to retrieve version from OpenSearch/Elasticsearch node {}:{}: unknown error - an exception occurred while deserializing error response: {}", host.getHost(), host.getPort(), e);
}
};
@@ -138,17 +147,27 @@ public class VersionProbe {
.flatMap(this::parseVersion);
}
- private OkHttpClient addAuthenticationIfPresent(URI host, OkHttpClient okHttpClient) {
- if (Strings.emptyToNull(host.getUserInfo()) != null) {
+ private Optional getAuthToken(final URI host, final boolean useJWT) {
+ if(useJWT) {
+ return Optional.of(jwtBearerToken);
+ } else if (Strings.emptyToNull(host.getUserInfo()) != null) {
final String[] credentials = host.getUserInfo().split(":");
final String username = credentials[0];
final String password = credentials[1];
- final String authToken = Credentials.basic(username, password);
+ return Optional.of(Credentials.basic(username, password));
+ }
+ return Optional.empty();
+ }
+
+ private OkHttpClient addAuthenticationIfPresent(URI host, OkHttpClient okHttpClient) {
+ final Optional authToken = getAuthToken(host, isJwtAuthentication);
+
+ if(authToken.isPresent()) {
return okHttpClient.newBuilder()
.addInterceptor(chain -> {
final Request originalRequest = chain.request();
- final Request.Builder builder = originalRequest.newBuilder().header("Authorization", authToken);
+ final Request.Builder builder = originalRequest.newBuilder().header("Authorization", authToken.get());
final Request newRequest = builder.build();
return chain.proceed(newRequest);
})
diff --git a/graylog2-server/src/test/java/org/graylog/testing/completebackend/ContainerizedGraylogBackend.java b/graylog2-server/src/test/java/org/graylog/testing/completebackend/ContainerizedGraylogBackend.java
index 6df1b05a81..c50b13dc7b 100644
--- a/graylog2-server/src/test/java/org/graylog/testing/completebackend/ContainerizedGraylogBackend.java
+++ b/graylog2-server/src/test/java/org/graylog/testing/completebackend/ContainerizedGraylogBackend.java
@@ -16,11 +16,9 @@
*/
package org.graylog.testing.completebackend;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.lang3.StringUtils;
import org.graylog.testing.containermatrix.MongodbServer;
import org.graylog.testing.elasticsearch.SearchServerInstance;
-import org.graylog.testing.graylognode.ExecutableNotFoundException;
import org.graylog.testing.graylognode.NodeInstance;
import org.graylog.testing.mongodb.MongoDBInstance;
import org.graylog2.storage.SearchVersion;
@@ -33,10 +31,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.stream.Collectors;
public class ContainerizedGraylogBackend implements GraylogBackend, AutoCloseable {
@@ -47,23 +41,30 @@ public class ContainerizedGraylogBackend implements GraylogBackend, AutoCloseabl
private MailServerContainer emailServerInstance;
private NodeInstance node;
- private ContainerizedGraylogBackend() {
+ private ContainerizedGraylogBackend() {}
+
+ public synchronized static ContainerizedGraylogBackend createStarted(final SearchVersion version,
+ final MongodbServer mongodbVersion,
+ final int[] extraPorts,
+ final List mongoDBFixtures,
+ final PluginJarsProvider pluginJarsProvider,
+ final MavenProjectDirProvider mavenProjectDirProvider,
+ final List enabledFeatureFlags,
+ final boolean preImportLicense,
+ final boolean withMailServerEnabled) {
+
+ return new ContainerizedGraylogBackend().create(version, mongodbVersion, extraPorts, mongoDBFixtures, pluginJarsProvider, mavenProjectDirProvider, enabledFeatureFlags, preImportLicense, withMailServerEnabled);
}
- public static ContainerizedGraylogBackend createStarted(SearchVersion version, MongodbServer mongodbVersion,
- int[] extraPorts, List mongoDBFixtures,
- PluginJarsProvider pluginJarsProvider, MavenProjectDirProvider mavenProjectDirProvider,
- final List enabledFeatureFlags, boolean preImportLicense, boolean withMailServerEnabled) {
-
- final ContainerizedGraylogBackend backend = new ContainerizedGraylogBackend();
- backend.create(version, mongodbVersion, extraPorts, mongoDBFixtures, pluginJarsProvider, mavenProjectDirProvider, enabledFeatureFlags, preImportLicense, withMailServerEnabled);
- return backend;
- }
-
- private void create(final SearchVersion version, MongodbServer mongodbVersion,
- int[] extraPorts, List mongoDBFixtures,
- PluginJarsProvider pluginJarsProvider, MavenProjectDirProvider mavenProjectDirProvider,
- List enabledFeatureFlags, boolean preImportLicense, boolean withMailServerEnabled) {
+ private ContainerizedGraylogBackend create(final SearchVersion version,
+ final MongodbServer mongodbVersion,
+ final int[] extraPorts,
+ final List mongoDBFixtures,
+ final PluginJarsProvider pluginJarsProvider,
+ final MavenProjectDirProvider mavenProjectDirProvider,
+ final List enabledFeatureFlags,
+ final boolean preImportLicense,
+ final boolean withMailServerEnabled) {
final var network = Network.newNetwork();
final var builder = SearchServerInstanceProvider.getBuilderFor(version).orElseThrow(() -> new UnsupportedOperationException("Search version " + version + " not supported."));
@@ -103,6 +104,7 @@ public class ContainerizedGraylogBackend implements GraylogBackend, AutoCloseabl
LOG.error("------------------------------ Search Server logs: --------------------------------------\n{}", searchServer.getLogs());
throw ex;
}
+ return this;
}
private void createLicenses(final MongoDBInstance mongoDBInstance, final String... licenseStrs) {
diff --git a/graylog2-server/src/test/java/org/graylog/testing/graylognode/NodeContainerFactory.java b/graylog2-server/src/test/java/org/graylog/testing/graylognode/NodeContainerFactory.java
index e6be7d8de7..64eb22a19e 100644
--- a/graylog2-server/src/test/java/org/graylog/testing/graylognode/NodeContainerFactory.java
+++ b/graylog2-server/src/test/java/org/graylog/testing/graylognode/NodeContainerFactory.java
@@ -127,7 +127,7 @@ public class NodeContainerFactory {
.withEnv("GRAYLOG_ELASTICSEARCH_HOSTS", config.elasticsearchUri)
// TODO: should we set this override search version or let graylog server to detect it from the search server itself?
.withEnv("GRAYLOG_ELASTICSEARCH_VERSION", config.elasticsearchVersion.encode())
- .withEnv("GRAYLOG_PASSWORD_SECRET", "M4lteserKreuzHerrStrack?")
+ .withEnv("GRAYLOG_PASSWORD_SECRET", "M4lteserKreuzHerrStrack?-warZuKurzDeshalbMussdaNochWasdran")
.withEnv("GRAYLOG_NODE_ID_FILE", "data/config/node-id")
.withEnv("GRAYLOG_HTTP_BIND_ADDRESS", "0.0.0.0:" + API_PORT)
.withEnv("GRAYLOG_ROOT_PASSWORD_SHA2", ADMIN_PW_SHA2)
diff --git a/pom.xml b/pom.xml
index 5240c75421..5fd12a6751 100644
--- a/pom.xml
+++ b/pom.xml
@@ -180,6 +180,7 @@
9.1.6
0.3.2
3.6
+ 0.11.5
2.0.0-M24
@@ -316,6 +317,21 @@
joda-time
${joda-time.version}
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jjwt.version}
+