Move certutil code to graylog-server (#15193)

This commit is contained in:
luk-kaminski
2023-04-13 08:03:01 +02:00
committed by GitHub
parent 6e2d839319
commit b48335a7dd
15 changed files with 38 additions and 24 deletions

View File

@@ -1,84 +0,0 @@
/*
* 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.bootstrap.commands.certutil;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
class CertutilCaTest {
@TempDir
static Path tempDir;
@Test
void testCaCertificateGeneration() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, InvalidAlgorithmParameterException, CertPathValidatorException, SignatureException, InvalidKeyException, NoSuchProviderException {
final Path certPath = tempDir.resolve("test-ca.p12");
final TestableConsole input = TestableConsole.empty()
.register("Enter CA password", "password");
final CertutilCa command = new CertutilCa(certPath.toAbsolutePath().toString(), input);
command.run();
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(certPath.toFile()), "password".toCharArray());
Assertions.assertThat(keyStore.getKey("root", "password".toCharArray())).isNotNull();
Assertions.assertThat(keyStore.getKey("ca", "password".toCharArray())).isNotNull();
final Certificate rootCert = keyStore.getCertificate("root");
Assertions.assertThat(rootCert).isNotNull();
final Certificate caCert = keyStore.getCertificate("ca");
Assertions.assertThatCode(() -> caCert.verify(rootCert.getPublicKey()))
.doesNotThrowAnyException();
/*
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
final ArrayList<Certificate> objects = new ArrayList<>();
objects.add(keyStore.getCertificate("root"));
objects.add(keyStore.getCertificate("ca"));
CertPath path = cf.generateCertPath(objects);
PKIXParameters params = new PKIXParameters(keyStore);
validator.validate(path, params);
*/
}
}

View File

@@ -1,87 +0,0 @@
/*
* 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.bootstrap.commands.certutil;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.file.Path;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
class CertutilCertTest {
@TempDir
static Path tempDir;
@Test
void testGenerateNodeCertificate() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, InvalidAlgorithmParameterException, CertPathValidatorException, SignatureException, InvalidKeyException, NoSuchProviderException {
final Path caPath = tempDir.resolve("test-ca.p12");
final Path nodePath = tempDir.resolve("test-node.p12");
final TestableConsole inputCa = TestableConsole.empty()
.register("Enter CA password", "password");
final CertutilCa certutilCa = new CertutilCa(caPath.toAbsolutePath().toString(), inputCa);
certutilCa.run();
// now we have a ROOT + CA keypair in the keystore, let's use it to generate node keypair
TestableConsole inputCert = TestableConsole.empty()
.register("Enter CA password", "password")
.register("Enter datanode certificate password", "changeme");
CertutilCert certutilCert = new CertutilCert(
caPath.toAbsolutePath().toString(),
nodePath.toAbsolutePath().toString(),
inputCert);
certutilCert.run();
KeyStore caKeyStore = KeyStore.getInstance("PKCS12");
caKeyStore.load(new FileInputStream(caPath.toFile()), "password".toCharArray());
KeyStore nodeKeyStore = KeyStore.getInstance("PKCS12");
nodeKeyStore.load(new FileInputStream(nodePath.toFile()), "changeme".toCharArray());
final Key nodeKey = nodeKeyStore.getKey(CertutilCert.DATANODE_KEY_ALIAS, "changeme".toCharArray());
Assertions.assertThat(nodeKey).isNotNull();
Assertions.assertThatCode(() -> nodeKeyStore.getCertificate(CertutilCert.DATANODE_KEY_ALIAS).verify(caKeyStore.getCertificate("ca").getPublicKey()))
.doesNotThrowAnyException();
final Certificate[] certificateChain = nodeKeyStore.getCertificateChain(CertutilCert.DATANODE_KEY_ALIAS);
Assertions.assertThat(certificateChain)
.hasSize(3)
.extracting(c ->(X509Certificate)c)
.extracting(c -> c.getSubjectX500Principal().getName())
.contains("CN=root", "CN=ca", "CN=localhost");
}
}

View File

@@ -1,99 +0,0 @@
/*
* 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.bootstrap.commands.certutil;
import org.assertj.core.api.Assertions;
import org.bouncycastle.asn1.x509.Extension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
class CertutilHttpTest {
@TempDir
static Path tempDir;
@Test
void testGenerateNodeCertificate() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, InvalidAlgorithmParameterException, CertPathValidatorException, SignatureException, InvalidKeyException, NoSuchProviderException {
final Path caPath = tempDir.resolve("test-ca.p12");
final Path nodePath = tempDir.resolve("test-node.p12");
final TestableConsole inputCa = TestableConsole.empty()
.register("Enter CA password", "password");
final CertutilCa certutilCa = new CertutilCa(caPath.toAbsolutePath().toString(), inputCa);
certutilCa.run();
// now we have a ROOT + CA keypair in the keystore, let's use it to generate node keypair
TestableConsole inputHttp = TestableConsole.empty()
.register("Do you want to use your own certificate authority? Respond with y/n?", "n")
.register("Enter CA password", "password")
.register("Enter certificate validity in days", "90")
.register("Enter alternative names (addresses) of this node [comma separated]", "example.com")
.register("Enter HTTP certificate password", "changeme");
CertutilHttp certutilCert = new CertutilHttp(
caPath.toAbsolutePath().toString(),
nodePath.toAbsolutePath().toString(),
inputHttp);
certutilCert.run();
KeyStore caKeyStore = KeyStore.getInstance("PKCS12");
caKeyStore.load(new FileInputStream(caPath.toFile()), "password".toCharArray());
KeyStore nodeKeyStore = KeyStore.getInstance("PKCS12");
nodeKeyStore.load(new FileInputStream(nodePath.toFile()), "changeme".toCharArray());
final Key nodeKey = nodeKeyStore.getKey(CertutilCert.DATANODE_KEY_ALIAS, "changeme".toCharArray());
Assertions.assertThat(nodeKey).isNotNull();
final Certificate nodeCertificate = nodeKeyStore.getCertificate(CertutilCert.DATANODE_KEY_ALIAS);
Assertions.assertThatCode(() -> nodeCertificate.verify(caKeyStore.getCertificate("ca").getPublicKey()))
.doesNotThrowAnyException();
final Collection<List<?>> alternativeNames = ((X509Certificate) nodeCertificate).getSubjectAlternativeNames();
Assertions.assertThat(alternativeNames)
.isNotEmpty()
.extracting(item -> (String) item.get(1))
.contains("localhost", "example.com");
final Certificate[] certificateChain = nodeKeyStore.getCertificateChain(CertutilCert.DATANODE_KEY_ALIAS);
Assertions.assertThat(certificateChain)
.hasSize(3)
.extracting(c ->(X509Certificate)c)
.extracting(c -> c.getSubjectX500Principal().getName())
.contains("CN=root", "CN=ca", "CN=localhost");
}
}

View File

@@ -1,90 +0,0 @@
/*
* 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.bootstrap.commands.certutil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public class TestableConsole implements CommandLineConsole {
private static final Logger LOG = LoggerFactory.getLogger(TestableConsole.class);
private final List<Pair<String, String>> providedResponses = new LinkedList<>();
private final List<String> output = new LinkedList<>();
private boolean silent = false;
public static TestableConsole empty() {
return new TestableConsole();
}
public TestableConsole silent() {
this.silent = true;
return this;
}
public TestableConsole register(String question, String response) {
this.providedResponses.add(Pair.of(cleanQuestion(question), response));
return this;
}
@Override
public String readLine(String format, Object... args) {
final Pair<String, String> response = consumeResponse(format, args);
return response.getValue();
}
@Override
public char[] readPassword(String format, Object... args) {
final Pair<String, String> response = consumeResponse(format, args);
return response.getValue().toCharArray();
}
@Override
public void printLine(String line) {
if (!silent) {
LOG.info(line);
}
this.output.add(line);
}
public List<String> getOutput() {
return output;
}
private Pair<String, String> consumeResponse(String format, Object[] args) {
final String question = cleanQuestion(String.format(Locale.ROOT, format, args));
final Pair<String, String> response = providedResponses.stream().filter(r -> r.getKey().equals(question)).findFirst()
.orElseThrow(() -> new ConsoleException("Unexpected input question:" + question));
providedResponses.remove(response);
return response;
}
private String cleanQuestion(String input) {
return Optional.of(input)
.map(String::trim)
.map(s -> StringUtils.removeEnd(s, ":"))
.map(String::trim)
.get();
}
}

View File

@@ -24,11 +24,11 @@ import com.github.rholder.retry.WaitStrategies;
import io.restassured.RestAssured;
import io.restassured.response.ValidatableResponse;
import org.apache.http.NoHttpResponseException;
import org.graylog.datanode.bootstrap.commands.certutil.CertutilCa;
import org.graylog.datanode.bootstrap.commands.certutil.CertutilCert;
import org.graylog.datanode.bootstrap.commands.certutil.CertutilHttp;
import org.graylog.datanode.bootstrap.commands.certutil.TestableConsole;
import org.graylog.datanode.testinfra.DatanodeContainerizedBackend;
import org.graylog.security.certutil.CertutilCa;
import org.graylog.security.certutil.CertutilCert;
import org.graylog.security.certutil.CertutilHttp;
import org.graylog.security.certutil.console.TestableConsole;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;