diff --git a/data-node/src/main/java/org/graylog/datanode/configuration/variants/OpensearchSecurityConfiguration.java b/data-node/src/main/java/org/graylog/datanode/configuration/variants/OpensearchSecurityConfiguration.java index f96a8670fd..b83b1b26fa 100644 --- a/data-node/src/main/java/org/graylog/datanode/configuration/variants/OpensearchSecurityConfiguration.java +++ b/data-node/src/main/java/org/graylog/datanode/configuration/variants/OpensearchSecurityConfiguration.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.RandomStringUtils; -import org.graylog.datanode.Configuration; import org.graylog.datanode.configuration.DatanodeConfiguration; import org.graylog.datanode.configuration.TruststoreCreator; import org.graylog.security.certutil.CertConstants; @@ -103,8 +102,6 @@ public class OpensearchSecurityConfiguration { if (securityEnabled()) { config.putAll(commonSecureConfig()); - config.put("plugins.security.ssl.transport.enforce_hostname_verification", "false"); - config.put("plugins.security.ssl.transport.keystore_type", KEYSTORE_FORMAT); config.put("plugins.security.ssl.transport.keystore_filepath", transportCertificate.location().getFileName().toString()); // todo: this should be computed as a relative path config.put("plugins.security.ssl.transport.keystore_password", transportCertificate.passwordAsString()); diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java b/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java index 705000e857..ae7d2a3904 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/CaServiceImpl.java @@ -18,7 +18,7 @@ package org.graylog.security.certutil; import org.glassfish.jersey.media.multipart.BodyPart; import org.glassfish.jersey.media.multipart.FormDataBodyPart; -import org.graylog.security.certutil.ca.CACreator; +import org.graylog.security.certutil.ca.CAKeyPair; import org.graylog.security.certutil.ca.PemCaReader; import org.graylog.security.certutil.ca.exceptions.CACreationException; import org.graylog.security.certutil.ca.exceptions.KeyStoreStorageException; @@ -63,7 +63,6 @@ public class CaServiceImpl implements CaService { private final SmartKeystoreStorage keystoreStorage; private final KeystoreMongoLocation mongoDbCaLocation; private final KeystoreFileLocation manuallyProvidedCALocation; - private final CACreator caCreator; private final PemCaReader pemCaReader; private final CaConfiguration configuration; private final CertificatesService certificatesService; @@ -74,12 +73,10 @@ public class CaServiceImpl implements CaService { @Inject public CaServiceImpl(final Configuration configuration, final SmartKeystoreStorage keystoreStorage, - final CACreator caCreator, final PemCaReader pemCaReader, final CertificatesService certificatesService, final @Named("password_secret") String passwordSecret, ClusterEventBus eventBus) { this.keystoreStorage = keystoreStorage; - this.caCreator = caCreator; this.pemCaReader = pemCaReader; this.configuration = configuration; this.certificatesService = certificatesService; @@ -102,7 +99,7 @@ public class CaServiceImpl implements CaService { @Override public void create(final String organization, final Integer daysValid, char[] password) throws CACreationException, KeyStoreStorageException, KeyStoreException { final Duration certificateValidity = Duration.ofDays(daysValid == null || daysValid == 0 ? DEFAULT_VALIDITY : daysValid); - KeyStore keyStore = caCreator.createCA(organization, passwordSecret.toCharArray(), certificateValidity); + KeyStore keyStore = CAKeyPair.create(organization, passwordSecret.toCharArray(), certificateValidity).toKeyStore(); keystoreStorage.writeKeyStore(mongoDbCaLocation, keyStore, passwordSecret.toCharArray(), password); LOG.debug("Generated a new CA."); triggerCaChangedEvent(); diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java b/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java index a8c2a9bf44..9d44c0910a 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/CertutilCa.java @@ -18,7 +18,7 @@ package org.graylog.security.certutil; import com.github.rvesse.airline.annotations.Command; import com.github.rvesse.airline.annotations.Option; -import org.graylog.security.certutil.ca.CACreator; +import org.graylog.security.certutil.ca.CAKeyPair; import org.graylog.security.certutil.console.CommandLineConsole; import org.graylog.security.certutil.console.SystemConsole; import org.graylog.security.certutil.keystore.storage.KeystoreFileStorage; @@ -37,20 +37,17 @@ public class CertutilCa implements CliCommand { @Option(name = "--filename", description = "Filename for the CA keystore") protected String keystoreFilename = "datanode-ca.p12"; private final CommandLineConsole console; - private final CACreator caCreator; private final KeystoreFileStorage caKeystoreStorage; public static final CommandLineConsole.Prompt PROMPT_ENTER_CA_PASSWORD = CommandLineConsole.prompt("Enter CA password: "); public CertutilCa() { this.console = new SystemConsole(); - this.caCreator = new CACreator(); this.caKeystoreStorage = new KeystoreFileStorage(new SinglePasswordKeystoreContentMover()); } public CertutilCa(String keystoreFilename, CommandLineConsole console) { this.keystoreFilename = keystoreFilename; this.console = console; - this.caCreator = new CACreator(); this.caKeystoreStorage = new KeystoreFileStorage(new SinglePasswordKeystoreContentMover()); } @@ -62,7 +59,7 @@ public class CertutilCa implements CliCommand { console.printLine("Generating datanode CA"); final Duration certificateValidity = Duration.ofDays(10 * 365); - KeyStore caKeystore = caCreator.createCA(DEFAULT_ORGANIZATION_NAME, password, certificateValidity); + KeyStore caKeystore = CAKeyPair.create(DEFAULT_ORGANIZATION_NAME, password, certificateValidity).toKeyStore(); console.printLine("Private keys and certificates for root and intermediate CA generated"); diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/ca/CACreator.java b/graylog2-server/src/main/java/org/graylog/security/certutil/ca/CAKeyPair.java similarity index 74% rename from graylog2-server/src/main/java/org/graylog/security/certutil/ca/CACreator.java rename to graylog2-server/src/main/java/org/graylog/security/certutil/ca/CAKeyPair.java index 67789c01de..a6b4bfbdef 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/ca/CACreator.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/ca/CAKeyPair.java @@ -28,9 +28,11 @@ import java.time.Duration; import static org.graylog.security.certutil.CertConstants.CA_KEY_ALIAS; import static org.graylog.security.certutil.CertConstants.PKCS12; -public class CACreator { - public KeyStore createCA(final String organization, final char[] password, - final Duration certificateValidity) throws CACreationException { +public class CAKeyPair { + private final KeyStore keystore; + + private CAKeyPair(final String organization, final char[] password, + final Duration certificateValidity) throws CACreationException { try { final KeyPair rootCA = CertificateGenerator.generate( @@ -48,10 +50,20 @@ public class CACreator { password, new X509Certificate[]{rootCA.certificate()}); - return caKeystore; + this.keystore = caKeystore; } catch (Exception e) { throw new CACreationException("Failed to create a Certificate Authority", e); } } + + public static CAKeyPair create(final String organization, + final char[] password, + final Duration certificateValidity) throws CACreationException { + return new CAKeyPair(organization, password, certificateValidity); + } + + public KeyStore toKeyStore() { + return this.keystore; + } } diff --git a/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CsrSigner.java b/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CsrSigner.java index e4ad1e1aa8..6d6104bfe0 100644 --- a/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CsrSigner.java +++ b/graylog2-server/src/main/java/org/graylog/security/certutil/csr/CsrSigner.java @@ -16,7 +16,7 @@ */ package org.graylog.security.certutil.csr; -import org.bouncycastle.asn1.pkcs.Attribute; +import com.google.common.collect.Sets; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.Extension; @@ -32,6 +32,8 @@ import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.graylog2.plugin.certificates.RenewalPolicy; import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.time.Clock; @@ -39,9 +41,12 @@ import java.time.Duration; import java.time.Instant; import java.time.Period; import java.time.format.DateTimeParseException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.bouncycastle.asn1.x509.GeneralName.dNSName; import static org.bouncycastle.asn1.x509.GeneralName.iPAddress; @@ -49,6 +54,12 @@ import static org.bouncycastle.asn1.x509.GeneralName.rfc822Name; import static org.graylog.security.certutil.CertConstants.SIGNING_ALGORITHM; public class CsrSigner { + private static final Set localhostAttributes = Set.of( + new GeneralName(dNSName, "localhost"), + new GeneralName(iPAddress, "127.0.0.1"), + new GeneralName(iPAddress, "0:0:0:0:0:0:0:1") + ); + private final Clock clock; public CsrSigner() { @@ -66,6 +77,10 @@ public class CsrSigner { }; } + private boolean isDNSName(final int name) { + return name == dNSName; + } + private Duration periodToDuration(Period period) { return Duration.ofDays(period.getYears() * 365L + period.getMonths() * 30L + period.getDays()); } @@ -101,7 +116,6 @@ public class CsrSigner { BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); var issuerName = X500Name.getInstance(caCertificate.getSubjectX500Principal().getEncoded()); - var issuerKey = caPrivateKey; var builder = new X509v3CertificateBuilder( issuerName, @@ -109,27 +123,35 @@ public class CsrSigner { Date.from(validFrom), Date.from(validUntil), csr.getSubject(), csr.getSubjectPublicKeyInfo()); - var certAttributes = csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); - if (certAttributes != null && certAttributes.length > 0) { - ArrayList altNames = new ArrayList<>(); - - for (Attribute attribute : certAttributes) { - Extensions extensions = Extensions.getInstance(attribute.getAttrValues().getObjectAt(0)); - GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); - if (gns != null && gns.getNames() != null) { - Arrays.stream(gns.getNames()).filter(n -> isValidName(n.getTagNo())).forEach(altNames::add); - } - } - if (!altNames.isEmpty()) { - builder.addExtension(Extension.subjectAlternativeName, false, - new GeneralNames(altNames.toArray(new GeneralName[altNames.size()]))); - } + var altNames = Optional.ofNullable(csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) + .stream() + .flatMap(Arrays::stream) + .map(attribute -> Extensions.getInstance(attribute.getAttrValues().getObjectAt(0))) + .flatMap(extensions -> Optional.ofNullable(GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName)) + .flatMap(gns -> Optional.ofNullable(gns.getNames())) + .stream() + .flatMap(Arrays::stream)) + .filter(name -> isValidName(name.getTagNo())) + .flatMap(name -> isDNSName(name.getTagNo()) ? resolveDNSName(name) : Stream.of(name)) + .collect(Collectors.toSet()); + if (!altNames.isEmpty()) { + builder.addExtension(Extension.subjectAlternativeName, false, + new GeneralNames(Sets.union(localhostAttributes, altNames).toArray(new GeneralName[0]))); } - ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).build(issuerKey); + ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).build(caPrivateKey); X509CertificateHolder certHolder = builder.build(signer); - X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder); - return cert; + return new JcaX509CertificateConverter().getCertificate(certHolder); + } + + private Stream resolveDNSName(GeneralName name) { + final var hostname = name.getName().toString(); + try { + final var inetAddress = InetAddress.getByName(hostname); + return Stream.of(name, new GeneralName(iPAddress, inetAddress.getHostAddress())); + } catch (UnknownHostException e) { + return Stream.of(name); + } } }