You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jo...@apache.org on 2019/10/01 17:26:53 UTC
[nifi] 02/02: NIFI-4573 This closes #3460. Refactored logic
handling flow XML encryption migration. Added unit tests.
This is an automated email from the ASF dual-hosted git repository.
joewitt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit c2746fac5ff210a56704bf5ffe278359b9c85b54
Author: Andy LoPresto <al...@apache.org>
AuthorDate: Thu May 2 18:53:03 2019 -0700
NIFI-4573 This closes #3460. Refactored logic handling flow XML encryption migration.
Added unit tests.
Signed-off-by: Joe Witt <jo...@apache.org>
---
.../nifi/properties/ConfigEncryptionTool.groovy | 109 ++++++++++++---------
.../properties/ConfigEncryptionToolTest.groovy | 97 ++++++++++++++++++
2 files changed, 157 insertions(+), 49 deletions(-)
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 b20399d..7c9164b 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
@@ -39,6 +39,7 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.xml.sax.SAXException
+import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
@@ -1536,58 +1537,12 @@ class ConfigEncryptionTool {
try {
tool.flowXml = tool.loadFlowXml()
} catch (Exception e) {
- tool.printUsageAndThrow("Cannot load flow.xml.gz", ExitCode.ERROR_READING_NIFI_PROPERTIES)
- }
-
- // If the flow password was not set in nifi.properties, use the hard-coded default
- String existingFlowPassword = tool.getExistingFlowPassword()
-
- // If the new password was not provided in the arguments, read from the console. If that is empty, use the same value (essentially a copy no-op)
- String newFlowPassword = tool.flowPropertiesPassword ?: tool.getFlowPassword()
- if (!newFlowPassword) {
- newFlowPassword = existingFlowPassword
- }
-
- // Get the algorithms and providers
- NiFiProperties nfp = tool.niFiProperties
- String existingAlgorithm = nfp?.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM) ?: DEFAULT_FLOW_ALGORITHM
- String existingProvider = nfp?.getProperty(NiFiProperties.SENSITIVE_PROPS_PROVIDER) ?: DEFAULT_PROVIDER
-
- String newAlgorithm = tool.newFlowAlgorithm ?: existingAlgorithm
- String newProvider = tool.newFlowProvider ?: existingProvider
-
- try {
- tool.flowXml = tool.migrateFlowXmlContent(tool.flowXml, existingFlowPassword, newFlowPassword, existingAlgorithm, existingProvider, newAlgorithm, newProvider)
- } catch (Exception e) {
if (tool.isVerbose) {
- logger.error("Encountered an error", e, "Check your password?")
+ logger.error("Encountered an error: ", e)
}
- tool.printUsageAndThrow("Encountered an error migrating flow content", ExitCode.ERROR_MIGRATING_FLOW)
- }
-
- // If the new key is the hard-coded internal value, don't persist it to nifi.properties
- if (newFlowPassword != DEFAULT_NIFI_SENSITIVE_PROPS_KEY && newFlowPassword != existingFlowPassword) {
- // Update the NiFiProperties object with the new flow password before it gets encrypted (wasteful, but NiFiProperties instances are immutable)
- Properties rawProperties = new Properties()
- nfp.getPropertyKeys().each { String k ->
- rawProperties.put(k, nfp.getProperty(k))
- }
-
- // If the tool is not going to encrypt NiFiProperties and the existing file is already encrypted, encrypt and update the new sensitive props key
- if (!tool.handlingNiFiProperties && existingNiFiPropertiesAreEncrypted) {
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(tool.keyHex)
- String encryptedSPK = spp.protect(newFlowPassword)
- rawProperties.put(NiFiProperties.SENSITIVE_PROPS_KEY, encryptedSPK)
- // Manually update the protection scheme or it will be lost
- rawProperties.put(ProtectedNiFiProperties.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY), spp.getIdentifierKey())
- if (tool.isVerbose) {
- logger.info("Tool is not configured to encrypt nifi.properties, but the existing nifi.properties is encrypted and flow.xml.gz was migrated, so manually persisting the new encrypted value to nifi.properties")
- }
- } else {
- rawProperties.put(NiFiProperties.SENSITIVE_PROPS_KEY, newFlowPassword)
- }
- tool.niFiProperties = new StandardNiFiProperties(rawProperties)
+ tool.printUsageAndThrow("Cannot load flow.xml.gz", ExitCode.ERROR_READING_NIFI_PROPERTIES)
}
+ tool.handleFlowXml(existingNiFiPropertiesAreEncrypted)
}
if (tool.handlingNiFiProperties) {
@@ -1637,6 +1592,62 @@ class ConfigEncryptionTool {
System.exit(ExitCode.SUCCESS.ordinal())
}
+ void handleFlowXml(boolean existingNiFiPropertiesAreEncrypted = false) {
+ // If the flow password was not set in nifi.properties, use the hard-coded default
+ String existingFlowPassword = getExistingFlowPassword()
+
+ // If the new password was not provided in the arguments, read from the console. If that is empty, use the same value (essentially a copy no-op)
+ String newFlowPassword = flowPropertiesPassword ?: getFlowPassword()
+ if (!newFlowPassword) {
+ newFlowPassword = existingFlowPassword
+ }
+
+ // Get the algorithms and providers
+ NiFiProperties nfp = niFiProperties
+ String existingAlgorithm = nfp?.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM) ?: DEFAULT_FLOW_ALGORITHM
+ String existingProvider = nfp?.getProperty(NiFiProperties.SENSITIVE_PROPS_PROVIDER) ?: DEFAULT_PROVIDER
+
+ String newAlgorithm = newFlowAlgorithm ?: existingAlgorithm
+ String newProvider = newFlowProvider ?: existingProvider
+
+ try {
+ flowXml = migrateFlowXmlContent(flowXml, existingFlowPassword, newFlowPassword, existingAlgorithm, existingProvider, newAlgorithm, newProvider)
+ } catch (Exception e) {
+ logger.error("Encountered an error: ${e.getLocalizedMessage()}")
+ if (e instanceof BadPaddingException) {
+ logger.error("This error is likely caused by providing the wrong existing flow password. Check that the existing flow password [-p] is the one used to encrypt the provided flow.xml.gz file")
+ }
+ if (isVerbose) {
+ logger.error("Exception: ", e)
+ }
+ printUsageAndThrow("Encountered an error migrating flow content", ExitCode.ERROR_MIGRATING_FLOW)
+ }
+
+ // If the new key is the hard-coded internal value, don't persist it to nifi.properties
+ if (newFlowPassword != DEFAULT_NIFI_SENSITIVE_PROPS_KEY && newFlowPassword != existingFlowPassword) {
+ // Update the NiFiProperties object with the new flow password before it gets encrypted (wasteful, but NiFiProperties instances are immutable)
+ Properties rawProperties = new Properties()
+ nfp.getPropertyKeys().each { String k ->
+ rawProperties.put(k, nfp.getProperty(k))
+ }
+
+ // If the tool is not going to encrypt NiFiProperties and the existing file is already encrypted, encrypt and update the new sensitive props key
+ if (!handlingNiFiProperties && existingNiFiPropertiesAreEncrypted) {
+ AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(keyHex)
+ String encryptedSPK = spp.protect(newFlowPassword)
+ rawProperties.put(NiFiProperties.SENSITIVE_PROPS_KEY, encryptedSPK)
+ // Manually update the protection scheme or it will be lost
+ rawProperties.put(ProtectedNiFiProperties.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY), spp.getIdentifierKey())
+ if (isVerbose) {
+ logger.info("Tool is not configured to encrypt nifi.properties, but the existing nifi.properties is encrypted and flow.xml.gz was migrated, so manually persisting the new encrypted value to nifi.properties")
+ }
+ } else {
+ rawProperties.put(NiFiProperties.SENSITIVE_PROPS_KEY, newFlowPassword)
+ }
+ niFiProperties = new StandardNiFiProperties(rawProperties)
+ }
+ }
+
String translateNiFiPropertiesToCLI() {
// Assemble the baseUrl
String baseUrl = determineBaseUrl(niFiProperties)
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
index e2d1ec1..317c779 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
@@ -38,6 +38,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.contrib.java.lang.system.Assertion
import org.junit.contrib.java.lang.system.ExpectedSystemExit
+import org.junit.contrib.java.lang.system.SystemErrRule
import org.junit.contrib.java.lang.system.SystemOutRule
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -48,6 +49,7 @@ import org.xmlunit.diff.DefaultNodeMatcher
import org.xmlunit.diff.Diff
import org.xmlunit.diff.ElementSelectors
+import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
@@ -68,6 +70,9 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
@Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog()
+ @Rule
+ public final SystemErrRule systemErrRule = new SystemErrRule().enableLog()
+
private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
private static final String KEY_HEX_256 = KEY_HEX_128 * 2
public static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
@@ -4527,6 +4532,98 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
}
+ /**
+ * This test is tightly scoped to the migration of the flow XML content to ensure the expected exception type is thrown.
+ */
+ @Test
+ void testMigrateFlowXmlContentWithIncorrectExistingPasswordShouldFailWithBadPaddingException() {
+ // Arrange
+ String flowXmlPath = "src/test/resources/flow.xml"
+ File flowXmlFile = new File(flowXmlPath)
+
+ File tmpDir = setupTmpDir()
+
+ File workingFile = new File("target/tmp/tmp-flow.xml")
+ workingFile.delete()
+ Files.copy(flowXmlFile.toPath(), workingFile.toPath())
+ ConfigEncryptionTool tool = new ConfigEncryptionTool()
+ tool.isVerbose = true
+
+ // Use the wrong existing password
+ String wrongExistingFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY.reverse()
+ String newFlowPassword = FLOW_PASSWORD
+
+ String xmlContent = workingFile.text
+
+ // Act
+ def message = shouldFail(BadPaddingException) {
+ String migratedXmlContent = tool.migrateFlowXmlContent(xmlContent, wrongExistingFlowPassword, newFlowPassword)
+ logger.info("Migrated flow.xml: \n${migratedXmlContent}")
+ }
+ logger.expected(message)
+
+ // Assert
+ assert message =~ "pad block corrupted"
+ }
+
+ /**
+ * This test is scoped to the higher-level method to ensure that if a bad padding exception is thrown, the right errors are displayed.
+ */
+ @Test
+ void testHandleFlowXmlMigrationWithIncorrectExistingPasswordShouldProvideHelpfulErrorMessage() {
+ // Arrange
+// exit.expectSystemExitWithStatus(ExitCode.ERROR_MIGRATING_FLOW.ordinal())
+ systemOutRule.clearLog()
+
+ String flowXmlPath = "src/test/resources/flow.xml"
+ File flowXmlFile = new File(flowXmlPath)
+
+ File tmpDir = setupTmpDir()
+
+ File workingFile = new File("target/tmp/tmp-flow.xml")
+ workingFile.delete()
+ Files.copy(flowXmlFile.toPath(), workingFile.toPath())
+ ConfigEncryptionTool tool = new ConfigEncryptionTool()
+ tool.isVerbose = true
+
+ // Use the wrong existing password
+ String wrongExistingFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY.reverse()
+ String newFlowPassword = FLOW_PASSWORD
+
+ tool.flowXml = workingFile.text
+ def nifiProperties = wrapNFP([(NiFiProperties.SENSITIVE_PROPS_KEY): wrongExistingFlowPassword])
+ tool.niFiProperties = nifiProperties
+ tool.flowPropertiesPassword = newFlowPassword
+ tool.handlingNiFiProperties = false
+
+ // Act
+ def message = shouldFail(Exception) {
+ tool.handleFlowXml()
+ logger.info("Migrated flow.xml: \n${tool.flowXml}")
+ }
+ logger.expected(message)
+
+// final String standardOutput = systemOutRule.getLog()
+// List<String> lines = standardOutput.split("\n")
+// logger.info("Captured ${lines.size()} lines of log output")
+// lines.each { String l -> logger.info("\t$l") }
+
+// final String errorOutput = systemErrRule.getLog()
+// List<String> errorlines = errorOutput.split("\n")
+// logger.info("Captured ${errorlines.size()} lines of error log output")
+// errorlines.each { String l -> logger.info("\t$l") }
+
+ // Assert
+ // TODO: Assert that this message was in the log output (neither the STDOUT and STDERR buffers contain it, but it is printed)
+// assert message =~ "Error performing flow XML content migration because some sensitive values could not be decrypted. Ensure that the existing flow password \\[\\-p\\] is correct."
+ assert message == "Encountered an error migrating flow content"
+ }
+
+ private static StandardNiFiProperties wrapNFP(Map<String, String> map) {
+ new StandardNiFiProperties(
+ new Properties(map))
+ }
+
@Test
void testShouldLoadFlowXmlContent() {
// Arrange