componentComparisonIdLookup;
private final ComponentScheduler componentScheduler;
- private final PropertyDecryptor propertyDecryptor;
+ private final boolean dropEncryptedValues;
+ private final PropertyEncryptionProvider propertyEncryptionProvider;
private final boolean ignoreLocalModifications;
private final boolean updateSettings;
private final boolean updateDescendantVersionedFlows;
@@ -41,7 +43,8 @@ private FlowSynchronizationOptions(final Builder builder) {
this.componentIdGenerator = builder.componentIdGenerator;
this.componentComparisonIdLookup = builder.componentComparisonIdLookup;
this.componentScheduler = builder.componentScheduler;
- this.propertyDecryptor = builder.propertyDecryptor;
+ this.dropEncryptedValues = builder.dropEncryptedValues;
+ this.propertyEncryptionProvider = builder.propertyEncryptionProvider;
this.ignoreLocalModifications = builder.ignoreLocalModifications;
this.updateSettings = builder.updateSettings;
this.updateDescendantVersionedFlows = builder.updateDescendantVersionedFlows;
@@ -85,8 +88,23 @@ public boolean isPreservePublicPortNames() {
return preservePublicPortNames;
}
- public PropertyDecryptor getPropertyDecryptor() {
- return propertyDecryptor;
+ /**
+ * Indicates whether encrypted values in the proposed flow are dropped rather than decrypted. Dropping resolves an
+ * encrypted value to null, which leaves the corresponding sensitive property unset.
+ *
+ * @return true when encrypted values are dropped
+ */
+ public boolean isDropEncryptedValues() {
+ return dropEncryptedValues;
+ }
+
+ /**
+ * Get the Property Encryption Provider used to decrypt sensitive values in the proposed flow
+ *
+ * @return Property Encryption Provider, or null when encrypted values are dropped
+ */
+ public PropertyEncryptionProvider getPropertyEncryptionProvider() {
+ return propertyEncryptionProvider;
}
public Duration getComponentStopTimeout() {
@@ -115,7 +133,8 @@ public static class Builder {
private boolean updateRpgUrls = false;
private boolean preservePublicPortNames = false;
private ScheduledStateChangeListener scheduledStateChangeListener;
- private PropertyDecryptor propertyDecryptor = value -> value;
+ private boolean dropEncryptedValues = false;
+ private PropertyEncryptionProvider propertyEncryptionProvider;
private Duration componentStopTimeout = Duration.ofSeconds(30);
private ComponentStopTimeoutAction timeoutAction = ComponentStopTimeoutAction.THROW_TIMEOUT_EXCEPTION;
private String topLevelGroupId;
@@ -210,13 +229,27 @@ public Builder preservePublicPortNames(final boolean preservePublicPortNames) {
}
/**
- * Specifies the decryptor to use for sensitive properties
+ * Specifies that encrypted values in the proposed flow are dropped rather than decrypted, which leaves the
+ * corresponding sensitive properties unset. This is used when the proposed flow carries sensitive values that
+ * must not be copied to the components being synchronized, such as a flow retrieved from a Flow Registry.
*
- * @param decryptor the decryptor to use
+ * @param dropEncryptedValues whether to drop encrypted values
* @return the builder
*/
- public Builder propertyDecryptor(final PropertyDecryptor decryptor) {
- this.propertyDecryptor = decryptor;
+ public Builder dropEncryptedValues(final boolean dropEncryptedValues) {
+ this.dropEncryptedValues = dropEncryptedValues;
+ return this;
+ }
+
+ /**
+ * Specifies the Property Encryption Provider to use for decrypting sensitive properties. The Provider must be
+ * set unless {@link #dropEncryptedValues(boolean) dropEncryptedValues} is set.
+ *
+ * @param propertyEncryptionProvider the Property Encryption Provider to use
+ * @return the builder
+ */
+ public Builder propertyEncryptionProvider(final PropertyEncryptionProvider propertyEncryptionProvider) {
+ this.propertyEncryptionProvider = propertyEncryptionProvider;
return this;
}
@@ -260,6 +293,12 @@ public FlowSynchronizationOptions build() {
if (componentScheduler == null) {
throw new IllegalStateException("Must set Component Scheduler");
}
+ if (dropEncryptedValues && propertyEncryptionProvider != null) {
+ throw new IllegalStateException("Must not set Property Encryption Provider when dropping encrypted values");
+ }
+ if (!dropEncryptedValues && propertyEncryptionProvider == null) {
+ throw new IllegalStateException("Must set Property Encryption Provider or drop encrypted values");
+ }
if (scheduledStateChangeListener == null) {
scheduledStateChangeListener = ScheduledStateChangeListener.EMPTY;
}
@@ -287,7 +326,8 @@ public static Builder from(final FlowSynchronizationOptions options) {
builder.updateDescendantVersionedFlows = options.isUpdateDescendantVersionedFlows();
builder.updateRpgUrls = options.isUpdateRpgUrls();
builder.preservePublicPortNames = options.isPreservePublicPortNames();
- builder.propertyDecryptor = options.getPropertyDecryptor();
+ builder.dropEncryptedValues = options.isDropEncryptedValues();
+ builder.propertyEncryptionProvider = options.getPropertyEncryptionProvider();
builder.componentStopTimeout = options.getComponentStopTimeout();
builder.timeoutAction = options.getComponentStopTimeoutAction();
builder.scheduledStateChangeListener = options.getScheduledStateChangeListener();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/PropertyDecryptor.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/PropertyDecryptor.java
deleted file mode 100644
index b88bd68687a0..000000000000
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/PropertyDecryptor.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.groups;
-
-public interface PropertyDecryptor {
- String decrypt(String value);
-
- PropertyDecryptor NO_OP_DECRYPTOR = value -> value;
-}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/mapping/FlowMappingOptions.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/mapping/FlowMappingOptions.java
index eccf808188d2..020bb884bc92 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/mapping/FlowMappingOptions.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/mapping/FlowMappingOptions.java
@@ -18,11 +18,12 @@
package org.apache.nifi.registry.flow.mapping;
import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
import static java.util.Objects.requireNonNull;
public class FlowMappingOptions {
- private final SensitiveValueEncryptor encryptor;
+ private final PropertyEncryptionProvider propertyEncryptionProvider;
private final VersionedComponentStateLookup stateLookup;
private final ComponentIdLookup componentIdLookup;
private final boolean mapPropertyDescriptors;
@@ -36,7 +37,7 @@ public class FlowMappingOptions {
private final int localNodeOrdinal;
private FlowMappingOptions(final Builder builder) {
- encryptor = builder.encryptor;
+ propertyEncryptionProvider = builder.propertyEncryptionProvider;
stateLookup = builder.stateLookup;
componentIdLookup = builder.componentIdLookup;
mapPropertyDescriptors = builder.mapPropertyDescriptors;
@@ -50,8 +51,8 @@ private FlowMappingOptions(final Builder builder) {
localNodeOrdinal = builder.localNodeOrdinal;
}
- public SensitiveValueEncryptor getSensitiveValueEncryptor() {
- return encryptor;
+ public PropertyEncryptionProvider getPropertyEncryptionProvider() {
+ return propertyEncryptionProvider;
}
public VersionedComponentStateLookup getStateLookup() {
@@ -99,7 +100,7 @@ public int getLocalNodeOrdinal() {
}
public static class Builder {
- private SensitiveValueEncryptor encryptor;
+ private PropertyEncryptionProvider propertyEncryptionProvider;
private VersionedComponentStateLookup stateLookup;
private ComponentIdLookup componentIdLookup;
private boolean mapPropertyDescriptors;
@@ -113,14 +114,14 @@ public static class Builder {
private int localNodeOrdinal = 0;
/**
- * Sets the SensitiveValueEncryptor to use for encrypting sensitive values. This value must be set
- * if {@link #mapSensitiveConfiguration(boolean) mapSensitiveConfiguration} is set to true.
+ * Sets the Property Encryption Provider to use for encrypting sensitive values. Must be set when
+ * {@link #mapSensitiveConfiguration(boolean) mapSensitiveConfiguration} is set to true.
*
- * @param encryptor the PropertyEncryptor to use
+ * @param propertyEncryptionProvider the Property Encryption Provider to use
* @return the builder
*/
- public Builder sensitiveValueEncryptor(final SensitiveValueEncryptor encryptor) {
- this.encryptor = encryptor;
+ public Builder propertyEncryptionProvider(final PropertyEncryptionProvider propertyEncryptionProvider) {
+ this.propertyEncryptionProvider = propertyEncryptionProvider;
return this;
}
@@ -162,9 +163,9 @@ public Builder mapPropertyDescriptors(final boolean mapPropertyDescriptors) {
}
/**
- * Sets whether or not to map sensitive values. If true, the {@link #sensitiveValueEncryptor(SensitiveValueEncryptor)} must be set
+ * Sets whether to map sensitive values
*
- * @param mapSensitiveConfiguration whether or not sensitive values should be mapped
+ * @param mapSensitiveConfiguration whether sensitive values should be mapped
* @return the builder
*/
public Builder mapSensitiveConfiguration(final boolean mapSensitiveConfiguration) {
@@ -254,15 +255,18 @@ public Builder localNodeOrdinal(final int localNodeOrdinal) {
*
* @return the FlowMappingOptions
* @throws NullPointerException if the {@link #stateLookup(VersionedComponentStateLookup) StateLookup} is not set, the
- * {@link #componentIdLookup(ComponentIdLookup) ComponentIdLookup} is not set, or if {@link #mapSensitiveConfiguration(boolean) mapSensitiveConfiguration}
- * is set to true but the {@link #sensitiveValueEncryptor(SensitiveValueEncryptor) SensitiveValueEncryptor} has not been set
+ * {@link #componentIdLookup(ComponentIdLookup) ComponentIdLookup} is not set, or the
+ * {@link #stateManagerProvider(StateManagerProvider) StateManagerProvider} is not set when
+ * {@link #mapComponentState(boolean) mapComponentState} is set to true
+ * @throws IllegalArgumentException if {@link #mapSensitiveConfiguration(boolean) mapSensitiveConfiguration} is set to true but the
+ * {@link #propertyEncryptionProvider(PropertyEncryptionProvider) PropertyEncryptionProvider} has not been set
*/
public FlowMappingOptions build() {
requireNonNull(stateLookup, "State Lookup must be set");
requireNonNull(componentIdLookup, "Component ID Lookup must be set");
- if (mapSensitiveConfiguration) {
- requireNonNull(encryptor, "Property Encryptor must be set when sensitive configuration is to be mapped");
+ if (mapSensitiveConfiguration && propertyEncryptionProvider == null) {
+ throw new IllegalArgumentException("Property Encryption Provider must be set when sensitive configuration is to be mapped");
}
if (mapComponentState) {
@@ -278,7 +282,7 @@ public FlowMappingOptions build() {
* a dataflow to a NiFi Registry.
*/
public static final FlowMappingOptions DEFAULT_OPTIONS = new Builder()
- .sensitiveValueEncryptor(null)
+ .propertyEncryptionProvider(null)
.stateLookup(VersionedComponentStateLookup.ENABLED_OR_DISABLED)
.componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
.mapPropertyDescriptors(true)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/mapping/SensitiveValueEncryptor.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/mapping/SensitiveValueEncryptor.java
deleted file mode 100644
index ce43c465a85b..000000000000
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/registry/flow/mapping/SensitiveValueEncryptor.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.mapping;
-
-public interface SensitiveValueEncryptor {
- String encrypt(String value);
-}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/InternalPassThroughPropertyEncryptionProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/InternalPassThroughPropertyEncryptionProvider.java
new file mode 100644
index 000000000000..a47d7bc296b3
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/InternalPassThroughPropertyEncryptionProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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.security.encryption;
+
+/**
+ * Internal Pass Through implementation of Property Encryption Provider that does not modify property values.
+ *
+ * Intended for flows that are mapped and synchronized in memory rather than persisted. Sensitive values still pass
+ * through {@link SensitivePropertyCodec}, which represents them as hexadecimal, so a flow mapped using this Provider
+ * must also be synchronized using this Provider in order to recover the original values.
+ */
+public class InternalPassThroughPropertyEncryptionProvider implements PropertyEncryptionProvider {
+ @Override
+ public void initialize(final PropertyEncryptionProviderInitializationContext context) {
+
+ }
+
+ @Override
+ public byte[] encrypt(final byte[] property, final SensitivePropertyContext context) {
+ return property;
+ }
+
+ @Override
+ public byte[] decrypt(final byte[] encryptedProperty, final SensitivePropertyContext context) {
+ return encryptedProperty;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionEncoder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionEncoder.java
new file mode 100644
index 000000000000..dc1e4fad2c94
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionEncoder.java
@@ -0,0 +1,73 @@
+/*
+ * 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.security.encryption;
+
+import java.util.Objects;
+
+/**
+ * Encoder for the standard representation of encrypted sensitive values in serialized flows.
+ *
+ * Encrypted values are wrapped with the {@code enc{}} prefix and suffix. The wrapper distinguishes an encrypted
+ * value from a value stored as plaintext, which occurs when a sensitive property references a Parameter or when a flow
+ * is mapped without a Property Encryption Provider.
+ */
+public final class PropertyEncryptionEncoder {
+
+ private static final String PREFIX = "enc{";
+
+ private static final String SUFFIX = "}";
+
+ private PropertyEncryptionEncoder() {
+
+ }
+
+ /**
+ * Determine whether a value is encoded as an encrypted value
+ *
+ * @param value Value to be evaluated, which may be null
+ * @return Whether the value is wrapped with the encrypted value prefix and suffix
+ */
+ public static boolean isEncrypted(final String value) {
+ return value != null && value.startsWith(PREFIX) && value.endsWith(SUFFIX);
+ }
+
+ /**
+ * Get an encrypted value wrapped with the encrypted value prefix and suffix
+ *
+ * @param encryptedValue Encrypted value to be wrapped
+ * @return Encrypted value wrapped with the prefix and suffix
+ */
+ public static String getEncoded(final String encryptedValue) {
+ Objects.requireNonNull(encryptedValue, "Encrypted value required");
+ return PREFIX + encryptedValue + SUFFIX;
+ }
+
+ /**
+ * Get an encrypted value with the encrypted value prefix and suffix removed
+ *
+ * @param encodedValue Encrypted value wrapped with the prefix and suffix
+ * @return Encrypted value without the prefix and suffix
+ * @throws IllegalArgumentException Thrown when the value is not wrapped with the prefix and suffix
+ */
+ public static String getDecoded(final String encodedValue) {
+ if (isEncrypted(encodedValue)) {
+ return encodedValue.substring(PREFIX.length(), encodedValue.length() - SUFFIX.length());
+ }
+
+ throw new IllegalArgumentException("Value not encoded with required prefix and suffix delimiters");
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyCodec.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyCodec.java
new file mode 100644
index 000000000000..d95fa86b652a
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyCodec.java
@@ -0,0 +1,76 @@
+/*
+ * 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.security.encryption;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HexFormat;
+import java.util.Objects;
+
+/**
+ * Codec that adapts the byte-oriented Property Encryption Provider to the string representation of sensitive values
+ * stored in the flow configuration.
+ *
+ * Values are encoded as UTF-8 before encryption and encrypted values are represented as hexadecimal, matching the
+ * representation written inside the {@code enc{}} wrapper of persisted flows.
+ */
+public final class SensitivePropertyCodec {
+ private static final HexFormat HEX_FORMAT = HexFormat.of();
+
+ private SensitivePropertyCodec() {
+ }
+
+ /**
+ * Encrypt a sensitive value and return the hexadecimal representation of the encrypted value
+ *
+ * @param provider Property Encryption Provider
+ * @param value Sensitive value to be encrypted
+ * @param context Context describing the value being protected
+ * @return Hexadecimal representation of the encrypted value
+ * @throws PropertyEncryptionException Thrown when encryption fails
+ */
+ public static String encrypt(final PropertyEncryptionProvider provider, final String value, final SensitivePropertyContext context) {
+ Objects.requireNonNull(provider, "Property Encryption Provider required");
+ Objects.requireNonNull(value, "Value required");
+
+ final byte[] encrypted = provider.encrypt(value.getBytes(StandardCharsets.UTF_8), context);
+ return HEX_FORMAT.formatHex(encrypted);
+ }
+
+ /**
+ * Decrypt the hexadecimal representation of an encrypted sensitive value
+ *
+ * @param provider Property Encryption Provider
+ * @param encryptedValue Hexadecimal representation of the encrypted value
+ * @param context Context describing the value being protected, which must equal the context supplied on encryption
+ * @return Decrypted sensitive value
+ * @throws PropertyEncryptionException Thrown when decryption fails
+ */
+ public static String decrypt(final PropertyEncryptionProvider provider, final String encryptedValue, final SensitivePropertyContext context) {
+ Objects.requireNonNull(provider, "Property Encryption Provider required");
+ Objects.requireNonNull(encryptedValue, "Encrypted value required");
+
+ final byte[] encrypted;
+ try {
+ encrypted = HEX_FORMAT.parseHex(encryptedValue);
+ } catch (final IllegalArgumentException e) {
+ throw new PropertyEncryptionException("Sensitive property is not a valid hexadecimal encrypted value", e);
+ }
+
+ final byte[] decrypted = provider.decrypt(encrypted, context);
+ return new String(decrypted, StandardCharsets.UTF_8);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyContextFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyContextFactory.java
new file mode 100644
index 000000000000..09e24f0617e5
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyContextFactory.java
@@ -0,0 +1,95 @@
+/*
+ * 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.security.encryption;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Factory for the Sensitive Property Context supplied to Property Encryption Providers.
+ *
+ * A Provider may bind the context to the encrypted value, so the context supplied when decrypting a value must equal
+ * the context supplied when the value was encrypted. Encryption and decryption call sites therefore build contexts
+ * through this factory rather than assembling attribute maps directly.
+ *
+ * Attributes with a null value are omitted, which allows call sites that cannot resolve an attribute to produce the
+ * same context on both sides of the operation.
+ */
+public final class SensitivePropertyContextFactory {
+
+ private static final String PROXY_PASSWORD_PROPERTY_NAME = "Proxy Password";
+
+ private SensitivePropertyContextFactory() {
+ }
+
+ /**
+ * Get the context for a sensitive property configured on a flow component
+ *
+ * @param componentId Identifier of the component instance that owns the property
+ * @param componentType Type of the component that owns the property
+ * @param propertyName Name of the property
+ * @return Sensitive Property Context
+ */
+ public static SensitivePropertyContext forComponent(final String componentId, final String componentType, final String propertyName) {
+ final Map attributes = new LinkedHashMap<>();
+ putAttribute(attributes, SensitivePropertyAttribute.COMPONENT_ID, componentId);
+ putAttribute(attributes, SensitivePropertyAttribute.COMPONENT_TYPE, componentType);
+ putAttribute(attributes, SensitivePropertyAttribute.PROPERTY_NAME, propertyName);
+ return new SensitivePropertyContext(SensitivePropertyCategory.COMPONENT_PROPERTY, attributes);
+ }
+
+ /**
+ * Get the context for the proxy password of a Remote Process Group. A Remote Process Group is not a configurable
+ * extension, so the context carries no component type.
+ *
+ * @param remoteProcessGroupId Identifier of the Remote Process Group instance
+ * @return Sensitive Property Context
+ */
+ public static SensitivePropertyContext forRemoteProcessGroupProxyPassword(final String remoteProcessGroupId) {
+ return forComponent(remoteProcessGroupId, null, PROXY_PASSWORD_PROPERTY_NAME);
+ }
+
+ /**
+ * Get the context for a sensitive Parameter value
+ *
+ * @param parameterContextName Name of the Parameter Context that contains the Parameter
+ * @param parameterName Name of the Parameter
+ * @return Sensitive Property Context
+ */
+ public static SensitivePropertyContext forParameter(final String parameterContextName, final String parameterName) {
+ final Map attributes = new LinkedHashMap<>();
+ putAttribute(attributes, SensitivePropertyAttribute.PARAMETER_CONTEXT_NAME, parameterContextName);
+ putAttribute(attributes, SensitivePropertyAttribute.PARAMETER_NAME, parameterName);
+ return new SensitivePropertyContext(SensitivePropertyCategory.PARAMETER, attributes);
+ }
+
+ /**
+ * Get the context for an authorization token stored on behalf of an authenticated user. The context carries no
+ * attributes, because nothing describing the user is known before the stored token has been decrypted.
+ *
+ * @return Sensitive Property Context
+ */
+ public static SensitivePropertyContext forAuthorizationToken() {
+ return new SensitivePropertyContext(SensitivePropertyCategory.AUTHORIZATION_TOKEN, Map.of());
+ }
+
+ private static void putAttribute(final Map attributes, final SensitivePropertyAttribute attribute, final String value) {
+ if (value != null) {
+ attributes.put(attribute.getKey(), value);
+ }
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/InternalPassThroughPropertyEncryptionProviderTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/InternalPassThroughPropertyEncryptionProviderTest.java
new file mode 100644
index 000000000000..dbefe8b68711
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/InternalPassThroughPropertyEncryptionProviderTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.security.encryption;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+class InternalPassThroughPropertyEncryptionProviderTest {
+ private static final String VALUE = "Sensitive Value";
+
+ private static final SensitivePropertyContext CONTEXT = new SensitivePropertyContext(SensitivePropertyCategory.COMPONENT_PROPERTY, Map.of());
+
+ private final InternalPassThroughPropertyEncryptionProvider provider = new InternalPassThroughPropertyEncryptionProvider();
+
+ @Test
+ void testEncryptReturnsProperty() {
+ final byte[] property = VALUE.getBytes(StandardCharsets.UTF_8);
+
+ assertArrayEquals(property, provider.encrypt(property, CONTEXT));
+ }
+
+ @Test
+ void testDecryptReturnsEncryptedProperty() {
+ final byte[] encryptedProperty = VALUE.getBytes(StandardCharsets.UTF_8);
+
+ assertArrayEquals(encryptedProperty, provider.decrypt(encryptedProperty, CONTEXT));
+ }
+
+ @Test
+ void testCodecRoundTrip() {
+ final String encoded = SensitivePropertyCodec.encrypt(provider, VALUE, CONTEXT);
+ assertNotEquals(VALUE, encoded);
+
+ final String decoded = SensitivePropertyCodec.decrypt(provider, encoded, CONTEXT);
+ assertEquals(VALUE, decoded);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/PropertyEncryptionEncoderTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/PropertyEncryptionEncoderTest.java
new file mode 100644
index 000000000000..ba5cdb1bbd47
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/PropertyEncryptionEncoderTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.security.encryption;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class PropertyEncryptionEncoderTest {
+
+ private static final String ENCRYPTED_VALUE = "0123456789abcdef";
+
+ private static final String ENCODED_VALUE = "enc{0123456789abcdef}";
+
+ private static final String PARAMETER_REFERENCE = "#{Parameter}";
+
+ @Test
+ void testIsEncrypted() {
+ assertTrue(PropertyEncryptionEncoder.isEncrypted(ENCODED_VALUE));
+ }
+
+ @Test
+ void testIsEncryptedNull() {
+ assertFalse(PropertyEncryptionEncoder.isEncrypted(null));
+ }
+
+ @Test
+ void testIsEncryptedPlaintext() {
+ assertFalse(PropertyEncryptionEncoder.isEncrypted(ENCRYPTED_VALUE));
+ assertFalse(PropertyEncryptionEncoder.isEncrypted(PARAMETER_REFERENCE));
+ }
+
+ @Test
+ void testIsEncryptedSuffixNotFound() {
+ assertFalse(PropertyEncryptionEncoder.isEncrypted("enc{0123456789abcdef"));
+ }
+
+ @Test
+ void testGetEncoded() {
+ assertEquals(ENCODED_VALUE, PropertyEncryptionEncoder.getEncoded(ENCRYPTED_VALUE));
+ }
+
+ @Test
+ void testGetEncodedNull() {
+ assertThrows(NullPointerException.class, () -> PropertyEncryptionEncoder.getEncoded(null));
+ }
+
+ @Test
+ void testGetDecoded() {
+ assertEquals(ENCRYPTED_VALUE, PropertyEncryptionEncoder.getDecoded(ENCODED_VALUE));
+ }
+
+ @Test
+ void testGetDecodedNotEncoded() {
+ assertThrows(IllegalArgumentException.class, () -> PropertyEncryptionEncoder.getDecoded(ENCRYPTED_VALUE));
+ assertThrows(IllegalArgumentException.class, () -> PropertyEncryptionEncoder.getDecoded(null));
+ }
+
+ @Test
+ void testGetEncodedGetDecodedRoundTrip() {
+ final String encoded = PropertyEncryptionEncoder.getEncoded(ENCRYPTED_VALUE);
+ assertTrue(PropertyEncryptionEncoder.isEncrypted(encoded));
+ assertEquals(ENCRYPTED_VALUE, PropertyEncryptionEncoder.getDecoded(encoded));
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/SensitivePropertyCodecTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/SensitivePropertyCodecTest.java
new file mode 100644
index 000000000000..b2c6b3c0d839
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/SensitivePropertyCodecTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.security.encryption;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HexFormat;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class SensitivePropertyCodecTest {
+ private static final String VALUE = "Sensitive Value";
+
+ private static final String MULTIBYTE_VALUE = "Sensitive \u00e4\u00f6\u00fc Value";
+
+ private static final SensitivePropertyContext CONTEXT = new SensitivePropertyContext(SensitivePropertyCategory.COMPONENT_PROPERTY, Map.of());
+
+ private final PropertyEncryptionProvider provider = new ReversingPropertyEncryptionProvider();
+
+ @Test
+ void testEncryptDecrypt() {
+ final String encrypted = SensitivePropertyCodec.encrypt(provider, VALUE, CONTEXT);
+ assertNotNull(encrypted);
+
+ final String decrypted = SensitivePropertyCodec.decrypt(provider, encrypted, CONTEXT);
+ assertEquals(VALUE, decrypted);
+ }
+
+ @Test
+ void testEncryptDecryptMultibyteCharacters() {
+ final String encrypted = SensitivePropertyCodec.encrypt(provider, MULTIBYTE_VALUE, CONTEXT);
+
+ final String decrypted = SensitivePropertyCodec.decrypt(provider, encrypted, CONTEXT);
+ assertEquals(MULTIBYTE_VALUE, decrypted);
+ }
+
+ /**
+ * The encoded representation must be the hexadecimal encoding of the bytes returned from the Provider, which is the
+ * representation written inside the encryption wrapper of persisted flow configurations.
+ */
+ @Test
+ void testEncryptHexadecimalRepresentation() {
+ final PropertyEncryptionProvider passThroughProvider = new InternalPassThroughPropertyEncryptionProvider();
+
+ final String encrypted = SensitivePropertyCodec.encrypt(passThroughProvider, VALUE, CONTEXT);
+
+ assertEquals(HexFormat.of().formatHex(VALUE.getBytes(StandardCharsets.UTF_8)), encrypted);
+ }
+
+ @Test
+ void testDecryptHexadecimalNotValid() {
+ final PropertyEncryptionException exception = assertThrows(
+ PropertyEncryptionException.class,
+ () -> SensitivePropertyCodec.decrypt(provider, "Not Hexadecimal", CONTEXT)
+ );
+
+ assertNotNull(exception.getCause());
+ }
+
+ @Test
+ void testEncryptProviderRequired() {
+ assertThrows(NullPointerException.class, () -> SensitivePropertyCodec.encrypt(null, VALUE, CONTEXT));
+ }
+
+ @Test
+ void testDecryptProviderRequired() {
+ assertThrows(NullPointerException.class, () -> SensitivePropertyCodec.decrypt(null, "00", CONTEXT));
+ }
+
+ /**
+ * Provider that reverses the supplied bytes, which distinguishes the encoded representation from the plain value
+ */
+ private static class ReversingPropertyEncryptionProvider implements PropertyEncryptionProvider {
+ @Override
+ public void initialize(final PropertyEncryptionProviderInitializationContext context) {
+ }
+
+ @Override
+ public byte[] encrypt(final byte[] property, final SensitivePropertyContext context) {
+ return getReversed(property);
+ }
+
+ @Override
+ public byte[] decrypt(final byte[] encryptedProperty, final SensitivePropertyContext context) {
+ return getReversed(encryptedProperty);
+ }
+
+ private byte[] getReversed(final byte[] bytes) {
+ final byte[] reversed = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ reversed[i] = bytes[bytes.length - 1 - i];
+ }
+ return reversed;
+ }
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/SensitivePropertyContextFactoryTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/SensitivePropertyContextFactoryTest.java
new file mode 100644
index 000000000000..edd1a7a1ed0a
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/security/encryption/SensitivePropertyContextFactoryTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.security.encryption;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SensitivePropertyContextFactoryTest {
+ private static final String COMPONENT_ID = "0ce4d61b-e4f4-3f2a-b1d0-0d9f6b0e7a11";
+
+ private static final String COMPONENT_TYPE = "org.apache.nifi.processors.standard.InvokeHTTP";
+
+ private static final String PROPERTY_NAME = "Request Password";
+
+ private static final String PARAMETER_CONTEXT_NAME = "Production";
+
+ private static final String PARAMETER_NAME = "database.password";
+
+ @Test
+ void testForComponent() {
+ final SensitivePropertyContext context = SensitivePropertyContextFactory.forComponent(COMPONENT_ID, COMPONENT_TYPE, PROPERTY_NAME);
+
+ assertEquals(SensitivePropertyCategory.COMPONENT_PROPERTY, context.category());
+ assertEquals(
+ Map.of(
+ SensitivePropertyAttribute.COMPONENT_ID.getKey(), COMPONENT_ID,
+ SensitivePropertyAttribute.COMPONENT_TYPE.getKey(), COMPONENT_TYPE,
+ SensitivePropertyAttribute.PROPERTY_NAME.getKey(), PROPERTY_NAME
+ ),
+ context.attributes()
+ );
+ }
+
+ /**
+ * A call site that cannot resolve an attribute must produce the same context as the site that also cannot resolve
+ * it, so null attributes are omitted rather than stored
+ */
+ @Test
+ void testForComponentNullAttributesOmitted() {
+ final SensitivePropertyContext context = SensitivePropertyContextFactory.forComponent(COMPONENT_ID, null, PROPERTY_NAME);
+
+ assertEquals(
+ Map.of(
+ SensitivePropertyAttribute.COMPONENT_ID.getKey(), COMPONENT_ID,
+ SensitivePropertyAttribute.PROPERTY_NAME.getKey(), PROPERTY_NAME
+ ),
+ context.attributes()
+ );
+ }
+
+ @Test
+ void testForRemoteProcessGroupProxyPassword() {
+ final SensitivePropertyContext context = SensitivePropertyContextFactory.forRemoteProcessGroupProxyPassword(COMPONENT_ID);
+
+ assertEquals(SensitivePropertyCategory.COMPONENT_PROPERTY, context.category());
+ assertEquals(COMPONENT_ID, context.attributes().get(SensitivePropertyAttribute.COMPONENT_ID.getKey()));
+ assertTrue(context.attributes().containsKey(SensitivePropertyAttribute.PROPERTY_NAME.getKey()));
+ assertEquals(2, context.attributes().size());
+ }
+
+ @Test
+ void testForParameter() {
+ final SensitivePropertyContext context = SensitivePropertyContextFactory.forParameter(PARAMETER_CONTEXT_NAME, PARAMETER_NAME);
+
+ assertEquals(SensitivePropertyCategory.PARAMETER, context.category());
+ assertEquals(
+ Map.of(
+ SensitivePropertyAttribute.PARAMETER_CONTEXT_NAME.getKey(), PARAMETER_CONTEXT_NAME,
+ SensitivePropertyAttribute.PARAMETER_NAME.getKey(), PARAMETER_NAME
+ ),
+ context.attributes()
+ );
+ }
+
+ @Test
+ void testForAuthorizationToken() {
+ final SensitivePropertyContext context = SensitivePropertyContextFactory.forAuthorizationToken();
+
+ assertEquals(SensitivePropertyCategory.AUTHORIZATION_TOKEN, context.category());
+ assertEquals(Map.of(), context.attributes());
+ }
+
+ /**
+ * Contexts built for the same value must be equal regardless of the call site, because a Provider may bind the
+ * context to the encrypted value
+ */
+ @Test
+ void testContextsEqualForSameValue() {
+ assertEquals(
+ SensitivePropertyContextFactory.forComponent(COMPONENT_ID, COMPONENT_TYPE, PROPERTY_NAME),
+ SensitivePropertyContextFactory.forComponent(COMPONENT_ID, COMPONENT_TYPE, PROPERTY_NAME)
+ );
+ assertEquals(
+ SensitivePropertyContextFactory.forParameter(PARAMETER_CONTEXT_NAME, PARAMETER_NAME),
+ SensitivePropertyContextFactory.forParameter(PARAMETER_CONTEXT_NAME, PARAMETER_NAME)
+ );
+ assertEquals(SensitivePropertyContextFactory.forAuthorizationToken(), SensitivePropertyContextFactory.forAuthorizationToken());
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
index 7aeefd09b884..384ebf9132e7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
@@ -23,11 +23,6 @@
nifi-framework-core
jar
-
- org.apache.nifi
- nifi-property-encryptor
- 2.12.0-SNAPSHOT
-
org.apache.nifi
nifi-framework-core-api
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index 155b0520113e..32cfc3b7ae53 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -165,7 +165,6 @@
import org.apache.nifi.diagnostics.StorageUsage;
import org.apache.nifi.diagnostics.SystemDiagnostics;
import org.apache.nifi.diagnostics.SystemDiagnosticsFactory;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.events.BulletinFactory;
import org.apache.nifi.events.EventReporter;
@@ -223,6 +222,7 @@
import org.apache.nifi.reporting.StandardEventAccess;
import org.apache.nifi.reporting.UserAwareEventAccess;
import org.apache.nifi.scheduling.SchedulingStrategy;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
import org.apache.nifi.services.FlowService;
import org.apache.nifi.stream.io.LimitingInputStream;
import org.apache.nifi.stream.io.StreamUtils;
@@ -376,9 +376,9 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
private final int heartbeatDelaySeconds;
/**
- * The sensitive property string encryptor *
+ * Provider that protects sensitive values written to and read from the flow configuration
*/
- private final PropertyEncryptor encryptor;
+ private final PropertyEncryptionProvider propertyEncryptionProvider;
private final ScheduledExecutorService clusterTaskExecutor = new FlowEngine(3, "Clustering Tasks", true);
private final ResourceClaimManager resourceClaimManager = new StandardResourceClaimManager();
@@ -433,7 +433,7 @@ public static FlowController createStandaloneInstance(
final Authorizer authorizer,
final AuditService auditService,
final ComponentMetricReporter componentMetricReporter,
- final PropertyEncryptor encryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final BulletinRepository bulletinRepo,
final ExtensionDiscoveringManager extensionManager,
final StatusHistoryRepository statusHistoryRepository,
@@ -449,7 +449,7 @@ public static FlowController createStandaloneInstance(
authorizer,
auditService,
componentMetricReporter,
- encryptor,
+ propertyEncryptionProvider,
/* configuredForClustering */ false,
/* NodeProtocolSender */ null,
bulletinRepo,
@@ -472,7 +472,7 @@ public static FlowController createClusteredInstance(
final Authorizer authorizer,
final AuditService auditService,
final ComponentMetricReporter componentMetricReporter,
- final PropertyEncryptor encryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final NodeProtocolSender protocolSender,
final BulletinRepository bulletinRepo,
final ClusterCoordinator clusterCoordinator,
@@ -493,7 +493,7 @@ public static FlowController createClusteredInstance(
authorizer,
auditService,
componentMetricReporter,
- encryptor,
+ propertyEncryptionProvider,
/* configuredForClustering */ true,
protocolSender,
bulletinRepo,
@@ -516,7 +516,7 @@ private FlowController(
final Authorizer authorizer,
final AuditService auditService,
final ComponentMetricReporter componentMetricReporter,
- final PropertyEncryptor encryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final boolean configuredForClustering,
final NodeProtocolSender protocolSender,
final BulletinRepository bulletinRepo,
@@ -533,7 +533,7 @@ private FlowController(
maxTimerDrivenThreads = new AtomicInteger(10);
- this.encryptor = encryptor;
+ this.propertyEncryptionProvider = requireNonNull(propertyEncryptionProvider, "Property Encryption Provider required");
this.nifiProperties = nifiProperties;
this.heartbeatMonitor = heartbeatMonitor;
this.leaderElectionManager = leaderElectionManager;
@@ -1819,8 +1819,8 @@ public ConnectorValidationTrigger getConnectorValidationTrigger() {
return connectorValidationTrigger;
}
- public PropertyEncryptor getEncryptor() {
- return encryptor;
+ public PropertyEncryptionProvider getPropertyEncryptionProvider() {
+ return propertyEncryptionProvider;
}
/**
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
index d7467b1b28fe..341ba781b361 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
@@ -41,6 +41,7 @@
import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessGroup;
import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
+import org.apache.nifi.security.encryption.InternalPassThroughPropertyEncryptionProvider;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -93,7 +94,7 @@ private void copyGroupContents(final ProcessGroup sourceGroup, final ProcessGrou
.mapSensitiveConfiguration(true)
.mapPropertyDescriptors(true)
.stateLookup(VersionedComponentStateLookup.ENABLED_OR_DISABLED)
- .sensitiveValueEncryptor(value -> value)
+ .propertyEncryptionProvider(new InternalPassThroughPropertyEncryptionProvider())
.componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
.mapInstanceIdentifiers(true)
.mapControllerServiceReferencesToVersionedId(true)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerProcessGroupFacadeFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerProcessGroupFacadeFactory.java
index 53bbe05b9ff2..5d4c9c191bf4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerProcessGroupFacadeFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerProcessGroupFacadeFactory.java
@@ -32,6 +32,7 @@
import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
+import org.apache.nifi.security.encryption.InternalPassThroughPropertyEncryptionProvider;
public class FlowControllerProcessGroupFacadeFactory implements ProcessGroupFacadeFactory {
private final FlowController flowController;
@@ -46,7 +47,7 @@ public ProcessGroupFacade create(final ProcessGroup processGroup, final Componen
.mapSensitiveConfiguration(true)
.mapPropertyDescriptors(true)
.stateLookup(VersionedComponentStateLookup.IDENTITY_LOOKUP)
- .sensitiveValueEncryptor(value -> value)
+ .propertyEncryptionProvider(new InternalPassThroughPropertyEncryptionProvider())
.componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
.mapInstanceIdentifiers(true)
.mapControllerServiceReferencesToVersionedId(true)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index eefa16c8d1e5..6d155ec1f081 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -66,8 +66,6 @@
import org.apache.nifi.controller.service.StandardConfigurationContext;
import org.apache.nifi.deprecation.log.DeprecationLogger;
import org.apache.nifi.deprecation.log.DeprecationLoggerFactory;
-import org.apache.nifi.flow.VersionedExternalFlow;
-import org.apache.nifi.flow.VersionedParameterContext;
import org.apache.nifi.flowanalysis.FlowAnalysisRule;
import org.apache.nifi.flowfile.FlowFilePrioritizer;
import org.apache.nifi.groups.ProcessGroup;
@@ -93,11 +91,6 @@
import org.apache.nifi.parameter.StandardParameterReferenceManager;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.registry.flow.FlowRegistryClientNode;
-import org.apache.nifi.registry.flow.mapping.ComponentIdLookup;
-import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
-import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessGroup;
-import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
-import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
import org.apache.nifi.remote.PublicPort;
import org.apache.nifi.remote.StandardPublicPort;
import org.apache.nifi.remote.StandardRemoteProcessGroup;
@@ -305,7 +298,8 @@ public Port createLocalOutputPort(String id, String name) {
public ProcessGroup createProcessGroup(final String id, final String connectorId) {
final StatelessGroupNodeFactory statelessGroupNodeFactory = new StandardStatelessGroupNodeFactory(flowController, sslContext, flowController.createKerberosConfig(nifiProperties));
- final ProcessGroup group = new StandardProcessGroup(requireNonNull(id), flowController.getControllerServiceProvider(), processScheduler, flowController.getEncryptor(),
+ final ProcessGroup group = new StandardProcessGroup(requireNonNull(id), flowController.getControllerServiceProvider(), processScheduler,
+ flowController.getPropertyEncryptionProvider(),
flowController.getExtensionManager(), flowController.getStateManagerProvider(), this,
flowController.getReloadComponent(), flowController, flowController, nifiProperties, statelessGroupNodeFactory,
flowController.getAssetManager(), connectorId);
@@ -817,43 +811,6 @@ public ConnectorNode createConnector(final String type, final String id, final B
return connectorNode;
}
- private void copyGroupContents(final ProcessGroup sourceGroup, final ProcessGroup destinationGroup, final String componentIdSeed) {
- final FlowMappingOptions flowMappingOptions = new FlowMappingOptions.Builder()
- .mapSensitiveConfiguration(true)
- .mapPropertyDescriptors(true)
- .stateLookup(VersionedComponentStateLookup.ENABLED_OR_DISABLED)
- .sensitiveValueEncryptor(value -> value)
- .componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
- .mapInstanceIdentifiers(true)
- .mapControllerServiceReferencesToVersionedId(true)
- .mapFlowRegistryClientId(true)
- .mapAssetReferences(true)
- .build();
-
- final VersionedComponentFlowMapper flowMapper = new VersionedComponentFlowMapper(flowController.getExtensionManager(), flowMappingOptions);
- final Map parameterContexts = flowMapper.mapParameterContexts(sourceGroup, true, Map.of());
- final InstantiatedVersionedProcessGroup versionedGroup = flowMapper.mapProcessGroup(sourceGroup, flowController.getControllerServiceProvider(), this, true);
- final VersionedExternalFlow versionedExternalFlow = new VersionedExternalFlow();
- versionedExternalFlow.setFlowContents(versionedGroup);
- versionedExternalFlow.setExternalControllerServices(Map.of());
- versionedExternalFlow.setParameterProviders(Map.of());
- versionedExternalFlow.setParameterContexts(parameterContexts);
-
- destinationGroup.updateFlow(versionedExternalFlow, componentIdSeed, false, true, true);
- }
-
- private void gatherParameterContexts(final ProcessGroup sourceGroup, final Map parameterContexts) {
- final ParameterContext parameterContext = sourceGroup.getParameterContext();
- if (parameterContext != null && !parameterContexts.containsKey(parameterContext.getIdentifier())) {
- parameterContexts.put(parameterContext.getIdentifier(), parameterContext);
- }
-
- for (final ProcessGroup childGroup : sourceGroup.getProcessGroups()) {
- gatherParameterContexts(childGroup, parameterContexts);
- }
- }
-
-
@Override
public List getAllConnectors() {
return flowController.getConnectorRepository().getConnectors(ConnectorSyncMode.LOCAL_ONLY);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardStatelessGroupNodeFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardStatelessGroupNodeFactory.java
index a7d25e24a165..6cba62b68a5c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardStatelessGroupNodeFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardStatelessGroupNodeFactory.java
@@ -64,6 +64,7 @@
import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
import org.apache.nifi.reporting.BulletinRepository;
+import org.apache.nifi.security.encryption.InternalPassThroughPropertyEncryptionProvider;
import org.apache.nifi.stateless.engine.ProcessContextFactory;
import org.apache.nifi.stateless.engine.StandardStatelessEngine;
import org.apache.nifi.stateless.engine.StatelessEngine;
@@ -136,7 +137,7 @@ public StatelessGroupNode createStatelessGroupNode(final ProcessGroup group) {
.mapInstanceIdentifiers(true)
.mapPropertyDescriptors(false)
.mapSensitiveConfiguration(true)
- .sensitiveValueEncryptor(value -> value) // No need to encrypt, since we won't be persisting the flow
+ .propertyEncryptionProvider(new InternalPassThroughPropertyEncryptionProvider())
.stateLookup(VersionedComponentStateLookup.IDENTITY_LOOKUP)
.mapAssetReferences(true)
.build();
@@ -246,7 +247,6 @@ public Future> fetch(final Set bundleCoordinates,
final StatelessEngine statelessEngine = new StandardStatelessEngine.Builder()
.bulletinRepository(flowController.getBulletinRepository())
.counterRepository(flowController.getCounterRepository())
- .encryptor(flowController.getEncryptor())
.extensionManager(flowController.getExtensionManager())
.assetManager(flowController.getAssetManager())
.extensionRepository(extensionRepository)
@@ -299,7 +299,7 @@ public Future> fetch(final Set bundleCoordinates,
.componentIdGenerator(idGenerator)
.componentScheduler(ComponentScheduler.NOP_SCHEDULER)
.componentStopTimeout(Duration.ofSeconds(60))
- .propertyDecryptor(value -> value)
+ .propertyEncryptionProvider(new InternalPassThroughPropertyEncryptionProvider())
.topLevelGroupId(group.getIdentifier())
.updateDescendantVersionedFlows(true)
.updateGroupSettings(true)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSerializer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSerializer.java
index 403aee09489e..fdbd1f425855 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSerializer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSerializer.java
@@ -26,9 +26,6 @@
*/
public interface FlowSerializer {
- String ENC_PREFIX = "enc{";
- String ENC_SUFFIX = "}";
-
/**
* Transforms the flow configuration of a controller instance into something that can serialized
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSynchronizationUtils.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSynchronizationUtils.java
index 743eb2497a8a..ed3ebc1c0519 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSynchronizationUtils.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/FlowSynchronizationUtils.java
@@ -19,12 +19,18 @@
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.controller.ComponentNode;
-import org.apache.nifi.encrypt.EncryptionException;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.VersionedComponent;
import org.apache.nifi.flow.VersionedConfigurableExtension;
+import org.apache.nifi.flow.VersionedExtensionComponent;
import org.apache.nifi.flow.VersionedPropertyDescriptor;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.security.encryption.PropertyEncryptionEncoder;
+import org.apache.nifi.security.encryption.PropertyEncryptionException;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
+import org.apache.nifi.security.encryption.SensitivePropertyCodec;
+import org.apache.nifi.security.encryption.SensitivePropertyContext;
+import org.apache.nifi.security.encryption.SensitivePropertyContextFactory;
import org.apache.nifi.util.BundleUtils;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.slf4j.Logger;
@@ -62,7 +68,7 @@ static Set getSensitiveDynamicPropertyNames(final ComponentNode componen
extension.getProperties()
.entrySet()
.stream()
- .filter(entry -> isValueSensitive(entry.getValue()))
+ .filter(entry -> PropertyEncryptionEncoder.isEncrypted(entry.getValue()))
.map(Map.Entry::getKey)
.forEach(versionedSensitivePropertyNames::add);
@@ -82,25 +88,42 @@ static Set getSensitiveDynamicPropertyNames(final ComponentNode componen
.collect(Collectors.toSet());
}
- static boolean isValueSensitive(final String value) {
- return value != null && value.startsWith(FlowSerializer.ENC_PREFIX) && value.endsWith(FlowSerializer.ENC_SUFFIX);
- }
-
- static Map decryptProperties(final Map encrypted, final PropertyEncryptor encryptor) {
+ /**
+ * Decrypt the properties of a versioned component. The context supplied for each value is built from the instance identifier
+ * and type of the component, matching the context supplied when the flow was serialized.
+ *
+ * @param component Versioned component that owns the properties
+ * @param encrypted Properties of the component, which may contain encrypted values
+ * @param propertyEncryptionProvider Provider used to decrypt sensitive values
+ * @return Properties with sensitive values decrypted
+ */
+ static Map decryptProperties(final VersionedComponent component, final Map encrypted,
+ final PropertyEncryptionProvider propertyEncryptionProvider) {
final Map decrypted = new HashMap<>(encrypted.size());
- encrypted.forEach((key, value) -> decrypted.put(key, decrypt(value, encryptor)));
+ encrypted.forEach((key, value) -> decrypted.put(key, decrypt(value, getSensitivePropertyContext(component, key), propertyEncryptionProvider)));
return decrypted;
}
- static String decrypt(final String value, final PropertyEncryptor encryptor) {
- if (isValueSensitive(value)) {
+ /**
+ * Get the context describing a sensitive property of a versioned component. The instance identifier is used rather than the
+ * identifier, because the identifier of a mapped component is a generated versioned identifier while the context supplied when
+ * the value was encrypted described the component instance.
+ */
+ static SensitivePropertyContext getSensitivePropertyContext(final VersionedComponent component, final String propertyName) {
+ final String componentType = component instanceof final VersionedExtensionComponent extension ? extension.getType() : null;
+ return SensitivePropertyContextFactory.forComponent(component.getInstanceIdentifier(), componentType, propertyName);
+ }
+
+ static String decrypt(final String value, final SensitivePropertyContext context, final PropertyEncryptionProvider propertyEncryptionProvider) {
+ if (PropertyEncryptionEncoder.isEncrypted(value)) {
+ final String encryptedValue = PropertyEncryptionEncoder.getDecoded(value);
try {
- return encryptor.decrypt(value.substring(FlowSerializer.ENC_PREFIX.length(), value.length() - FlowSerializer.ENC_SUFFIX.length()));
- } catch (EncryptionException e) {
+ return SensitivePropertyCodec.decrypt(propertyEncryptionProvider, encryptedValue, context);
+ } catch (final PropertyEncryptionException 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.json.gz file";
+ "Check that the Property Encryption Provider configuration matches the configuration used to encrypt the flow.json.gz file";
logger.error(moreDescriptiveMessage, e);
- throw new EncryptionException(moreDescriptiveMessage, e);
+ throw new PropertyEncryptionException(moreDescriptiveMessage, e);
}
} else {
return value;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/StandardVersionedReportingTaskImporter.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/StandardVersionedReportingTaskImporter.java
index d48d77d85650..248118971b33 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/StandardVersionedReportingTaskImporter.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/StandardVersionedReportingTaskImporter.java
@@ -21,13 +21,13 @@
import org.apache.nifi.controller.ReportingTaskNode;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.controller.service.ControllerServiceNode;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedReportingTask;
import org.apache.nifi.flow.VersionedReportingTaskSnapshot;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.scheduling.SchedulingStrategy;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
import java.util.Collections;
import java.util.HashSet;
@@ -80,7 +80,7 @@ private ReportingTaskNode addReportingTask(final VersionedReportingTask reportin
taskNode.setAnnotationData(reportingTask.getAnnotationData());
final Set sensitiveDynamicPropertyNames = getSensitiveDynamicPropertyNames(taskNode, reportingTask);
- final Map decryptedProperties = decryptProperties(reportingTask.getProperties(), flowController.getEncryptor());
+ final Map decryptedProperties = decryptProperties(reportingTask, reportingTask.getProperties(), flowController.getPropertyEncryptionProvider());
taskNode.setProperties(decryptedProperties, false, sensitiveDynamicPropertyNames);
return taskNode;
}
@@ -98,7 +98,7 @@ private Set importControllerServices(final List sensitiveDynamicPropertyNames = getSensitiveDynamicPropertyNames(serviceNode, controllerService);
- final Map decryptedProperties = decryptProperties(controllerService.getProperties(), encryptor);
+ final Map decryptedProperties = decryptProperties(controllerService, controllerService.getProperties(), propertyEncryptionProvider);
serviceNode.setProperties(decryptedProperties, false, sensitiveDynamicPropertyNames);
} finally {
serviceNode.resumeValidationTrigger();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java
index 3b84ff727201..086a74c54d3c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java
@@ -45,9 +45,9 @@
import org.apache.nifi.registry.flow.FlowRegistryClientNode;
import org.apache.nifi.registry.flow.mapping.ComponentIdLookup;
import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
-import org.apache.nifi.registry.flow.mapping.SensitiveValueEncryptor;
import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
import java.util.ArrayList;
import java.util.Collections;
@@ -60,7 +60,8 @@ public class VersionedDataflowMapper {
private final VersionedComponentFlowMapper flowMapper;
private final ScheduledStateLookup stateLookup;
- public VersionedDataflowMapper(final FlowController flowController, final ExtensionManager extensionManager, final SensitiveValueEncryptor encryptor, final ScheduledStateLookup stateLookup) {
+ public VersionedDataflowMapper(final FlowController flowController, final ExtensionManager extensionManager,
+ final PropertyEncryptionProvider propertyEncryptionProvider, final ScheduledStateLookup stateLookup) {
this.flowController = flowController;
this.stateLookup = stateLookup;
@@ -70,7 +71,7 @@ public VersionedDataflowMapper(final FlowController flowController, final Extens
.mapSensitiveConfiguration(true)
.mapPropertyDescriptors(false)
.stateLookup(versionedComponentStateLookup)
- .sensitiveValueEncryptor(encryptor)
+ .propertyEncryptionProvider(propertyEncryptionProvider)
.componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
.mapInstanceIdentifiers(true)
.mapControllerServiceReferencesToVersionedId(false)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSerializer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSerializer.java
index f57f00d3a994..c7485faa1919 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSerializer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSerializer.java
@@ -25,7 +25,6 @@
import com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.flow.VersionedDataflow;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.nar.ExtensionManager;
import java.io.IOException;
@@ -47,8 +46,7 @@ public VersionedFlowSerializer(final ExtensionManager extensionManager) {
@Override
public VersionedDataflow transform(final FlowController controller, final ScheduledStateLookup stateLookup) throws FlowSerializationException {
- final PropertyEncryptor encryptor = controller.getEncryptor();
- final VersionedDataflowMapper dataflowMapper = new VersionedDataflowMapper(controller, extensionManager, encryptor::encrypt, stateLookup);
+ final VersionedDataflowMapper dataflowMapper = new VersionedDataflowMapper(controller, extensionManager, controller.getPropertyEncryptionProvider(), stateLookup);
final VersionedDataflow dataflow = dataflowMapper.createMapping();
return dataflow;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
index bfee5269475b..1e75666801f7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
@@ -54,7 +54,6 @@
import org.apache.nifi.controller.inheritance.FlowInheritability;
import org.apache.nifi.controller.inheritance.FlowInheritabilityCheck;
import org.apache.nifi.controller.service.ControllerServiceNode;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.ExecutionEngine;
import org.apache.nifi.flow.ScheduledState;
@@ -106,6 +105,10 @@
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
import org.apache.nifi.remote.RemoteGroupPort;
import org.apache.nifi.scheduling.SchedulingStrategy;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
+import org.apache.nifi.security.encryption.ProviderSensitiveValueDecryptor;
+import org.apache.nifi.security.encryption.SensitivePropertyContext;
+import org.apache.nifi.security.encryption.SensitivePropertyContextFactory;
import org.apache.nifi.services.FlowService;
import org.apache.nifi.util.BundleUtils;
import org.apache.nifi.util.FlowDifferenceFilters;
@@ -192,7 +195,7 @@ public synchronized void sync(final FlowController controller, final DataFlow pr
AffectedComponentSet activeSet = null;
if (!existingFlowEmpty) {
- flowComparison = compareFlows(existingDataFlow, proposedFlow, controller.getEncryptor());
+ flowComparison = compareFlows(existingDataFlow, proposedFlow, controller.getPropertyEncryptionProvider());
final Set flowDifferences = flowComparison.getDifferences();
if (flowDifferences.isEmpty()) {
@@ -439,7 +442,7 @@ private void synchronizeFlow(final FlowController controller, final DataFlow exi
try {
final VersionedDataflow versionedFlow = proposedFlow.getVersionedDataflow();
- final PropertyEncryptor encryptor = controller.getEncryptor();
+ final PropertyEncryptionProvider propertyEncryptionProvider = controller.getPropertyEncryptionProvider();
if (versionedFlow != null) {
controller.setMaxTimerDrivenThreadCount(versionedFlow.getMaxTimerDrivenThreadCount());
@@ -489,14 +492,14 @@ private void synchronizeFlow(final FlowController controller, final DataFlow exi
.updateGroupSettings(true)
.updateDescendantVersionedFlows(true)
.updateRpgUrls(true)
- .propertyDecryptor(encryptor::decrypt)
+ .propertyEncryptionProvider(propertyEncryptionProvider)
.build();
final FlowMappingOptions flowMappingOptions = new FlowMappingOptions.Builder()
.mapSensitiveConfiguration(true)
.mapPropertyDescriptors(false)
.stateLookup(stateLookup)
- .sensitiveValueEncryptor(encryptor::encrypt)
+ .propertyEncryptionProvider(propertyEncryptionProvider)
.componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
.mapInstanceIdentifiers(true)
.mapControllerServiceReferencesToVersionedId(false)
@@ -517,7 +520,7 @@ private void synchronizeFlow(final FlowController controller, final DataFlow exi
}
}
- private FlowComparison compareFlows(final DataFlow existingFlow, final DataFlow proposedFlow, final PropertyEncryptor encryptor) {
+ private FlowComparison compareFlows(final DataFlow existingFlow, final DataFlow proposedFlow, final PropertyEncryptionProvider propertyEncryptionProvider) {
final DifferenceDescriptor differenceDescriptor = new StaticDifferenceDescriptor();
final VersionedDataflow clusterVersionedFlow = proposedFlow.getVersionedDataflow();
@@ -548,7 +551,8 @@ private FlowComparison compareFlows(final DataFlow existingFlow, final DataFlow
);
final FlowComparator flowComparator = new StandardFlowComparator(localDataFlow, clusterDataFlow,
- differenceDescriptor, encryptor::decrypt, VersionedComponent::getInstanceIdentifier, FlowComparatorVersionedStrategy.DEEP);
+ differenceDescriptor, new ProviderSensitiveValueDecryptor(propertyEncryptionProvider),
+ VersionedComponent::getInstanceIdentifier, FlowComparatorVersionedStrategy.DEEP);
return flowComparator.compare();
}
@@ -602,7 +606,7 @@ private void inheritRegistryClients(final FlowController controller, final Versi
if (existing == null) {
addFlowRegistryClient(controller, versionedFlowRegistryClient);
} else if (affectedComponentSet.isFlowRegistryClientAffected(existing.getIdentifier())) {
- final Map decryptedProperties = decryptProperties(versionedFlowRegistryClient.getProperties(), controller.getEncryptor());
+ final Map decryptedProperties = decryptProperties(versionedFlowRegistryClient, versionedFlowRegistryClient.getProperties(), controller.getPropertyEncryptionProvider());
updateRegistry(existing, versionedFlowRegistryClient, decryptedProperties);
}
}
@@ -628,7 +632,7 @@ private void addFlowRegistryClient(final FlowController flowController, final Ve
final FlowRegistryClientNode flowRegistryClient = flowController.getFlowManager().createFlowRegistryClient(
versionedFlowRegistryClient.getType(), versionedFlowRegistryClient.getIdentifier(), coordinate, Collections.emptySet(), false, true, null);
- final Map decryptedProperties = decryptProperties(versionedFlowRegistryClient.getProperties(), flowController.getEncryptor());
+ final Map decryptedProperties = decryptProperties(versionedFlowRegistryClient, versionedFlowRegistryClient.getProperties(), flowController.getPropertyEncryptionProvider());
updateRegistry(flowRegistryClient, versionedFlowRegistryClient, decryptedProperties);
final ControllerServiceFactory serviceFactory = new StandardControllerServiceFactory(flowController.getExtensionManager(), flowController.getFlowManager(),
@@ -669,7 +673,7 @@ private void addReportingTask(final FlowController controller, final VersionedRe
final ReportingTaskNode taskNode = controller.createReportingTask(reportingTask.getType(), reportingTask.getInstanceIdentifier(), coordinate, false);
- final Map decryptedProperties = decryptProperties(reportingTask.getProperties(), controller.getEncryptor());
+ final Map decryptedProperties = decryptProperties(reportingTask, reportingTask.getProperties(), controller.getPropertyEncryptionProvider());
configureReportingTask(taskNode, reportingTask, decryptedProperties);
final ControllerServiceFactory serviceFactory = new StandardControllerServiceFactory(controller.getExtensionManager(), controller.getFlowManager(),
@@ -719,7 +723,7 @@ private void startReportingTask(final ReportingTaskNode taskNode, final Versione
}
private void updateReportingTask(final ReportingTaskNode taskNode, final VersionedReportingTask reportingTask, final FlowController controller) {
- final Map decryptedProperties = decryptProperties(reportingTask.getProperties(), controller.getEncryptor());
+ final Map decryptedProperties = decryptProperties(reportingTask, reportingTask.getProperties(), controller.getPropertyEncryptionProvider());
configureReportingTask(taskNode, reportingTask, decryptedProperties);
startReportingTask(taskNode, reportingTask, controller);
}
@@ -763,7 +767,7 @@ private void updateFlowAnalysisRule(final FlowAnalysisRuleNode ruleNode, final V
ruleNode.setEnforcementPolicy(flowAnalysisRule.getEnforcementPolicy());
final Set sensitiveDynamicPropertyNames = getSensitiveDynamicPropertyNames(ruleNode, flowAnalysisRule);
- final Map decryptedProperties = decryptProperties(flowAnalysisRule.getProperties(), controller.getEncryptor());
+ final Map decryptedProperties = decryptProperties(flowAnalysisRule, flowAnalysisRule.getProperties(), controller.getPropertyEncryptionProvider());
ruleNode.setProperties(decryptedProperties, false, sensitiveDynamicPropertyNames);
switch (flowAnalysisRule.getScheduledState()) {
@@ -793,9 +797,9 @@ private void inheritParameterProviders(final FlowController controller, final Ve
final ParameterProviderNode existing = flowManager.getParameterProvider(versionedParameterProvider.getInstanceIdentifier());
if (existing == null) {
- addParameterProvider(controller, versionedParameterProvider, controller.getEncryptor());
+ addParameterProvider(controller, versionedParameterProvider, controller.getPropertyEncryptionProvider());
} else if (affectedComponentSet.isParameterProviderAffected(existing.getIdentifier())) {
- final Map decryptedProperties = decryptProperties(versionedParameterProvider.getProperties(), controller.getEncryptor());
+ final Map decryptedProperties = decryptProperties(versionedParameterProvider, versionedParameterProvider.getProperties(), controller.getPropertyEncryptionProvider());
updateParameterProvider(existing, versionedParameterProvider, decryptedProperties);
}
}
@@ -807,13 +811,14 @@ private void inheritParameterProviders(final FlowController controller, final Ve
}
}
- private void addParameterProvider(final FlowController controller, final VersionedParameterProvider parameterProvider, final PropertyEncryptor encryptor) {
+ private void addParameterProvider(final FlowController controller, final VersionedParameterProvider parameterProvider,
+ final PropertyEncryptionProvider propertyEncryptionProvider) {
final BundleCoordinate coordinate = createBundleCoordinate(extensionManager, parameterProvider.getBundle(), parameterProvider.getType());
final ParameterProviderNode parameterProviderNode = controller.getFlowManager()
.createParameterProvider(parameterProvider.getType(), parameterProvider.getInstanceIdentifier(), coordinate, false);
- final Map decryptedProperties = decryptProperties(parameterProvider.getProperties(), encryptor);
+ final Map decryptedProperties = decryptProperties(parameterProvider, parameterProvider.getProperties(), propertyEncryptionProvider);
updateParameterProvider(parameterProviderNode, parameterProvider, decryptedProperties);
final ControllerServiceFactory serviceFactory = new StandardControllerServiceFactory(controller.getExtensionManager(), controller.getFlowManager(),
@@ -865,7 +870,7 @@ private void inheritParameterContexts(final FlowController controller, final Ver
parameterContexts.forEach(context -> namedParameterContexts.put(context.getName(), context));
for (final VersionedParameterContext versionedParameterContext : parameterContexts) {
- inheritParameterContext(versionedParameterContext, controller.getFlowManager(), namedParameterContexts, controller.getEncryptor(), controller.getAssetManager());
+ inheritParameterContext(versionedParameterContext, controller.getFlowManager(), namedParameterContexts, controller.getPropertyEncryptionProvider(), controller.getAssetManager());
}
});
}
@@ -874,15 +879,15 @@ private void inheritParameterContext(
final VersionedParameterContext versionedParameterContext,
final FlowManager flowManager,
final Map namedParameterContexts,
- final PropertyEncryptor encryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final AssetManager assetManager
) {
final ParameterContextManager contextManager = flowManager.getParameterContextManager();
final ParameterContext existingContext = contextManager.getParameterContextNameMapping().get(versionedParameterContext.getName());
if (existingContext == null) {
- addParameterContext(versionedParameterContext, flowManager, namedParameterContexts, encryptor, assetManager);
+ addParameterContext(versionedParameterContext, flowManager, namedParameterContexts, propertyEncryptionProvider, assetManager);
} else {
- updateParameterContext(versionedParameterContext, existingContext, flowManager, namedParameterContexts, encryptor, assetManager);
+ updateParameterContext(versionedParameterContext, existingContext, flowManager, namedParameterContexts, propertyEncryptionProvider, assetManager);
}
}
@@ -890,10 +895,10 @@ private void addParameterContext(
final VersionedParameterContext versionedParameterContext,
final FlowManager flowManager,
final Map namedParameterContexts,
- final PropertyEncryptor encryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final AssetManager assetManager
) {
- final Map parameters = createParameterMap(flowManager, versionedParameterContext, encryptor, assetManager);
+ final Map parameters = createParameterMap(flowManager, versionedParameterContext, propertyEncryptionProvider, assetManager);
final ParameterContextManager contextManager = flowManager.getParameterContextManager();
final List referenceIds = findReferencedParameterContextIds(versionedParameterContext, contextManager, namedParameterContexts);
@@ -941,7 +946,7 @@ private List findReferencedParameterContextIds(
private Map createParameterMap(
final FlowManager flowManager,
final VersionedParameterContext versionedParameterContext,
- final PropertyEncryptor encryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final AssetManager assetManager
) {
final Map providedParameters = getProvidedParameters(flowManager, versionedParameterContext);
@@ -972,7 +977,8 @@ private Map createParameterMap(
parameterValue = providedParameter.getValue();
}
} else if (versioned.isSensitive()) {
- parameterValue = decrypt(rawValue, encryptor);
+ final SensitivePropertyContext context = SensitivePropertyContextFactory.forParameter(versionedParameterContext.getName(), name);
+ parameterValue = decrypt(rawValue, context, propertyEncryptionProvider);
} else {
parameterValue = rawValue;
}
@@ -1018,10 +1024,10 @@ private void updateParameterContext(
final ParameterContext parameterContext,
final FlowManager flowManager,
final Map namedParameterContexts,
- final PropertyEncryptor encryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final AssetManager assetManager
) {
- final Map parameters = createParameterMap(flowManager, versionedParameterContext, encryptor, assetManager);
+ final Map parameters = createParameterMap(flowManager, versionedParameterContext, propertyEncryptionProvider, assetManager);
final Map currentValues = new HashMap<>();
final Map> currentAssetReferences = new HashMap<>();
@@ -1151,7 +1157,7 @@ private void inheritControllerServices(final FlowController controller, final Ve
final ControllerServiceNode serviceNode = flowManager.getRootControllerService(versionedControllerService.getInstanceIdentifier());
if (controllerServicesAddedAndProperties.containsKey(serviceNode) || affectedComponentSet.isControllerServiceAffected(serviceNode.getIdentifier())) {
// Set Decrypted Properties for subsequent migrate configuration using actual values
- final Map decryptedProperties = decryptProperties(versionedControllerService.getProperties(), controller.getEncryptor());
+ final Map decryptedProperties = decryptProperties(versionedControllerService, versionedControllerService.getProperties(), controller.getPropertyEncryptionProvider());
controllerServicesAddedAndProperties.put(serviceNode, decryptedProperties);
updateRootControllerService(serviceNode, versionedControllerService, decryptedProperties);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/PropertyEncryptorFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/PropertyEncryptorFactory.java
deleted file mode 100644
index 8a2e8a70b0cf..000000000000
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/PropertyEncryptorFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.encrypt;
-
-import org.apache.nifi.util.NiFiProperties;
-
-import java.util.Objects;
-
-/**
- * Property Encryptor Factory for encapsulating instantiation of Property Encryptors based on various parameters
- */
-public class PropertyEncryptorFactory {
- private static final String KEY_REQUIRED = String.format("NiFi Sensitive Properties Key [%s] is required", NiFiProperties.SENSITIVE_PROPS_KEY);
-
- /**
- * Get Property Encryptor using NiFi Properties
- *
- * @param properties NiFi Properties
- * @return Property Encryptor
- */
- public static PropertyEncryptor getPropertyEncryptor(final NiFiProperties properties) {
- Objects.requireNonNull(properties, "NiFi Properties is required");
- final String algorithm = properties.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM);
- String password = properties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY);
-
- if (password == null || password.isEmpty()) {
- throw new IllegalArgumentException(KEY_REQUIRED);
- }
-
- return new PropertyEncryptorBuilder(password).setAlgorithm(algorithm).build();
- }
-}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
index a73c625ea92f..4ea40e13b604 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
@@ -47,14 +47,11 @@
import org.apache.nifi.controller.status.history.VolatileComponentStatusRepository;
import org.apache.nifi.diagnostics.DiagnosticsFactory;
import org.apache.nifi.diagnostics.bootstrap.BootstrapDiagnosticsFactory;
-import org.apache.nifi.encrypt.PropertyEncryptor;
-import org.apache.nifi.encrypt.PropertyEncryptorFactory;
import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser;
import org.apache.nifi.extension.manifest.parser.jaxb.JAXBExtensionManifestParser;
import org.apache.nifi.manifest.RuntimeManifestService;
import org.apache.nifi.manifest.StandardRuntimeManifestService;
import org.apache.nifi.nar.ExtensionDiscoveringManager;
-import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.nar.NarComponentManager;
import org.apache.nifi.nar.NarLoader;
import org.apache.nifi.nar.NarLoaderHolder;
@@ -66,9 +63,7 @@
import org.apache.nifi.nar.StandardNarManager;
import org.apache.nifi.reporting.BulletinRepository;
import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
-import org.apache.nifi.security.encryption.PropertyEncryptionProviderInitializationContext;
-import org.apache.nifi.security.encryption.SensitivePropertyContext;
-import org.apache.nifi.security.encryption.StandardPropertyEncryptionProviderInitializationContext;
+import org.apache.nifi.security.encryption.PropertyEncryptionProviderFactory;
import org.apache.nifi.services.FlowService;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
@@ -84,12 +79,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import java.io.IOException;
import java.time.Duration;
-import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
@@ -221,7 +213,7 @@ public FlowController flowController() throws Exception {
authorizer,
auditService,
componentMetricReporter(),
- propertyEncryptor(),
+ propertyEncryptionProvider(),
bulletinRepository,
extensionManager,
statusHistoryRepository(),
@@ -237,7 +229,7 @@ public FlowController flowController() throws Exception {
authorizer,
auditService,
componentMetricReporter(),
- propertyEncryptor(),
+ propertyEncryptionProvider(),
nodeProtocolSender,
bulletinRepository,
clusterCoordinator,
@@ -312,16 +304,6 @@ public RuleViolationsManager ruleViolationsManager() {
return new StandardRuleViolationsManager();
}
- /**
- * Property Encryptor configured using Application Properties
- *
- * @return Property Encryptor
- */
- @Bean
- public PropertyEncryptor propertyEncryptor() {
- return PropertyEncryptorFactory.getPropertyEncryptor(properties);
- }
-
/**
* Status History Repository configured from NiFi Application Properties
*
@@ -558,88 +540,13 @@ public ComponentMetricReporter componentMetricReporter() {
}
/**
- * Property Encryption Provider configured from NiFi Application Properties
+ * Property Encryption Provider configured from NiFi Application Properties. Installations that do not configure an
+ * implementation class use the password-based provider, which derives a secret key from the sensitive properties key.
*
* @return Property Encryption Provider
*/
@Bean
public PropertyEncryptionProvider propertyEncryptionProvider() {
- final PropertyEncryptionProvider propertyEncryptionProvider;
-
- final String configuredClassName = properties.getProperty(NiFiProperties.PROPERTY_ENCRYPTION_PROVIDER_IMPLEMENTATION);
- if (configuredClassName == null || configuredClassName.isBlank()) {
- // Set null implementation for initial configuration pending wiring to framework components
- propertyEncryptionProvider = null;
- } else {
- try {
- final PropertyEncryptionProvider provider = NarThreadContextClassLoader.createInstance(
- extensionManager, configuredClassName, PropertyEncryptionProvider.class, properties
- );
- propertyEncryptionProvider = initializePropertyEncryptionProvider(provider);
- } catch (final Exception e) {
- throw new IllegalStateException("Failed to create PropertyEncryptionProvider with class [%s]".formatted(configuredClassName), e);
- }
- }
-
- return propertyEncryptionProvider;
- }
-
- private PropertyEncryptionProvider initializePropertyEncryptionProvider(final PropertyEncryptionProvider propertyEncryptionProvider) {
- final PropertyEncryptionProvider wrappedPropertyEncryptionProvider = wrapWithComponentNarLoader(propertyEncryptionProvider);
- try {
- final PropertyEncryptionProviderInitializationContext initializationContext = new StandardPropertyEncryptionProviderInitializationContext(
- getPropertyEncryptionProviderProperties(), sslContext, trustManager
- );
- wrappedPropertyEncryptionProvider.initialize(initializationContext);
- return wrappedPropertyEncryptionProvider;
- } catch (final RuntimeException e) {
- try {
- wrappedPropertyEncryptionProvider.close();
- } catch (final Exception closeException) {
- e.addSuppressed(closeException);
- }
- throw e;
- }
- }
-
- private PropertyEncryptionProvider wrapWithComponentNarLoader(final PropertyEncryptionProvider propertyEncryptionProvider) {
- final ClassLoader componentClassLoader = propertyEncryptionProvider.getClass().getClassLoader();
- return new PropertyEncryptionProvider() {
- @Override
- public void initialize(final PropertyEncryptionProviderInitializationContext context) {
- try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(componentClassLoader)) {
- propertyEncryptionProvider.initialize(context);
- }
- }
-
- @Override
- public byte[] encrypt(final byte[] property, final SensitivePropertyContext context) {
- try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(componentClassLoader)) {
- return propertyEncryptionProvider.encrypt(property, context);
- }
- }
-
- @Override
- public byte[] decrypt(final byte[] encryptedProperty, final SensitivePropertyContext context) {
- try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(componentClassLoader)) {
- return propertyEncryptionProvider.decrypt(encryptedProperty, context);
- }
- }
-
- @Override
- public void close() throws IOException {
- try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(componentClassLoader)) {
- propertyEncryptionProvider.close();
- }
- }
- };
- }
-
- private Map getPropertyEncryptionProviderProperties() {
- final String prefix = NiFiProperties.PROPERTY_ENCRYPTION_PROVIDER_PREFIX;
- return properties.getPropertiesWithPrefix(prefix)
- .entrySet()
- .stream()
- .collect(Collectors.toMap(entry -> entry.getKey().substring(prefix.length()), Map.Entry::getValue));
+ return PropertyEncryptionProviderFactory.getPropertyEncryptionProvider(extensionManager, properties, sslContext, trustManager);
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java
index 221f730de0fc..82a041e97576 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java
@@ -34,7 +34,6 @@
import org.apache.nifi.controller.parameter.ParameterProviderLookup;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceProvider;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.ScheduledState;
import org.apache.nifi.flow.VersionedConnector;
@@ -58,6 +57,8 @@
import org.apache.nifi.parameter.StandardParameterProviderConfiguration;
import org.apache.nifi.persistence.FlowConfigurationArchiveManager;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
+import org.apache.nifi.security.encryption.PropertyEncryptionEncoder;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
import org.apache.nifi.services.FlowService;
import org.apache.nifi.util.NiFiProperties;
import org.junit.jupiter.api.BeforeEach;
@@ -109,10 +110,12 @@ class VersionedFlowSynchronizerTest {
private static final String SENSITIVE_PROPERTY_NAME = "Protected";
- private static final String ENCRYPTED_PROPERTY_VALUE = "enc{encoded}";
+ private static final String ENCRYPTED_PROPERTY_VALUE = PropertyEncryptionEncoder.getEncoded("656e636f646564");
private static final String DECRYPTED_PROPERTY_VALUE = "decoded";
+ private static final byte[] DECRYPTED_PROPERTY_BYTES = DECRYPTED_PROPERTY_VALUE.getBytes(StandardCharsets.UTF_8);
+
private static final String REPORTING_TASK_INSTANCE_ID = "reporting-task-instance-id";
private static final String REPORTING_TASK_TYPE = "org.apache.nifi.reporting.TestReportingTask";
@@ -145,7 +148,7 @@ class VersionedFlowSynchronizerTest {
private SnippetManager snippetManager;
@Mock
- private PropertyEncryptor encryptor;
+ private PropertyEncryptionProvider propertyEncryptionProvider;
@Mock
private VersionedComponentStateLookup stateLookup;
@@ -197,7 +200,7 @@ void testSyncInheritControllerServicesMigrateConfiguration() {
// Mock Property Descriptor for sensitive Property with decrypted value
final PropertyDescriptor sensitivePropertyDescriptor = mock(PropertyDescriptor.class);
when(controllerServiceNode.getPropertyDescriptor(eq(SENSITIVE_PROPERTY_NAME))).thenReturn(sensitivePropertyDescriptor);
- when(encryptor.decrypt(any())).thenReturn(DECRYPTED_PROPERTY_VALUE);
+ when(propertyEncryptionProvider.decrypt(any(), any())).thenReturn(DECRYPTED_PROPERTY_BYTES);
// Return created Controller Service Node as a result of null returned for initial lookup method
when(flowManager.createControllerService(any(), any(), any(), any(), eq(true), eq(true), any())).thenReturn(controllerServiceNode);
@@ -232,7 +235,7 @@ void testSyncInheritReportingTasksMigrateConfigurationBeforeStart() {
// Mock Property Descriptor for sensitive Property with decrypted value
final PropertyDescriptor sensitivePropertyDescriptor = mock(PropertyDescriptor.class);
when(reportingTaskNode.getPropertyDescriptor(eq(SENSITIVE_PROPERTY_NAME))).thenReturn(sensitivePropertyDescriptor);
- when(encryptor.decrypt(any())).thenReturn(DECRYPTED_PROPERTY_VALUE);
+ when(propertyEncryptionProvider.decrypt(any(), any())).thenReturn(DECRYPTED_PROPERTY_BYTES);
// Return created Reporting Task Node
when(flowController.createReportingTask(any(), eq(REPORTING_TASK_INSTANCE_ID), any(), eq(false))).thenReturn(reportingTaskNode);
@@ -478,7 +481,7 @@ private void setFlowController(final ConnectorRepository connectorRepository) {
when(dataFlow.getVersionedDataflow()).thenReturn(versionedDataflow);
when(dataFlow.getFlow()).thenReturn("{}".getBytes(StandardCharsets.UTF_8));
when(versionedDataflow.getRootGroup()).thenReturn(versionedRootGroup);
- when(flowController.getEncryptor()).thenReturn(encryptor);
+ when(flowController.getPropertyEncryptionProvider()).thenReturn(propertyEncryptionProvider);
when(flowController.createVersionedComponentStateLookup(any())).thenReturn(stateLookup);
when(flowController.getControllerServiceProvider()).thenReturn(controllerServiceProvider);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-headless-server/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-headless-server/pom.xml
index 998a4bffc34b..60937abe5750 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-headless-server/pom.xml
+++ b/nifi-framework-bundle/nifi-framework/nifi-headless-server/pom.xml
@@ -57,11 +57,6 @@
nifi-framework-core-api
2.12.0-SNAPSHOT
-
- org.apache.nifi
- nifi-property-encryptor
- 2.12.0-SNAPSHOT
-
org.apache.nifi
nifi-framework-nar-utils
diff --git a/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java b/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java
index 08419a64e0e2..c38afaa0391c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java
@@ -47,8 +47,6 @@
import org.apache.nifi.diagnostics.DiagnosticsFactory;
import org.apache.nifi.diagnostics.ThreadDumpTask;
import org.apache.nifi.diagnostics.bootstrap.BootstrapDiagnosticsFactory;
-import org.apache.nifi.encrypt.PropertyEncryptor;
-import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
import org.apache.nifi.events.VolatileBulletinRepository;
import org.apache.nifi.framework.ssl.FrameworkSslContextProvider;
import org.apache.nifi.nar.ExtensionManager;
@@ -63,6 +61,8 @@
import org.apache.nifi.nar.StandardNarLoader;
import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.reporting.BulletinRepository;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
+import org.apache.nifi.security.encryption.PropertyEncryptionProviderFactory;
import org.apache.nifi.services.FlowService;
import org.apache.nifi.util.FlowParser;
import org.apache.nifi.util.NiFiProperties;
@@ -129,15 +129,14 @@ public void preDestruction() throws AuthorizerDestructionException {
}
};
- final String propertiesKey = props.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY);
- final String propertiesAlgorithm = props.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM);
- final PropertyEncryptor encryptor = new PropertyEncryptorBuilder(propertiesKey).setAlgorithm(propertiesAlgorithm).build();
final BulletinRepository bulletinRepository = new VolatileBulletinRepository();
final StatusHistoryRepository statusHistoryRepository = getStatusHistoryRepository(extensionManager);
final FrameworkSslContextProvider sslContextProvider = new FrameworkSslContextProvider(props);
final SSLContext sslContext = sslContextProvider.loadSslContext().orElse(null);
final StateManagerProvider stateManagerProvider = StandardStateManagerProvider.create(props, sslContext, extensionManager, ParameterLookup.EMPTY);
+ final PropertyEncryptionProvider propertyEncryptionProvider =
+ PropertyEncryptionProviderFactory.getPropertyEncryptionProvider(extensionManager, props, sslContext, null);
flowController = FlowController.createStandaloneInstance(
flowFileEventRepository,
@@ -146,7 +145,7 @@ public void preDestruction() throws AuthorizerDestructionException {
authorizer,
auditService,
componentMetricReporter,
- encryptor,
+ propertyEncryptionProvider,
bulletinRepository,
extensionManager,
statusHistoryRepository,
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 90db89eea4db..b178d39d6769 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -6233,7 +6233,7 @@ public CopyResponseEntity copyComponents(final String groupId, final CopyRequest
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
final FlowMappingOptions mappingOptions = new FlowMappingOptions.Builder()
- .sensitiveValueEncryptor(null)
+ .propertyEncryptionProvider(null)
.stateLookup(VersionedComponentStateLookup.ENABLED_OR_DISABLED)
.componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
.mapPropertyDescriptors(true)
@@ -6374,7 +6374,7 @@ public RegisteredFlowSnapshot getCurrentFlowSnapshotByGroupId(final String proce
}
final FlowMappingOptions mappingOptions = new FlowMappingOptions.Builder()
- .sensitiveValueEncryptor(null)
+ .propertyEncryptionProvider(null)
.stateLookup(VersionedComponentStateLookup.ENABLED_OR_DISABLED)
.componentIdLookup(ComponentIdLookup.VERSIONED_OR_GENERATE)
.mapPropertyDescriptors(true)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
index 306edc48fd83..ef23c1e085c6 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml
@@ -97,11 +97,6 @@
nifi-framework-nar-utils
2.12.0-SNAPSHOT
-
- org.apache.nifi
- nifi-property-encryptor
- 2.12.0-SNAPSHOT
-
org.apache.nifi
nifi-framework-core-api
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
index e62f7c3d7149..a8b763866301 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
@@ -21,7 +21,7 @@
import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.components.state.StateManager;
import org.apache.nifi.components.state.StateManagerProvider;
-import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.StandardAuthenticationEntryPoint;
@@ -99,7 +99,7 @@ public class OidcSecurityConfiguration {
private final StateManagerProvider stateManagerProvider;
- private final PropertyEncryptor propertyEncryptor;
+ private final PropertyEncryptionProvider propertyEncryptionProvider;
private final BearerTokenProvider bearerTokenProvider;
@@ -122,7 +122,7 @@ public OidcSecurityConfiguration(
final NiFiProperties properties,
final TaskScheduler taskScheduler,
final StateManagerProvider stateManagerProvider,
- final PropertyEncryptor propertyEncryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final BearerTokenProvider bearerTokenProvider,
final BearerTokenResolver bearerTokenResolver,
final ClientRegistrationRepository clientRegistrationRepository,
@@ -137,7 +137,7 @@ public OidcSecurityConfiguration(
this.properties = Objects.requireNonNull(properties, "Properties required");
this.taskScheduler = Objects.requireNonNull(taskScheduler, "Task Scheduler required");
this.stateManagerProvider = Objects.requireNonNull(stateManagerProvider, "State Manager Provider required");
- this.propertyEncryptor = Objects.requireNonNull(propertyEncryptor, "Property Encryptor required");
+ this.propertyEncryptionProvider = Objects.requireNonNull(propertyEncryptionProvider, "Property Encryption Provider required");
this.bearerTokenProvider = Objects.requireNonNull(bearerTokenProvider, "Bearer Token Provider required");
this.bearerTokenResolver = Objects.requireNonNull(bearerTokenResolver, "Bearer Token Resolver required");
this.clientRegistrationRepository = Objects.requireNonNull(clientRegistrationRepository, "Registration Repository required");
@@ -328,7 +328,7 @@ public AuthorizedClientExpirationCommand authorizedClientExpirationCommand() {
*/
@Bean
public AuthorizedClientConverter authorizedClientConverter() {
- return new StandardAuthorizedClientConverter(propertyEncryptor, clientRegistrationRepository);
+ return new StandardAuthorizedClientConverter(propertyEncryptionProvider, clientRegistrationRepository);
}
/**
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverter.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverter.java
index b92e5a1720fe..6169fbff9a35 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverter.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverter.java
@@ -18,7 +18,10 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
+import org.apache.nifi.security.encryption.SensitivePropertyCodec;
+import org.apache.nifi.security.encryption.SensitivePropertyContext;
+import org.apache.nifi.security.encryption.SensitivePropertyContextFactory;
import org.apache.nifi.web.security.jwt.provider.SupportedClaim;
import org.apache.nifi.web.security.oidc.OidcConfigurationException;
import org.apache.nifi.web.security.oidc.client.web.OidcAuthorizedClient;
@@ -43,15 +46,17 @@ public class StandardAuthorizedClientConverter implements AuthorizedClientConver
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModules(new JavaTimeModule());
- private final PropertyEncryptor propertyEncryptor;
+ private static final SensitivePropertyContext AUTHORIZATION_TOKEN_CONTEXT = SensitivePropertyContextFactory.forAuthorizationToken();
+
+ private final PropertyEncryptionProvider propertyEncryptionProvider;
private final ClientRegistrationRepository clientRegistrationRepository;
public StandardAuthorizedClientConverter(
- final PropertyEncryptor propertyEncryptor,
+ final PropertyEncryptionProvider propertyEncryptionProvider,
final ClientRegistrationRepository clientRegistrationRepository
) {
- this.propertyEncryptor = Objects.requireNonNull(propertyEncryptor, "Property Encryptor required");
+ this.propertyEncryptionProvider = Objects.requireNonNull(propertyEncryptionProvider, "Property Encryption Provider required");
this.clientRegistrationRepository = Objects.requireNonNull(clientRegistrationRepository, "Client Registry Repository required");
}
@@ -68,7 +73,7 @@ public String getEncoded(final OidcAuthorizedClient oidcAuthorizedClient) {
try {
final AuthorizedClient authorizedClient = writeAuthorizedClient(oidcAuthorizedClient);
final String serialized = OBJECT_MAPPER.writeValueAsString(authorizedClient);
- return propertyEncryptor.encrypt(serialized);
+ return SensitivePropertyCodec.encrypt(propertyEncryptionProvider, serialized, AUTHORIZATION_TOKEN_CONTEXT);
} catch (final Exception e) {
throw new OidcConfigurationException("OIDC Authorized Client serialization failed", e);
}
@@ -85,7 +90,7 @@ public OidcAuthorizedClient getDecoded(final String encoded) {
Objects.requireNonNull(encoded, "Encoded representation required");
try {
- final String decrypted = propertyEncryptor.decrypt(encoded);
+ final String decrypted = SensitivePropertyCodec.decrypt(propertyEncryptionProvider, encoded, AUTHORIZATION_TOKEN_CONTEXT);
final AuthorizedClient authorizedClient = OBJECT_MAPPER.readValue(decrypted, AuthorizedClient.class);
return readAuthorizedClient(authorizedClient);
} catch (final Exception e) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverterTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverterTest.java
index d0ae0e6e19a5..d7ecc2f6a8d8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverterTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/converter/StandardAuthorizedClientConverterTest.java
@@ -16,7 +16,9 @@
*/
package org.apache.nifi.web.security.oidc.client.web.converter;
-import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
+import org.apache.nifi.security.encryption.PropertyEncryptionProviderInitializationContext;
+import org.apache.nifi.security.encryption.SensitivePropertyContext;
import org.apache.nifi.web.security.jwt.provider.SupportedClaim;
import org.apache.nifi.web.security.logout.LogoutRequest;
import org.apache.nifi.web.security.oidc.client.web.OidcAuthorizedClient;
@@ -68,7 +70,7 @@ class StandardAuthorizedClientConverterTest {
@BeforeEach
void setConverter() {
- converter = new StandardAuthorizedClientConverter(new StringPropertyEncryptor(), clientRegistrationRepository);
+ converter = new StandardAuthorizedClientConverter(new PassThroughPropertyEncryptionProvider(), clientRegistrationRepository);
}
@Test
@@ -168,15 +170,19 @@ ClientRegistration getClientRegistration() {
.build();
}
- private static class StringPropertyEncryptor implements PropertyEncryptor {
+ private static class PassThroughPropertyEncryptionProvider implements PropertyEncryptionProvider {
@Override
- public String encrypt(String property) {
+ public void initialize(final PropertyEncryptionProviderInitializationContext context) {
+ }
+
+ @Override
+ public byte[] encrypt(final byte[] property, final SensitivePropertyContext context) {
return property;
}
@Override
- public String decrypt(String encryptedProperty) {
+ public byte[] decrypt(final byte[] encryptedProperty, final SensitivePropertyContext context) {
return encryptedProperty;
}
}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/SensitiveValueDecryptor.java b/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/SensitiveValueDecryptor.java
new file mode 100644
index 000000000000..15251bd21e93
--- /dev/null
+++ b/nifi-registry/nifi-registry-core/nifi-registry-flow-diff/src/main/java/org/apache/nifi/registry/flow/diff/SensitiveValueDecryptor.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Decrypts a sensitive value so that two flows can be compared on the decrypted values.
+ *
+ * The owning component and the name of the value are supplied because an implementation may need to describe the
+ * location of a value in order to decrypt it.
+ */
+@FunctionalInterface
+public interface SensitiveValueDecryptor {
+ /**
+ * Decrypt a sensitive value
+ *
+ * @param owner Component that owns the value, which is the Parameter Context for a Parameter value
+ * @param valueName Name of the property or Parameter that holds the value
+ * @param encryptedValue Encrypted value without the surrounding encryption markers
+ * @return Decrypted value
+ */
+ String decrypt(VersionedComponent owner, String valueName, String encryptedValue);
+}
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 6ed621456526..021185369532 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
@@ -39,8 +39,6 @@
import org.apache.nifi.flow.VersionedRemoteGroupPort;
import org.apache.nifi.flow.VersionedRemoteProcessGroup;
import org.apache.nifi.flow.VersionedReportingTask;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
@@ -55,8 +53,6 @@
import java.util.stream.Stream;
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 = "}";
private static final String FLOW_VERSION = "Flow Version";
@@ -72,13 +68,30 @@ public class StandardFlowComparator implements FlowComparator {
private final ComparableDataFlow flowA;
private final ComparableDataFlow flowB;
private final DifferenceDescriptor differenceDescriptor;
- private final Function propertyDecryptor;
+ private final SensitiveValueDecryptor propertyDecryptor;
private final Function idLookup;
private final FlowComparatorVersionedStrategy flowComparatorVersionedStrategy;
+ /**
+ * Create a comparator with a decryptor that does not require the location of a sensitive value
+ *
+ * @param propertyDecryptor Decryptor that receives only the encrypted value
+ */
public StandardFlowComparator(final ComparableDataFlow flowA, final ComparableDataFlow flowB,
final DifferenceDescriptor differenceDescriptor, final Function propertyDecryptor,
final Function idLookup, final FlowComparatorVersionedStrategy flowComparatorVersionedStrategy) {
+ this(flowA, flowB, differenceDescriptor, (owner, valueName, encryptedValue) -> propertyDecryptor.apply(encryptedValue),
+ idLookup, flowComparatorVersionedStrategy);
+ }
+
+ /**
+ * Create a comparator with a decryptor that receives the component and value name that locate each sensitive value
+ *
+ * @param propertyDecryptor Decryptor that receives the owning component and the name of the value
+ */
+ public StandardFlowComparator(final ComparableDataFlow flowA, final ComparableDataFlow flowB,
+ final DifferenceDescriptor differenceDescriptor, final SensitiveValueDecryptor propertyDecryptor,
+ final Function idLookup, final FlowComparatorVersionedStrategy flowComparatorVersionedStrategy) {
this.flowA = flowA;
this.flowB = flowB;
this.differenceDescriptor = differenceDescriptor;
@@ -267,8 +280,8 @@ void compare(final VersionedParameterContext contextA, final VersionedParameterC
continue;
}
- final String decryptedValueA = decryptValue(parameterA);
- final String decryptedValueB = decryptValue(parameterB);
+ final String decryptedValueA = decryptValue(contextA, parameterA);
+ final String decryptedValueB = decryptValue(contextB, parameterB);
if (!Objects.equals(decryptedValueA, decryptedValueB)) {
final String valueA = parameterA.isSensitive() ? "" : parameterA.getValue();
final String valueB = parameterB.isSensitive() ? "" : parameterB.getValue();
@@ -354,32 +367,50 @@ private void compare(final VersionedControllerService serviceA, final VersionedC
addIfDifferent(differences, DifferenceType.BULLETIN_LEVEL_CHANGED, serviceA, serviceB, VersionedControllerService::getBulletinLevel, true, "WARN");
}
- private String decrypt(final String value, final VersionedPropertyDescriptor descriptor) {
- if (value == null) {
- return null;
- }
+ private String decrypt(final String value, final VersionedComponent component, final String propertyName) {
+ final String decrypted;
- final boolean sensitive = (descriptor == null || descriptor.isSensitive()) && value.startsWith(ENCRYPTED_VALUE_PREFIX) && value.endsWith(ENCRYPTED_VALUE_SUFFIX);
- if (!sensitive) {
- return value;
+ if (isEncrypted(value)) {
+ decrypted = propertyDecryptor.decrypt(component, propertyName, getDecoded(value));
+ } else {
+ decrypted = value;
}
- return propertyDecryptor.apply(value.substring(ENCRYPTED_VALUE_PREFIX.length(), value.length() - ENCRYPTED_VALUE_SUFFIX.length()));
+ return decrypted;
}
- private String decryptValue(final VersionedParameter parameter) {
+ private String decryptValue(final VersionedParameterContext parameterContext, final VersionedParameter parameter) {
+ final String decrypted;
+
final String rawValue = parameter.getValue();
- if (rawValue == null) {
- return null;
+ if (isEncrypted(rawValue)) {
+ decrypted = propertyDecryptor.decrypt(parameterContext, parameter.getName(), getDecoded(rawValue));
+ } else {
+ decrypted = rawValue;
}
- 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 decrypted;
+ }
+
+ /**
+ * Determine whether a value is wrapped with the prefix and suffix that a serialized flow uses to mark an encrypted
+ * value. The wrapper distinguishes an encrypted value from a value stored as plaintext, such as a Parameter reference.
+ *
+ * @param value Value to be evaluated, which may be null
+ * @return Whether the value is wrapped with the encrypted value prefix and suffix
+ */
+ private static boolean isEncrypted(final String value) {
+ return value != null && value.startsWith(ENCRYPTED_VALUE_PREFIX) && value.endsWith(ENCRYPTED_VALUE_SUFFIX);
+ }
- return propertyDecryptor.apply(rawValue.substring(ENCRYPTED_VALUE_PREFIX.length(), rawValue.length() - ENCRYPTED_VALUE_SUFFIX.length()));
+ /**
+ * Get an encrypted value with the encrypted value prefix and suffix removed
+ *
+ * @param encodedValue Encrypted value wrapped with the prefix and suffix
+ * @return Encrypted value without the prefix and suffix
+ */
+ private static String getDecoded(final String encodedValue) {
+ return encodedValue.substring(ENCRYPTED_VALUE_PREFIX.length(), encodedValue.length() - ENCRYPTED_VALUE_SUFFIX.length());
}
private void compareProperties(final VersionedComponent componentA, final VersionedComponent componentB,
@@ -389,8 +420,8 @@ private void compareProperties(final VersionedComponent componentA, final Versio
propertiesA.forEach((key, rawValueA) -> {
final String rawValueB = propertiesB.get(key);
- final String valueB = decrypt(rawValueB, descriptorsB.get(key));
- final String valueA = decrypt(rawValueA, descriptorsA.get(key));
+ final String valueB = decrypt(rawValueB, componentB, key);
+ final String valueA = decrypt(rawValueA, componentA, key);
final VersionedPropertyDescriptor descriptorA = descriptorsA.get(key);
final VersionedPropertyDescriptor descriptorB = descriptorsB.get(key);
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
index a14a7cb7fe96..a404f3591d6e 100644
--- 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
@@ -47,6 +47,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestStandardFlowComparator {
+ private static final String ENCRYPTED_VALUE_PREFIX = "enc{";
+
+ private static final String ENCRYPTED_VALUE_SUFFIX = "}";
+
private Map decryptedToEncrypted;
private Map encryptedToDecrypted;
private StandardFlowComparator comparator;
@@ -184,6 +188,46 @@ public void testMultipleParametersNamesChanged() {
assertEquals(4, differences.size());
}
+ @Test
+ public void testSensitiveParameterReferenceComparedWithoutDecryption() {
+ final String propertyName = "Password";
+ final String parameterReference = "#{secret.password}";
+
+ final VersionedProcessGroup groupA = new VersionedProcessGroup();
+ groupA.setIdentifier("rootPG");
+ groupA.getProcessors().add(createProcessorWithSensitiveProperty("processor", propertyName, parameterReference));
+
+ final VersionedProcessGroup groupB = new VersionedProcessGroup();
+ groupB.setIdentifier("rootPG");
+ groupB.getProcessors().add(createProcessorWithSensitiveProperty("processor", propertyName, parameterReference));
+
+ final SensitiveValueDecryptor failingDecryptor = (owner, valueName, encryptedValue) -> {
+ throw new IllegalArgumentException("Value is not encrypted: " + encryptedValue);
+ };
+
+ final StandardFlowComparator testComparator = new StandardFlowComparator(
+ new StandardComparableDataFlow("Flow A", groupA),
+ new StandardComparableDataFlow("Flow B", groupB),
+ new StaticDifferenceDescriptor(),
+ failingDecryptor,
+ VersionedComponent::getIdentifier,
+ FlowComparatorVersionedStrategy.SHALLOW);
+
+ assertTrue(testComparator.compare().getDifferences().isEmpty());
+ }
+
+ private VersionedProcessor createProcessorWithSensitiveProperty(final String identifier, final String propertyName, final String propertyValue) {
+ final VersionedPropertyDescriptor descriptor = new VersionedPropertyDescriptor();
+ descriptor.setName(propertyName);
+ descriptor.setSensitive(true);
+
+ final VersionedProcessor processor = new VersionedProcessor();
+ processor.setIdentifier(identifier);
+ processor.setProperties(Map.of(propertyName, propertyValue));
+ processor.setPropertyDescriptors(Map.of(propertyName, descriptor));
+ return processor;
+ }
+
@Test
public void testDeepStrategyWithChildPGs() {
final Function decryptor = encryptedToDecrypted::get;
@@ -571,12 +615,23 @@ private VersionedParameter createParameter(final String name, final String value
private VersionedParameter createParameter(final String name, final String value, final boolean sensitive, final List referencedAssets) {
final VersionedParameter parameter = new VersionedParameter();
parameter.setName(name);
- parameter.setValue(sensitive ? "enc{" + decryptedToEncrypted.get(value) + "}" : value);
+ parameter.setValue(sensitive ? encode(decryptedToEncrypted.get(value)) : value);
parameter.setSensitive(sensitive);
parameter.setReferencedAssets(referencedAssets);
return parameter;
}
+ /**
+ * Wrap an encrypted value with the prefix and suffix that a serialized flow uses to mark an encrypted value. Only
+ * values carrying this wrapper are decrypted during comparison.
+ *
+ * @param encryptedValue Encrypted value, which may be null
+ * @return Wrapped encrypted value, or null when no encrypted value is supplied
+ */
+ private String encode(final String encryptedValue) {
+ return encryptedValue == null ? null : ENCRYPTED_VALUE_PREFIX + encryptedValue + ENCRYPTED_VALUE_SUFFIX;
+ }
+
private VersionedAsset createAsset(final String id, final String name) {
final VersionedAsset asset = new VersionedAsset();
asset.setIdentifier(id);
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/pom.xml b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/pom.xml
index 7937e9849ef4..17c6da5d5b75 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/pom.xml
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/pom.xml
@@ -43,11 +43,6 @@
nifi-expression-language
2.12.0-SNAPSHOT
-
- org.apache.nifi
- nifi-property-encryptor
- 2.12.0-SNAPSHOT
-
org.apache.commons
commons-lang3
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java
index 29b94d45bf25..d1ee3d289954 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java
@@ -45,7 +45,6 @@
import org.apache.nifi.controller.repository.metrics.tracking.StatsTracker;
import org.apache.nifi.controller.scheduling.LifecycleStateManager;
import org.apache.nifi.controller.service.ControllerServiceProvider;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.extensions.ExtensionRepository;
import org.apache.nifi.flow.VersionedExternalFlowMetadata;
@@ -111,7 +110,6 @@ public class StandardStatelessEngine implements StatelessEngine {
private final ExtensionManager extensionManager;
private final BulletinRepository bulletinRepository;
private final StatelessStateManagerProvider stateManagerProvider;
- private final PropertyEncryptor propertyEncryptor;
private final ProcessScheduler processScheduler;
private final AssetManager assetManager;
private final KerberosConfig kerberosConfig;
@@ -139,7 +137,6 @@ private StandardStatelessEngine(final Builder builder) {
this.extensionManager = requireNonNull(builder.extensionManager, "Extension Manager must be provided");
this.bulletinRepository = requireNonNull(builder.bulletinRepository, "Bulletin Repository must be provided");
this.stateManagerProvider = requireNonNull(builder.stateManagerProvider, "State Manager Provider must be provided");
- this.propertyEncryptor = requireNonNull(builder.propertyEncryptor, "Encryptor must be provided");
this.processScheduler = requireNonNull(builder.processScheduler, "Process Scheduler must be provided");
this.kerberosConfig = requireNonNull(builder.kerberosConfig, "Kerberos Configuration must be provided");
this.flowFileEventRepository = requireNonNull(builder.flowFileEventRepository, "FlowFile Event Repository must be provided");
@@ -614,11 +611,6 @@ public StateManagerProvider getStateManagerProvider() {
return stateManagerProvider;
}
- @Override
- public PropertyEncryptor getPropertyEncryptor() {
- return propertyEncryptor;
- }
-
@Override
public ProcessScheduler getProcessScheduler() {
return processScheduler;
@@ -683,7 +675,6 @@ public static class Builder {
private ExtensionManager extensionManager = null;
private BulletinRepository bulletinRepository = null;
private StatelessStateManagerProvider stateManagerProvider = null;
- private PropertyEncryptor propertyEncryptor = null;
private ProcessScheduler processScheduler = null;
private KerberosConfig kerberosConfig = null;
private FlowFileEventRepository flowFileEventRepository = null;
@@ -710,10 +701,6 @@ public Builder stateManagerProvider(final StatelessStateManagerProvider stateMan
return this;
}
- public Builder encryptor(final PropertyEncryptor propertyEncryptor) {
- this.propertyEncryptor = propertyEncryptor;
- return this;
- }
public Builder processScheduler(final ProcessScheduler processScheduler) {
this.processScheduler = processScheduler;
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessEngine.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessEngine.java
index 16ba936c4673..5d518ace34c5 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessEngine.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessEngine.java
@@ -28,7 +28,6 @@
import org.apache.nifi.controller.repository.CounterRepository;
import org.apache.nifi.controller.repository.FlowFileEventRepository;
import org.apache.nifi.controller.service.ControllerServiceProvider;
-import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.provenance.ProvenanceRepository;
import org.apache.nifi.registry.EnvironmentVariables;
@@ -50,7 +49,6 @@ public interface StatelessEngine {
StateManagerProvider getStateManagerProvider();
- PropertyEncryptor getPropertyEncryptor();
FlowManager getFlowManager();
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java
index cc5c78688e01..bc2a2b242337 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java
@@ -242,7 +242,9 @@ public Port createLocalOutputPort(final String id, final String name) {
public ProcessGroup createProcessGroup(final String id, final String connectorId) {
final ProcessGroup created = new StandardProcessGroup(id, statelessEngine.getControllerServiceProvider(),
statelessEngine.getProcessScheduler(),
- statelessEngine.getPropertyEncryptor(),
+ // The Stateless runtime neither restores a persisted flow nor places a group under version control, so no
+ // sensitive value passes through the group and no Property Encryption Provider is required
+ null,
statelessEngine.getExtensionManager(),
statelessEngine.getStateManagerProvider(),
this,
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java
index 2aac3ced7131..44f7b16b1f3b 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java
@@ -41,9 +41,6 @@
import org.apache.nifi.controller.scheduling.StatelessProcessSchedulerInitializationContext;
import org.apache.nifi.controller.service.ControllerServiceProvider;
import org.apache.nifi.controller.service.StandardControllerServiceProvider;
-import org.apache.nifi.encrypt.PropertyEncryptionMethod;
-import org.apache.nifi.encrypt.PropertyEncryptor;
-import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.events.BulletinFactory;
import org.apache.nifi.events.EventReporter;
@@ -183,31 +180,6 @@ public NodeTypeProvider getNodeTypeProvider() {
final ExtensionRepository extensionRepository = new FileSystemExtensionRepository(extensionManager, engineConfiguration, narClassLoaders, extensionClients);
extensionRepository.initialize();
- final PropertyEncryptor lazyInitializedEncryptor = new PropertyEncryptor() {
- private PropertyEncryptor created = null;
-
- @Override
- public String encrypt(final String property) {
- return getEncryptor().encrypt(property);
- }
-
- @Override
- public String decrypt(final String encryptedProperty) {
- return getEncryptor().decrypt(encryptedProperty);
- }
-
- private synchronized PropertyEncryptor getEncryptor() {
- if (created != null) {
- return created;
- }
-
- created = new PropertyEncryptorBuilder(engineConfiguration.getSensitivePropsKey())
- .setAlgorithm(PropertyEncryptionMethod.NIFI_PBKDF2_AES_GCM_256.toString())
- .build();
- return created;
- }
- };
-
final CounterRepository counterRepo = new StandardCounterRepository();
final File krb5File = engineConfiguration.getKrb5File();
@@ -219,7 +191,6 @@ private synchronized PropertyEncryptor getEncryptor() {
final StatelessEngine statelessEngine = new StandardStatelessEngine.Builder()
.bulletinRepository(bulletinRepository)
- .encryptor(lazyInitializedEncryptor)
.extensionManager(extensionManager)
.assetManager(assetManager)
.stateManagerProvider(stateManagerProvider)
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/encryption/PropertyEncryptionProviderIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/encryption/PropertyEncryptionProviderIT.java
new file mode 100644
index 000000000000..103b1c992199
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/encryption/PropertyEncryptionProviderIT.java
@@ -0,0 +1,158 @@
+/*
+ * 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.tests.system.encryption;
+
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.api.entity.ConnectionEntity;
+import org.apache.nifi.web.api.entity.ParameterContextEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+/**
+ * Verifies that sensitive component properties and sensitive Parameters protected by a Property Encryption Provider are
+ * written to the flow configuration in encrypted form and recovered after a restart.
+ *
+ * The Provider is configured explicitly rather than relying on the default, so that the configured implementation is
+ * loaded from its NAR and used for both flow serialization and flow synchronization.
+ */
+class PropertyEncryptionProviderIT extends NiFiSystemIT {
+ private static final String PROVIDER_IMPLEMENTATION = "org.apache.nifi.security.encryption.password.PasswordBasedPropertyEncryptionProvider";
+
+ private static final String SENSITIVE_PROPERTY_VALUE = "Sensitive Property Value 4c1cf47b";
+
+ private static final String SENSITIVE_PARAMETER_VALUE = "Sensitive Parameter Value 9a2be830";
+
+ private static final String PARAMETER_CONTEXT_NAME = "Property Encryption Provider Context";
+
+ private static final String PARAMETER_NAME = "sensitiveParameter";
+
+ private static final String PARAMETER_REFERENCE = "#{%s}".formatted(PARAMETER_NAME);
+
+ private static final String SENSITIVE_CONTENT_PROPERTY = "Sensitive Content";
+
+ private static final String UPDATE_STRATEGY_PROPERTY = "Update Strategy";
+
+ private static final String REPLACE_STRATEGY = "Replace";
+
+ private static final String SUCCESS_RELATIONSHIP = "success";
+
+ private static final String FLOW_CONFIGURATION_FILENAME = "conf/flow.json.gz";
+
+ @Override
+ protected Map getNifiPropertiesOverrides() {
+ return Map.of(NiFiProperties.PROPERTY_ENCRYPTION_PROVIDER_IMPLEMENTATION, PROVIDER_IMPLEMENTATION);
+ }
+
+ @Override
+ protected boolean isAllowFactoryReuse() {
+ return false;
+ }
+
+ @Override
+ protected boolean isDestroyEnvironmentAfterEachTest() {
+ return true;
+ }
+
+ @Test
+ void testSensitivePropertyAndParameterRecoveredAfterRestart() throws NiFiClientException, IOException, InterruptedException {
+ final ParameterContextEntity parameterContext = getClientUtil().createParameterContext(PARAMETER_CONTEXT_NAME, PARAMETER_NAME, SENSITIVE_PARAMETER_VALUE, true);
+ getClientUtil().setParameterContext("root", parameterContext);
+
+ final ProcessorEntity createdGenerate = getClientUtil().createProcessor("GenerateFlowFile");
+ final ProcessorEntity generate = getClientUtil().updateProcessorProperties(createdGenerate, Map.of("Max FlowFiles", "1"));
+
+ final ProcessorEntity propertyUpdateContent = createUpdateContent(SENSITIVE_PROPERTY_VALUE);
+ final ProcessorEntity parameterUpdateContent = createUpdateContent(PARAMETER_REFERENCE);
+
+ final ConnectionEntity propertyConnection = createSensitiveContentFlow(generate, propertyUpdateContent);
+ final ConnectionEntity parameterConnection = createSensitiveContentFlow(generate, parameterUpdateContent);
+
+ restart();
+
+ final String flowConfiguration = readFlowConfiguration();
+ assertFalse(flowConfiguration.contains(SENSITIVE_PROPERTY_VALUE), "Sensitive property value written to flow configuration without encryption");
+ assertFalse(flowConfiguration.contains(SENSITIVE_PARAMETER_VALUE), "Sensitive Parameter value written to flow configuration without encryption");
+
+ startProcessor(propertyUpdateContent.getId());
+ startProcessor(parameterUpdateContent.getId());
+ startProcessor(generate.getId());
+
+ waitForQueueCount(propertyConnection.getId(), getNumberOfNodes());
+ waitForQueueCount(parameterConnection.getId(), getNumberOfNodes());
+
+ assertEquals(SENSITIVE_PROPERTY_VALUE, getClientUtil().getFlowFileContentAsUtf8(propertyConnection.getId(), 0));
+ assertEquals(SENSITIVE_PARAMETER_VALUE, getClientUtil().getFlowFileContentAsUtf8(parameterConnection.getId(), 0));
+ }
+
+ private ProcessorEntity createUpdateContent(final String sensitiveContent) throws NiFiClientException, IOException {
+ final ProcessorEntity updateContent = getClientUtil().createProcessor("UpdateContent");
+ return getClientUtil().updateProcessorProperties(updateContent, Map.of(SENSITIVE_CONTENT_PROPERTY, sensitiveContent, UPDATE_STRATEGY_PROPERTY, REPLACE_STRATEGY));
+ }
+
+ /**
+ * Connect the source Processor to the UpdateContent Processor and connect UpdateContent to a TerminateFlowFile
+ * Processor that is left stopped, so that the FlowFile written by UpdateContent stays queued for inspection
+ *
+ * @param generate Source Processor
+ * @param updateContent Processor that writes the sensitive value to the FlowFile
+ * @return Connection holding the FlowFiles written by UpdateContent
+ */
+ private ConnectionEntity createSensitiveContentFlow(final ProcessorEntity generate, final ProcessorEntity updateContent) throws NiFiClientException, IOException {
+ final ProcessorEntity terminate = getClientUtil().createProcessor("TerminateFlowFile");
+
+ getClientUtil().createConnection(generate, updateContent, SUCCESS_RELATIONSHIP);
+ return getClientUtil().createConnection(updateContent, terminate, SUCCESS_RELATIONSHIP);
+ }
+
+ private void restart() throws IOException {
+ getNiFiInstance().stop();
+ getNiFiInstance().start(true);
+ setupClient();
+ }
+
+ private void startProcessor(final String processorId) throws NiFiClientException, IOException, InterruptedException {
+ getClientUtil().waitForValidProcessor(processorId);
+ getClientUtil().startProcessor(getNifiClient().getProcessorClient().getProcessor(processorId));
+ }
+
+ private String readFlowConfiguration() throws IOException {
+ final File flowConfiguration = new File(getNiFiInstance().getInstanceDirectory(), FLOW_CONFIGURATION_FILENAME);
+
+ try (
+ InputStream inputStream = Files.newInputStream(flowConfiguration.toPath());
+ InputStream compressed = new GZIPInputStream(inputStream);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
+ ) {
+ compressed.transferTo(outputStream);
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+ }
+}