Github user kevdoran commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2376#discussion_r159974781
  
    --- Diff: 
nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
 ---
    @@ -0,0 +1,383 @@
    +/*
    + * 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.encryptconfig
    +
    +import org.apache.commons.cli.HelpFormatter
    +import org.apache.commons.cli.Options
    +import org.apache.http.annotation.Experimental
    +import org.apache.nifi.properties.AESSensitivePropertyProvider
    +import org.apache.nifi.properties.SensitivePropertyProvider
    +import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
    +import 
org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryAuthorizersXmlEncryptor
    +import 
org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryIdentityProvidersXmlEncryptor
    +import 
org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryPropertiesEncryptor
    +import org.apache.nifi.toolkit.encryptconfig.util.ToolUtilities
    +import org.apache.nifi.util.console.TextDevices
    +import org.slf4j.Logger
    +import org.slf4j.LoggerFactory
    +
    +@Experimental
    +class NiFiRegistryMode implements ToolMode {
    +
    +    private static final Logger logger = 
LoggerFactory.getLogger(NiFiRegistryMode.class)
    +
    +    CliBuilder cli
    +
    +    NiFiRegistryMode() {
    +        cli = cliBuilder()
    +    }
    +
    +//    private void printUsage(String message = "") {
    +//        if (message) {
    +//            System.out.println(message)
    +//            System.out.println()
    +//        }
    +//        cli.usage()
    +//    }
    +
    +    @Override
    +    void run(String[] args) {
    +        logger.warn("The NiFi Registry capabilities of this tool is still 
considered experimental. The results should be manually verified.")
    +        try {
    +
    +            def options = cli.parse(args)
    +
    +            if (!options || options.h) {
    +                EncryptConfigMain.printUsageAndExit("", 
EncryptConfigMain.EXIT_STATUS_OTHER)
    +            }
    +
    +            EncryptConfigLogger.configureLogger(options.v)
    +
    +            Configuration config = new Configuration(options)
    +            run(config)
    +
    +        } catch (Exception e) {
    +            logger.error("Encountered an error: ${e.getMessage()}")
    +            logger.debug("", e) // stack trace only when verbose enabled
    +            EncryptConfigMain.printUsageAndExit(e.getMessage(), 
EncryptConfigMain.EXIT_STATUS_FAILURE)
    +        }
    +    }
    +
    +    void run(Configuration config) throws Exception {
    +
    +        if (config.usingPassword) {
    +            logger.info("Using encryption key derived from password.")
    +        } else if (config.usingRawKeyHex) {
    +            logger.info("Using encryption key provided.")
    +        } else if (config.usingBootstrapKey) {
    +            logger.info("Using encryption key from input bootstrap.conf.")
    +        }
    +
    +        logger.debug("(src)  bootstrap.conf:         
${config.inputBootstrapPath}")
    +        logger.debug("(dest) bootstrap.conf:         
${config.outputBootstrapPath}")
    +        logger.debug("(src)  nifi.properties:        
${config.inputNiFiRegistryPropertiesPath}")
    +        logger.debug("(dest) nifi.properties:        
${config.outputNiFiRegistryPropertiesPath}")
    +        logger.debug("(src)  identity-providers.xml: 
${config.inputIdentityProvidersPath}")
    +        logger.debug("(dest) identity-providers.xml: 
${config.outputIdentityProvidersPath}")
    +        logger.debug("(src)  authorizers.xml:        
${config.inputAuthorizersPath}")
    +        logger.debug("(dest) authorizers.xml:        
${config.outputAuthorizersPath}")
    +
    +        Properties niFiRegistryProperties = null
    +        if (config.handlingNiFiRegistryProperties) {
    +            try {
    +                niFiRegistryProperties = 
config.propertiesEncryptor.loadFile(config.inputNiFiRegistryPropertiesPath)
    +                // if properties are not protected, then the call to 
decrypt is a no-op
    +                niFiRegistryProperties = 
config.propertiesEncryptor.decrypt(niFiRegistryProperties)
    +                niFiRegistryProperties = 
config.propertiesEncryptor.encrypt(niFiRegistryProperties)
    +            } catch (Exception e) {
    +                throw new RuntimeException("Encountered error trying to 
load and encrypt NiFi Registry Properties in 
${config.inputNiFiRegistryPropertiesPath}: ${e.getMessage()}", e)
    +            }
    +        }
    +
    +        String identityProvidersXml = null
    +        if (config.handlingIdentityProviders) {
    +            try {
    +                identityProvidersXml = 
config.identityProvidersXmlEncryptor.loadXmlFile(config.inputIdentityProvidersPath)
    +                // if xml is not protected, then the call to decrypt is a 
no-op
    +                identityProvidersXml = 
config.identityProvidersXmlEncryptor.decrypt(identityProvidersXml)
    +                identityProvidersXml = 
config.identityProvidersXmlEncryptor.encrypt(identityProvidersXml)
    +            } catch (Exception e) {
    +                throw new RuntimeException("Encountered error trying to 
load and encrypt Identity Providers XML in 
${config.inputIdentityProvidersPath}: ${e.getMessage()}", e)
    +            }
    +        }
    +
    +        String authorizersXml = null
    +        if (config.handlingAuthorizers) {
    +            try {
    +                authorizersXml = 
config.authorizersXmlEncryptor.loadXmlFile(config.inputAuthorizersPath)
    +                // if xml is not protected, then the call to decrypt is a 
no-op
    +                authorizersXml = 
config.authorizersXmlEncryptor.decrypt(authorizersXml)
    +                authorizersXml = 
config.authorizersXmlEncryptor.encrypt(authorizersXml)
    +            } catch (Exception e) {
    +                throw new RuntimeException("Encountered error trying to 
load and encrypt Authorizers XML in ${config.inputAuthorizersPath}: 
${e.getMessage()}", e)
    +            }
    +        }
    +
    +        try {
    +            // Do this as part of a transaction?
    +            synchronized (this) {
    +
    +                if (config.writingKeyToBootstrap) {
    +                    
BootstrapUtil.writeKeyToBootstrapFile(config.encryptionKey, 
BootstrapUtil.REGISTRY_BOOTSTRAP_KEY_PROPERTY, config.outputBootstrapPath, 
config.inputBootstrapPath)
    +                    logger.info("Updated bootstrap config file with master 
key: ${config.outputBootstrapPath}")
    +                }
    +
    +                if (config.handlingNiFiRegistryProperties) {
    +                    
config.propertiesEncryptor.write(niFiRegistryProperties, 
config.outputNiFiRegistryPropertiesPath, config.inputNiFiRegistryPropertiesPath)
    +                    logger.info("Updated NiFi Registry Properties file 
with protected values: ${config.outputNiFiRegistryPropertiesPath}")
    +                }
    +                if (config.handlingIdentityProviders) {
    +                    
config.identityProvidersXmlEncryptor.writeXmlFile(identityProvidersXml, 
config.outputIdentityProvidersPath, config.inputIdentityProvidersPath)
    +                    logger.info("Updated Identity Providers XML file with 
protected values: ${config.outputIdentityProvidersPath}")
    +                }
    +                if (config.handlingAuthorizers) {
    +                    
config.authorizersXmlEncryptor.writeXmlFile(authorizersXml, 
config.outputAuthorizersPath, config.inputAuthorizersPath)
    +                    logger.info("Updated Authorizers XML file with 
protected values: ${config.outputAuthorizersPath}")
    +                }
    +            }
    +        } catch (Exception e) {
    +            throw new RuntimeException("Encountered error while writing 
the output files: ${e.getMessage()}", e)
    +        }
    +    }
    +
    +    static Options getCliOptions() {
    +        return cliBuilder().options
    +    }
    +
    +    static CliBuilder cliBuilder() {
    +
    +        String usage = "${NiFiRegistryMode.class.getCanonicalName()} 
[options]"
    +
    +        int formatWidth = EncryptConfigMain.HELP_FORMAT_WIDTH
    +        HelpFormatter formatter = new HelpFormatter()
    +        formatter.setWidth(formatWidth)
    +        formatter.setOptionComparator(null) // preserve order of options 
below in help text
    +
    +        CliBuilder cli = new CliBuilder(
    +                usage: usage,
    +                width: formatWidth,
    +                formatter: formatter)
    +
    +        cli.h(longOpt: 'help', 'Show usage information (this message)')
    +        cli.v(longOpt: 'verbose', 'Sets verbose mode (default false)')
    +
    +        // Options for the new password or key
    +        cli.p(longOpt: 'password',
    +                args: 1,
    +                argName: 'password',
    +                optionalArg: true,
    +                'Protect the files using a password-derived key. If an 
argument is not provided to this flag, interactive mode will be triggered to 
prompt the user to enter the password.')
    +        cli.k(longOpt: 'key',
    +                args: 1,
    +                argName: 'keyhex',
    +                optionalArg: true,
    +                'Protect the files using a raw hexadecimal key. If an 
argument is not provided to this flag, interactive mode will be triggered to 
prompt the user to enter the key.')
    +
    +        // Options for the old password or key, if running the tool to 
migrate keys
    +        cli._(longOpt: 'oldPassword',
    +                args: 1,
    +                argName: 'password',
    +                'If the input files are already protected using a 
password-derived key, this specifies the old password so that the files can be 
unprotected before re-protecting.')
    +        cli._(longOpt: 'oldKey',
    +                args: 1,
    +                argName: 'keyhex',
    +                'If the input files are already protected using a key, 
this specifies the raw hexadecimal key so that the files can be unprotected 
before re-protecting.')
    +
    +        // Options for output bootstrap.conf file
    +        cli.b(longOpt: 'bootstrapConf',
    +                args: 1,
    +                argName: 'file',
    +                'The bootstrap.conf file containing no master key or an 
existing master key. If a new password or key is specified (using -p or -k) and 
no output bootstrap.conf file is specified, then this file will be overwritten 
to persist the new master key.')
    +        cli.B(longOpt: 'outputBootstrapConf',
    +                args: 1,
    +                argName: 'file',
    +                'The destination bootstrap.conf file to persist master 
key. If specified, the input bootstrap.conf will not be modified.')
    +
    +        // Options for input/output nifi-registry.properties files
    +        cli.r(longOpt: 'nifiRegistryProperties',
    +                args: 1,
    +                argName: 'file',
    +                'The nifi-registry.properties file containing unprotected 
config values, overwritten if no output file specified.')
    +        cli.R(longOpt: 'outputNifiRegistryProperties',
    +                args: 1,
    +                argName: 'file',
    +                'The destination nifi-registry.properties file containing 
protected config values.')
    +
    +        // Options for input/output authorizers.xml files
    +        cli.a(longOpt: 'authorizersXml',
    +                args: 1,
    +                argName: 'file',
    +                'The authorizers.xml file containing unprotected config 
values, overwritten if no output file specified.')
    +        cli.A(longOpt: 'outputAuthorizersXml',
    +                args: 1,
    +                argName: 'file',
    +                'The destination authorizers.xml file containing protected 
config values.')
    +
    +        // Options for input/output identity-providers.xml files
    +        cli.i(longOpt: 'identityProvidersXml',
    +                args: 1,
    +                argName: 'file',
    +                'The identity-providers.xml file containing unprotected 
config values, overwritten if no output file specified.')
    +        cli.I(longOpt: 'outputIdentityProvidersXml',
    +                args: 1,
    +                argName: 'file',
    +                'The destination identity-providers.xml file containing 
protected config values.')
    +
    +        return cli
    +
    +    }
    +
    +    class Configuration {
    +
    +        OptionAccessor rawOptions
    +
    +        boolean usingRawKeyHex
    +        boolean usingPassword
    +        boolean usingBootstrapKey
    +
    +        String encryptionKey
    +        String decryptionKey
    +
    +        SensitivePropertyProvider encryptionProvider
    +        SensitivePropertyProvider decryptionProvider
    +
    +        boolean writingKeyToBootstrap = false
    +        String inputBootstrapPath
    +        String outputBootstrapPath
    +
    +        boolean handlingNiFiRegistryProperties = false
    +        String inputNiFiRegistryPropertiesPath
    +        String outputNiFiRegistryPropertiesPath
    +        NiFiRegistryPropertiesEncryptor propertiesEncryptor
    +
    +        boolean handlingIdentityProviders = false
    +        String inputIdentityProvidersPath
    +        String outputIdentityProvidersPath
    +        NiFiRegistryIdentityProvidersXmlEncryptor 
identityProvidersXmlEncryptor
    +
    +        boolean handlingAuthorizers = false
    +        String inputAuthorizersPath
    +        String outputAuthorizersPath
    +        NiFiRegistryAuthorizersXmlEncryptor authorizersXmlEncryptor
    +
    +        Configuration() {
    +        }
    +
    +        Configuration(OptionAccessor options) {
    +            this.rawOptions = options
    +
    +            validateOptions()
    +
    +            // Set input bootstrap.conf path
    +            inputBootstrapPath = rawOptions.b
    +
    +            // Determine key for encryption (required)
    +            determineEncryptionKey()
    +            if (!encryptionKey) {
    +                throw new RuntimeException("Failed to configure tool, 
could not determine encryption key. Must provide -p, -k, or -b. If using -b, 
bootstrap.conf argument must already contain master key.")
    +            }
    +            encryptionProvider = new 
AESSensitivePropertyProvider(encryptionKey)
    +
    +            // Determine key for decryption (if migrating)
    +            determineDecryptionKey()
    +            if (!decryptionKey) {
    +                logger.debug("No decryption key specified via options, so 
if any input files require decryption prior to re-encryption (i.e., migration), 
this tool will fail.")
    +            }
    +            decryptionProvider = decryptionKey ? new 
AESSensitivePropertyProvider(decryptionKey) : null
    +
    +            writingKeyToBootstrap = (usingPassword || usingRawKeyHex || 
rawOptions.B)
    +            if (writingKeyToBootstrap) {
    +                outputBootstrapPath = rawOptions.B ? rawOptions.B : 
inputBootstrapPath
    --- End diff --
    
    Oh cool, I didn't know about that Groovy idiom - thanks!
    
    - [ ] (optional, time permitting) change to using Elvis operator where 
applicable


---

Reply via email to