Data Node: Add reverse lookups for local loopback-addresses as well. (#17022)

* Include local reverse lookups for loopback addresses in CSR.

* Add reverse lookups for local loopback-addresses to CSR.

* Suppressing forbidden method invocation warning.
This commit is contained in:
Dennis Oelkers
2023-10-20 14:15:07 +02:00
committed by GitHub
parent 63679db27b
commit 5e86e92bb0
9 changed files with 115 additions and 72 deletions

View File

@@ -16,6 +16,7 @@
*/
package org.graylog.datanode.bootstrap.preflight;
import com.google.common.collect.ImmutableList;
import org.bouncycastle.operator.OperatorException;
import org.graylog.datanode.configuration.DatanodeConfiguration;
import org.graylog.security.certutil.CertConstants;
@@ -36,6 +37,7 @@ import org.graylog2.cluster.preflight.DataNodeProvisioningConfig;
import org.graylog2.cluster.preflight.DataNodeProvisioningService;
import org.graylog2.plugin.periodical.Periodical;
import org.graylog2.plugin.system.NodeId;
import org.graylog2.shared.SuppressForbidden;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,8 +45,13 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.net.InetAddress;
import java.security.KeyStore;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Singleton
public class DataNodeConfigurationPeriodical extends Periodical {
@@ -89,48 +96,92 @@ public class DataNodeConfigurationPeriodical extends Periodical {
public void doRun() {
LOG.debug("checking if this DataNode is supposed to take configuration steps.");
var cfg = dataNodeProvisioningService.getPreflightConfigFor(nodeId.getNodeId());
if (cfg == null) {
if (cfg.isEmpty()) {
// write default config if none exists for this node
dataNodeProvisioningService.save(DataNodeProvisioningConfig.builder().nodeId(nodeId.getNodeId()).state(DataNodeProvisioningConfig.State.UNCONFIGURED).build());
} else if (DataNodeProvisioningConfig.State.CONFIGURED.equals(cfg.state())) {
writeInitialProvisioningConfig();
return;
}
cfg.ifPresent(c -> {
final var state = c.state();
if (state == null) {
return;
}
switch (state) {
case CONFIGURED -> writeCsr(c);
case SIGNED -> readSignedCertificate(c);
}
});
}
private void readSignedCertificate(DataNodeProvisioningConfig cfg) {
if (cfg.certificate() == null) {
LOG.error("Config entry in signed state, but no certificate data present in Mongo");
} else {
try {
var node = nodeService.byNodeId(nodeId);
var csr = csrGenerator.generateCSR(passwordSecret, node.getHostname(), cfg.altNames(), privateKeyEncryptedStorage);
csrStorage.writeCsr(csr, nodeId.getNodeId());
LOG.info("created CSR for this node");
} catch (CSRGenerationException | IOException | NodeNotFoundException | OperatorException ex) {
LOG.error("error generating a CSR: " + ex.getMessage(), ex);
dataNodeProvisioningService.save(cfg.toBuilder().state(DataNodeProvisioningConfig.State.ERROR).errorMsg(ex.getMessage()).build());
}
} else if (DataNodeProvisioningConfig.State.SIGNED.equals(cfg.state())) {
if (cfg.certificate() == null) {
LOG.error("Config entry in signed state, but no certificate data present in Mongo");
} else {
try {
final Optional<CertificateChain> certificateChain = certMongoStorage.readCertChain(nodeId.getNodeId());
if (certificateChain.isPresent()) {
final char[] secret = passwordSecret;
KeyStore nodeKeystore = certificateAndPrivateKeyMerger.merge(
certificateChain.get(),
privateKeyEncryptedStorage,
secret,
secret,
CertConstants.DATANODE_KEY_ALIAS
);
final Optional<CertificateChain> certificateChain = certMongoStorage.readCertChain(nodeId.getNodeId());
if (certificateChain.isPresent()) {
final char[] secret = passwordSecret;
KeyStore nodeKeystore = certificateAndPrivateKeyMerger.merge(
certificateChain.get(),
privateKeyEncryptedStorage,
secret,
secret,
CertConstants.DATANODE_KEY_ALIAS
);
final KeystoreMongoLocation location = new KeystoreMongoLocation(nodeId.getNodeId(), KeystoreMongoCollections.DATA_NODE_KEYSTORE_COLLECTION);
keystoreStorage.writeKeyStore(location, nodeKeystore, secret, secret);
final KeystoreMongoLocation location = new KeystoreMongoLocation(nodeId.getNodeId(), KeystoreMongoCollections.DATA_NODE_KEYSTORE_COLLECTION);
keystoreStorage.writeKeyStore(location, nodeKeystore, secret, secret);
//should be in one transaction, but we miss transactions...
dataNodeProvisioningService.changeState(nodeId.getNodeId(), DataNodeProvisioningConfig.State.STORED);
}
} catch (Exception ex) {
LOG.error("Config entry in signed state, but wrong certificate data present in Mongo");
//should be in one transaction, but we miss transactions...
dataNodeProvisioningService.changeState(nodeId.getNodeId(), DataNodeProvisioningConfig.State.STORED);
}
} catch (Exception ex) {
LOG.error("Config entry in signed state, but wrong certificate data present in Mongo");
}
}
}
private void writeCsr(DataNodeProvisioningConfig cfg) {
try {
final var node = nodeService.byNodeId(nodeId);
final var altNames = ImmutableList.<String>builder()
.addAll(Optional.ofNullable(cfg.altNames()).orElse(Collections.emptyList()))
.addAll(determineAltNames())
.build();
final var csr = csrGenerator.generateCSR(passwordSecret, node.getHostname(), altNames, privateKeyEncryptedStorage);
csrStorage.writeCsr(csr, nodeId.getNodeId());
LOG.info("created CSR for this node");
} catch (CSRGenerationException | IOException | NodeNotFoundException | OperatorException ex) {
LOG.error("error generating a CSR: " + ex.getMessage(), ex);
dataNodeProvisioningService.save(cfg.asError(ex.getMessage()));
}
}
private void writeInitialProvisioningConfig() {
dataNodeProvisioningService.save(DataNodeProvisioningConfig.builder()
.nodeId(nodeId.getNodeId())
.state(DataNodeProvisioningConfig.State.UNCONFIGURED)
.build());
}
private Iterable<String> determineAltNames() {
return Stream.of("127.0.0.1", "::1")
.map(this::reverseLookup)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@SuppressForbidden("Deliberate use of InetAddress#getHostName")
private String reverseLookup(String ipAddress) {
try {
final var inetAddress = InetAddress.getByName(ipAddress);
final var reverseLookup = inetAddress.getHostName();
return reverseLookup.equals(ipAddress) ? null : reverseLookup;
} catch (Exception e) {
return null;
}
}
@Override
protected Logger getLogger() {
return LOG;