You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by al...@apache.org on 2018/01/08 19:20:07 UTC

[4/4] nifi git commit: NIFI-4708 Add Registry support to encrypt-config. Adds support for NiFI Registry config files to the encrypt-config tool in NiFi Toolkit. Also adds decryption capability to encrypt-config tool.

NIFI-4708 Add Registry support to encrypt-config.
Adds support for NiFI Registry config files to the encrypt-config tool
in NiFi Toolkit.
Also adds decryption capability to encrypt-config tool.

This closes #2376.

Signed-off-by: Andy LoPresto <al...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/a8817e02
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/a8817e02
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/a8817e02

Branch: refs/heads/master
Commit: a8817e023805499491f9fc62495208d198de84f0
Parents: b611774
Author: Kevin Doran <kd...@gmail.com>
Authored: Sat Dec 30 08:54:18 2017 -0500
Committer: Andy LoPresto <al...@apache.org>
Committed: Mon Jan 8 11:17:21 2018 -0800

----------------------------------------------------------------------
 .../AESSensitivePropertyProvider.java           |   4 +-
 nifi-toolkit/nifi-toolkit-assembly/NOTICE       |  10 +
 .../src/main/resources/bin/encrypt-config.bat   |   2 +-
 .../src/main/resources/bin/encrypt-config.sh    |   2 +-
 .../nifi-toolkit-encrypt-config/pom.xml         |  22 ++
 .../nifi/properties/ConfigEncryptionTool.groovy |  47 ++-
 .../toolkit/encryptconfig/Configuration.groovy  |  29 ++
 .../toolkit/encryptconfig/DecryptMode.groovy    | 327 ++++++++++++++++
 .../encryptconfig/EncryptConfigLogger.groovy    |  83 ++++
 .../encryptconfig/EncryptConfigMain.groovy      | 138 +++++++
 .../toolkit/encryptconfig/LegacyMode.groovy     |  32 ++
 .../NiFiRegistryDecryptMode.groovy              | 124 ++++++
 .../encryptconfig/NiFiRegistryMode.groovy       | 382 +++++++++++++++++++
 .../nifi/toolkit/encryptconfig/ToolMode.groovy  |  23 ++
 .../encryptconfig/util/BootstrapUtil.groovy     | 132 +++++++
 .../util/NiFiPropertiesEncryptor.groovy         |  54 +++
 .../NiFiRegistryAuthorizersXmlEncryptor.groovy  | 103 +++++
 ...RegistryIdentityProvidersXmlEncryptor.groovy | 102 +++++
 .../util/NiFiRegistryPropertiesEncryptor.groovy |  65 ++++
 .../util/PropertiesEncryptor.groovy             | 269 +++++++++++++
 .../encryptconfig/util/ToolUtilities.groovy     | 164 ++++++++
 .../encryptconfig/util/XmlEncryptor.groovy      | 200 ++++++++++
 .../src/main/resources/log4j.properties         |   3 +-
 .../encryptconfig/EncryptConfigMainTest.groovy  | 285 ++++++++++++++
 .../NiFiRegistryDecryptModeSpec.groovy          | 117 ++++++
 .../encryptconfig/NiFiRegistryModeSpec.groovy   | 331 ++++++++++++++++
 .../nifi/toolkit/encryptconfig/TestUtil.groovy  | 376 ++++++++++++++++++
 .../encryptconfig/util/BootstrapUtilSpec.groovy | 113 ++++++
 .../nifi-registry/authorizers-commented.xml     | 242 ++++++++++++
 .../nifi-registry/authorizers-empty.xml         | 240 ++++++++++++
 .../authorizers-populated-unprotected.xml       | 246 ++++++++++++
 .../nifi-registry/bootstrap_default.conf        |  48 +++
 .../bootstrap_with_empty_master_key.conf        |  48 +++
 .../bootstrap_with_master_key_128.conf          |  48 +++
 ...strap_with_master_key_from_password_128.conf |  48 +++
 .../bootstrap_without_master_key.conf           |  45 +++
 .../identity-providers-commented.xml            | 106 +++++
 .../nifi-registry/identity-providers-empty.xml  | 104 +++++
 ...identity-providers-populated-unprotected.xml |  97 +++++
 .../nifi-registry-commented.properties          |  31 ++
 .../nifi-registry-empty.properties              |  31 ++
 ...istry-populated-protected-key-128.properties |  50 +++
 ...istry-populated-protected-key-256.properties |  50 +++
 ...-populated-protected-password-256.properties |  52 +++
 ...fi-registry-populated-unprotected.properties |  45 +++
 45 files changed, 5044 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
index 1df398e..062e352 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
@@ -175,7 +175,7 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider {
 
             byte[] plainBytes = unprotectedValue.getBytes(StandardCharsets.UTF_8);
             byte[] cipherBytes = cipher.doFinal(plainBytes);
-            logger.info(getName() + " encrypted a sensitive value successfully");
+            logger.debug(getName() + " encrypted a sensitive value successfully");
             return base64Encode(iv) + DELIMITER + base64Encode(cipherBytes);
             // return Base64.toBase64String(iv) + DELIMITER + Base64.toBase64String(cipherBytes);
         } catch (BadPaddingException | IllegalBlockSizeException | EncoderException | InvalidAlgorithmParameterException | InvalidKeyException e) {
@@ -238,7 +238,7 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider {
 
             cipher.init(Cipher.DECRYPT_MODE, this.key, new IvParameterSpec(iv));
             byte[] plainBytes = cipher.doFinal(cipherBytes);
-            logger.info(getName() + " decrypted a sensitive value successfully");
+            logger.debug(getName() + " decrypted a sensitive value successfully");
             return new String(plainBytes, StandardCharsets.UTF_8);
         } catch (BadPaddingException | IllegalBlockSizeException | DecoderException | InvalidAlgorithmParameterException | InvalidKeyException e) {
             final String msg = "Error decrypting a protected value";

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-assembly/NOTICE
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-assembly/NOTICE b/nifi-toolkit/nifi-toolkit-assembly/NOTICE
index ff89f5e..dd99d2d 100644
--- a/nifi-toolkit/nifi-toolkit-assembly/NOTICE
+++ b/nifi-toolkit/nifi-toolkit-assembly/NOTICE
@@ -15,6 +15,11 @@ The following binary components are provided under the Apache Software License v
       Apache NiFi
       Copyright 2014-2016 The Apache Software Foundation
 
+  (ASLv2) Apache Commons BeanUtils
+    The following NOTICE information applies:
+      Apache Commons BeanUtils
+      Copyright 2000-2016 The Apache Software Foundation
+
   (ASLv2) Apache Commons CLI
     The following NOTICE information applies:
       Apache Commons CLI
@@ -37,6 +42,11 @@ The following binary components are provided under the Apache Software License v
       Original source copyright:
       Copyright (c) 2008 Alexander Beider & Stephen P. Morse.
 
+  (ASLv2) Apache Commons Configuration
+    The following NOTICE information applies:
+      Apache Commons Configuration
+      Copyright 2001-2017 The Apache Software Foundation
+
   (ASLv2) Apache Commons IO
     The following NOTICE information applies:
       Apache Commons IO

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat
index 29e42e6..6ed0668 100644
--- a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.bat
@@ -35,7 +35,7 @@ set LIB_DIR=%~dp0..\classpath;%~dp0..\lib
 
 if "%JAVA_OPTS%" == "" set JAVA_OPTS=-Xms128m -Xmx256m
 
-SET JAVA_PARAMS=-cp %LIB_DIR%\* %JAVA_OPTS% org.apache.nifi.properties.ConfigEncryptionTool
+SET JAVA_PARAMS=-cp %LIB_DIR%\* %JAVA_OPTS% org.apache.nifi.toolkit.encryptconfig.EncryptConfigMain
 
 cmd.exe /C ""%JAVA_EXE%" %JAVA_PARAMS% %* ""
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh
index 4acaab6..891b5ad 100644
--- a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/encrypt-config.sh
@@ -111,7 +111,7 @@ run() {
    export NIFI_TOOLKIT_HOME="$NIFI_TOOLKIT_HOME"
 
    umask 0077
-   "${JAVA}" -cp "${CLASSPATH}" ${JAVA_OPTS:--Xms128m -Xmx256m} org.apache.nifi.properties.ConfigEncryptionTool "$@"
+   "${JAVA}" -cp "${CLASSPATH}" ${JAVA_OPTS:--Xms128m -Xmx256m} org.apache.nifi.toolkit.encryptconfig.EncryptConfigMain "$@"
    return $?
 }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
index f4b9c3e..b20f159 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
@@ -70,6 +70,28 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-configuration2</artifactId>
+            <version>2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>1.9.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-core</artifactId>
+            <version>1.0-groovy-2.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib-nodep</artifactId>
+            <version>2.2.2</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
index 0a1112f..0e507c8 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
@@ -22,6 +22,7 @@ import org.apache.commons.cli.CommandLine
 import org.apache.commons.cli.CommandLineParser
 import org.apache.commons.cli.DefaultParser
 import org.apache.commons.cli.HelpFormatter
+import org.apache.commons.cli.Option
 import org.apache.commons.cli.Options
 import org.apache.commons.cli.ParseException
 import org.apache.commons.codec.binary.Hex
@@ -196,27 +197,31 @@ class ConfigEncryptionTool {
     ConfigEncryptionTool(String description) {
         this.header = buildHeader(description)
         this.options = new Options()
-        options.addOption("h", HELP_ARG, false, "Prints this usage message")
-        options.addOption("v", VERBOSE_ARG, false, "Sets verbose mode (default false)")
-        options.addOption("n", NIFI_PROPERTIES_ARG, true, "The nifi.properties file containing unprotected config values (will be overwritten)")
-        options.addOption("l", LOGIN_IDENTITY_PROVIDERS_ARG, true, "The login-identity-providers.xml file containing unprotected config values (will be overwritten)")
-        options.addOption("a", AUTHORIZERS_ARG, true, "The authorizers.xml file containing unprotected config values (will be overwritten)")
-        options.addOption("f", FLOW_XML_ARG, true, "The flow.xml.gz file currently protected with old password (will be overwritten)")
-        options.addOption("b", BOOTSTRAP_CONF_ARG, true, "The bootstrap.conf file to persist master key")
-        options.addOption("o", OUTPUT_NIFI_PROPERTIES_ARG, true, "The destination nifi.properties file containing protected config values (will not modify input nifi.properties)")
-        options.addOption("i", OUTPUT_LOGIN_IDENTITY_PROVIDERS_ARG, true, "The destination login-identity-providers.xml file containing protected config values (will not modify input login-identity-providers.xml)")
-        options.addOption("u", OUTPUT_AUTHORIZERS_ARG, true, "The destination authorizers.xml file containing protected config values (will not modify input authorizers.xml)")
-        options.addOption("g", OUTPUT_FLOW_XML_ARG, true, "The destination flow.xml.gz file containing protected config values (will not modify input flow.xml.gz)")
-        options.addOption("k", KEY_ARG, true, "The raw hexadecimal key to use to encrypt the sensitive properties")
-        options.addOption("e", KEY_MIGRATION_ARG, true, "The old raw hexadecimal key to use during key migration")
-        options.addOption("p", PASSWORD_ARG, true, "The password from which to derive the key to use to encrypt the sensitive properties")
-        options.addOption("w", PASSWORD_MIGRATION_ARG, true, "The old password from which to derive the key during migration")
-        options.addOption("r", USE_KEY_ARG, false, "If provided, the secure console will prompt for the raw key value in hexadecimal form")
-        options.addOption("m", MIGRATION_ARG, false, "If provided, the nifi.properties and/or login-identity-providers.xml sensitive properties will be re-encrypted with a new key")
-        options.addOption("x", DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG, false, "If provided, the properties in flow.xml.gz will be re-encrypted with a new key but the nifi.properties and/or login-identity-providers.xml files will not be modified")
-        options.addOption("s", PROPS_KEY_ARG, true, "The password or key to use to encrypt the sensitive processor properties in flow.xml.gz")
-        options.addOption("A", NEW_FLOW_ALGORITHM_ARG, true, "The algorithm to use to encrypt the sensitive processor properties in flow.xml.gz")
-        options.addOption("P", NEW_FLOW_PROVIDER_ARG, true, "The security provider to use to encrypt the sensitive processor properties in flow.xml.gz")
+        options.addOption(Option.builder("h").longOpt(HELP_ARG).hasArg(false).desc("Show usage information (this message)").build())
+        options.addOption(Option.builder("v").longOpt(VERBOSE_ARG).hasArg(false).desc("Sets verbose mode (default false)").build())
+        options.addOption(Option.builder("n").longOpt(NIFI_PROPERTIES_ARG).hasArg(true).argName("file").desc("The nifi.properties file containing unprotected config values (will be overwritten unless -o is specified)").build())
+        options.addOption(Option.builder("o").longOpt(OUTPUT_NIFI_PROPERTIES_ARG).hasArg(true).argName("file").desc("The destination nifi.properties file containing protected config values (will not modify input nifi.properties)").build())
+        options.addOption(Option.builder("l").longOpt(LOGIN_IDENTITY_PROVIDERS_ARG).hasArg(true).argName("file").desc("The login-identity-providers.xml file containing unprotected config values (will be overwritten unless -i is specified)").build())
+        options.addOption(Option.builder("i").longOpt(OUTPUT_LOGIN_IDENTITY_PROVIDERS_ARG).hasArg(true).argName("file").desc("The destination login-identity-providers.xml file containing protected config values (will not modify input login-identity-providers.xml)").build())
+        options.addOption(Option.builder("a").longOpt(AUTHORIZERS_ARG).hasArg(true).argName("file").desc("The authorizers.xml file containing unprotected config values (will be overwritten unless -u is specified)").build())
+        options.addOption(Option.builder("u").longOpt(OUTPUT_AUTHORIZERS_ARG).hasArg(true).argName("file").desc("The destination authorizers.xml file containing protected config values (will not modify input authorizers.xml)").build())
+        options.addOption(Option.builder("f").longOpt(FLOW_XML_ARG).hasArg(true).argName("file").desc("The flow.xml.gz file currently protected with old password (will be overwritten unless -g is specified)").build())
+        options.addOption(Option.builder("g").longOpt(OUTPUT_FLOW_XML_ARG).hasArg(true).argName("file").desc("The destination flow.xml.gz file containing protected config values (will not modify input flow.xml.gz)").build())
+        options.addOption(Option.builder("b").longOpt(BOOTSTRAP_CONF_ARG).hasArg(true).argName("file").desc("The bootstrap.conf file to persist master key").build())
+        options.addOption(Option.builder("k").longOpt(KEY_ARG).hasArg(true).argName("keyhex").desc("The raw hexadecimal key to use to encrypt the sensitive properties").build())
+        options.addOption(Option.builder("e").longOpt(KEY_MIGRATION_ARG).hasArg(true).argName("keyhex").desc("The old raw hexadecimal key to use during key migration").build())
+        options.addOption(Option.builder("p").longOpt(PASSWORD_ARG).hasArg(true).argName("password").desc("The password from which to derive the key to use to encrypt the sensitive properties").build())
+        options.addOption(Option.builder("w").longOpt(PASSWORD_MIGRATION_ARG).hasArg(true).argName("password").desc("The old password from which to derive the key during migration").build())
+        options.addOption(Option.builder("r").longOpt(USE_KEY_ARG).hasArg(false).desc("If provided, the secure console will prompt for the raw key value in hexadecimal form").build())
+        options.addOption(Option.builder("m").longOpt(MIGRATION_ARG).hasArg(false).desc("If provided, the nifi.properties and/or login-identity-providers.xml sensitive properties will be re-encrypted with a new key").build())
+        options.addOption(Option.builder("x").longOpt(DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG).hasArg(false).desc("If provided, the properties in flow.xml.gz will be re-encrypted with a new key but the nifi.properties and/or login-identity-providers.xml files will not be modified").build())
+        options.addOption(Option.builder("s").longOpt(PROPS_KEY_ARG).hasArg(true).argName("password|keyhex").desc("The password or key to use to encrypt the sensitive processor properties in flow.xml.gz").build())
+        options.addOption(Option.builder("A").longOpt(NEW_FLOW_ALGORITHM_ARG).hasArg(true).argName("algorithm").desc("The algorithm to use to encrypt the sensitive processor properties in flow.xml.gz").build())
+        options.addOption(Option.builder("P").longOpt(NEW_FLOW_PROVIDER_ARG).hasArg(true).argName("algorithm").desc("The security provider to use to encrypt the sensitive processor properties in flow.xml.gz").build())
+    }
+
+    static Options getCliOptions() {
+        return new ConfigEncryptionTool().options
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/Configuration.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/Configuration.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/Configuration.groovy
new file mode 100644
index 0000000..e5a8b23
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/Configuration.groovy
@@ -0,0 +1,29 @@
+/*
+ * 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
+
+interface Configuration {
+
+    enum KeySource {
+        PASSWORD,
+        KEY_HEX,
+        BOOTSTRAP_FILE
+    }
+
+    // Future enhancement: configuration field accessors that are common to multiple (action, domain) combinations can go here
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
new file mode 100644
index 0000000..4dbef47
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
@@ -0,0 +1,327 @@
+/*
+ * 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.nifi.properties.AESSensitivePropertyProvider
+import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
+import org.apache.nifi.toolkit.encryptconfig.util.PropertiesEncryptor
+import org.apache.nifi.toolkit.encryptconfig.util.ToolUtilities
+import org.apache.nifi.toolkit.encryptconfig.util.XmlEncryptor
+import org.apache.nifi.util.console.TextDevices
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+class DecryptMode implements ToolMode {
+
+    private static final Logger logger = LoggerFactory.getLogger(DecryptMode.class)
+
+    static enum FileType {
+        properties,
+        xml
+    }
+
+    CliBuilder cli
+    boolean verboseEnabled
+
+    DecryptMode() {
+        cli = cliBuilder()
+        verboseEnabled = false
+    }
+
+    void printUsage(String message = "") {
+        if (message) {
+            System.out.println(message)
+            System.out.println()
+        }
+        cli.usage()
+    }
+
+    void printUsageAndExit(String message = "", int exitStatusCode) {
+        printUsage(message)
+        System.exit(exitStatusCode)
+    }
+
+    @Override
+    void run(String[] args) {
+        try {
+
+            def options = cli.parse(args)
+
+            if (!options || options.h) {
+                printUsageAndExit("", EncryptConfigMain.EXIT_STATUS_OTHER)
+            }
+
+            if (options.v) {
+                verboseEnabled = true
+            }
+            EncryptConfigLogger.configureLogger(verboseEnabled)
+
+            DecryptConfiguration config = new DecryptConfiguration(options)
+
+            run(config)
+
+        } catch (Exception e) {
+            if (verboseEnabled) {
+                logger.error("Encountered an error: ${e.getMessage()}", e)
+            }
+            printUsageAndExit(e.getMessage(), EncryptConfigMain.EXIT_STATUS_FAILURE)
+        }
+    }
+
+    void run(DecryptConfiguration config) throws Exception {
+
+        if (!config.fileType) {
+
+            // Try to load the input file to auto-detect the file type
+            boolean isPropertiesFile = PropertiesEncryptor.supportsFile(config.inputFilePath)
+
+            boolean isXmlFile = XmlEncryptor.supportsFile(config.inputFilePath)
+
+            if (ToolUtilities.isExactlyOneTrue(isPropertiesFile, isXmlFile)) {
+                if (isPropertiesFile) {
+                    config.fileType = FileType.properties
+                    logger.debug("Auto-detection of input file type determined the type to be: ${FileType.properties}")
+                }
+                if (isXmlFile) {
+                    config.fileType = FileType.xml
+                    logger.debug("Auto-detection of input file type determined the type to be: ${FileType.xml}")
+                }
+            }
+
+            // Could we successfully auto-detect?
+            if (!config.fileType) {
+                throw new RuntimeException("Auto-detection of input file type failed. Please re-run the tool specifying the file type with the -t/--fileType flag.")
+            }
+        }
+
+        String decryptedSerializedContent = null
+        switch (config.fileType) {
+
+            case FileType.properties:
+                PropertiesEncryptor propertiesEncryptor = new PropertiesEncryptor(null, config.decryptionProvider)
+                Properties properties = propertiesEncryptor.loadFile(config.inputFilePath)
+                properties = propertiesEncryptor.decrypt(properties)
+                decryptedSerializedContent = propertiesEncryptor.serializePropertiesAndPreserveFormatIfPossible(properties, config.inputFilePath)
+                break
+
+            case FileType.xml:
+                XmlEncryptor xmlEncryptor = new XmlEncryptor(null, config.decryptionProvider) {
+                    @Override
+                    List<String> serializeXmlContentAndPreserveFormat(String updatedXmlContent, String originalXmlContent) {
+                        // For decrypting unknown, generic XML, this tool will not support preserving the format
+                        return updatedXmlContent.split("\n")
+                    }
+                }
+
+                String xmlContent = xmlEncryptor.loadXmlFile(config.inputFilePath)
+                xmlContent = xmlEncryptor.decrypt(xmlContent)
+                decryptedSerializedContent = xmlEncryptor.serializeXmlContentAndPreserveFormatIfPossible(xmlContent, config.inputFilePath)
+                break
+
+            default:
+                throw new RuntimeException("Unsupported file type '${config.fileType}'")
+        }
+
+        if (!decryptedSerializedContent) {
+            throw new RuntimeException("Failed to load and decrypt input file.")
+        }
+
+        if (config.outputToFile) {
+            try {
+                File outputFile = new File(config.outputFilePath)
+                if (ToolUtilities.isSafeToWrite(outputFile)) {
+                    outputFile.text = decryptedSerializedContent
+                    logger.info("Wrote decrypted file contents to '${config.outputFilePath}'")
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Encountered an exception writing the decrypted content to '${config.outputFilePath}': ${e.getMessage()}", e)
+            }
+        } else {
+            System.out.println(decryptedSerializedContent)
+        }
+
+    }
+
+    private CliBuilder cliBuilder() {
+
+        String usage = "${EncryptConfigMain.class.getCanonicalName()} decrypt [options] file"
+
+        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,
+                stopAtNonOption: false)
+
+        cli.h(longOpt: 'help', 'Show usage information (this message)')
+        cli.v(longOpt: 'verbose', 'Enables verbose mode (off by default)')
+
+        // Options for the password or key or bootstrap.conf
+        cli.p(longOpt: 'password',
+                args: 1,
+                argName: 'password',
+                optionalArg: true,
+                'Use a password to derive the key to decrypt the input file. 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,
+                'Use a raw hexadecimal key to decrypt the input file. If an argument is not provided to this flag, interactive mode will be triggered to prompt the user to enter the key.')
+        cli.b(longOpt: 'bootstrapConf',
+                args: 1,
+                argName: 'file',
+                'Use a bootstrap.conf file containing the master key to decrypt the input file (as an alternative to -p or -k)')
+
+        cli.o(longOpt: 'output',
+                args: 1,
+                argName: 'file',
+                'Specify an output file. If omitted, Standard Out is used. Output file can be set to the input file to decrypt the file in-place.')
+
+        return cli
+
+    }
+
+    static class DecryptConfiguration implements Configuration {
+
+        OptionAccessor rawOptions
+
+        Configuration.KeySource keySource
+        String key
+        SensitivePropertyProvider decryptionProvider
+        String inputBootstrapPath
+
+        FileType fileType
+
+        String inputFilePath
+
+        boolean outputToFile = false
+        String outputFilePath
+
+        DecryptConfiguration() {
+        }
+
+        DecryptConfiguration(OptionAccessor options) {
+            this.rawOptions = options
+
+            validateOptions()
+            determineInputFileFromRemainingArgs()
+
+            determineKey()
+            if (!key) {
+                throw new RuntimeException("Failed to configure tool, could not determine key.")
+            }
+            decryptionProvider = new AESSensitivePropertyProvider(key)
+
+            if (rawOptions.t) {
+                fileType = FileType.valueOf(rawOptions.t)
+            }
+
+            if (rawOptions.o) {
+                outputToFile = true
+                outputFilePath = rawOptions.o
+            }
+        }
+
+        private void validateOptions() {
+
+            String validationFailedMessage = null
+
+            if (!rawOptions.b && !rawOptions.p && !rawOptions.k) {
+                validationFailedMessage = "-p, -k, or -b is required in order to determine the master key to use for decryption."
+            }
+
+            if (validationFailedMessage) {
+                throw new RuntimeException("Invalid options: " + validationFailedMessage)
+            }
+
+        }
+
+        private void determineInputFileFromRemainingArgs() {
+            String[] remainingArgs = this.rawOptions.getInner().getArgs()
+            if (remainingArgs.length == 0) {
+                throw new RuntimeException("Missing argument: Input file must be provided.")
+            } else if (remainingArgs.length > 1) {
+                throw new RuntimeException("Too many arguments: Please specify exactly one input file in addition to the options.")
+            }
+            this.inputFilePath = remainingArgs[0]
+        }
+
+        private void determineKey() {
+
+            boolean usingPassword = false
+            boolean usingRawKeyHex = false
+            boolean usingBootstrapKey = false
+
+            if (rawOptions.p) {
+                usingPassword = true
+            }
+            if (rawOptions.k) {
+                usingRawKeyHex = true
+            }
+            if (rawOptions.b) {
+                usingBootstrapKey = true
+            }
+
+            if (!ToolUtilities.isExactlyOneTrue(usingPassword, usingRawKeyHex, usingBootstrapKey)) {
+                throw new RuntimeException("Invalid options: Only one of [-p, -k, -b] is allowed for specifying the decryption password/key.")
+            }
+
+            if (usingPassword || usingRawKeyHex) {
+                String password = null
+                String keyHex = null
+                if (usingPassword) {
+                    logger.debug("Using password to derive master key for decryption")
+                    password = rawOptions.getInner().getOptionValue("p")
+                    keySource = Configuration.KeySource.PASSWORD
+                } else {
+                    logger.debug("Using raw key hex as master key for decryption")
+                    keyHex = rawOptions.getInner().getOptionValue("k")
+                    keySource = Configuration.KeySource.KEY_HEX
+                }
+                key = ToolUtilities.determineKey(TextDevices.defaultTextDevice(), keyHex, password, usingPassword)
+            } else if (usingBootstrapKey) {
+                inputBootstrapPath = rawOptions.b
+                logger.debug("Looking in bootstrap conf file ${inputBootstrapPath} for master key for decryption.")
+
+                // first, try to treat the bootstrap file as a NiFi bootstrap.conf
+                logger.debug("Checking expected NiFi bootstrap.conf format")
+                key = BootstrapUtil.extractKeyFromBootstrapFile(inputBootstrapPath, BootstrapUtil.NIFI_BOOTSTRAP_KEY_PROPERTY)
+
+                // if the key is still null, try again, this time treating the bootstrap file as a NiFi Registry bootstrap.conf
+                if (!key) {
+                    logger.debug("Checking expected NiFi Registry bootstrap.conf format")
+                    key = BootstrapUtil.extractKeyFromBootstrapFile(inputBootstrapPath, BootstrapUtil.REGISTRY_BOOTSTRAP_KEY_PROPERTY)
+                }
+
+                // check we have found the key after trying all bootstrap formats
+                if (key) {
+                    logger.debug("Master key found in ${inputBootstrapPath}. This key will be used for decryption operations.")
+                    keySource = Configuration.KeySource.BOOTSTRAP_FILE
+                } else {
+                    logger.warn("Bootstrap Conf flag present, but master key could not be found in ${inputBootstrapPath}.")
+                }
+            }
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigLogger.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigLogger.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigLogger.groovy
new file mode 100644
index 0000000..07c9577
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigLogger.groovy
@@ -0,0 +1,83 @@
+/*
+ * 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.log4j.LogManager
+import org.apache.log4j.PropertyConfigurator
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+class EncryptConfigLogger {
+    private static final Logger logger = LoggerFactory.getLogger(EncryptConfigLogger.class)
+
+    /**
+     * Configures the logger.
+     *
+     * The nifi-toolkit module uses log4j, which will be configured to append all
+     * log output to the system STDERR. The log level can be specified using the verboseEnabled
+     * argument. A value of <code>true</code> will set the log level to DEBUG, a value of
+     * <code>false</code> will set the log level to INFO.
+     *
+     * @param verboseEnabled flag to indicate if verbose mode is enabled, which sets the log level to DEBUG
+     */
+    static configureLogger(boolean verboseEnabled) {
+
+        Properties log4jProps = null
+        URL log4jPropsPath = this.getClass().getResource("log4j.properties")
+        if (log4jPropsPath) {
+            try {
+                log4jPropsPath.withReader { reader ->
+                    log4jProps = new Properties()
+                    log4jProps.load(reader)
+                }
+            } catch (IOException e) {
+                // do nothing, we will fallback to hardcoded defaults below
+            }
+        }
+
+        if (!log4jProps) {
+            log4jProps = defaultProperties()
+        }
+
+        if (verboseEnabled) {
+            // Override the log level for this package. For this to work as intended, this class must belong
+            // to the same package (or a parent package) of all the encrypt-config classes
+            log4jProps.put("log4j.logger." + EncryptConfigLogger.class.package.name, "DEBUG")
+        }
+
+        LogManager.resetConfiguration()
+        PropertyConfigurator.configure(log4jProps)
+
+        if (verboseEnabled) {
+            logger.debug("Verbose mode is enabled (goes to stderr by default).")
+        }
+    }
+
+    /**
+     * A copy of the settings in /src/main/resources/log4j.properties, in case that is not on the classpath at runtime
+     * @return Properties containing the default properties for Log4j
+     */
+    static Properties defaultProperties() {
+        Properties defaultProperties = new Properties()
+        defaultProperties.setProperty("log4j.rootLogger", "INFO,console")
+        defaultProperties.setProperty("log4j.appender.console", "org.apache.log4j.ConsoleAppender")
+        defaultProperties.setProperty("log4j.appender.console.Target", "System.err")
+        defaultProperties.setProperty("log4j.appender.console.layout", "org.apache.log4j.PatternLayout")
+        defaultProperties.setProperty("log4j.appender.console.layout.ConversionPattern", "%d{yyyy-mm-dd HH:mm:ss} %p %c{1}: %m%n")
+        return defaultProperties
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMain.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMain.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMain.groovy
new file mode 100644
index 0000000..e6ce68e
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMain.groovy
@@ -0,0 +1,138 @@
+/*
+ * 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.nifi.properties.ConfigEncryptionTool
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import java.security.Security
+
+class EncryptConfigMain {
+
+    private static final Logger logger = LoggerFactory.getLogger(EncryptConfigMain.class)
+
+    static final int EXIT_STATUS_SUCCESS = 0
+    static final int EXIT_STATUS_FAILURE = -1
+    static final int EXIT_STATUS_OTHER = 1
+
+    static final String NIFI_REGISTRY_OPT = "nifiRegistry"
+    static final String NIFI_REGISTRY_FLAG = "--${NIFI_REGISTRY_OPT}".toString()
+    static final String DECRYPT_OPT = "decrypt"
+    static final String DECRYPT_FLAG = "--${DECRYPT_OPT}".toString()
+
+    static final int HELP_FORMAT_WIDTH = 160
+
+    // Access should only be through static methods
+    private EncryptConfigMain() {
+    }
+
+    static printUsage(String message = "") {
+
+        if (message) {
+            System.out.println(message)
+            System.out.println()
+        }
+
+        String header = "\nThis tool enables easy encryption and decryption of configuration files for NiFi and its sub-projects. " +
+                "Unprotected files can be input to this tool to be protected by a key in a manner that is understood by NiFi. " +
+                "Protected files, along with a key, can be input to this tool to be unprotected, for troubleshooting or automation purposes.\n\n"
+
+        def options = new Options()
+        options.addOption("h", "help", false, "Show usage information (this message)")
+        options.addOption(null, NIFI_REGISTRY_OPT, false, "Specifies to target NiFi Registry. When this flag is not included, NiFi is the target.")
+
+        HelpFormatter helpFormatter = new HelpFormatter()
+        helpFormatter.setWidth(160)
+        helpFormatter.setOptionComparator(null)
+        helpFormatter.printHelp("${EncryptConfigMain.class.getCanonicalName()} [-h] [options]", header, options, "\n")
+        System.out.println()
+
+        helpFormatter.setSyntaxPrefix("") // disable "usage: " prefix for the following outputs
+
+        Options nifiModeOptions = ConfigEncryptionTool.getCliOptions()
+        helpFormatter.printHelp(
+                "When targeting NiFi:",
+                nifiModeOptions,
+                false)
+        System.out.println()
+
+        Options nifiRegistryModeOptions = NiFiRegistryMode.getCliOptions()
+        nifiRegistryModeOptions.addOption(null, DECRYPT_OPT, false, "Can be used with -r to decrypt a previously encrypted NiFi Registry Properties file. Decrypted content is printed to STDOUT.")
+        helpFormatter.printHelp(
+                "When targeting NiFi Registry using the ${NIFI_REGISTRY_FLAG} flag:",
+                nifiRegistryModeOptions,
+                false)
+        System.out.println()
+
+    }
+
+    static void printUsageAndExit(String message = "", int exitStatusCode) {
+        printUsage(message)
+        System.exit(exitStatusCode)
+    }
+
+    static void main(String[] args) {
+        Security.addProvider(new BouncyCastleProvider())
+
+        if (args.length < 1) {
+            printUsageAndExit(EXIT_STATUS_FAILURE)
+        }
+
+        String firstArg = args[0]
+
+        if (["-h", "--help"].contains(firstArg)) {
+            printUsageAndExit(EXIT_STATUS_OTHER)
+        }
+
+        try {
+            List<String> argsList = args
+            ToolMode toolMode = determineModeFromArgs(argsList)
+            if (toolMode) {
+                toolMode.run((String[])argsList.toArray())
+                System.exit(EXIT_STATUS_SUCCESS)
+            } else {
+                printUsageAndExit(EXIT_STATUS_FAILURE)
+            }
+        } catch (Throwable t) {
+            logger.error("", t)
+            printUsageAndExit(t.getMessage(), EXIT_STATUS_FAILURE)
+        }
+    }
+
+    static ToolMode determineModeFromArgs(List<String> args) {
+        if (args.contains(NIFI_REGISTRY_FLAG)) {
+            args.remove(NIFI_REGISTRY_FLAG)
+            if (args.contains(DECRYPT_FLAG)) {
+                args.remove(DECRYPT_FLAG)
+                return new NiFiRegistryDecryptMode()
+            } else {
+                return new NiFiRegistryMode()
+            }
+        } else {
+            if (args.contains(DECRYPT_FLAG)) {
+                logger.error("The ${DECRYPT_FLAG} flag is only available when running in ${NIFI_REGISTRY_FLAG} mode and targeting nifi-registry.properties to allow for the inline TLS status check.")
+                return null
+            } else {
+                return new LegacyMode()
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/LegacyMode.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/LegacyMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/LegacyMode.groovy
new file mode 100644
index 0000000..09fbfbd
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/LegacyMode.groovy
@@ -0,0 +1,32 @@
+/*
+ * 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.nifi.properties.ConfigEncryptionTool
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+class LegacyMode extends ConfigEncryptionTool implements ToolMode {
+
+    private static final Logger logger = LoggerFactory.getLogger(LegacyMode.class)
+
+    @Override
+    void run(String[] args) {
+        logger.debug("Invoking NiFi Config Encryption Tool")
+        ConfigEncryptionTool.main(args)
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
new file mode 100644
index 0000000..18d773c
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
@@ -0,0 +1,124 @@
+/*
+ * 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.nifi.properties.AESSensitivePropertyProvider
+import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
+import org.apache.nifi.toolkit.encryptconfig.util.ToolUtilities
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * A special DecryptMode that can run using NiFiRegistry CLI Options
+ */
+class NiFiRegistryDecryptMode extends DecryptMode {
+
+    private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryDecryptMode.class)
+
+    CliBuilder cli
+    boolean verboseEnabled
+
+    NiFiRegistryDecryptMode() {
+        cli = NiFiRegistryMode.cliBuilder()
+        verboseEnabled = false
+    }
+
+    @Override
+    void run(String[] args) {
+        try {
+
+            def options = cli.parse(args)
+
+            if (!options || options.h) {
+                EncryptConfigMain.printUsageAndExit("", EncryptConfigMain.EXIT_STATUS_OTHER)
+            }
+
+            if (options.v) {
+                verboseEnabled = true
+            }
+            EncryptConfigLogger.configureLogger(verboseEnabled)
+
+            DecryptConfiguration config = new DecryptConfiguration()
+
+            /* Invalid fields when used with --decrypt: */
+            def invalidDecryptOptions = ["R", "i", "I", "a", "A", "oldPassword", "oldKey"]
+            def presentInvalidOptions = Arrays.stream(options.getInner().getOptions()).findAll {
+                invalidDecryptOptions.contains(it.getOpt())
+            }
+            if (presentInvalidOptions.size() > 0) {
+                throw new RuntimeException("Invalid options: ${EncryptConfigMain.DECRYPT_FLAG} cannot be used with [${presentInvalidOptions.join(", ")}]. It should only be used with -r and one of [-p, -k, -b].")
+            }
+
+            /* Required fields when using --decrypt */
+            // registryPropertiesFile (-r)
+            if (!options.r) {
+                throw new RuntimeException("Invalid options: Input nifiRegistryProperties (-r) is required when using --decrypt")
+            }
+            config.inputFilePath = options.r
+            config.fileType = FileType.properties  // disables auto-detection, which is still experimental
+
+            // one of [-p, -k, -b]
+            String keyHex = null
+            String password = null
+            config.keySource = null
+            if (options.p) {
+                config.keySource = Configuration.KeySource.PASSWORD
+                password = options.getInner().getOptionValue("p")
+            }
+            if (options.k) {
+                if (config.keySource != null) {
+                    throw new RuntimeException("Invalid options: Only one of [-b, -p, -k] is allowed for specifying the decryption password/key.")
+                }
+                config.keySource = Configuration.KeySource.KEY_HEX
+                keyHex = options.getInner().getOptionValue("k")
+            }
+
+            if (config.keySource) {
+                config.key = ToolUtilities.determineKey(keyHex, password, Configuration.KeySource.PASSWORD == config.keySource)
+            }
+
+            if (options.b) {
+                if (config.keySource != null) {
+                    throw new RuntimeException("Invalid options: Only one of [-b, -p, -k] is allowed for specifying the decryption password/key.")
+                }
+                config.keySource = Configuration.KeySource.BOOTSTRAP_FILE
+                config.inputBootstrapPath = options.b
+
+                logger.debug("Checking expected NiFi Registry bootstrap.conf format")
+                config.key = BootstrapUtil.extractKeyFromBootstrapFile(config.inputBootstrapPath, BootstrapUtil.REGISTRY_BOOTSTRAP_KEY_PROPERTY)
+
+                // check we have found the key
+                if (config.key) {
+                    logger.debug("Master key found in ${config.inputBootstrapPath}. This key will be used for decryption operations.")
+                } else {
+                    logger.warn("Bootstrap Conf flag present, but master key could not be found in ${config.inputBootstrapPath}.")
+                }
+            }
+
+            config.decryptionProvider = new AESSensitivePropertyProvider(config.key)
+
+            run(config)
+
+        } catch (Exception e) {
+            if (verboseEnabled) {
+                logger.error("Encountered an error: ${e.getMessage()}", e)
+            }
+            EncryptConfigMain.printUsageAndExit(e.getMessage(), EncryptConfigMain.EXIT_STATUS_FAILURE)
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
new file mode 100644
index 0000000..2034f00
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
@@ -0,0 +1,382 @@
+/*
+ * 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
+
+class NiFiRegistryMode implements ToolMode {
+
+    private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryMode.class)
+
+    CliBuilder cli
+    boolean verboseEnabled
+
+    NiFiRegistryMode() {
+        cli = cliBuilder()
+        verboseEnabled = false
+    }
+
+    @Override
+    void run(String[] args) {
+        try {
+
+            def options = cli.parse(args)
+
+            if (!options || options.h) {
+                EncryptConfigMain.printUsageAndExit("", EncryptConfigMain.EXIT_STATUS_OTHER)
+            }
+
+            if (options.v) {
+                verboseEnabled = true
+            }
+            EncryptConfigLogger.configureLogger(verboseEnabled)
+
+            NiFiRegistryConfiguration config = new NiFiRegistryConfiguration(options)
+            run(config)
+
+        } catch (Exception e) {
+            if (verboseEnabled) {
+                logger.error("Encountered an error: ${e.getMessage()}")
+            }
+            EncryptConfigMain.printUsageAndExit(e.getMessage(), EncryptConfigMain.EXIT_STATUS_FAILURE)
+        }
+    }
+
+    void run(NiFiRegistryConfiguration 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-registry.properties: ${config.inputNiFiRegistryPropertiesPath}")
+        logger.debug("(dest) nifi-registry.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 {
+                logger.debug("Encrypting NiFi Registry Properties")
+                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 {
+                logger.debug("Encrypting Identity Providers XML")
+                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 {
+                logger.debug("Encrypting Authorizers XML")
+                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
+
+    }
+
+    static class NiFiRegistryConfiguration implements 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
+
+        NiFiRegistryConfiguration() {
+        }
+
+        NiFiRegistryConfiguration(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 ?: inputBootstrapPath
+            }
+
+            handlingNiFiRegistryProperties = rawOptions.r
+            if (handlingNiFiRegistryProperties) {
+                inputNiFiRegistryPropertiesPath = rawOptions.r
+                outputNiFiRegistryPropertiesPath = rawOptions.R ?: inputNiFiRegistryPropertiesPath
+                propertiesEncryptor = new NiFiRegistryPropertiesEncryptor(encryptionProvider, decryptionProvider)
+            }
+
+            handlingIdentityProviders = rawOptions.i
+            if (handlingIdentityProviders) {
+                inputIdentityProvidersPath = rawOptions.i
+                outputIdentityProvidersPath = rawOptions.I ?: inputIdentityProvidersPath
+                identityProvidersXmlEncryptor = new NiFiRegistryIdentityProvidersXmlEncryptor(encryptionProvider, decryptionProvider)
+            }
+
+            handlingAuthorizers = rawOptions.a
+            if (handlingAuthorizers) {
+                inputAuthorizersPath = rawOptions.a
+                outputAuthorizersPath = rawOptions.A ?: inputAuthorizersPath
+                authorizersXmlEncryptor = new NiFiRegistryAuthorizersXmlEncryptor(encryptionProvider, decryptionProvider)
+            }
+
+        }
+
+        private void validateOptions() {
+
+            String validationFailedMessage = null
+
+            if (!rawOptions.b) {
+                validationFailedMessage = "-b flag for bootstrap.conf is required."
+                if (rawOptions.B) {
+                    validationFailedMessage += " Input bootsrap.conf will be used as template for output bootstrap.conf"
+                } else if (rawOptions.p || rawOptions.k) {
+                    validationFailedMessage = " Encryption key will be persisted to bootstrap.conf"
+                }
+            }
+
+            if (validationFailedMessage) {
+                throw new RuntimeException("Invalid options: " + validationFailedMessage)
+            }
+
+        }
+
+        private void determineEncryptionKey() {
+            if (rawOptions.p || rawOptions.k) {
+                String password = null
+                String keyHex = null
+                if (rawOptions.p) {
+                    logger.debug("Attempting to generate key from password.")
+                    usingPassword = true
+                    password = rawOptions.getInner().getOptionValue("p")
+                } else {
+                    usingRawKeyHex = true
+                    keyHex = rawOptions.getInner().getOptionValue("k")
+                }
+                encryptionKey = ToolUtilities.determineKey(TextDevices.defaultTextDevice(), keyHex, password, usingPassword)
+            } else if (rawOptions.b) {
+                logger.debug("Attempting to read master key from input bootstrap.conf file.")
+                usingBootstrapKey = true
+                encryptionKey = BootstrapUtil.extractKeyFromBootstrapFile(inputBootstrapPath, BootstrapUtil.REGISTRY_BOOTSTRAP_KEY_PROPERTY)
+                if (!encryptionKey) {
+                    logger.warn("-b specified without -p or -k, but the input bootstrap.conf file did not contain a master key.")
+                }
+            }
+        }
+
+        private String determineDecryptionKey() {
+            if (rawOptions.oldPassword) {
+                logger.debug("Attempting to generate decryption key (for migration) from old password.")
+                encryptionKey = ToolUtilities.determineKey(TextDevices.defaultTextDevice(), null, rawOptions.oldPassword, true)
+            } else if (rawOptions.oldKey) {
+                decryptionKey = rawOptions.oldKey
+            }
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/ToolMode.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/ToolMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/ToolMode.groovy
new file mode 100644
index 0000000..3b49718
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/ToolMode.groovy
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+interface ToolMode {
+
+    void run(String[] args)
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/BootstrapUtil.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/BootstrapUtil.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/BootstrapUtil.groovy
new file mode 100644
index 0000000..85f0ebd
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/BootstrapUtil.groovy
@@ -0,0 +1,132 @@
+/*
+ * 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.util
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+class BootstrapUtil {
+
+    static final String NIFI_BOOTSTRAP_KEY_PROPERTY = "nifi.bootstrap.sensitive.key";
+    static final String REGISTRY_BOOTSTRAP_KEY_PROPERTY = "nifi.registry.bootstrap.sensitive.key";
+
+    private static final Logger logger = LoggerFactory.getLogger(BootstrapUtil.class)
+
+    private static final String BOOTSTRAP_KEY_COMMENT = "# Master key in hexadecimal format for encrypted sensitive configuration values"
+
+    /**
+     * Tries to load keyHex from input bootstrap.conf
+     *
+     * @return keyHex, if present in input bootstrap file; otherwise, null
+     */
+    static String extractKeyFromBootstrapFile(String inputBootstrapPath, String bootstrapKeyPropertyName) throws IOException {
+
+        File inputBootstrapConfFile = new File(inputBootstrapPath)
+        if (!(inputBootstrapPath && ToolUtilities.canRead(inputBootstrapConfFile))) {
+            throw new IOException("The bootstrap.conf file at ${inputBootstrapPath} must exist and be readable by the user running this tool")
+        }
+
+        String keyValue = null
+        try {
+            List<String> lines = inputBootstrapConfFile.readLines()
+            int keyLineIndex = lines.findIndexOf { it.startsWith("${bootstrapKeyPropertyName}=") }
+
+            if (keyLineIndex != -1) {
+                logger.debug("The key property was detected in bootstrap.conf")
+                String keyLine = lines[keyLineIndex]
+                keyValue = keyLine.split("=", 2)[1]
+                if (keyValue.trim().isEmpty()) {
+                    keyValue = null
+                }
+            } else {
+                logger.debug("The key property was not detected in input bootstrap.conf.")
+            }
+
+
+        } catch (IOException e) {
+            logger.error("Encountered an exception reading the master key from the input bootstrap.conf file: ${e.getMessage()}")
+            throw e
+        }
+
+        return keyValue;
+
+    }
+
+    /**
+     * Writes key to output bootstrap.conf
+     *
+     * @param keyHex
+     */
+    static void writeKeyToBootstrapFile(String keyHex, String bootstrapKeyPropertyName, String outputBootstrapPath, String inputBootstrapPath) throws IOException {
+        File inputBootstrapConfFile = new File(inputBootstrapPath)
+        File outputBootstrapConfFile = new File(outputBootstrapPath)
+
+        if (!ToolUtilities.canRead(inputBootstrapConfFile)) {
+            throw new IOException("The bootstrap.conf file at ${inputBootstrapPath} must exist and be readable by the user running this tool")
+        }
+
+        if (!ToolUtilities.isSafeToWrite(outputBootstrapConfFile)) {
+            throw new IOException("The bootstrap.conf file at ${outputBootstrapPath} must exist and be readable and writable by the user running this tool")
+        }
+
+        try {
+            List<String> lines = inputBootstrapConfFile.readLines()
+
+            updateBootstrapContentsWithKey(lines, keyHex, bootstrapKeyPropertyName)
+
+            // Write the updated values to the output file
+            outputBootstrapConfFile.text = lines.join("\n")
+        } catch (IOException e) {
+            logger.error("Encountered an exception reading the master key from the input bootstrap.conf file: ${e.getMessage()}")
+            throw e
+        }
+    }
+
+
+    /**
+     * Accepts the lines of the {@code bootstrap.conf} file as a {@code List <String>} and updates or adds the key property (and associated comment).
+     *
+     * @param lines the lines of the bootstrap file
+     * @return the updated lines
+     */
+    private static List<String> updateBootstrapContentsWithKey(List<String> lines, String newKeyHex, String bootstrapKeyPropertyName) {
+        String keyLine = "${bootstrapKeyPropertyName}=${newKeyHex}"
+        // Try to locate the key property line
+        int keyLineIndex = lines.findIndexOf { it.startsWith("${bootstrapKeyPropertyName}=") }
+
+        // If it was found, update inline
+        if (keyLineIndex != -1) {
+            logger.debug("The key property was detected in bootstrap.conf")
+            lines[keyLineIndex] = keyLine
+            logger.debug("The bootstrap key value was updated")
+
+            // Ensure the comment explaining the property immediately precedes it (check for edge case where key is first line)
+            int keyCommentLineIndex = keyLineIndex > 0 ? keyLineIndex - 1 : 0
+            if (lines[keyCommentLineIndex] != BOOTSTRAP_KEY_COMMENT) {
+                lines.add(keyCommentLineIndex, BOOTSTRAP_KEY_COMMENT)
+                logger.debug("A comment explaining the bootstrap key property was added")
+            }
+        } else {
+            // If it wasn't present originally, add the comment and key property
+            lines.addAll(["\n", BOOTSTRAP_KEY_COMMENT, keyLine])
+            logger.debug("The key property was not detected in bootstrap.conf so it was added along with a comment explaining it")
+        }
+
+        return lines
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a8817e02/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiPropertiesEncryptor.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiPropertiesEncryptor.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiPropertiesEncryptor.groovy
new file mode 100644
index 0000000..28c9ee0
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiPropertiesEncryptor.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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.util
+
+import org.apache.nifi.properties.ProtectedNiFiProperties
+import org.apache.nifi.properties.SensitivePropertyProvider
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import java.util.regex.Pattern
+
+class NiFiPropertiesEncryptor extends PropertiesEncryptor {
+
+    private static final Logger logger = LoggerFactory.getLogger(NiFiPropertiesEncryptor.class)
+
+    private static final String ADDITIONAL_SENSITIVE_PROPERTIES_KEY = ProtectedNiFiProperties.ADDITIONAL_SENSITIVE_PROPERTIES_KEY
+    private static final String[] DEFAULT_SENSITIVE_PROPERTIES = ProtectedNiFiProperties.DEFAULT_SENSITIVE_PROPERTIES
+
+    NiFiPropertiesEncryptor(SensitivePropertyProvider encryptionProvider, SensitivePropertyProvider decryptionProvider) {
+        super(encryptionProvider, decryptionProvider)
+    }
+
+    @Override
+    Properties encrypt(Properties properties) {
+        Set<String> propertiesToEncrypt = new HashSet<>()
+        propertiesToEncrypt.addAll(DEFAULT_SENSITIVE_PROPERTIES)
+        propertiesToEncrypt.addAll(getAdditionalSensitivePropertyKeys(properties))
+
+        return encrypt(properties, propertiesToEncrypt)
+    }
+
+    private static String[] getAdditionalSensitivePropertyKeys(Properties properties) {
+        String rawAdditionalSensitivePropertyKeys = properties.getProperty(ADDITIONAL_SENSITIVE_PROPERTIES_KEY)
+        if (!rawAdditionalSensitivePropertyKeys) {
+            return []
+        }
+        return rawAdditionalSensitivePropertyKeys.split(Pattern.quote(","))
+    }
+
+}