-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into close-investigation-drawer-in-security-per…
…spective
- Loading branch information
Showing
23 changed files
with
770 additions
and
223 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
type="a" | ||
message="Add option to select all fields for messages export widget" | ||
|
||
pulls = ["19463"] | ||
issues=["Graylog2/graylog-plugin-enterprise#5616"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
type = "c" | ||
message = "Run remote reindex connection checks from datanodes, aggregate results." | ||
|
||
pulls = ["19879"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
data-node/src/main/java/org/graylog/datanode/configuration/DatanodeTrustManagerProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* 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 | ||
* <http://www.mongodb.com/licensing/server-side-public-license>. | ||
*/ | ||
package org.graylog.datanode.configuration; | ||
|
||
import com.google.common.eventbus.EventBus; | ||
import com.google.common.eventbus.Subscribe; | ||
import jakarta.inject.Inject; | ||
import jakarta.inject.Provider; | ||
import org.graylog.datanode.configuration.variants.OpensearchSecurityConfiguration; | ||
import org.graylog.datanode.opensearch.OpensearchConfigurationChangeEvent; | ||
import org.graylog2.security.CustomCAX509TrustManager; | ||
import org.graylog2.security.TrustManagerAggregator; | ||
|
||
import javax.net.ssl.X509TrustManager; | ||
import java.io.IOException; | ||
import java.security.KeyStore; | ||
import java.security.KeyStoreException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.cert.CertificateException; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
public class DatanodeTrustManagerProvider implements Provider<X509TrustManager> { | ||
|
||
private final CustomCAX509TrustManager customCAX509TrustManager; | ||
private volatile KeyStore datanodeTruststore; | ||
|
||
@Inject | ||
public DatanodeTrustManagerProvider(CustomCAX509TrustManager CustomCAX509TrustManager, EventBus eventBus) { | ||
customCAX509TrustManager = CustomCAX509TrustManager; | ||
eventBus.register(this); | ||
} | ||
|
||
@Subscribe | ||
public void onOpensearchConfigurationChange(OpensearchConfigurationChangeEvent e) { | ||
Optional.ofNullable(e.config().opensearchSecurityConfiguration()) | ||
.flatMap(OpensearchSecurityConfiguration::getTruststore) | ||
.map(t -> { | ||
try { | ||
return t.loadKeystore(); | ||
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException ex) { | ||
throw new RuntimeException(ex); | ||
} | ||
}) | ||
.ifPresent(this::setTruststore); | ||
} | ||
|
||
private void setTruststore(KeyStore keyStore) { | ||
this.datanodeTruststore = keyStore; | ||
} | ||
|
||
|
||
@Override | ||
public X509TrustManager get() { | ||
final X509TrustManager datanodeTrustManager = TrustManagerAggregator.trustManagerFromKeystore(this.datanodeTruststore); | ||
return new TrustManagerAggregator(List.of(datanodeTrustManager, customCAX509TrustManager)); | ||
} | ||
} |
130 changes: 130 additions & 0 deletions
130
data-node/src/main/java/org/graylog/datanode/rest/OpensearchConnectionCheckController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* 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 | ||
* <http://www.mongodb.com/licensing/server-side-public-license>. | ||
*/ | ||
package org.graylog.datanode.rest; | ||
|
||
import jakarta.annotation.Nonnull; | ||
import jakarta.inject.Inject; | ||
import jakarta.ws.rs.POST; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.Produces; | ||
import jakarta.ws.rs.core.MediaType; | ||
import okhttp3.Credentials; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.Request; | ||
import org.graylog.datanode.configuration.DatanodeTrustManagerProvider; | ||
import org.graylog.storage.opensearch2.ConnectionCheckRequest; | ||
import org.graylog.storage.opensearch2.ConnectionCheckResponse; | ||
import org.graylog2.security.TrustAllX509TrustManager; | ||
import org.graylog2.security.untrusted.UntrustedCertificateExtractor; | ||
|
||
import javax.net.ssl.SSLContext; | ||
import javax.net.ssl.TrustManager; | ||
import javax.net.ssl.X509TrustManager; | ||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.StringReader; | ||
import java.security.KeyManagementException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.SecureRandom; | ||
import java.security.cert.X509Certificate; | ||
import java.time.Duration; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
@Path("/connection-check") | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public class OpensearchConnectionCheckController { | ||
|
||
public static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10); | ||
public static final Duration WRITE_TIMEOUT = Duration.ofSeconds(10); | ||
public static final Duration READ_TIMEOUT = Duration.ofSeconds(10); | ||
private final DatanodeTrustManagerProvider datanodeTrustManagerProvider; | ||
|
||
private final OkHttpClient httpClient; | ||
|
||
@Inject | ||
public OpensearchConnectionCheckController(DatanodeTrustManagerProvider datanodeTrustManagerProvider) { | ||
this.datanodeTrustManagerProvider = datanodeTrustManagerProvider; | ||
this.httpClient = new OkHttpClient.Builder() | ||
.retryOnConnectionFailure(true) | ||
.connectTimeout(CONNECT_TIMEOUT) | ||
.writeTimeout(WRITE_TIMEOUT) | ||
.readTimeout(READ_TIMEOUT) | ||
.build(); | ||
} | ||
|
||
@POST | ||
@Path("opensearch") | ||
public ConnectionCheckResponse status(ConnectionCheckRequest request) { | ||
final List<X509Certificate> unknownCertificates = new LinkedList<>(); | ||
try { | ||
unknownCertificates.addAll(extractUnknownCertificates(request.host())); | ||
final List<String> indices = getAllIndicesFrom(request.host(), request.username(), request.password(), request.trustUnknownCerts()); | ||
return ConnectionCheckResponse.success(indices, unknownCertificates); | ||
} catch (Exception e) { | ||
return ConnectionCheckResponse.error(e, unknownCertificates); | ||
} | ||
} | ||
|
||
List<String> getAllIndicesFrom(final String host, final String username, final String password, boolean trustUnknownCerts) { | ||
var url = (host.endsWith("/") ? host : host + "/") + "_cat/indices?h=index"; | ||
try (var response = getClient(trustUnknownCerts).newCall(new Request.Builder().url(url).header("Authorization", Credentials.basic(username, password)).build()).execute()) { | ||
if (response.isSuccessful() && response.body() != null) { | ||
// filtering all indices that start with "." as they indicate a system index - we don't want to reindex those | ||
return new BufferedReader(new StringReader(response.body().string())).lines().filter(i -> !i.startsWith(".")).sorted().toList(); | ||
} else { | ||
String message = String.format(Locale.ROOT, "Could not read list of indices from %s. Code=%d, message=%s", host, response.code(), response.message()); | ||
throw new RuntimeException(message); | ||
} | ||
} catch (IOException e) { | ||
throw new RuntimeException("Could not read list of indices from " + host + ", " + e.getMessage(), e); | ||
} | ||
} | ||
|
||
|
||
private OkHttpClient getClient(boolean trustUnknownCerts) { | ||
try { | ||
final SSLContext ctx = SSLContext.getInstance("TLS"); | ||
final X509TrustManager trustManager = getTrustManager(trustUnknownCerts); | ||
ctx.init(null, new TrustManager[]{trustManager}, new SecureRandom()); | ||
return httpClient.newBuilder().sslSocketFactory(ctx.getSocketFactory(), trustManager).build(); | ||
} catch (NoSuchAlgorithmException | KeyManagementException e) { | ||
throw new RuntimeException(e); | ||
|
||
} | ||
} | ||
|
||
@Nonnull | ||
private X509TrustManager getTrustManager(boolean trustUnknownCerts) { | ||
if (trustUnknownCerts) { | ||
return new TrustAllX509TrustManager(); | ||
} else { | ||
return datanodeTrustManagerProvider.get(); | ||
} | ||
} | ||
|
||
@Nonnull | ||
private List<X509Certificate> extractUnknownCertificates(String host) { | ||
final UntrustedCertificateExtractor extractor = new UntrustedCertificateExtractor(httpClient); | ||
try { | ||
return extractor.extractUntrustedCerts(host); | ||
} catch (NoSuchAlgorithmException | IOException | KeyManagementException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
...ensearch2/src/main/java/org/graylog/storage/opensearch2/AggregatedConnectionResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
* 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 | ||
* <http://www.mongodb.com/licensing/server-side-public-license>. | ||
*/ | ||
package org.graylog.storage.opensearch2; | ||
|
||
import org.bouncycastle.cert.X509CertificateHolder; | ||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | ||
import org.bouncycastle.openssl.PEMParser; | ||
|
||
import javax.xml.bind.DatatypeConverter; | ||
import java.io.IOException; | ||
import java.io.StringReader; | ||
import java.security.MessageDigest; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.cert.CertificateEncodingException; | ||
import java.security.cert.CertificateException; | ||
import java.security.cert.X509Certificate; | ||
import java.util.Comparator; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
|
||
public record AggregatedConnectionResponse(Map<String, ConnectionCheckResponse> responses) { | ||
public List<String> indices() { | ||
return responses.values().stream().flatMap(v -> v.indices().stream()).sorted(Comparator.naturalOrder()).collect(Collectors.toList()); | ||
} | ||
|
||
public String error() { | ||
|
||
final String errorMessage = responses.entrySet().stream().filter(e -> e.getValue().error() != null).map(e -> e.getKey() + ": " + e.getValue().error()).collect(Collectors.joining(";")); | ||
|
||
if (errorMessage.isEmpty()) { | ||
return null; | ||
} | ||
|
||
final StringBuilder errorBuilder = new StringBuilder(); | ||
errorBuilder.append(errorMessage); | ||
|
||
if (!certificates().isEmpty()) { | ||
errorBuilder.append("\n").append("Unknown certificates: \n").append(certificates().stream().map(AggregatedConnectionResponse::decode).map(this::info).collect(Collectors.joining("\n\n"))); | ||
} | ||
return errorBuilder.toString(); | ||
} | ||
|
||
private String info(X509Certificate certificate) { | ||
return """ | ||
Issued to: %s, | ||
Issued by: %s, | ||
Serial number: %s, | ||
Issued on: %s, | ||
Expires on: %s, | ||
SHA-256 fingerprint: %s, | ||
SHA-1 Fingerprint: %s | ||
""".formatted( | ||
certificate.getSubjectX500Principal().getName(), | ||
certificate.getIssuerX500Principal().getName(), | ||
certificate.getSerialNumber(), | ||
certificate.getNotBefore(), | ||
certificate.getNotAfter(), | ||
getfingerprint(certificate, "SHA-256"), | ||
getfingerprint(certificate, "SHA-1") | ||
); | ||
} | ||
|
||
public List<String> certificates() { | ||
return responses.values().stream() | ||
.filter(v -> v.certificates() != null) | ||
.flatMap(v -> v.certificates().stream()).distinct().collect(Collectors.toList()); | ||
} | ||
|
||
private static X509Certificate decode(String pemEncodedCert) { | ||
final PEMParser pemParser = new PEMParser(new StringReader(pemEncodedCert)); | ||
try { | ||
Object parsed = pemParser.readObject(); | ||
if (parsed instanceof X509CertificateHolder certificate) { | ||
return new JcaX509CertificateConverter().getCertificate(certificate); | ||
} else { | ||
throw new IllegalArgumentException("Couldn't parse x509 certificate from provided string, unknown type"); | ||
} | ||
} catch (IOException | CertificateException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static String getfingerprint(X509Certificate cert, String type) { | ||
try { | ||
MessageDigest md = MessageDigest.getInstance(type); | ||
byte[] der = cert.getEncoded(); | ||
md.update(der); | ||
byte[] digest = md.digest(); | ||
String digestHex = DatatypeConverter.printHexBinary(digest); | ||
return digestHex.toLowerCase(Locale.ROOT); | ||
} catch (CertificateEncodingException | NoSuchAlgorithmException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...age-opensearch2/src/main/java/org/graylog/storage/opensearch2/ConnectionCheckRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* 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 | ||
* <http://www.mongodb.com/licensing/server-side-public-license>. | ||
*/ | ||
package org.graylog.storage.opensearch2; | ||
|
||
import java.net.URI; | ||
|
||
public record ConnectionCheckRequest(String host, String username, String password, boolean trustUnknownCerts) { | ||
} |
Oops, something went wrong.