[Security] Apply validation when parsing certgen input (elastic/x-pack-elasticsearch#2711)
When certgen configuration was read from an input file (`-in` option) validation errors were collected but never reported. Depending on the type of error this may have caused the tool to exit with an internal error (e.g. NPE). Validation is now applied after parsing the file and if errors are found the tool exits. Original commit: elastic/x-pack-elasticsearch@b2262ed1d7
This commit is contained in:
parent
5d0388ccb3
commit
bc038b323d
|
@ -38,6 +38,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ -360,6 +361,9 @@ public class CertUtils {
|
||||||
*/
|
*/
|
||||||
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList)
|
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList)
|
||||||
throws IOException, OperatorCreationException {
|
throws IOException, OperatorCreationException {
|
||||||
|
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
||||||
|
Objects.requireNonNull(keyPair.getPublic(), "Public-Key must not be null");
|
||||||
|
Objects.requireNonNull(principal, "Principal must not be null");
|
||||||
JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(principal, keyPair.getPublic());
|
JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(principal, keyPair.getPublic());
|
||||||
if (sanList != null) {
|
if (sanList != null) {
|
||||||
ExtensionsGenerator extGen = new ExtensionsGenerator();
|
ExtensionsGenerator extGen = new ExtensionsGenerator();
|
||||||
|
|
|
@ -47,7 +47,10 @@ import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
import org.elasticsearch.cli.EnvironmentAwareCommand;
|
import org.elasticsearch.cli.EnvironmentAwareCommand;
|
||||||
|
import org.elasticsearch.cli.ExitCodes;
|
||||||
import org.elasticsearch.cli.Terminal;
|
import org.elasticsearch.cli.Terminal;
|
||||||
|
import org.elasticsearch.cli.Terminal.Verbosity;
|
||||||
|
import org.elasticsearch.cli.UserException;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
|
@ -222,7 +225,7 @@ public class CertificateTool extends EnvironmentAwareCommand {
|
||||||
static Collection<CertificateInformation> getCertificateInformationList(Terminal terminal, String inputFile)
|
static Collection<CertificateInformation> getCertificateInformationList(Terminal terminal, String inputFile)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
if (inputFile != null) {
|
if (inputFile != null) {
|
||||||
return parseFile(resolvePath(inputFile).toAbsolutePath());
|
return parseAndValidateFile(terminal, resolvePath(inputFile).toAbsolutePath());
|
||||||
}
|
}
|
||||||
Map<String, CertificateInformation> map = new HashMap<>();
|
Map<String, CertificateInformation> map = new HashMap<>();
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
|
@ -267,6 +270,26 @@ public class CertificateTool extends EnvironmentAwareCommand {
|
||||||
return map.values();
|
return map.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Collection<CertificateInformation> parseAndValidateFile(Terminal terminal, Path file) throws Exception {
|
||||||
|
final Collection<CertificateInformation> config = parseFile(file);
|
||||||
|
boolean hasError = false;
|
||||||
|
for (CertificateInformation certInfo : config) {
|
||||||
|
final List<String> errors = certInfo.validate();
|
||||||
|
if (errors.size() > 0) {
|
||||||
|
hasError = true;
|
||||||
|
terminal.println(Verbosity.SILENT, "Configuration for instance " + certInfo.name.originalName + " has invalid details");
|
||||||
|
for (String message : errors) {
|
||||||
|
terminal.println(Verbosity.SILENT, " * " + message);
|
||||||
|
}
|
||||||
|
terminal.println("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasError) {
|
||||||
|
throw new UserException(ExitCodes.CONFIG, "File " + file + " contains invalid configuration details (see messages above)");
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the input file to retrieve the certificate information
|
* Parses the input file to retrieve the certificate information
|
||||||
* @param file the file to parse
|
* @param file the file to parse
|
||||||
|
|
|
@ -56,6 +56,8 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair;
|
||||||
import org.bouncycastle.openssl.PEMParser;
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||||
import org.elasticsearch.cli.MockTerminal;
|
import org.elasticsearch.cli.MockTerminal;
|
||||||
|
import org.elasticsearch.cli.Terminal;
|
||||||
|
import org.elasticsearch.cli.UserException;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
import org.elasticsearch.common.io.PathUtils;
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
|
@ -211,6 +213,18 @@ public class CertificateToolTests extends ESTestCase {
|
||||||
assertEquals("different file", certInfo.name.filename);
|
assertEquals("different file", certInfo.name.filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testParsingFileWithInvalidDetails() throws Exception {
|
||||||
|
Path tempDir = initTempDir();
|
||||||
|
Path instanceFile = writeInvalidInstanceInformation(tempDir.resolve("instances-invalid.yml"));
|
||||||
|
final MockTerminal terminal = new MockTerminal();
|
||||||
|
final UserException exception = expectThrows(UserException.class,
|
||||||
|
() -> CertificateTool.parseAndValidateFile(terminal, instanceFile));
|
||||||
|
assertThat(exception.getMessage(), containsString("invalid configuration"));
|
||||||
|
assertThat(exception.getMessage(), containsString(instanceFile.toString()));
|
||||||
|
assertThat(terminal.getOutput(), containsString("THIS=not a,valid DN"));
|
||||||
|
assertThat(terminal.getOutput(), containsString("could not be converted to a valid DN"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testGeneratingCsr() throws Exception {
|
public void testGeneratingCsr() throws Exception {
|
||||||
Path tempDir = initTempDir();
|
Path tempDir = initTempDir();
|
||||||
Path outputFile = tempDir.resolve("out.zip");
|
Path outputFile = tempDir.resolve("out.zip");
|
||||||
|
@ -528,6 +542,17 @@ public class CertificateToolTests extends ESTestCase {
|
||||||
return Files.write(path, instances, StandardCharsets.UTF_8);
|
return Files.write(path, instances, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the description of instances to a given {@link Path}
|
||||||
|
*/
|
||||||
|
private Path writeInvalidInstanceInformation(Path path) throws IOException {
|
||||||
|
Iterable<String> instances = Arrays.asList(
|
||||||
|
"instances:",
|
||||||
|
" - name: \"THIS=not a,valid DN\"",
|
||||||
|
" ip: \"127.0.0.1\"");
|
||||||
|
return Files.write(path, instances, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressForbidden(reason = "resolve paths against CWD for a CLI tool")
|
@SuppressForbidden(reason = "resolve paths against CWD for a CLI tool")
|
||||||
private static Path resolvePath(String path) {
|
private static Path resolvePath(String path) {
|
||||||
return PathUtils.get(path).toAbsolutePath();
|
return PathUtils.get(path).toAbsolutePath();
|
||||||
|
|
Loading…
Reference in New Issue