This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 92750c2746 NIFI-10261: Ensure that when comparing Sensitive Parameters 
during flow sync we decrypt values for the comparison
92750c2746 is described below

commit 92750c2746924c0f31377787ffcffe619b6c41e7
Author: Mark Payne <[email protected]>
AuthorDate: Thu Jul 21 16:31:25 2022 -0400

    NIFI-10261: Ensure that when comparing Sensitive Parameters during flow 
sync we decrypt values for the comparison
    
    This closes #6231
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../controller/service/ServiceStateTransition.java |   4 +-
 .../service/StandardControllerServiceNode.java     |  19 +++-
 .../serialization/VersionedFlowSynchronizer.java   |   9 ++
 .../registry/flow/diff/StandardFlowComparator.java |  38 ++++---
 .../flow/diff/TestStandardFlowComparator.java      | 120 +++++++++++++++++++++
 5 files changed, 171 insertions(+), 19 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java
index 21dccaa67e..90f6fa0925 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java
@@ -76,7 +76,7 @@ public class ServiceStateTransition {
             }
 
             state = ControllerServiceState.ENABLED;
-            logger.debug("{} transitioned to ENABLED", controllerServiceNode);
+            logger.debug("{} is now fully ENABLED", controllerServiceNode);
 
             enabledFutures.forEach(future -> future.complete(null));
         } finally {
@@ -124,7 +124,7 @@ public class ServiceStateTransition {
         writeLock.lock();
         try {
             state = ControllerServiceState.DISABLED;
-            logger.debug("{} transitioned to DISABLED", controllerServiceNode);
+            logger.info("{} is now fully DISABLED", controllerServiceNode);
 
             stateChangeCondition.signalAll();
             disabledFutures.forEach(future -> future.complete(null));
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
index 02189a1579..934d738a90 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
@@ -304,8 +304,16 @@ public class StandardControllerServiceNode extends 
AbstractComponentNode impleme
 
     @Override
     public void verifyModifiable() throws IllegalStateException {
-        if (getState() != ControllerServiceState.DISABLED) {
-            throw new IllegalStateException("Cannot modify Controller Service 
configuration because it is currently enabled. Please disable the Controller 
Service first.");
+        final ControllerServiceState state = getState();
+
+        if (state == ControllerServiceState.DISABLING) {
+            // Provide precise/accurate error message for DISABLING case
+            throw new IllegalStateException("Cannot modify Controller Service 
configuration because it is currently still disabling. " +
+                "Please wait for the service to fully disable before 
attempting to modify it.");
+        }
+        if (state != ControllerServiceState.DISABLED) {
+            throw new IllegalStateException("Cannot modify Controller Service 
configuration because it is currently not disabled - it has a state of " + state
+                + ". Please disable the Controller Service first.");
         }
     }
 
@@ -654,6 +662,11 @@ public class StandardControllerServiceNode extends 
AbstractComponentNode impleme
         }
 
         final CompletableFuture<Void> future = new CompletableFuture<>();
+        final boolean transitioned = 
this.stateTransition.transitionToDisabling(ControllerServiceState.ENABLING, 
future);
+        if (transitioned) {
+            return future;
+        }
+
         if 
(this.stateTransition.transitionToDisabling(ControllerServiceState.ENABLED, 
future)) {
             final ConfigurationContext configContext = new 
StandardConfigurationContext(this, this.serviceProvider, null, 
getVariableRegistry());
             scheduler.execute(new Runnable() {
@@ -672,8 +685,6 @@ public class StandardControllerServiceNode extends 
AbstractComponentNode impleme
                     }
                 }
             });
-        } else {
-            
this.stateTransition.transitionToDisabling(ControllerServiceState.ENABLING, 
future);
         }
 
         return future;
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
index 48c69824aa..61b91ca362 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
@@ -179,6 +179,15 @@ public class VersionedFlowSynchronizer implements 
FlowSynchronizer {
 
         // Stop the active components, and then wait for all components to be 
stopped.
         logger.info("In order to inherit proposed dataflow, will stop any 
components that will be affected by the update");
+        if (logger.isDebugEnabled()) {
+            logger.debug("Will stop the following components:");
+            logger.debug(activeSet.toString());
+            final String differencesToString = flowDifferences.stream()
+                .map(FlowDifference::toString)
+                .collect(Collectors.joining("\n"));
+            logger.debug("This Active Set was determined from the following 
Flow Differences:\n{}", differencesToString);
+        }
+
         activeSet.stop();
 
         try {
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java
index f8da42e777..08c7aa9072 100644
--- 
a/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/StandardFlowComparator.java
@@ -23,6 +23,8 @@ import org.apache.nifi.flow.VersionedControllerService;
 import org.apache.nifi.flow.VersionedFlowCoordinates;
 import org.apache.nifi.flow.VersionedFunnel;
 import org.apache.nifi.flow.VersionedLabel;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
 import org.apache.nifi.flow.VersionedPort;
 import org.apache.nifi.flow.VersionedProcessGroup;
 import org.apache.nifi.flow.VersionedProcessor;
@@ -30,8 +32,8 @@ import org.apache.nifi.flow.VersionedPropertyDescriptor;
 import org.apache.nifi.flow.VersionedRemoteGroupPort;
 import org.apache.nifi.flow.VersionedRemoteProcessGroup;
 import org.apache.nifi.flow.VersionedReportingTask;
-import org.apache.nifi.flow.VersionedParameter;
-import org.apache.nifi.flow.VersionedParameterContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -44,6 +46,8 @@ import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 public class StandardFlowComparator implements FlowComparator {
+    private static final Logger logger = 
LoggerFactory.getLogger(StandardFlowComparator.class);
+
     private static final String ENCRYPTED_VALUE_PREFIX = "enc{";
     private static final String ENCRYPTED_VALUE_SUFFIX = "}";
 
@@ -86,7 +90,6 @@ public class StandardFlowComparator implements FlowComparator 
{
     }
 
 
-
     private Set<FlowDifference> compare(final VersionedProcessGroup groupA, 
final VersionedProcessGroup groupB) {
         final Set<FlowDifference> differences = new HashSet<>();
         // Note that we do not compare the names, because when we import a 
Flow into NiFi, we may well give it a new name.
@@ -95,14 +98,6 @@ public class StandardFlowComparator implements 
FlowComparator {
         return differences;
     }
 
-    private boolean allHaveInstanceId(Set<? extends VersionedComponent> 
components) {
-        if (components == null) {
-            return false;
-        }
-
-        return components.stream().allMatch(component -> 
component.getInstanceIdentifier() != null);
-    }
-
     private <T extends VersionedComponent> Set<FlowDifference> 
compareComponents(final Set<T> componentsA, final Set<T> componentsB, final 
ComponentComparator<T> comparator) {
         final Map<String, T> componentMapA = byId(componentsA == null ? 
Collections.emptySet() : componentsA);
         final Map<String, T> componentMapB = byId(componentsB == null ? 
Collections.emptySet() : componentsB);
@@ -198,7 +193,7 @@ public class StandardFlowComparator implements 
FlowComparator {
         compareProperties(taskA, taskB, taskA.getProperties(), 
taskB.getProperties(), taskA.getPropertyDescriptors(), 
taskB.getPropertyDescriptors(), differences);
     }
 
-    private void compare(final VersionedParameterContext contextA, final 
VersionedParameterContext contextB, final Set<FlowDifference> differences) {
+    void compare(final VersionedParameterContext contextA, final 
VersionedParameterContext contextB, final Set<FlowDifference> differences) {
         if (compareComponents(contextA, contextB, differences)) {
             return;
         }
@@ -218,7 +213,9 @@ public class StandardFlowComparator implements 
FlowComparator {
                 continue;
             }
 
-            if (!Objects.equals(parameterA.getValue(), parameterB.getValue())) 
{
+            final String decryptedValueA = decryptValue(parameterA);
+            final String decryptedValueB = decryptValue(parameterB);
+            if (!Objects.equals(decryptedValueA, decryptedValueB)) {
                 final String valueA = parameterA.isSensitive() ? "<Sensitive 
Value A>" : parameterA.getValue();
                 final String valueB = parameterB.isSensitive() ? "<Sensitive 
Value B>" : parameterB.getValue();
                 final String description = 
differenceDescriptor.describeDifference(DifferenceType.PARAMETER_VALUE_CHANGED, 
flowA.getName(), flowB.getName(), contextA, contextB, name, valueA, valueB);
@@ -282,6 +279,21 @@ public class StandardFlowComparator implements 
FlowComparator {
         return 
propertyDecryptor.apply(value.substring(ENCRYPTED_VALUE_PREFIX.length(), 
value.length() - ENCRYPTED_VALUE_SUFFIX.length()));
     }
 
+    private String decryptValue(final VersionedParameter parameter) {
+        final String rawValue = parameter.getValue();
+        if (rawValue == null) {
+            return null;
+        }
+
+        final boolean sensitive = parameter.isSensitive() && 
rawValue.startsWith(ENCRYPTED_VALUE_PREFIX) && 
rawValue.endsWith(ENCRYPTED_VALUE_SUFFIX);
+        if (!sensitive) {
+            logger.debug("Will not decrypt value for parameter {} because it 
is not encrypted", parameter.getName());
+            return rawValue;
+        }
+
+        return 
propertyDecryptor.apply(rawValue.substring(ENCRYPTED_VALUE_PREFIX.length(), 
rawValue.length() - ENCRYPTED_VALUE_SUFFIX.length()));
+    }
+
     private void compareProperties(final VersionedComponent componentA, final 
VersionedComponent componentB,
         final Map<String, String> propertiesA, final Map<String, String> 
propertiesB,
         final Map<String, VersionedPropertyDescriptor> descriptorsA, final 
Map<String, VersionedPropertyDescriptor> descriptorsB,
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/test/java/org/apache/nifi/registry/flow/diff/TestStandardFlowComparator.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/test/java/org/apache/nifi/registry/flow/diff/TestStandardFlowComparator.java
new file mode 100644
index 0000000000..6baf46a44f
--- /dev/null
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/test/java/org/apache/nifi/registry/flow/diff/TestStandardFlowComparator.java
@@ -0,0 +1,120 @@
+/*
+ * 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.registry.flow.diff;
+
+import org.apache.nifi.flow.VersionedComponent;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+public class TestStandardFlowComparator {
+    private Map<String, String> decryptedToEncrypted;
+    private Map<String, String> encryptedToDecrypted;
+    private StandardFlowComparator comparator;
+
+    @BeforeEach
+    public void setup() {
+        decryptedToEncrypted = new HashMap<>();
+        decryptedToEncrypted.put("XYZ", "Hello");
+        decryptedToEncrypted.put("xyz", "hola");
+
+        encryptedToDecrypted = new HashMap<>();
+        encryptedToDecrypted.put("Hello", "XYZ");
+        encryptedToDecrypted.put("hola", "xyz");
+
+        final Function<String, String> decryptor = encryptedToDecrypted::get;
+        final ComparableDataFlow flowA = new StandardComparableDataFlow("Flow 
A", new VersionedProcessGroup());
+        final ComparableDataFlow flowB = new StandardComparableDataFlow("Flow 
B", new VersionedProcessGroup());
+        comparator = new StandardFlowComparator(flowA, flowB, 
Collections.emptySet(),
+            new StaticDifferenceDescriptor(), decryptor, 
VersionedComponent::getInstanceIdentifier);
+    }
+
+    // Ensure that when we are comparing parameter values that we compare the 
decrypted values, but we don't include any
+    // decrypted values in the descriptions of the Flow Difference.
+    @Test
+    public void testSensitiveParametersDecryptedBeforeCompare() {
+        final Set<FlowDifference> differences = new HashSet<>();
+
+        final Set<VersionedParameter> parametersA = new HashSet<>();
+        parametersA.add(createParameter("Param 1", "xyz", false));
+        parametersA.add(createParameter("Param 2", "XYZ", false));
+        parametersA.add(createParameter("Param 3", "Hi there", false));
+        parametersA.add(createParameter("Param 4", "xyz", true));
+        parametersA.add(createParameter("Param 5", "XYZ", true));
+
+        // Now that we've created the parameters, change the mapping of 
decrypted to encrypted so that we encrypt the values
+        // differently in each context but have the same decrypted value
+        decryptedToEncrypted.put("xyz", "bonjour");
+        encryptedToDecrypted.put("bonjour", "xyz");
+
+        final Set<VersionedParameter> parametersB = new HashSet<>();
+        parametersB.add(createParameter("Param 1", "xyz", false));
+        parametersB.add(createParameter("Param 2", "XYZ", false));
+        parametersB.add(createParameter("Param 3", "Hey", false));
+        parametersB.add(createParameter("Param 4", "xyz", true));
+        parametersB.add(createParameter("Param 5", "xyz", true));
+
+        final VersionedParameterContext contextA = new 
VersionedParameterContext();
+        contextA.setIdentifier("id");
+        contextA.setInstanceIdentifier("instanceId");
+        contextA.setParameters(parametersA);
+
+        final VersionedParameterContext contextB = new 
VersionedParameterContext();
+        contextB.setIdentifier("id");
+        contextB.setInstanceIdentifier("instanceId");
+        contextB.setParameters(parametersB);
+
+        comparator.compare(contextA, contextB, differences);
+
+        assertEquals(2, differences.size());
+        for (final FlowDifference difference : differences) {
+            assertSame(DifferenceType.PARAMETER_VALUE_CHANGED, 
difference.getDifferenceType());
+
+            // Ensure that the sensitive values are not contained in the 
description
+            assertFalse(difference.getDescription().contains("Hello"));
+            assertFalse(difference.getDescription().contains("Hola"));
+            assertFalse(difference.getDescription().contains("bonjour"));
+        }
+
+        final long numContainingValue = differences.stream()
+            .filter(diff -> diff.getDescription().contains("Hey") && 
diff.getDescription().contains("Hi there"))
+            .count();
+        assertEquals(1, numContainingValue);
+    }
+
+    private VersionedParameter createParameter(final String name, final String 
value, final boolean sensitive) {
+        final VersionedParameter parameter = new VersionedParameter();
+        parameter.setName(name);
+        parameter.setValue(sensitive ? "enc{" + 
decryptedToEncrypted.get(value) + "}" : value);
+        parameter.setSensitive(sensitive);
+        return parameter;
+    }
+}

Reply via email to