org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.nifi.toolkit.tls.standalone;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.apache.commons.cli.CommandLine;
import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine;
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition;
import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig;
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory;
import org.apache.nifi.toolkit.tls.util.PasswordUtil;
import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Command line parser for a StandaloneConfig object and a main entry point to invoke the parser and run the standalone generator
 */
public class TlsToolkitStandaloneCommandLine extends BaseCommandLine {
    public static final String OUTPUT_DIRECTORY_ARG = "outputDirectory";
    public static final String NIFI_PROPERTIES_FILE_ARG = "nifiPropertiesFile";
    public static final String KEY_STORE_PASSWORD_ARG = "keyStorePassword";
    public static final String TRUST_STORE_PASSWORD_ARG = "trustStorePassword";
    public static final String KEY_PASSWORD_ARG = "keyPassword";
    public static final String HOSTNAMES_ARG = "hostnames";
    public static final String OVERWRITE_ARG = "isOverwrite";
    public static final String CLIENT_CERT_DN_ARG = "clientCertDn";
    public static final String CLIENT_CERT_PASSWORD_ARG = "clientCertPassword";
    public static final String GLOBAL_PORT_SEQUENCE_ARG = "globalPortSequence";
    public static final String NIFI_DN_PREFIX_ARG = "nifiDnPrefix";
    public static final String NIFI_DN_SUFFIX_ARG = "nifiDnSuffix";
    public static final String SUBJECT_ALTERNATIVE_NAMES = "subjectAlternativeNames";

    public static final String DEFAULT_OUTPUT_DIRECTORY = calculateDefaultOutputDirectory(Paths.get("."));

    protected static String calculateDefaultOutputDirectory(Path currentPath) {
        Path currentAbsolutePath = currentPath.toAbsolutePath();
        Path parent = currentAbsolutePath.getParent();
        if (parent == currentAbsolutePath.getRoot()) {
            return parent.toString();
        } else {
            Path currentNormalizedPath = currentAbsolutePath.normalize();
            return "../" + currentNormalizedPath.getFileName().toString();
        }
    }

    public static final String DESCRIPTION = "Creates certificates and config files for nifi cluster.";

    private final Logger logger = LoggerFactory.getLogger(TlsToolkitStandaloneCommandLine.class);

    private final PasswordUtil passwordUtil;
    private File baseDir;
    private List<InstanceDefinition> instanceDefinitions;
    private NiFiPropertiesWriterFactory niFiPropertiesWriterFactory;
    private List<String> clientDns;
    private List<String> clientPasswords;
    private boolean clientPasswordsGenerated;
    private boolean overwrite;
    private String dnPrefix;
    private String dnSuffix;
    private String domainAlternativeNames;

    public TlsToolkitStandaloneCommandLine() {
        this(new PasswordUtil());
    }

    protected TlsToolkitStandaloneCommandLine(PasswordUtil passwordUtil) {
        super(DESCRIPTION);
        this.passwordUtil = passwordUtil;
        addOptionWithArg("o", OUTPUT_DIRECTORY_ARG, "The directory to output keystores, truststore, config files.",
                DEFAULT_OUTPUT_DIRECTORY);
        addOptionWithArg("n", HOSTNAMES_ARG, "Comma separated list of hostnames.");
        addOptionWithArg("f", NIFI_PROPERTIES_FILE_ARG,
                "Base nifi.properties file to update. (Embedded file identical to the one in a default NiFi install will be used if not specified.)");
        addOptionWithArg("S", KEY_STORE_PASSWORD_ARG,
                "Keystore password to use.  Must either be one value or one for each host. (autogenerate if not specified)");
        addOptionWithArg("K", KEY_PASSWORD_ARG,
                "Key password to use.  Must either be one value or one for each host. (autogenerate if not specified)");
        addOptionWithArg("P", TRUST_STORE_PASSWORD_ARG,
                "Keystore password to use.  Must either be one value or one for each host. (autogenerate if not specified)");
        addOptionWithArg("C", CLIENT_CERT_DN_ARG,
                "Generate client certificate suitable for use in browser with specified DN. (Can be specified multiple times.)");
        addOptionWithArg("B", CLIENT_CERT_PASSWORD_ARG,
                "Password for client certificate.  Must either be one value or one for each client DN. (autogenerate if not specified)");
        addOptionWithArg("G", GLOBAL_PORT_SEQUENCE_ARG,
                "Use sequential ports that are calculated for all hosts according to the provided hostname expressions. "
                        + "(Can be specified multiple times, MUST BE SAME FROM RUN TO RUN.)");
        addOptionWithArg(null, SUBJECT_ALTERNATIVE_NAMES,
                "Comma-separated list of domains to use as Subject Alternative Names in the certificate");
        addOptionWithArg(null, NIFI_DN_PREFIX_ARG, "String to prepend to hostname(s) when determining DN.",
                TlsConfig.DEFAULT_DN_PREFIX);
        addOptionWithArg(null, NIFI_DN_SUFFIX_ARG, "String to append to hostname(s) when determining DN.",
                TlsConfig.DEFAULT_DN_SUFFIX);
        addOptionNoArg("O", OVERWRITE_ARG, "Overwrite existing host output.");
    }

    public static void main(String[] args) {
        TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine();
        try {
            tlsToolkitStandaloneCommandLine.parse(args);
        } catch (CommandLineParseException e) {
            System.exit(e.getExitCode().ordinal());
        }
        try {
            new TlsToolkitStandalone()
                    .createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig());
        } catch (Exception e) {
            tlsToolkitStandaloneCommandLine
                    .printUsage("Error creating generating tls configuration. (" + e.getMessage() + ")");
            System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal());
        }
        System.exit(ExitCode.SUCCESS.ordinal());
    }

    @Override
    protected CommandLine doParse(String... args) throws CommandLineParseException {
        CommandLine commandLine = super.doParse(args);
        String outputDirectory = commandLine.getOptionValue(OUTPUT_DIRECTORY_ARG, DEFAULT_OUTPUT_DIRECTORY);
        baseDir = new File(outputDirectory);

        dnPrefix = commandLine.getOptionValue(NIFI_DN_PREFIX_ARG, TlsConfig.DEFAULT_DN_PREFIX);
        dnSuffix = commandLine.getOptionValue(NIFI_DN_SUFFIX_ARG, TlsConfig.DEFAULT_DN_SUFFIX);
        domainAlternativeNames = commandLine.getOptionValue(SUBJECT_ALTERNATIVE_NAMES);

        Stream<String> globalOrderExpressions = null;
        if (commandLine.hasOption(GLOBAL_PORT_SEQUENCE_ARG)) {
            globalOrderExpressions = Arrays.stream(commandLine.getOptionValues(GLOBAL_PORT_SEQUENCE_ARG))
                    .flatMap(s -> Arrays.stream(s.split(","))).map(String::trim);
        }

        if (commandLine.hasOption(HOSTNAMES_ARG)) {
            instanceDefinitions = Collections.unmodifiableList(InstanceDefinition.createDefinitions(
                    globalOrderExpressions,
                    Arrays.stream(commandLine.getOptionValues(HOSTNAMES_ARG))
                            .flatMap(s -> Arrays.stream(s.split(",")).map(String::trim)),
                    parsePasswordSupplier(commandLine, KEY_STORE_PASSWORD_ARG, passwordUtil.passwordSupplier()),
                    parsePasswordSupplier(commandLine, KEY_PASSWORD_ARG,
                            commandLine.hasOption(DIFFERENT_KEY_AND_KEYSTORE_PASSWORDS_ARG)
                                    ? passwordUtil.passwordSupplier()
                                    : null),
                    parsePasswordSupplier(commandLine, TRUST_STORE_PASSWORD_ARG, passwordUtil.passwordSupplier())));
        } else {
            instanceDefinitions = Collections.emptyList();
        }

        String[] clientDnValues = commandLine.getOptionValues(CLIENT_CERT_DN_ARG);
        if (clientDnValues != null) {
            clientDns = Collections.unmodifiableList(Arrays.stream(clientDnValues).collect(Collectors.toList()));
        } else {
            clientDns = Collections.emptyList();
        }

        clientPasswords = Collections.unmodifiableList(
                getPasswords(CLIENT_CERT_PASSWORD_ARG, commandLine, clientDns.size(), CLIENT_CERT_DN_ARG));
        clientPasswordsGenerated = commandLine.getOptionValues(CLIENT_CERT_PASSWORD_ARG) == null;
        overwrite = commandLine.hasOption(OVERWRITE_ARG);

        String nifiPropertiesFile = commandLine.getOptionValue(NIFI_PROPERTIES_FILE_ARG, "");
        try {
            if (StringUtils.isEmpty(nifiPropertiesFile)) {
                logger.info("No " + NIFI_PROPERTIES_FILE_ARG + " specified, using embedded one.");
                niFiPropertiesWriterFactory = new NiFiPropertiesWriterFactory();
            } else {
                logger.info("Using " + nifiPropertiesFile + " as template.");
                niFiPropertiesWriterFactory = new NiFiPropertiesWriterFactory(
                        new FileInputStream(nifiPropertiesFile));
            }
        } catch (IOException e) {
            printUsageAndThrow(
                    "Unable to read nifi.properties from "
                            + (StringUtils.isEmpty(nifiPropertiesFile) ? "classpath" : nifiPropertiesFile),
                    ExitCode.ERROR_READING_NIFI_PROPERTIES);
        }
        return commandLine;
    }

    private List<String> getPasswords(String arg, CommandLine commandLine, int num, String numArg)
            throws CommandLineParseException {
        String[] optionValues = commandLine.getOptionValues(arg);
        if (optionValues == null) {
            return IntStream.range(0, num).mapToObj(operand -> passwordUtil.generatePassword())
                    .collect(Collectors.toList());
        }
        if (optionValues.length == 1) {
            return IntStream.range(0, num).mapToObj(value -> optionValues[0]).collect(Collectors.toList());
        } else if (optionValues.length == num) {
            return Arrays.stream(optionValues).collect(Collectors.toList());
        }
        return printUsageAndThrow(
                "Expected either 1 value or " + num + " (the number of " + numArg + ") values for " + arg,
                ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS);
    }

    private Supplier<String> parsePasswordSupplier(CommandLine commandLine, String option,
            Supplier<String> defaultSupplier) {
        if (commandLine.hasOption(option)) {
            String[] values = commandLine.getOptionValues(option);
            if (values.length == 1) {
                return PasswordUtil.passwordSupplier(values[0]);
            } else {
                return PasswordUtil.passwordSupplier("Provided " + option + " exhausted, please don't specify "
                        + option
                        + ", specify one value to be used for all NiFi instances, or specify one value for each NiFi instance.",
                        values);
            }
        } else {
            return defaultSupplier;
        }
    }

    /**
     * Creates the StandaloneConfig for use in running TlsToolkitStandalone
     *
     * @return the StandaloneConfig for use in running TlsToolkitStandalone
     */
    public StandaloneConfig createConfig() {
        StandaloneConfig standaloneConfig = new StandaloneConfig();

        standaloneConfig.setBaseDir(baseDir);
        standaloneConfig.setNiFiPropertiesWriterFactory(niFiPropertiesWriterFactory);
        standaloneConfig.setInstanceDefinitions(instanceDefinitions);
        standaloneConfig.setOverwrite(overwrite);
        standaloneConfig.setClientDns(clientDns);
        standaloneConfig.setClientPasswords(clientPasswords);
        standaloneConfig.setClientPasswordsGenerated(clientPasswordsGenerated);

        standaloneConfig.setCaHostname(getCertificateAuthorityHostname());
        standaloneConfig.setKeyStore("nifi-ca-" + KEYSTORE + getKeyStoreType().toLowerCase());
        standaloneConfig.setKeyStoreType(getKeyStoreType());
        standaloneConfig.setKeySize(getKeySize());
        standaloneConfig.setKeyPairAlgorithm(getKeyAlgorithm());
        standaloneConfig.setSigningAlgorithm(getSigningAlgorithm());
        standaloneConfig.setDays(getDays());
        standaloneConfig.setDnPrefix(dnPrefix);
        standaloneConfig.setDnSuffix(dnSuffix);
        standaloneConfig.setDomainAlternativeNames(domainAlternativeNames);
        standaloneConfig.initDefaults();

        return standaloneConfig;
    }
}