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 <[email protected]>


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 <[email protected]>
Authored: Sat Dec 30 08:54:18 2017 -0500
Committer: Andy LoPresto <[email protected]>
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(","))
+    }
+
+}

Reply via email to