Tie zip output to command line options in certutil (elastic/x-pack-elasticsearch#4354)

Previously "certutil" would generate a zip file if there were multiple
certificates.
However, this means that if the user specified "-multiple" or "-in"
then the output format will vary based on whether they entered
multiple instance names (-multiple) or whether the input file
contained multiple instance records (-in).
It is better if the output format is predictable based on the command
line options, so this change forces zip output whenever any of the
following command line options are supplied:
    -pem
    -keep-ca-key
    -multiple
    -in

Original commit: elastic/x-pack-elasticsearch@344baa5f17
This commit is contained in:
Tim Vernum 2018-04-17 11:05:30 +10:00 committed by GitHub
parent 5eac9fd1a4
commit eccf3899a2
3 changed files with 54 additions and 16 deletions

View File

@ -82,8 +82,9 @@ generating a CA, see the <<certutil-ca,CA mode of this command>>.
By default, the `cert` mode produces a single PKCS#12 output file which holds
the instance certificate, the instance private key, and the CA certificate. If
you specify the `--pem` parameter, the command generates PEM formatted
certificates and keys and packages them into a zip file. Likewise if you chose
to generate output for multiple instances, the command produces a zip file.
certificates and keys and packages them into a zip file.
If you specify the `--keep-ca-key`, `--multiple` or `--in` parameters,
the command produces a zip file containing the generated certificates and keys.
[float]
[[certutil-csr]]

View File

@ -183,7 +183,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
OptionSpec<String> caDnSpec;
OptionSpec<Void> keepCaKeySpec;
OptionSpec<Void> multipleNodes;
OptionSpec<Void> multipleNodesSpec;
OptionSpec<String> nameSpec;
OptionSpec<String> dnsNamesSpec;
OptionSpec<String> ipAddressesSpec;
@ -236,10 +236,10 @@ public class CertificateTool extends LoggingAwareMultiCommand {
}
final void acceptInstanceDetails() {
multipleNodes = parser.accepts("multiple", "generate files for multiple instances");
nameSpec = parser.accepts("name", "name of the generated certificate").availableUnless(multipleNodes).withRequiredArg();
dnsNamesSpec = parser.accepts("dns", "comma separated DNS names").availableUnless(multipleNodes).withRequiredArg();
ipAddressesSpec = parser.accepts("ip", "comma separated IP addresses").availableUnless(multipleNodes).withRequiredArg();
multipleNodesSpec = parser.accepts("multiple", "generate files for multiple instances");
nameSpec = parser.accepts("name", "name of the generated certificate").availableUnless(multipleNodesSpec).withRequiredArg();
dnsNamesSpec = parser.accepts("dns", "comma separated DNS names").availableUnless(multipleNodesSpec).withRequiredArg();
ipAddressesSpec = parser.accepts("ip", "comma separated IP addresses").availableUnless(multipleNodesSpec).withRequiredArg();
}
final void acceptInputFile() {
@ -394,7 +394,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
if (input != null) {
return parseAndValidateFile(terminal, input.toAbsolutePath());
}
if (options.has(multipleNodes)) {
if (options.has(multipleNodesSpec)) {
return readMultipleCertificateInformation(terminal);
} else {
final Function<String, Stream<? extends String>> splitByComma = v -> Arrays.stream(Strings.splitStringByCommaToArray(v));
@ -669,18 +669,19 @@ public class CertificateTool extends LoggingAwareMultiCommand {
terminal.println(" * The private key for the instance certificate");
terminal.println(" * The CA certificate");
terminal.println("");
terminal.println("If you elect to generate PEM format certificates (the -pem option), then the output will");
terminal.println("be a zip file containing individual files for the instance certificate, the key and the CA certificate");
terminal.println("");
terminal.println("If you elect to generate multiple instances certificates, the output will be a zip file");
terminal.println("containing all the generated certificates");
terminal.println("If you specify any of the following options:");
terminal.println(" * -pem (PEM formatted output)");
terminal.println(" * -keep-ca-key (retain generated CA key)");
terminal.println(" * -multiple (generate multiple certificates)");
terminal.println(" * -in (generate certificates from an input file)");
terminal.println("then the output will be be a zip file containing individual certificate/key files");
terminal.println("");
CAInfo caInfo = getCAInfo(terminal, options, env);
Collection<CertificateInformation> certInfo = getCertificateInformationList(terminal, options);
final boolean keepCaKey = keepCaKey(options);
final boolean usePemFormat = usePemFormat(options);
final boolean writeZipFile = certInfo.size() > 1 || keepCaKey || usePemFormat;
final boolean writeZipFile = options.has(multipleNodesSpec) || options.has(inputFileSpec) || keepCaKey || usePemFormat;
final String outputName;
if (writeZipFile) {

View File

@ -9,7 +9,6 @@ import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.elasticsearch.core.internal.io.IOUtils;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
@ -34,6 +33,7 @@ import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.network.NetworkAddress;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.test.ESTestCase;
@ -53,7 +53,6 @@ import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.security.auth.x500.X500Principal;
import java.io.IOException;
import java.io.Reader;
import java.net.InetAddress;
@ -83,6 +82,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -725,6 +725,42 @@ public class CertificateToolTests extends ESTestCase {
checkTrust(node2KeyStore, new char[0], node1TrustStore, true);
}
public void testZipOutputFromCommandLineOptions() throws Exception {
final Path tempDir = initTempDir();
final MockTerminal terminal = new MockTerminal();
Environment env = TestEnvironment.newEnvironment(Settings.builder().put("path.home", tempDir).build());
final Path zip = tempDir.resolve("pem.zip");
final AtomicBoolean isZip = new AtomicBoolean(false);
final GenerateCertificateCommand genCommand = new PathAwareGenerateCertificateCommand(null, zip) {
@Override
void generateAndWriteSignedCertificates(Path output, boolean writeZipFile, OptionSet options,
Collection<CertificateInformation> certs, CAInfo caInfo,
Terminal terminal) throws Exception {
isZip.set(writeZipFile);
// do nothing, all we care about is the "zip" flag
}
@Override
Collection<CertificateInformation> getCertificateInformationList(Terminal terminal, OptionSet options) throws Exception {
// Regardless of the commandline options, just work with a single cert
return Collections.singleton(new CertificateInformation("node", "node",
Collections.emptyList(), Collections.emptyList(), Collections.emptyList()));
}
};
final String optionThatTriggersZip = randomFrom("-pem", "-keep-ca-key", "-multiple", "-in=input.yml");
final OptionSet genOptions = genCommand.getParser().parse(
"-out", "<zip>",
optionThatTriggersZip
);
genCommand.execute(terminal, genOptions, env);
assertThat("For command line option " + optionThatTriggersZip, isZip.get(), equalTo(true));
}
private int getKeySize(Key node1Key) {
assertThat(node1Key, instanceOf(RSAKey.class));
return ((RSAKey) node1Key).getModulus().bitLength();