You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2017/08/12 04:13:27 UTC
nifi git commit: NIFI-4237 Added working test for StringEncryptor
decryption of sensitive flow values in FlowFromDOMFactory.
Repository: nifi
Updated Branches:
refs/heads/master 28d5a70ec -> ae940d862
NIFI-4237 Added working test for StringEncryptor decryption of sensitive flow values in FlowFromDOMFactory.
NIFI-4237 Cleaned up unused alternate approaches.
NIFI-4237 Added failing unit test for better error message.
NIFI-4237 Added logic to capture unhelpful encryption exception and provide context in message. All tests pass.
This closes #2077
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/ae940d86
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/ae940d86
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/ae940d86
Branch: refs/heads/master
Commit: ae940d862420e00023eca2e996ad03646f3ed5a4
Parents: 28d5a70
Author: Andy LoPresto <al...@apache.org>
Authored: Fri Aug 11 13:15:16 2017 -0700
Committer: Matt Burgess <ma...@apache.org>
Committed: Fri Aug 11 23:57:15 2017 -0400
----------------------------------------------------------------------
.../serialization/FlowFromDOMFactory.java | 31 ++--
.../serialization/FlowFromDOMFactoryTest.groovy | 150 +++++++++++++++++++
2 files changed, 171 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/ae940d86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java
index 3bd037d..61d9d29 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowFromDOMFactory.java
@@ -16,9 +16,18 @@
*/
package org.apache.nifi.controller.serialization;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.apache.nifi.connectable.Size;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.encrypt.EncryptionException;
import org.apache.nifi.encrypt.StringEncryptor;
import org.apache.nifi.groups.RemoteProcessGroupPortDescriptor;
import org.apache.nifi.remote.StandardRemoteProcessGroupPortDescriptor;
@@ -39,19 +48,14 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
public class FlowFromDOMFactory {
+ private static final Logger logger = LoggerFactory.getLogger(FlowFromDOMFactory.class);
public static BundleDTO getBundle(final Element bundleElement) {
if (bundleElement == null) {
@@ -492,7 +496,14 @@ public class FlowFromDOMFactory {
private static String decrypt(final String value, final StringEncryptor encryptor) {
if (value != null && value.startsWith(FlowSerializer.ENC_PREFIX) && value.endsWith(FlowSerializer.ENC_SUFFIX)) {
- return encryptor.decrypt(value.substring(FlowSerializer.ENC_PREFIX.length(), value.length() - FlowSerializer.ENC_SUFFIX.length()));
+ try {
+ return encryptor.decrypt(value.substring(FlowSerializer.ENC_PREFIX.length(), value.length() - FlowSerializer.ENC_SUFFIX.length()));
+ } catch (EncryptionException | EncryptionOperationNotPossibleException e) {
+ final String moreDescriptiveMessage = "There was a problem decrypting a sensitive flow configuration value. " +
+ "Check that the nifi.sensitive.props.key value in nifi.properties matches the value used to encrypt the flow.xml.gz file";
+ logger.error(moreDescriptiveMessage, e);
+ throw new EncryptionException(moreDescriptiveMessage, e);
+ }
} else {
return value;
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/ae940d86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/serialization/FlowFromDOMFactoryTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/serialization/FlowFromDOMFactoryTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/serialization/FlowFromDOMFactoryTest.groovy
new file mode 100644
index 0000000..7c45503
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/serialization/FlowFromDOMFactoryTest.groovy
@@ -0,0 +1,150 @@
+/*
+ * 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.controller.serialization
+
+import org.apache.commons.codec.binary.Hex
+import org.apache.nifi.encrypt.EncryptionException
+import org.apache.nifi.encrypt.StringEncryptor
+import org.apache.nifi.properties.StandardNiFiProperties
+import org.apache.nifi.security.kms.CryptoUtils
+import org.apache.nifi.security.util.EncryptionMethod
+import org.apache.nifi.util.NiFiProperties
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import javax.crypto.Cipher
+import javax.crypto.SecretKey
+import javax.crypto.SecretKeyFactory
+import javax.crypto.spec.PBEKeySpec
+import javax.crypto.spec.PBEParameterSpec
+import java.security.Security
+
+import static groovy.test.GroovyAssert.shouldFail
+
+@RunWith(JUnit4.class)
+class FlowFromDOMFactoryTest {
+ private static final Logger logger = LoggerFactory.getLogger(FlowFromDOMFactoryTest.class)
+
+ private static final String DEFAULT_PASSWORD = "nififtw!"
+ private static final byte[] DEFAULT_SALT = new byte[8]
+ private static final int DEFAULT_ITERATION_COUNT = 0
+
+ private static final String ALGO = NiFiProperties.NF_SENSITIVE_PROPS_ALGORITHM
+ private static final String PROVIDER = NiFiProperties.NF_SENSITIVE_PROPS_PROVIDER
+ private static final String KEY = NiFiProperties.NF_SENSITIVE_PROPS_KEY
+
+ @BeforeClass
+ static void setUpOnce() throws Exception {
+ Security.addProvider(new BouncyCastleProvider())
+
+ logger.metaClass.methodMissing = { String name, args ->
+ logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
+ }
+ }
+
+ @Before
+ void setUp() throws Exception {
+
+ }
+
+ @After
+ void tearDown() throws Exception {
+
+ }
+
+ @Test
+ void testShouldDecryptSensitiveFlowValue() throws Exception {
+ // Arrange
+ final String plaintext = "This is a plaintext message."
+
+ // Encrypt the value
+
+ // Hard-coded 0x00 * 16
+ byte[] salt = new byte[16]
+ Cipher cipher = generateCipher(true, DEFAULT_PASSWORD, salt)
+
+ byte[] cipherBytes = cipher.doFinal(plaintext.bytes)
+ byte[] saltAndCipherBytes = CryptoUtils.concatByteArrays(salt, cipherBytes)
+ String cipherTextHex = Hex.encodeHexString(saltAndCipherBytes)
+ String wrappedCipherText = "enc{${cipherTextHex}}"
+ logger.info("Cipher text: ${wrappedCipherText}")
+
+ final Map MOCK_PROPERTIES = [(ALGO): EncryptionMethod.MD5_128AES.algorithm, (PROVIDER): EncryptionMethod.MD5_128AES.provider, (KEY): DEFAULT_PASSWORD]
+ NiFiProperties mockProperties = new StandardNiFiProperties(new Properties(MOCK_PROPERTIES))
+ StringEncryptor flowEncryptor = StringEncryptor.createEncryptor(mockProperties)
+
+ // Act
+ String recovered = FlowFromDOMFactory.decrypt(wrappedCipherText, flowEncryptor)
+ logger.info("Recovered: ${recovered}")
+
+ // Assert
+ assert plaintext == recovered
+ }
+
+ @Test
+ void testShouldProvideBetterErrorMessageOnDecryptionFailure() throws Exception {
+ // Arrange
+ final String plaintext = "This is a plaintext message."
+
+ // Encrypt the value
+
+ // Hard-coded 0x00 * 16
+ byte[] salt = new byte[16]
+ Cipher cipher = generateCipher(true, DEFAULT_PASSWORD, salt)
+
+ byte[] cipherBytes = cipher.doFinal(plaintext.bytes)
+ byte[] saltAndCipherBytes = CryptoUtils.concatByteArrays(salt, cipherBytes)
+ String cipherTextHex = Hex.encodeHexString(saltAndCipherBytes)
+ String wrappedCipherText = "enc{${cipherTextHex}}"
+ logger.info("Cipher text: ${wrappedCipherText}")
+
+ // Change the password in "nifi.properties" so it doesn't match the "flow"
+ final Map MOCK_PROPERTIES = [(ALGO): EncryptionMethod.MD5_128AES.algorithm, (PROVIDER): EncryptionMethod.MD5_128AES.provider, (KEY): DEFAULT_PASSWORD.reverse()]
+ NiFiProperties mockProperties = new StandardNiFiProperties(new Properties(MOCK_PROPERTIES))
+ StringEncryptor flowEncryptor = StringEncryptor.createEncryptor(mockProperties)
+
+ // Act
+ def msg = shouldFail(EncryptionException) {
+ String recovered = FlowFromDOMFactory.decrypt(wrappedCipherText, flowEncryptor)
+ logger.info("Recovered: ${recovered}")
+ }
+ logger.expected(msg)
+
+ // Assert
+ assert msg.message =~ "Check that the ${KEY} value in nifi.properties matches the value used to encrypt the flow.xml.gz file"
+ }
+
+ private
+ static Cipher generateCipher(boolean encryptMode, String password = DEFAULT_PASSWORD, byte[] salt = DEFAULT_SALT, int iterationCount = DEFAULT_ITERATION_COUNT) {
+ // Initialize secret key from password
+ final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray())
+ final SecretKeyFactory factory = SecretKeyFactory.getInstance(EncryptionMethod.MD5_128AES.algorithm, EncryptionMethod.MD5_128AES.provider)
+ SecretKey tempKey = factory.generateSecret(pbeKeySpec)
+
+ final PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, iterationCount)
+ Cipher cipher = Cipher.getInstance(EncryptionMethod.MD5_128AES.algorithm, EncryptionMethod.MD5_128AES.provider)
+ cipher.init((encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, tempKey, parameterSpec)
+ cipher
+ }
+}