This is an automated email from the ASF dual-hosted git repository.
pvillard31 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 54d738b6102 NIFI-16282 Add Property Encryption Provider framework
extension (#11617)
54d738b6102 is described below
commit 54d738b6102fac2a505673ffb06b7736070c7a69
Author: David Handermann <[email protected]>
AuthorDate: Thu Sep 3 09:47:07 2026 -0500
NIFI-16282 Add Property Encryption Provider framework extension (#11617)
- Added nifi.security.property.encryption.provider properties
- Added PasswordBasedPropertyEncryptionProvider default implementation
---
nifi-assembly/pom.xml | 6 +
.../java/org/apache/nifi/util/NiFiProperties.java | 2 +
.../src/main/asciidoc/administration-guide.adoc | 24 +++
.../encryption/PropertyEncryptionException.java | 30 +++
.../encryption/PropertyEncryptionProvider.java | 64 ++++++
...rtyEncryptionProviderInitializationContext.java | 48 +++++
.../encryption/SensitivePropertyAttribute.java | 64 ++++++
.../encryption/SensitivePropertyCategory.java | 37 ++++
.../encryption/SensitivePropertyContext.java | 38 ++++
.../pom.xml | 24 +--
.../src/main/resources/META-INF/LICENSE | 232 +++++++++++++++++++++
.../src/main/resources/META-INF/NOTICE | 13 ++
.../pom.xml | 28 +--
.../PasswordBasedPropertyEncryptionProvider.java | 100 +++++++++
....security.encryption.PropertyEncryptionProvider | 15 ++
...asswordBasedPropertyEncryptionProviderTest.java | 183 ++++++++++++++++
.../pom.xml | 15 +-
.../nifi-framework-extensions/pom.xml | 1 +
...rtyEncryptionProviderInitializationContext.java | 53 +++++
.../configuration/FlowControllerConfiguration.java | 94 +++++++++
.../nar/StandardExtensionDiscoveringManager.java | 2 +
.../nifi-framework/nifi-resources/pom.xml | 1 +
.../src/main/resources/conf/nifi.properties | 1 +
nifi-system-tests/nifi-system-test-suite/pom.xml | 6 +
.../src/test/assembly/dependencies.xml | 1 +
25 files changed, 1043 insertions(+), 39 deletions(-)
diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index e3cdf316ca5..ac7567693a3 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -225,6 +225,12 @@ language governing permissions and limitations under the
License. -->
<version>2.12.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+
<artifactId>nifi-password-based-property-encryption-nar</artifactId>
+ <version>2.12.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-provenance-repository-nar</artifactId>
diff --git
a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index ad89e7dd6b9..48681c77c70 100644
---
a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++
b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -73,6 +73,8 @@ public class NiFiProperties extends ApplicationProperties {
public static final String UNPACK_NARS_TO_UBER_JAR =
"nifi.nar.unpack.uber.jar";
public static final String SENSITIVE_PROPS_KEY =
"nifi.sensitive.props.key";
public static final String SENSITIVE_PROPS_ALGORITHM =
"nifi.sensitive.props.algorithm";
+ public static final String PROPERTY_ENCRYPTION_PROVIDER_IMPLEMENTATION =
"nifi.security.property.encryption.provider.implementation";
+ public static final String PROPERTY_ENCRYPTION_PROVIDER_PREFIX =
"nifi.security.property.encryption.provider.properties.";
public static final String REMOTE_INPUT_HOST = "nifi.remote.input.host";
public static final String REMOTE_INPUT_PORT =
"nifi.remote.input.socket.port";
public static final String SITE_TO_SITE_SECURE =
"nifi.remote.input.secure";
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 0ffda8071ea..df67ba22a1e 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -1923,6 +1923,28 @@ empty. NiFi writes the generated value to
`nifi.properties` and logs a warning.
Clustered installations of NiFi require the same value to be configured on all
nodes.
+[[property-encryption-providers]]
+=== Property Encryption Providers
+
+A Property Encryption Provider is a framework extension, packaged in a NiFi
Archive, for protecting sensitive values.
+NiFi loads and initializes the provider during startup using the class name
configured in
+`nifi.security.property.encryption.provider.implementation`, and does not load
a provider when the property is empty.
+When the property is missing, NiFi loads the standard password-based
implementation to support existing configurations.
+
+The standard distribution includes
`org.apache.nifi.security.encryption.password.PasswordBasedPropertyEncryptionProvider`,
+which derives a secret key from `nifi.sensitive.props.key` using the Key
Derivation Function selected in
+`nifi.sensitive.props.algorithm`. This provider reads and writes the encrypted
representation described in
+<<property-encryption-algorithms>>.
+
+Flow serialization continues to use `nifi.sensitive.props.key` and
`nifi.sensitive.props.algorithm` directly.
+Loading a Property Encryption Provider does not change the encryption used for
persisted flow configuration.
+
+A provider can read additional configuration from properties that begin with
+`nifi.security.property.encryption.provider.properties.`. NiFi removes the
prefix before supplying the properties to the
+provider, so `nifi.security.property.encryption.provider.properties.endpoint`
is supplied to the provider as `endpoint`.
+Providers that call remote services, such as implementations backed by an
external Key Management Service, can also
+use the TLS configuration that NiFi supplies during initialization.
+
[[admin-toolkit]]
== NiFi Toolkit Administrative Tools
The NiFi Toolkit also contains command line utilities for administrators to
support NiFi maintenance in standalone and clustered environments.
@@ -3598,6 +3620,8 @@ These properties pertain to various security features in
NiFi. Many of these pro
|*Property*|*Description*
|`nifi.sensitive.props.key`|This is the password used to encrypt any sensitive
property values that are configured in processors. By default, it is blank, but
the system administrator should provide a value for it. It can be a string of
any length, although the recommended minimum length is 10 characters. Be aware
that once this password is set and one or more sensitive processor properties
have been configured, this password should not be changed.
|`nifi.sensitive.props.algorithm`|The algorithm used to encrypt sensitive
properties. The default value is `NIFI_PBKDF2_AES_GCM_256`.
+|`nifi.security.property.encryption.provider.implementation`|The class name of
the Property Encryption Provider loaded and initialized during application
startup. The default value is
`org.apache.nifi.security.encryption.password.PasswordBasedPropertyEncryptionProvider`,
which derives a secret key from `nifi.sensitive.props.key`. NiFi does not load
a provider when the value is blank. See <<property-encryption-providers>> for
additional information.
+|`nifi.security.property.encryption.provider.properties.`|The prefix for
properties supplied to the configured Property Encryption Provider. NiFi
removes the prefix from the property name before supplying the property to the
provider. No properties are configured by default.
|`nifi.security.autoreload.enabled`|Specifies whether the SSL context factory
should be automatically reloaded if updates to the keystore and truststore are
detected. By default, it is set to `false`.
|`nifi.security.autoreload.interval`|Specifies the interval at which the
keystore and truststore are checked for updates. Only applies if
`nifi.security.autoreload.enabled` is set to `true`. The default value is `10
secs`.
|`nifi.security.keystore`*|The full path and name of the keystore. The default
value is `./conf/keystore.p12`.
diff --git
a/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionException.java
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionException.java
new file mode 100644
index 00000000000..367bad03d6a
--- /dev/null
+++
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionException.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating a failure during Property Encryption Provider
initialization or cipher operations
+ */
+public class PropertyEncryptionException extends RuntimeException {
+ public PropertyEncryptionException(final String message) {
+ super(message);
+ }
+
+ public PropertyEncryptionException(final String message, final Throwable
cause) {
+ super(message, cause);
+ }
+}
diff --git
a/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionProvider.java
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionProvider.java
new file mode 100644
index 00000000000..ae2dbf44c15
--- /dev/null
+++
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionProvider.java
@@ -0,0 +1,64 @@
+/*
+ * 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.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Framework extension point for protecting sensitive values stored in the
flow configuration. Implementations must be
+ * thread safe, because the framework holds a single shared instance and
invokes it from multiple threads.
+ */
+public interface PropertyEncryptionProvider extends Closeable {
+ /**
+ * Configuration lifecycle method that the framework invokes after
instantiating the class and before requesting
+ * cipher operations
+ *
+ * @param context Initialization context containing configured properties
+ * @throws PropertyEncryptionException Thrown when initialization fails
+ */
+ void initialize(PropertyEncryptionProviderInitializationContext context);
+
+ /**
+ * Encrypt a sensitive value using the supplied context
+ *
+ * @param property Sensitive value to be encrypted
+ * @param context Context describing the value being protected
+ * @return Encrypted value
+ * @throws PropertyEncryptionException Thrown when encryption fails
+ */
+ byte[] encrypt(byte[] property, SensitivePropertyContext context);
+
+ /**
+ * Decrypt a sensitive value using the context supplied when the value was
encrypted
+ *
+ * @param encryptedProperty Encrypted value to be decrypted
+ * @param context Context describing the value being protected
+ * @return Decrypted value
+ * @throws PropertyEncryptionException Thrown when decryption fails
+ */
+ byte[] decrypt(byte[] encryptedProperty, SensitivePropertyContext context);
+
+ /**
+ * Close resources created during provider initialization and processing
+ *
+ * @throws IOException Thrown when closing resources fails
+ */
+ @Override
+ default void close() throws IOException {
+ }
+}
diff --git
a/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionProviderInitializationContext.java
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionProviderInitializationContext.java
new file mode 100644
index 00000000000..d2a7075e669
--- /dev/null
+++
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/PropertyEncryptionProviderInitializationContext.java
@@ -0,0 +1,48 @@
+/*
+ * 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.Map;
+import java.util.Optional;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Initialization context for a {@link PropertyEncryptionProvider}
+ */
+public interface PropertyEncryptionProviderInitializationContext {
+ /**
+ * Get the properties configured for the Property Encryption Provider
+ *
+ * @return Configured properties with the application property prefix
removed
+ */
+ Map<String, String> getProperties();
+
+ /**
+ * Get the SSLContext for implementations that communicate with remote
services
+ *
+ * @return SSLContext or empty when not configured
+ */
+ Optional<SSLContext> getSSLContext();
+
+ /**
+ * Get the trust manager for implementations that construct their own
client TLS configuration
+ *
+ * @return X509TrustManager or empty when not configured
+ */
+ Optional<X509TrustManager> getTrustManager();
+}
diff --git
a/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyAttribute.java
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyAttribute.java
new file mode 100644
index 00000000000..179719066de
--- /dev/null
+++
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyAttribute.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+/**
+ * Attribute describing the location of a sensitive value within the flow
configuration. Each attribute has a stable key
+ * suitable for supplying as authenticated additional data or audit
information to an external key management service.
+ * Key values are part of the extension contract and must not change once
published.
+ */
+public enum SensitivePropertyAttribute {
+ /**
+ * Identifier of the component that owns the value
+ */
+ COMPONENT_ID("componentId"),
+
+ /**
+ * Type of the component that owns the value
+ */
+ COMPONENT_TYPE("componentType"),
+
+ /**
+ * Name of the property that holds the value
+ */
+ PROPERTY_NAME("propertyName"),
+
+ /**
+ * Name of the Parameter Context that contains the Parameter
+ */
+ PARAMETER_CONTEXT_NAME("parameterContextName"),
+
+ /**
+ * Name of the Parameter that holds the value
+ */
+ PARAMETER_NAME("parameterName");
+
+ private final String key;
+
+ SensitivePropertyAttribute(final String key) {
+ this.key = key;
+ }
+
+ /**
+ * Get the stable key for the attribute
+ *
+ * @return Attribute key
+ */
+ public String getKey() {
+ return key;
+ }
+}
diff --git
a/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyCategory.java
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyCategory.java
new file mode 100644
index 00000000000..a6436f1a596
--- /dev/null
+++
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyCategory.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * Category describing the kind of sensitive value being protected
+ */
+public enum SensitivePropertyCategory {
+ /**
+ * Sensitive Property value configured on a flow component such as a
Processor or Controller Service
+ */
+ COMPONENT_PROPERTY,
+
+ /**
+ * Sensitive Parameter value configured in a Parameter Context
+ */
+ PARAMETER,
+
+ /**
+ * Authorization token stored on behalf of an authenticated user
+ */
+ AUTHORIZATION_TOKEN
+}
diff --git
a/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyContext.java
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyContext.java
new file mode 100644
index 00000000000..009236d5079
--- /dev/null
+++
b/nifi-framework-api/src/main/java/org/apache/nifi/security/encryption/SensitivePropertyContext.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.security.encryption;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Context describing the sensitive value supplied to encryption and
decryption operations. Implementations backed by an
+ * external key management service can supply the attributes as encryption
context and rely on the service to record
+ * them for auditing. The same context must be supplied for decryption as was
supplied for encryption.
+ *
+ * @param category Category of the value being protected
+ * @param attributes Attributes describing the location of the value, stored
as an unmodifiable copy and never null
+ */
+public record SensitivePropertyContext(
+ SensitivePropertyCategory category,
+ Map<String, String> attributes
+) {
+ public SensitivePropertyContext {
+ Objects.requireNonNull(category, "Category required");
+ attributes = attributes == null ? Map.of() : Map.copyOf(attributes);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/pom.xml
similarity index 65%
copy from nifi-framework-bundle/nifi-framework-extensions/pom.xml
copy to
nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/pom.xml
index 2481ae38874..47dc3ec2689 100644
--- a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/pom.xml
@@ -17,20 +17,16 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-framework-bundle</artifactId>
+ <artifactId>nifi-password-based-property-encryption-bundle</artifactId>
<version>2.12.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-framework-extensions</artifactId>
- <packaging>pom</packaging>
- <modules>
- <module>nifi-framework-kubernetes-bundle</module>
- <module>nifi-framework-zookeeper-bundle</module>
- <module>nifi-kerberos-iaa-providers-bundle</module>
- <module>nifi-ldap-iaa-providers-bundle</module>
- <module>nifi-provenance-repository-bundle</module>
- <module>nifi-py4j-framework-bundle</module>
- <module>nifi-questdb-bundle</module>
- <module>nifi-single-user-iaa-providers-bundle</module>
- </modules>
+ <artifactId>nifi-password-based-property-encryption-nar</artifactId>
+ <packaging>nar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-password-based-property-encryption</artifactId>
+ <version>2.12.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
</project>
-
diff --git
a/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/src/main/resources/META-INF/LICENSE
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/src/main/resources/META-INF/LICENSE
new file mode 100644
index 00000000000..b68e5e12819
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,232 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
+
+APACHE NIFI SUBCOMPONENTS:
+
+The Apache NiFi project contains subcomponents with separate copyright
+notices and license terms. Your use of the source code for these
+subcomponents is subject to the terms and conditions of the following
+licenses.
+
+ The binary distribution of this product bundles 'Bouncy Castle Provider'
+ under the MIT License.
+
+ Copyright (c) 2000-2026 The Legion of the Bouncy Castle Inc.
(https://www.bouncycastle.org).
+
+ Permission is hereby granted, free of charge, to any person obtaining a
copy
+ of this software and associated documentation files (the "Software"), to
deal
+ in the Software without restriction, including without limitation the
rights
+ to use, copy, modify, merge, publish, distribute, sub license, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
\ No newline at end of file
diff --git
a/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/src/main/resources/META-INF/NOTICE
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/src/main/resources/META-INF/NOTICE
new file mode 100644
index 00000000000..6bc3f7c3e68
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption-nar/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,13 @@
+nifi-password-based-property-encryption-nar
+Copyright 2014-2026 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+******************
+Bouncy Castle
+******************
+
+The following binary components are provided under an MIT style license. See
project link for details.
+
+ (MIT) Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on -
https://www.bouncycastle.org/)
diff --git a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/pom.xml
similarity index 65%
copy from nifi-framework-bundle/nifi-framework-extensions/pom.xml
copy to
nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/pom.xml
index 2481ae38874..684e57413a0 100644
--- a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/pom.xml
@@ -17,20 +17,20 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-framework-bundle</artifactId>
+ <artifactId>nifi-password-based-property-encryption-bundle</artifactId>
<version>2.12.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-framework-extensions</artifactId>
- <packaging>pom</packaging>
- <modules>
- <module>nifi-framework-kubernetes-bundle</module>
- <module>nifi-framework-zookeeper-bundle</module>
- <module>nifi-kerberos-iaa-providers-bundle</module>
- <module>nifi-ldap-iaa-providers-bundle</module>
- <module>nifi-provenance-repository-bundle</module>
- <module>nifi-py4j-framework-bundle</module>
- <module>nifi-questdb-bundle</module>
- <module>nifi-single-user-iaa-providers-bundle</module>
- </modules>
+ <artifactId>nifi-password-based-property-encryption</artifactId>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-encryptor</artifactId>
+ <version>2.12.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
</project>
-
diff --git
a/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/main/java/org/apache/nifi/security/encryption/password/PasswordBasedPropertyEncryptionProvider.java
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/main/java/org/apache/nifi/security/encryption/password/PasswordBasedPropertyEncryptionProvider.java
new file mode 100644
index 00000000000..3f880bd4752
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/main/java/org/apache/nifi/security/encryption/password/PasswordBasedPropertyEncryptionProvider.java
@@ -0,0 +1,100 @@
+/*
+ * 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.password;
+
+import org.apache.nifi.encrypt.PropertyEncryptionMethod;
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
+import org.apache.nifi.security.encryption.PropertyEncryptionException;
+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.util.NiFiProperties;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HexFormat;
+import java.util.Objects;
+
+/**
+ * Property Encryption Provider that derives an AES-GCM secret key from the
sensitive properties password configured in
+ * application properties. Encrypted values consist of a random initialization
vector followed by cipher text, matching
+ * the binary representation of sensitive values protected using the sensitive
properties key and algorithm.
+ */
+public class PasswordBasedPropertyEncryptionProvider implements
PropertyEncryptionProvider {
+ private static final String DEFAULT_ALGORITHM =
PropertyEncryptionMethod.NIFI_PBKDF2_AES_GCM_256.toString();
+
+ private static final HexFormat HEX_FORMAT = HexFormat.of();
+
+ private static final String PASSWORD_REQUIRED = String.format("Sensitive
Properties Key [%s] required", NiFiProperties.SENSITIVE_PROPS_KEY);
+
+ private final NiFiProperties properties;
+
+ private volatile PropertyEncryptor propertyEncryptor;
+
+ public PasswordBasedPropertyEncryptionProvider(final NiFiProperties
properties) {
+ this.properties = Objects.requireNonNull(properties, "Properties
required");
+ }
+
+ /**
+ * Derive the secret key from the configured sensitive properties password
and algorithm
+ *
+ * @param context Initialization context containing configured properties
+ */
+ @Override
+ public void initialize(final
PropertyEncryptionProviderInitializationContext context) {
+ final String password =
properties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY);
+ if (password == null || password.isBlank()) {
+ throw new PropertyEncryptionException(PASSWORD_REQUIRED);
+ }
+
+ final PropertyEncryptorBuilder builder = new
PropertyEncryptorBuilder(password);
+ final String algorithm =
properties.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM,
DEFAULT_ALGORITHM);
+ builder.setAlgorithm(algorithm);
+
+ try {
+ propertyEncryptor = builder.build();
+ } catch (final RuntimeException e) {
+ throw new PropertyEncryptionException("Secret Key derivation
failed", e);
+ }
+ }
+
+ @Override
+ public byte[] encrypt(final byte[] property, final
SensitivePropertyContext context) {
+ Objects.requireNonNull(property, "Property required");
+ final String propertyValue = new String(property,
StandardCharsets.UTF_8);
+
+ try {
+ final String encrypted = propertyEncryptor.encrypt(propertyValue);
+ return HEX_FORMAT.parseHex(encrypted);
+ } catch (final RuntimeException e) {
+ throw new PropertyEncryptionException("Property encryption
failed", e);
+ }
+ }
+
+ @Override
+ public byte[] decrypt(final byte[] encryptedProperty, final
SensitivePropertyContext context) {
+ Objects.requireNonNull(encryptedProperty, "Encrypted Property
required");
+ final String encrypted = HEX_FORMAT.formatHex(encryptedProperty);
+
+ try {
+ final String decrypted = propertyEncryptor.decrypt(encrypted);
+ return decrypted.getBytes(StandardCharsets.UTF_8);
+ } catch (final RuntimeException e) {
+ throw new PropertyEncryptionException("Property decryption
failed", e);
+ }
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/main/resources/META-INF/services/org.apache.nifi.security.encryption.PropertyEncryptionProvider
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/main/resources/META-INF/services/org.apache.nifi.security.encryption.PropertyEncryptionProvider
new file mode 100644
index 00000000000..134831ff89e
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/main/resources/META-INF/services/org.apache.nifi.security.encryption.PropertyEncryptionProvider
@@ -0,0 +1,15 @@
+# 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.
+org.apache.nifi.security.encryption.password.PasswordBasedPropertyEncryptionProvider
diff --git
a/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/test/java/org/apache/nifi/security/encryption/password/PasswordBasedPropertyEncryptionProviderTest.java
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/test/java/org/apache/nifi/security/encryption/password/PasswordBasedPropertyEncryptionProviderTest.java
new file mode 100644
index 00000000000..99178ca9bb0
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/nifi-password-based-property-encryption/src/test/java/org/apache/nifi/security/encryption/password/PasswordBasedPropertyEncryptionProviderTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.password;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
+import org.apache.nifi.security.encryption.PropertyEncryptionException;
+import
org.apache.nifi.security.encryption.PropertyEncryptionProviderInitializationContext;
+import org.apache.nifi.security.encryption.SensitivePropertyAttribute;
+import org.apache.nifi.security.encryption.SensitivePropertyCategory;
+import org.apache.nifi.security.encryption.SensitivePropertyContext;
+import org.apache.nifi.util.NiFiProperties;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HexFormat;
+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.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class PasswordBasedPropertyEncryptionProviderTest {
+ /** Empty path disables reading application properties from a file */
+ private static final String PROPERTIES_FILE_PATH_DISABLED = "";
+
+ private static final String PASSWORD = "sensitive-properties-password";
+
+ private static final String ALGORITHM = "NIFI_PBKDF2_AES_GCM_256";
+
+ private static final String PROPERTY = "Property Value";
+
+ private static final byte[] PROPERTY_BINARY =
PROPERTY.getBytes(StandardCharsets.UTF_8);
+
+ private static final String UNICODE_PROPERTY = "Property Value café 東京";
+
+ private static final byte[] UNICODE_PROPERTY_BINARY =
UNICODE_PROPERTY.getBytes(StandardCharsets.UTF_8);
+
+ private static final String UNICODE_PBKDF2_ENCRYPTED =
+
"000102030405060708090a0b0c0d0e0f8d220b8ce0944d07a30bfb912087fc564bf73f06da92cd5efe6917eed3404f913236ea843a88a44e699276";
+
+ private static final String DIFFERENT_PASSWORD =
"different-sensitive-password";
+
+ private static final HexFormat HEX_FORMAT = HexFormat.of();
+
+ private static final SensitivePropertyContext CONTEXT = new
SensitivePropertyContext(
+ SensitivePropertyCategory.COMPONENT_PROPERTY,
+ Map.of(SensitivePropertyAttribute.PROPERTY_NAME.getKey(),
"Password")
+ );
+
+ private static final PropertyEncryptionProviderInitializationContext
INITIALIZATION_CONTEXT =
Mockito.mock(PropertyEncryptionProviderInitializationContext.class);
+
+ private static PasswordBasedPropertyEncryptionProvider provider;
+
+ @BeforeAll
+ static void setProvider() {
+ provider = getProvider(PASSWORD, ALGORITHM);
+ provider.initialize(INITIALIZATION_CONTEXT);
+ }
+
+ @Test
+ void testEncryptDecrypt() {
+ final byte[] encrypted = provider.encrypt(PROPERTY_BINARY, CONTEXT);
+ assertFalse(Arrays.equals(PROPERTY_BINARY, encrypted));
+
+ final byte[] decrypted = provider.decrypt(encrypted, CONTEXT);
+ assertArrayEquals(PROPERTY_BINARY, decrypted);
+ }
+
+ @Test
+ void testEncryptRandomizedPerInvocation() {
+ final byte[] firstEncrypted = provider.encrypt(PROPERTY_BINARY,
CONTEXT);
+ final byte[] secondEncrypted = provider.encrypt(PROPERTY_BINARY,
CONTEXT);
+
+ assertFalse(Arrays.equals(firstEncrypted, secondEncrypted));
+ assertArrayEquals(PROPERTY_BINARY, provider.decrypt(firstEncrypted,
CONTEXT));
+ assertArrayEquals(PROPERTY_BINARY, provider.decrypt(secondEncrypted,
CONTEXT));
+ }
+
+ @Test
+ void testDecryptSensitivePropertiesBinary() {
+ final byte[] encrypted = HEX_FORMAT.parseHex(UNICODE_PBKDF2_ENCRYPTED);
+
+ final byte[] decrypted = provider.decrypt(encrypted, CONTEXT);
+
+ assertArrayEquals(UNICODE_PROPERTY_BINARY, decrypted);
+ }
+
+ @Test
+ void testEncryptSensitivePropertiesBinary() {
+ final byte[] encrypted = provider.encrypt(PROPERTY_BINARY, CONTEXT);
+
+ final PropertyEncryptor propertyEncryptor = new
PropertyEncryptorBuilder(PASSWORD).setAlgorithm(ALGORITHM).build();
+ final String decrypted =
propertyEncryptor.decrypt(HEX_FORMAT.formatHex(encrypted));
+
+ assertEquals(PROPERTY, decrypted);
+ }
+
+ @Test
+ void testDecryptBinaryNotValid() {
+ assertThrows(PropertyEncryptionException.class, () ->
provider.decrypt(PROPERTY_BINARY, CONTEXT));
+ }
+
+ @Test
+ void testDecryptAuthenticationFailed() {
+ final byte[] encrypted = provider.encrypt(PROPERTY_BINARY, CONTEXT);
+ encrypted[0] = 0;
+
+ assertThrows(PropertyEncryptionException.class, () ->
provider.decrypt(encrypted, CONTEXT));
+ }
+
+ @Test
+ void testDecryptDifferentPassword() {
+ final PasswordBasedPropertyEncryptionProvider
differentPasswordProvider = getProvider(DIFFERENT_PASSWORD, ALGORITHM);
+ differentPasswordProvider.initialize(INITIALIZATION_CONTEXT);
+ final byte[] encrypted = provider.encrypt(PROPERTY_BINARY, CONTEXT);
+
+ assertThrows(PropertyEncryptionException.class, () ->
differentPasswordProvider.decrypt(encrypted, CONTEXT));
+ }
+
+ @Test
+ void testInitializeAlgorithmNotConfigured() {
+ final PasswordBasedPropertyEncryptionProvider defaultAlgorithmProvider
= getProvider(PASSWORD, null);
+ defaultAlgorithmProvider.initialize(INITIALIZATION_CONTEXT);
+
+ final byte[] encrypted =
defaultAlgorithmProvider.encrypt(PROPERTY_BINARY, CONTEXT);
+
+ assertArrayEquals(PROPERTY_BINARY,
defaultAlgorithmProvider.decrypt(encrypted, CONTEXT));
+ }
+
+ @Test
+ void testInitializePasswordNotConfigured() {
+ final PasswordBasedPropertyEncryptionProvider
passwordNotConfiguredProvider = getProvider(null, ALGORITHM);
+
+ assertThrows(PropertyEncryptionException.class, () ->
passwordNotConfiguredProvider.initialize(INITIALIZATION_CONTEXT));
+ }
+
+ @Test
+ void testInitializeAlgorithmNotSupported() {
+ final PasswordBasedPropertyEncryptionProvider
algorithmNotSupportedProvider = getProvider(PASSWORD,
"ALGORITHM_NOT_SUPPORTED");
+
+ assertThrows(PropertyEncryptionException.class, () ->
algorithmNotSupportedProvider.initialize(INITIALIZATION_CONTEXT));
+ }
+
+ @Test
+ void testEncryptNotInitialized() {
+ final PasswordBasedPropertyEncryptionProvider notInitializedProvider =
getProvider(PASSWORD, ALGORITHM);
+
+ assertThrows(PropertyEncryptionException.class, () ->
notInitializedProvider.encrypt(PROPERTY_BINARY, CONTEXT));
+ }
+
+ private static PasswordBasedPropertyEncryptionProvider getProvider(final
String password, final String algorithm) {
+ final Map<String, String> configuredProperties = new HashMap<>();
+ if (password != null) {
+ configuredProperties.put(NiFiProperties.SENSITIVE_PROPS_KEY,
password);
+ }
+ if (algorithm != null) {
+ configuredProperties.put(NiFiProperties.SENSITIVE_PROPS_ALGORITHM,
algorithm);
+ }
+
+ final NiFiProperties properties =
NiFiProperties.createBasicNiFiProperties(PROPERTIES_FILE_PATH_DISABLED,
configuredProperties);
+ return new PasswordBasedPropertyEncryptionProvider(properties);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/pom.xml
similarity index 69%
copy from nifi-framework-bundle/nifi-framework-extensions/pom.xml
copy to
nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/pom.xml
index 2481ae38874..a42813669f9 100644
--- a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
+++
b/nifi-framework-bundle/nifi-framework-extensions/nifi-password-based-property-encryption-bundle/pom.xml
@@ -17,20 +17,13 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-framework-bundle</artifactId>
+ <artifactId>nifi-framework-extensions</artifactId>
<version>2.12.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-framework-extensions</artifactId>
+ <artifactId>nifi-password-based-property-encryption-bundle</artifactId>
<packaging>pom</packaging>
<modules>
- <module>nifi-framework-kubernetes-bundle</module>
- <module>nifi-framework-zookeeper-bundle</module>
- <module>nifi-kerberos-iaa-providers-bundle</module>
- <module>nifi-ldap-iaa-providers-bundle</module>
- <module>nifi-provenance-repository-bundle</module>
- <module>nifi-py4j-framework-bundle</module>
- <module>nifi-questdb-bundle</module>
- <module>nifi-single-user-iaa-providers-bundle</module>
+ <module>nifi-password-based-property-encryption</module>
+ <module>nifi-password-based-property-encryption-nar</module>
</modules>
</project>
-
diff --git a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
b/nifi-framework-bundle/nifi-framework-extensions/pom.xml
index 2481ae38874..5216961eb74 100644
--- a/nifi-framework-bundle/nifi-framework-extensions/pom.xml
+++ b/nifi-framework-bundle/nifi-framework-extensions/pom.xml
@@ -27,6 +27,7 @@
<module>nifi-framework-zookeeper-bundle</module>
<module>nifi-kerberos-iaa-providers-bundle</module>
<module>nifi-ldap-iaa-providers-bundle</module>
+ <module>nifi-password-based-property-encryption-bundle</module>
<module>nifi-provenance-repository-bundle</module>
<module>nifi-py4j-framework-bundle</module>
<module>nifi-questdb-bundle</module>
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/security/encryption/StandardPropertyEncryptionProviderInitializationContext.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/security/encryption/StandardPropertyEncryptionProviderInitializationContext.java
new file mode 100644
index 00000000000..e67bae3d074
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/security/encryption/StandardPropertyEncryptionProviderInitializationContext.java
@@ -0,0 +1,53 @@
+/*
+ * 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.Map;
+import java.util.Objects;
+import java.util.Optional;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * A context object that provides the configuration for a Property Encryption
Provider.
+ */
+public class StandardPropertyEncryptionProviderInitializationContext
implements PropertyEncryptionProviderInitializationContext {
+ private final Map<String, String> properties;
+ private final SSLContext sslContext;
+ private final X509TrustManager trustManager;
+
+ public StandardPropertyEncryptionProviderInitializationContext(final
Map<String, String> properties, final SSLContext sslContext, final
X509TrustManager trustManager) {
+ this.properties = Map.copyOf(Objects.requireNonNull(properties,
"Properties required"));
+ this.sslContext = sslContext;
+ this.trustManager = trustManager;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ @Override
+ public Optional<SSLContext> getSSLContext() {
+ return Optional.ofNullable(sslContext);
+ }
+
+ @Override
+ public Optional<X509TrustManager> getTrustManager() {
+ return Optional.ofNullable(trustManager);
+ }
+}
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 04d75012247..a73c625ea92 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
@@ -54,6 +54,7 @@ import
org.apache.nifi.extension.manifest.parser.jaxb.JAXBExtensionManifestParse
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;
@@ -64,6 +65,10 @@ import org.apache.nifi.nar.NarThreadContextClassLoader;
import org.apache.nifi.nar.StandardNarComponentManager;
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.services.FlowService;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
@@ -79,9 +84,12 @@ import
org.springframework.beans.factory.annotation.Qualifier;
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;
@@ -548,4 +556,90 @@ public class FlowControllerConfiguration {
return componentMetricReporter;
}
+
+ /**
+ * Property Encryption Provider configured from NiFi Application Properties
+ *
+ * @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<String, String> 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));
+ }
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
index 833562719dd..4a399c20cbf 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
@@ -54,6 +54,7 @@ import org.apache.nifi.python.PythonProcessorDetails;
import org.apache.nifi.registry.flow.FlowRegistryClient;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.security.encryption.PropertyEncryptionProvider;
import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -137,6 +138,7 @@ public class StandardExtensionDiscoveringManager implements
ExtensionDiscovering
definitionMap.put(AssetManager.class, new HashSet<>());
definitionMap.put(FlowActionReporter.class, new HashSet<>());
definitionMap.put(ComponentMetricReporter.class, new HashSet<>());
+ definitionMap.put(PropertyEncryptionProvider.class, new HashSet<>());
definitionMap.put(Connector.class, new HashSet<>());
additionalExtensionTypes.forEach(type ->
definitionMap.putIfAbsent(type, new HashSet<>()));
diff --git a/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
b/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index 819cd4ef672..79877e885a0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -82,6 +82,7 @@
<nifi.upload.working.directory>./work/uploads</nifi.upload.working.directory>
<nifi.sensitive.props.algorithm>NIFI_PBKDF2_AES_GCM_256</nifi.sensitive.props.algorithm>
+
<nifi.security.property.encryption.provider.implementation>org.apache.nifi.security.encryption.password.PasswordBasedPropertyEncryptionProvider</nifi.security.property.encryption.provider.implementation>
<nifi.remote.input.socket.port>9990</nifi.remote.input.socket.port>
<nifi.remote.input.secure>true</nifi.remote.input.secure>
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
b/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index 772e4792c1e..a7c8256a02b 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++
b/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -192,6 +192,7 @@ nifi.web.https.ciphersuites.exclude=
# security properties #
nifi.sensitive.props.key=
nifi.sensitive.props.algorithm=${nifi.sensitive.props.algorithm}
+nifi.security.property.encryption.provider.implementation=${nifi.security.property.encryption.provider.implementation}
nifi.security.autoreload.enabled=${nifi.security.autoreload.enabled}
nifi.security.autoreload.interval=${nifi.security.autoreload.interval}
diff --git a/nifi-system-tests/nifi-system-test-suite/pom.xml
b/nifi-system-tests/nifi-system-test-suite/pom.xml
index 5de9717a222..84715457d5c 100644
--- a/nifi-system-tests/nifi-system-test-suite/pom.xml
+++ b/nifi-system-tests/nifi-system-test-suite/pom.xml
@@ -319,6 +319,12 @@
<version>2.12.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+
<artifactId>nifi-password-based-property-encryption-nar</artifactId>
+ <version>2.12.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-provenance-repository-nar</artifactId>
diff --git
a/nifi-system-tests/nifi-system-test-suite/src/test/assembly/dependencies.xml
b/nifi-system-tests/nifi-system-test-suite/src/test/assembly/dependencies.xml
index a6f3c31efa5..6c8cf4c7640 100644
---
a/nifi-system-tests/nifi-system-test-suite/src/test/assembly/dependencies.xml
+++
b/nifi-system-tests/nifi-system-test-suite/src/test/assembly/dependencies.xml
@@ -75,6 +75,7 @@
<include>*:nifi-framework-zookeeper-nar</include>
<include>*:nifi-server-nar</include>
<include>*:nifi-standard-services-api-nar</include>
+
<include>*:nifi-password-based-property-encryption-nar</include>
<include>*:nifi-provenance-repository-nar</include>
<include>*:nifi-standard-shared-nar</include>
<include>*:nifi-py4j-nar</include>