You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jg...@apache.org on 2022/01/13 18:18:22 UTC
[nifi] branch main updated: NIFI-9438 Refactored sensitive-property-provider to multiple modules
This is an automated email from the ASF dual-hosted git repository.
jgresock 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 2ffd4a5 NIFI-9438 Refactored sensitive-property-provider to multiple modules
2ffd4a5 is described below
commit 2ffd4a5a9a9ded05b39dabc13056a1edc67511c5
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Mon Jan 10 20:11:22 2022 -0600
NIFI-9438 Refactored sensitive-property-provider to multiple modules
- Added nifi-property-protection-api with provider interfaces
- Added nifi-property-protection-factory with implementation references
- Added ProtectionSchemeResolver for abstracting conversion from command arguments
- Refactored PropertyProtectionScheme to package private visibility
- Refactored multiple unit test and removed provider integration tests
- Renamed AESSensitivePropertyProvider to AesGcmSensitivePropertyProvider
- Added getSupportedProtectionSchemes() to StandardProtectionSchemeResolver
- Updated command argument descriptions for protection schemes to include supported values
Signed-off-by: Joe Gresock <jg...@gmail.com>
This closes #5650.
---
nifi-commons/nifi-property-protection-api/pom.xml | 31 +
.../SensitivePropertyProtectionException.java} | 32 +-
.../properties/SensitivePropertyProtector.java | 16 -
.../nifi/properties/SensitivePropertyProvider.java | 11 +-
.../SensitivePropertyProviderFactory.java | 23 +-
.../nifi/properties/scheme/ProtectionScheme.java} | 22 +-
.../scheme/ProtectionSchemeResolver.java} | 23 +-
.../scheme/StandardProtectionScheme.java} | 26 +-
nifi-commons/nifi-property-protection-aws/pom.xml | 84 ++
.../AwsKmsSensitivePropertyProvider.java | 9 +-
...AwsSecretsManagerSensitivePropertyProvider.java | 24 +-
.../configuration/AbstractAwsClientProvider.java | 0
.../configuration/AwsKmsClientProvider.java | 0
.../AwsSecretsManagerClientProvider.java | 0
.../AwsKmsSensitivePropertyProviderIT.java | 0
.../AwsKmsSensitivePropertyProviderTest.java | 8 +
...sSecretsManagerSensitivePropertyProviderIT.java | 0
...ecretsManagerSensitivePropertyProviderTest.java | 9 +
.../pom.xml | 110 +--
.../AzureKeyVaultKeySensitivePropertyProvider.java | 13 +-
...ureKeyVaultSecretSensitivePropertyProvider.java | 14 +-
.../configuration/AzureClientProvider.java | 0
.../AzureCryptographyClientProvider.java | 0
.../configuration/AzureSecretClientProvider.java | 0
...zureKeyVaultKeySensitivePropertyProviderIT.java | 0
...reKeyVaultKeySensitivePropertyProviderTest.java | 8 +
...eyVaultSecretSensitivePropertyProviderTest.java | 8 +
.../AzureCryptographyClientProviderTest.java | 3 +-
.../AzureSecretClientProviderTest.java | 3 +-
.../nifi-property-protection-cipher/pom.xml | 36 +
.../AesGcmSensitivePropertyProvider.java | 200 +++++
.../AesGcmSensitivePropertyProviderTest.java | 109 +++
.../nifi-property-protection-factory/pom.xml | 82 ++
.../properties/ApplicationPropertiesProtector.java | 92 +--
.../SensitivePropertyProviderFactoryAware.java | 4 +-
.../StandardSensitivePropertyProviderFactory.java | 190 +++--
.../scheme/PropertyProtectionScheme.java} | 42 +-
.../scheme/StandardProtectionSchemeResolver.java | 50 ++
...andardSensitivePropertyProviderFactoryTest.java | 200 ++---
.../StandardProtectionSchemeResolverTest.java | 54 ++
nifi-commons/nifi-property-protection-gcp/pom.xml | 65 ++
.../GcpKmsSensitivePropertyProvider.java | 9 +-
.../GoogleKeyManagementServiceClientProvider.java | 0
.../GcpKmsSensitivePropertyProviderIT.java | 0
.../nifi-property-protection-hashicorp/pom.xml | 45 ++
...actHashiCorpVaultSensitivePropertyProvider.java | 23 +-
...CorpVaultKeyValueSensitivePropertyProvider.java | 26 +-
...iCorpVaultTransitSensitivePropertyProvider.java | 28 +-
...VaultKeyValueSensitivePropertyProviderTest.java | 55 ++
...pVaultTransitSensitivePropertyProviderTest.java | 55 ++
.../nifi-property-protection-shared/pom.xml | 31 +
...lientBasedEncodedSensitivePropertyProvider.java | 4 +-
.../EncodedSensitivePropertyProvider.java | 26 -
.../BootstrapPropertiesClientProvider.java | 10 +-
.../properties/configuration/ClientProvider.java | 0
.../properties/AESSensitivePropertyProvider.java | 268 ------
.../AbstractSensitivePropertyProvider.java | 50 --
...ltipleSensitivePropertyProtectionException.java | 128 ---
.../nifi/properties/PropertyProtectionScheme.java | 86 --
.../SensitivePropertyProtectionException.java | 91 ---
.../AESSensitivePropertyProviderTest.groovy | 496 ------------
nifi-commons/pom.xml | 9 +-
.../nifi-framework/nifi-properties-loader/pom.xml | 6 +-
.../nifi/properties/NiFiPropertiesLoader.java | 4 +-
.../nifi/properties/ProtectedNiFiProperties.java | 21 +-
.../ProtectedNiFiPropertiesGroovyTest.groovy | 897 ---------------------
nifi-nar-bundles/nifi-framework-bundle/pom.xml | 7 +-
.../nifi-registry-framework/pom.xml | 5 +
.../authentication/IdentityProviderFactory.java | 20 +-
.../security/authorization/AuthorizerFactory.java | 4 +-
.../nifi-registry-properties/pom.xml | 2 +-
.../properties/NiFiRegistryPropertiesLoader.java | 9 +-
.../ProtectedNiFiRegistryProperties.java | 21 +-
...rotectedNiFiRegistryPropertiesGroovyTest.groovy | 350 +-------
nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml | 10 +
.../nifi/properties/ConfigEncryptionTool.groovy | 149 ++--
.../nifi/toolkit/encryptconfig/DecryptMode.groovy | 18 +-
.../encryptconfig/NiFiRegistryDecryptMode.groovy | 7 +-
.../toolkit/encryptconfig/NiFiRegistryMode.groovy | 20 +-
.../encryptconfig/util/PropertiesEncryptor.groovy | 11 -
.../toolkit/encryptconfig/util/XmlEncryptor.groovy | 6 -
.../properties/ConfigEncryptionToolTest.groovy | 210 +----
.../encryptconfig/EncryptConfigMainTest.groovy | 4 +-
.../NiFiRegistryDecryptModeSpec.groovy | 117 ---
.../nifi/toolkit/encryptconfig/TestUtil.groovy | 4 +-
85 files changed, 1481 insertions(+), 3462 deletions(-)
diff --git a/nifi-commons/nifi-property-protection-api/pom.xml b/nifi-commons/nifi-property-protection-api/pom.xml
new file mode 100644
index 0000000..a10401b
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-api/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-utils</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
index cee6a9c..e82c4a9 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
@@ -14,32 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.properties.configuration;
-
-import org.apache.nifi.properties.BootstrapProperties;
-
-import java.util.Optional;
-import java.util.Properties;
+package org.apache.nifi.properties;
/**
- * Client Provider responsible for reading Client Properties and instantiating client services
- *
- * @param <T> Client Type
+ * Sensitive Property Protection Exception indicating runtime failures
*/
-public interface ClientProvider<T> {
+public class SensitivePropertyProtectionException extends RuntimeException {
/**
- * Get Client Properties from Bootstrap Properties
+ * Sensitive Property Protection Exception constructor with message and without associated cause
*
- * @param bootstrapProperties Bootstrap Properties
- * @return Client Properties or empty when not configured
+ * @param message Message describing failure condition
*/
- Optional<Properties> getClientProperties(BootstrapProperties bootstrapProperties);
+ public SensitivePropertyProtectionException(final String message) {
+ super(message);
+ }
/**
- * Get Client using Client Properties
+ * Sensitive Property Protection Exception constructor with message and associated cause
*
- * @param properties Client Properties
- * @return Client or empty when not configured
+ * @param message Message describing failure condition
+ * @param cause Throwable containing associated cause of failure condition
*/
- Optional<T> getClient(Properties properties);
+ public SensitivePropertyProtectionException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
similarity index 91%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
rename to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
index b78dd6d..add7c94 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java
@@ -26,7 +26,6 @@ import java.util.Set;
* @param <U> The ApplicationProperties type
*/
public interface SensitivePropertyProtector<T extends ProtectedProperties<U>, U extends ApplicationProperties> {
-
/**
* Returns the number of properties, excluding protection scheme properties.
* <p>
@@ -80,13 +79,6 @@ public interface SensitivePropertyProtector<T extends ProtectedProperties<U>, U
boolean hasProtectedKeys();
/**
- * Returns the unique set of all protection schemes currently in use for this instance.
- *
- * @return the set of protection schemes
- */
- Set<String> getProtectionSchemes();
-
- /**
* Returns a Map of the keys identifying "sensitive" properties that are currently protected and the "protection" key for each.
* This may or may not include all properties marked as sensitive.
*
@@ -95,20 +87,12 @@ public interface SensitivePropertyProtector<T extends ProtectedProperties<U>, U
Map<String, String> getProtectedPropertyKeys();
/**
- * Returns the local provider cache (null-safe) as a Map of protection schemes -> implementations.
- *
- * @return the map
- */
- Map<String, SensitivePropertyProvider> getSensitivePropertyProviders();
-
- /**
* Returns true if the property identified by this key is considered sensitive in this instance of {@code ApplicationProperties}.
* Some properties are sensitive by default, while others can be specified by
* {@link ProtectedProperties#getAdditionalSensitivePropertiesKeys()}.
*
* @param key the key
* @return true if it is sensitive
- * @see ApplicationPropertiesProtector#getSensitivePropertyKeys()
*/
boolean isPropertySensitive(String key);
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
index e4d7c8c..2b57fc4 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java
@@ -16,15 +16,10 @@
*/
package org.apache.nifi.properties;
+/**
+ * Sensitive Property Provider abstracts persistence and retrieval of properties that require additional protection
+ */
public interface SensitivePropertyProvider {
-
- /**
- * Returns the name of the underlying implementation.
- *
- * @return the name of this sensitive property provider
- */
- String getName();
-
/**
* Returns the key used to identify the provider implementation in {@code nifi.properties}.
*
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
similarity index 74%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
rename to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
index 70ba62f..75bbee2 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
@@ -16,27 +16,34 @@
*/
package org.apache.nifi.properties;
+import org.apache.nifi.properties.scheme.ProtectionScheme;
+
import java.util.Collection;
+/**
+ * Sensitive Property Provider Factory abstracts instantiation of supported providers
+ */
public interface SensitivePropertyProviderFactory {
-
/**
- * Gives the appropriate SensitivePropertyProvider, given a protection scheme.
- * @param protectionScheme The protection scheme to use
- * @return The appropriate SensitivePropertyProvider
+ * Get Provider for specified Protection Strategy
+ *
+ * @param protectionScheme Protection Strategy requested
+ * @return Property Provider implementation
*/
- SensitivePropertyProvider getProvider(PropertyProtectionScheme protectionScheme);
+ SensitivePropertyProvider getProvider(ProtectionScheme protectionScheme);
/**
- * Returns a collection of all supported sensitive property providers.
- * @return The supported sensitive property providers
+ * Get Supported Property Providers
+ *
+ * @return Collection of supported provider implementations
*/
- Collection<SensitivePropertyProvider> getSupportedSensitivePropertyProviders();
+ Collection<SensitivePropertyProvider> getSupportedProviders();
/**
* Returns a ProtectedPropertyContext with the given property name. The ProtectedPropertyContext's
* contextName will be the name found in a matching context mapping from bootstrap.conf, or 'default' if
* no matching mapping was found.
+ *
* @param groupIdentifier The identifier of a group that contains the configuration property. The definition
* of a group depends on the type of configuration file.
* @param propertyName A property name
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionScheme.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionScheme.java
index bc48e45..cb70382 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionScheme.java
@@ -14,26 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.properties.configuration;
-
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+package org.apache.nifi.properties.scheme;
/**
- * Abstract Microsoft Azure Client Provider
+ * Definition of Protection Scheme
*/
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
- public AzureClientProvider() {
- super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
- }
-
+public interface ProtectionScheme {
/**
- * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
+ * Get path of the protection scheme definition
*
- * @return Token Credential
+ * @return Protection scheme path
*/
- protected TokenCredential getDefaultTokenCredential() {
- return new DefaultAzureCredentialBuilder().build();
- }
+ String getPath();
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionSchemeResolver.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionSchemeResolver.java
index bc48e45..adb780b 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/ProtectionSchemeResolver.java
@@ -14,26 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.properties.configuration;
-
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+package org.apache.nifi.properties.scheme;
/**
- * Abstract Microsoft Azure Client Provider
+ * Protection Scheme Resolver abstracts resolution of Protection Scheme references from string arguments
*/
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
- public AzureClientProvider() {
- super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
- }
-
+public interface ProtectionSchemeResolver {
/**
- * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
+ * Get Protection Scheme
*
- * @return Token Credential
+ * @param scheme Scheme name required
+ * @return Protection Scheme
*/
- protected TokenCredential getDefaultTokenCredential() {
- return new DefaultAzureCredentialBuilder().build();
- }
+ ProtectionScheme getProtectionScheme(String scheme);
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionScheme.java
similarity index 51%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionScheme.java
index bc48e45..c0ce8d9 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-api/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionScheme.java
@@ -14,26 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.properties.configuration;
+package org.apache.nifi.properties.scheme;
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+import java.util.Objects;
/**
- * Abstract Microsoft Azure Client Provider
+ * Standard implementation of Protection Scheme with configurable parameters
*/
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
- public AzureClientProvider() {
- super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
+public class StandardProtectionScheme implements ProtectionScheme {
+ private String path;
+
+ public StandardProtectionScheme(final String path) {
+ this.path = Objects.requireNonNull(path, "Path required");
}
- /**
- * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
- *
- * @return Token Credential
- */
- protected TokenCredential getDefaultTokenCredential() {
- return new DefaultAzureCredentialBuilder().build();
+ @Override
+ public String getPath() {
+ return path;
}
}
diff --git a/nifi-commons/nifi-property-protection-aws/pom.xml b/nifi-commons/nifi-property-protection-aws/pom.xml
new file mode 100644
index 0000000..973d89d
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-aws/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-property-protection-aws</artifactId>
+ <properties>
+ <aws.sdk.version>2.17.106</aws.sdk.version>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-shared</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.12.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>url-connection-client</artifactId>
+ <version>${aws.sdk.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>kms</artifactId>
+ <version>${aws.sdk.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>netty-nio-client</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>apache-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>secretsmanager</artifactId>
+ <version>${aws.sdk.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>netty-nio-client</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>apache-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
similarity index 96%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
index e9cb4ae..80a34f3 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
@@ -34,8 +34,15 @@ import java.util.Properties;
public class AwsKmsSensitivePropertyProvider extends ClientBasedEncodedSensitivePropertyProvider<KmsClient> {
protected static final String KEY_ID_PROPERTY = "aws.kms.key.id";
+ private static final String IDENTIFIER_KEY = "aws/kms";
+
AwsKmsSensitivePropertyProvider(final KmsClient kmsClient, final Properties properties) throws SensitivePropertyProtectionException {
- super(PropertyProtectionScheme.AWS_KMS, kmsClient, properties);
+ super(kmsClient, properties);
+ }
+
+ @Override
+ public String getIdentifierKey() {
+ return IDENTIFIER_KEY;
}
/**
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
index cb899fd..17c265f 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProvider.java
@@ -31,7 +31,12 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-public class AwsSecretsManagerSensitivePropertyProvider extends AbstractSensitivePropertyProvider {
+/**
+ * Amazon Web Services Secrets Manager implementation of Sensitive Property Provider
+ */
+public class AwsSecretsManagerSensitivePropertyProvider implements SensitivePropertyProvider {
+ private static final String IDENTIFIER_KEY = "aws/secretsmanager";
+
private final SecretsManagerClient client;
private final ObjectMapper objectMapper;
@@ -40,13 +45,16 @@ public class AwsSecretsManagerSensitivePropertyProvider extends AbstractSensitiv
private final Lock writeLock = rwLock.writeLock();
AwsSecretsManagerSensitivePropertyProvider(final SecretsManagerClient client) {
- super(null);
-
this.client = client;
this.objectMapper = new ObjectMapper();
}
@Override
+ public String getIdentifierKey() {
+ return IDENTIFIER_KEY;
+ }
+
+ @Override
public boolean isSupported() {
return client != null;
}
@@ -145,16 +153,6 @@ public class AwsSecretsManagerSensitivePropertyProvider extends AbstractSensitiv
}
@Override
- protected PropertyProtectionScheme getProtectionScheme() {
- return PropertyProtectionScheme.AWS_SECRETSMANAGER;
- }
-
- @Override
- public String getIdentifierKey() {
- return getProtectionScheme().getIdentifier();
- }
-
- @Override
public void cleanUp() {
if (client != null) {
client.close();
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AbstractAwsClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java b/nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java
rename to nifi-commons/nifi-property-protection-aws/src/main/java/org/apache/nifi/properties/configuration/AwsSecretsManagerClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
index b21b017..be747c6 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
@@ -58,6 +58,8 @@ public class AwsKmsSensitivePropertyProviderTest {
private static final Properties PROPERTIES = new Properties();
+ private static final String IDENTIFIER_KEY = "aws/kms";
+
static {
PROPERTIES.setProperty(AwsKmsSensitivePropertyProvider.KEY_ID_PROPERTY, KEY_ID);
}
@@ -116,4 +118,10 @@ public class AwsKmsSensitivePropertyProviderTest {
final String property = provider.unprotect(PROTECTED_PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
assertEquals(PROPERTY, property);
}
+
+ @Test
+ public void testGetIdentifierKey() {
+ final String identifierKey = provider.getIdentifierKey();
+ assertEquals(IDENTIFIER_KEY, identifierKey);
+ }
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
index 93addaa..09f85e6 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-aws/src/test/java/org/apache/nifi/properties/AwsSecretsManagerSensitivePropertyProviderTest.java
@@ -45,6 +45,8 @@ public class AwsSecretsManagerSensitivePropertyProviderTest {
private static final String SECRET_STRING = String.format("{ \"%s\": \"%s\" }", PROPERTY_NAME, PROPERTY_VALUE);
+ private static final String IDENTIFIER_KEY = "aws/secretsmanager";
+
@Mock
private SecretsManagerClient secretsManagerClient;
@@ -113,4 +115,11 @@ public class AwsSecretsManagerSensitivePropertyProviderTest {
final String property = provider.unprotect("anyValue", ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
assertEquals(PROPERTY_VALUE, property);
}
+
+
+ @Test
+ public void testGetIdentifierKey() {
+ final String identifierKey = provider.getIdentifierKey();
+ assertEquals(IDENTIFIER_KEY, identifierKey);
+ }
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/pom.xml b/nifi-commons/nifi-property-protection-azure/pom.xml
similarity index 52%
rename from nifi-commons/nifi-sensitive-property-provider/pom.xml
rename to nifi-commons/nifi-property-protection-azure/pom.xml
index ac4d307..72c8c5c 100644
--- a/nifi-commons/nifi-sensitive-property-provider/pom.xml
+++ b/nifi-commons/nifi-property-protection-azure/pom.xml
@@ -20,73 +20,19 @@
<artifactId>nifi-commons</artifactId>
<version>1.16.0-SNAPSHOT</version>
</parent>
- <artifactId>nifi-sensitive-property-provider</artifactId>
- <properties>
- <aws.sdk.version>2.17.1</aws.sdk.version>
- <gcp.sdk.version>22.0.0</gcp.sdk.version>
- </properties>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>com.google.cloud</groupId>
- <artifactId>libraries-bom</artifactId>
- <version>${gcp.sdk.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
+ <artifactId>nifi-property-protection-azure</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-properties</artifactId>
+ <artifactId>nifi-property-protection-api</artifactId>
<version>1.16.0-SNAPSHOT</version>
</dependency>
<dependency>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk15on</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.12.0</version>
- </dependency>
- <dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-security-utils</artifactId>
+ <artifactId>nifi-property-protection-shared</artifactId>
<version>1.16.0-SNAPSHOT</version>
</dependency>
<dependency>
- <groupId>software.amazon.awssdk</groupId>
- <artifactId>kms</artifactId>
- <version>${aws.sdk.version}</version>
- <exclusions>
- <exclusion>
- <groupId>software.amazon.awssdk</groupId>
- <artifactId>netty-nio-client</artifactId>
- </exclusion>
- <exclusion>
- <groupId>software.amazon.awssdk</groupId>
- <artifactId>apache-client</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>software.amazon.awssdk</groupId>
- <artifactId>secretsmanager</artifactId>
- <version>${aws.sdk.version}</version>
- <exclusions>
- <exclusion>
- <groupId>software.amazon.awssdk</groupId>
- <artifactId>netty-nio-client</artifactId>
- </exclusion>
- <exclusion>
- <groupId>software.amazon.awssdk</groupId>
- <artifactId>apache-client</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
<groupId>com.azure</groupId>
<artifactId>azure-security-keyvault-secrets</artifactId>
<version>4.3.3</version>
@@ -157,60 +103,10 @@
<version>1.7.1</version>
</dependency>
<dependency>
- <groupId>software.amazon.awssdk</groupId>
- <artifactId>url-connection-client</artifactId>
- <version>${aws.sdk.version}</version>
- </dependency>
- <dependency>
- <groupId>com.google.cloud</groupId>
- <artifactId>google-cloud-kms</artifactId>
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.apache.nifi</groupId>
- <artifactId>nifi-vault-utils</artifactId>
- <version>1.16.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>2.10.0</version>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
- <build>
- <!-- Required to run Groovy tests without any Java tests -->
- <plugins>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <version>1.5</version>
- <executions>
- <execution>
- <id>add-test-source</id>
- <phase>generate-test-sources</phase>
- <goals>
- <goal>add-test-source</goal>
- </goals>
- <configuration>
- <sources>
- <source>src/test/groovy</source>
- </sources>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
index f8f8fbe..1d60b2a 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
@@ -24,8 +24,6 @@ import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm;
import com.azure.security.keyvault.keys.models.KeyOperation;
import com.azure.security.keyvault.keys.models.KeyProperties;
-import org.apache.nifi.util.StringUtils;
-
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
@@ -38,10 +36,17 @@ public class AzureKeyVaultKeySensitivePropertyProvider extends ClientBasedEncode
protected static final List<KeyOperation> REQUIRED_OPERATIONS = Arrays.asList(KeyOperation.DECRYPT, KeyOperation.ENCRYPT);
+ private static final String IDENTIFIER_KEY = "azure/keyvault/key";
+
private EncryptionAlgorithm encryptionAlgorithm;
AzureKeyVaultKeySensitivePropertyProvider(final CryptographyClient cryptographyClient, final Properties properties) {
- super(PropertyProtectionScheme.AZURE_KEYVAULT_KEY, cryptographyClient, properties);
+ super(cryptographyClient, properties);
+ }
+
+ @Override
+ public String getIdentifierKey() {
+ return IDENTIFIER_KEY;
}
/**
@@ -72,7 +77,7 @@ public class AzureKeyVaultKeySensitivePropertyProvider extends ClientBasedEncode
throw new SensitivePropertyProtectionException("Azure Key Vault Key Validation Failed", e);
}
final String algorithm = getProperties().getProperty(ENCRYPTION_ALGORITHM_PROPERTY);
- if (StringUtils.isBlank(algorithm)) {
+ if (algorithm == null || algorithm.isEmpty()) {
throw new SensitivePropertyProtectionException("Azure Key Vault Key Algorithm not configured");
}
encryptionAlgorithm = EncryptionAlgorithm.fromString(algorithm);
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
similarity index 93%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
index ac9e1da..e2cbfc7 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
@@ -32,6 +32,8 @@ public class AzureKeyVaultSecretSensitivePropertyProvider implements SensitivePr
private static final String DASH = "-";
+ private static final String IDENTIFIER_KEY = "azure/keyvault/secret";
+
private SecretClient secretClient;
AzureKeyVaultSecretSensitivePropertyProvider(final SecretClient secretClient) {
@@ -39,23 +41,13 @@ public class AzureKeyVaultSecretSensitivePropertyProvider implements SensitivePr
}
/**
- * Get Provider name using Protection Scheme
- *
- * @return Provider name
- */
- @Override
- public String getName() {
- return PropertyProtectionScheme.AZURE_KEYVAULT_SECRET.getName();
- }
-
- /**
* Get Identifier key using Protection Scheme
*
* @return Identifier key
*/
@Override
public String getIdentifierKey() {
- return PropertyProtectionScheme.AZURE_KEYVAULT_SECRET.getIdentifier();
+ return IDENTIFIER_KEY;
}
/**
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
similarity index 100%
copy from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
copy to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java b/nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
rename to nifi-commons/nifi-property-protection-azure/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
index 412a9df..539a90e 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
@@ -58,6 +58,8 @@ public class AzureKeyVaultKeySensitivePropertyProviderTest {
private static final EncryptionAlgorithm ALGORITHM = EncryptionAlgorithm.A256GCM;
+ private static final String IDENTIFIER_KEY = "azure/keyvault/key";
+
static {
PROPERTIES.setProperty(AzureKeyVaultKeySensitivePropertyProvider.ENCRYPTION_ALGORITHM_PROPERTY, ALGORITHM.toString());
}
@@ -107,4 +109,10 @@ public class AzureKeyVaultKeySensitivePropertyProviderTest {
final String property = provider.unprotect(PROTECTED_PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
assertEquals(PROPERTY, property);
}
+
+ @Test
+ public void testGetIdentifierKey() {
+ final String identifierKey = provider.getIdentifierKey();
+ assertEquals(IDENTIFIER_KEY, identifierKey);
+ }
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
index 3ec2d4a..399d2fb 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
@@ -46,6 +46,8 @@ public class AzureKeyVaultSecretSensitivePropertyProviderTest {
private static final String ID = KeyVaultSecret.class.getName();
+ private static final String IDENTIFIER_KEY = "azure/keyvault/secret";
+
@Mock
private SecretClient secretClient;
@@ -105,4 +107,10 @@ public class AzureKeyVaultSecretSensitivePropertyProviderTest {
final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
assertThrows(SensitivePropertyProtectionException.class, () -> provider.unprotect(PROTECTED_PROPERTY, context));
}
+
+ @Test
+ public void testGetIdentifierKey() {
+ final String identifierKey = provider.getIdentifierKey();
+ assertEquals(IDENTIFIER_KEY, identifierKey);
+ }
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
index e898ea4..92ac1b9 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
@@ -17,7 +17,6 @@
package org.apache.nifi.properties.configuration;
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
-import org.apache.nifi.util.StringUtils;
import org.junit.jupiter.api.Test;
import java.util.Optional;
@@ -38,7 +37,7 @@ public class AzureCryptographyClientProviderTest {
public void testClientPropertiesKeyIdBlank() {
final AzureCryptographyClientProvider provider = new AzureCryptographyClientProvider();
final Properties clientProperties = new Properties();
- clientProperties.setProperty(AzureCryptographyClientProvider.KEY_ID_PROPERTY, StringUtils.EMPTY);
+ clientProperties.setProperty(AzureCryptographyClientProvider.KEY_ID_PROPERTY, "");
final Optional<CryptographyClient> optionalClient = provider.getClient(clientProperties);
assertFalse(optionalClient.isPresent());
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
similarity index 96%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
rename to nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
index 6f312df..417b522 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
+++ b/nifi-commons/nifi-property-protection-azure/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
@@ -17,7 +17,6 @@
package org.apache.nifi.properties.configuration;
import com.azure.security.keyvault.secrets.SecretClient;
-import org.apache.nifi.util.StringUtils;
import org.junit.jupiter.api.Test;
import java.util.Optional;
@@ -38,7 +37,7 @@ public class AzureSecretClientProviderTest {
public void testClientPropertiesUriBlank() {
final AzureSecretClientProvider provider = new AzureSecretClientProvider();
final Properties clientProperties = new Properties();
- clientProperties.setProperty(AzureSecretClientProvider.URI_PROPERTY, StringUtils.EMPTY);
+ clientProperties.setProperty(AzureSecretClientProvider.URI_PROPERTY, "");
final Optional<SecretClient> optionalClient = provider.getClient(clientProperties);
assertFalse(optionalClient.isPresent());
}
diff --git a/nifi-commons/nifi-property-protection-cipher/pom.xml b/nifi-commons/nifi-property-protection-cipher/pom.xml
new file mode 100644
index 0000000..c30dcf7
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-cipher/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-property-protection-cipher</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.15</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-property-protection-cipher/src/main/java/org/apache/nifi/properties/AesGcmSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-cipher/src/main/java/org/apache/nifi/properties/AesGcmSensitivePropertyProvider.java
new file mode 100644
index 0000000..1641bc1
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-cipher/src/main/java/org/apache/nifi/properties/AesGcmSensitivePropertyProvider.java
@@ -0,0 +1,200 @@
+/*
+ * 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.properties;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Hex;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Sensitive Property Provider implementation using AES-GCM with configurable key sizes
+ */
+class AesGcmSensitivePropertyProvider implements SensitivePropertyProvider {
+ private static final String ALGORITHM = "AES/GCM/NoPadding";
+ private static final String SECRET_KEY_ALGORITHM = "AES";
+ private static final String DELIMITER = "||"; // "|" is not a valid Base64 character, so ensured not to be present in cipher text
+ private static final int IV_LENGTH = 12;
+ private static final int TAG_LENGTH = 128;
+ private static final int MIN_CIPHER_TEXT_LENGTH = IV_LENGTH * 4 / 3 + DELIMITER.length() + 1;
+ private static final List<Integer> VALID_KEY_LENGTHS = Arrays.asList(128, 192, 256);
+
+ private static final String IDENTIFIER_KEY_FORMAT = "aes/gcm/%d";
+ private static final int BITS_PER_BYTE = 8;
+
+ private static final Base64.Encoder BASE_64_ENCODER = Base64.getEncoder();
+ private static final Base64.Decoder BASE_64_DECODER = Base64.getDecoder();
+
+ private final SecureRandom secureRandom;
+ private final Cipher cipher;
+ private final SecretKey key;
+ private final String identifierKey;
+
+ public AesGcmSensitivePropertyProvider(final String keyHex) {
+ byte[] keyBytes = validateKey(keyHex);
+
+ secureRandom = new SecureRandom();
+ try {
+ cipher = Cipher.getInstance(ALGORITHM);
+ key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM);
+
+ final int keySize = keyBytes.length * BITS_PER_BYTE;
+ identifierKey = String.format(IDENTIFIER_KEY_FORMAT, keySize);
+ } catch (final NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new SensitivePropertyProtectionException(String.format("Cipher [%s] initialization failed", ALGORITHM), e);
+ }
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ /**
+ * Returns the key used to identify the provider implementation in {@code nifi.properties}.
+ *
+ * @return the key to persist in the sibling property
+ */
+ @Override
+ public String getIdentifierKey() {
+ return identifierKey;
+ }
+
+ /**
+ * Returns the encrypted cipher text.
+ *
+ * @param unprotectedValue the sensitive value
+ * @param context The property context, unused in this provider
+ * @return the value to persist in the {@code nifi.properties} file
+ * @throws SensitivePropertyProtectionException if there is an exception encrypting the value
+ */
+ @Override
+ public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
+ Objects.requireNonNull(unprotectedValue, "Value required");
+
+ final byte[] iv = generateIV();
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, this.key, new GCMParameterSpec(TAG_LENGTH, iv));
+
+ byte[] plainBytes = unprotectedValue.getBytes(StandardCharsets.UTF_8);
+ byte[] cipherBytes = cipher.doFinal(plainBytes);
+ return BASE_64_ENCODER.encodeToString(iv) + DELIMITER + BASE_64_ENCODER.encodeToString(cipherBytes);
+ } catch (final BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
+ throw new SensitivePropertyProtectionException("AES-GCM encryption failed", e);
+ }
+ }
+
+ /**
+ * Returns the decrypted plaintext.
+ *
+ * @param protectedValue the cipher text read from the {@code nifi.properties} file
+ * @param context The property context, unused in this provider
+ * @return the raw value to be used by the application
+ * @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
+ */
+ @Override
+ public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
+ if (protectedValue == null || protectedValue.trim().length() < MIN_CIPHER_TEXT_LENGTH) {
+ throw new IllegalArgumentException("Cannot decrypt a cipher text shorter than " + MIN_CIPHER_TEXT_LENGTH + " chars");
+ }
+
+ if (!protectedValue.contains(DELIMITER)) {
+ throw new IllegalArgumentException("The cipher text does not contain the delimiter " + DELIMITER + " -- it should be of the form Base64(IV) || Base64(cipherText)");
+ }
+ final String trimmedProtectedValue = protectedValue.trim();
+
+ final String armoredIV = trimmedProtectedValue.substring(0, trimmedProtectedValue.indexOf(DELIMITER));
+ final byte[] iv = BASE_64_DECODER.decode(armoredIV);
+ if (iv.length < IV_LENGTH) {
+ throw new IllegalArgumentException(String.format("The IV (%s bytes) must be at least %s bytes", iv.length, IV_LENGTH));
+ }
+
+ final String encodedCipherBytes = trimmedProtectedValue.substring(trimmedProtectedValue.indexOf(DELIMITER) + 2);
+
+ try {
+ final byte[] cipherBytes = BASE_64_DECODER.decode(encodedCipherBytes);
+
+ cipher.init(Cipher.DECRYPT_MODE, this.key, new GCMParameterSpec(TAG_LENGTH, iv));
+ final byte[] plainBytes = cipher.doFinal(cipherBytes);
+ return new String(plainBytes, StandardCharsets.UTF_8);
+ } catch (final BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
+ throw new SensitivePropertyProtectionException("AES-GCM decryption failed", e);
+ }
+ }
+
+ /**
+ * No cleanup necessary
+ */
+ @Override
+ public void cleanUp() { }
+
+ private byte[] generateIV() {
+ final byte[] iv = new byte[IV_LENGTH];
+ secureRandom.nextBytes(iv);
+ return iv;
+ }
+
+ private byte[] validateKey(String keyHex) {
+ if (keyHex == null || keyHex.isEmpty()) {
+ throw new SensitivePropertyProtectionException("AES Key not provided");
+ }
+ keyHex = formatHexKey(keyHex);
+ if (!isHexKeyValid(keyHex)) {
+ throw new SensitivePropertyProtectionException("AES Key not hexadecimal");
+ }
+ final byte[] key;
+ try {
+ key = Hex.decodeHex(keyHex);
+ } catch (final DecoderException e) {
+ throw new SensitivePropertyProtectionException("AES Key Hexadecimal decoding failed", e);
+ }
+ final int keyLengthBits = key.length * BITS_PER_BYTE;
+ if (!VALID_KEY_LENGTHS.contains(keyLengthBits)) {
+ throw new SensitivePropertyProtectionException(String.format("AES Key length not valid [%d]", keyLengthBits));
+ }
+ return key;
+ }
+
+ private static String formatHexKey(String input) {
+ if (input == null || input.isEmpty()) {
+ return "";
+ }
+ return input.replaceAll("[^0-9a-fA-F]", "").toLowerCase();
+ }
+
+ private static boolean isHexKeyValid(String key) {
+ if (key == null || key.isEmpty()) {
+ return false;
+ }
+ return key.matches("^[0-9a-fA-F]*$");
+ }
+}
diff --git a/nifi-commons/nifi-property-protection-cipher/src/test/java/org/apache/nifi/properties/AesGcmSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-cipher/src/test/java/org/apache/nifi/properties/AesGcmSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..5e01fc2
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-cipher/src/test/java/org/apache/nifi/properties/AesGcmSensitivePropertyProviderTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.properties;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AesGcmSensitivePropertyProviderTest {
+ private static final String HEXADECIMAL_KEY_32 = "12345678";
+
+ private static final String HEXADECIMAL_KEY_128 = "12345678123456788765432187654321";
+
+ private static final String HEXADECIMAL_KEY_256 = "1234567812345678876543218765432112345678123456788765432187654321";
+
+ private static final String AES_GCM_128 = "aes/gcm/128";
+
+ private static final String AES_GCM_256 = "aes/gcm/256";
+
+ private static final ProtectedPropertyContext PROPERTY_CONTEXT = ProtectedPropertyContext.defaultContext("propertyName");
+
+ private static final String PROPERTY_VALUE = "propertyValue";
+
+ private static final String DELIMITER = "||";
+
+ private static final String DELIMITER_PATTERN = "\\|\\|";
+
+ private static final int DELIMITED_ELEMENTS = 2;
+
+ private static final int INITIALIZATION_VECTOR_LENGTH = 12;
+
+ @Test
+ public void testInvalidKeyLength() {
+ assertThrows(SensitivePropertyProtectionException.class, () -> new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_32));
+ }
+
+ @Test
+ public void testIsSupported() {
+ final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+ assertTrue(provider.isSupported());
+ }
+
+ @Test
+ public void testGetIdentifierKeyAesGcm128() {
+ final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+ final String identifierKey = provider.getIdentifierKey();
+ assertEquals(AES_GCM_128, identifierKey);
+ }
+
+ @Test
+ public void testGetIdentifierKeyAesGcm256() {
+ final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_256);
+ final String identifierKey = provider.getIdentifierKey();
+ assertEquals(AES_GCM_256, identifierKey);
+ }
+
+ @Test
+ public void testProtectUnprotectSuccess() {
+ final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+
+ final String protectedPropertyValue = provider.protect(PROPERTY_VALUE, PROPERTY_CONTEXT);
+ final String unprotectedPropertyValue = provider.unprotect(protectedPropertyValue, PROPERTY_CONTEXT);
+
+ assertEquals(PROPERTY_VALUE, unprotectedPropertyValue);
+ assertTrue(protectedPropertyValue.contains(DELIMITER));
+
+ final String[] elements = protectedPropertyValue.split(DELIMITER_PATTERN);
+ assertEquals(DELIMITED_ELEMENTS, elements.length);
+
+ final String initializationVectorEncoded = elements[0];
+ final byte[] initializationVector = Base64.getDecoder().decode(initializationVectorEncoded);
+ assertEquals(INITIALIZATION_VECTOR_LENGTH, initializationVector.length);
+ }
+
+ @Test
+ public void testProtectUnprotectDifferentKeyFailed() {
+ final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+
+ final String protectedPropertyValue = provider.protect(PROPERTY_VALUE, PROPERTY_CONTEXT);
+
+ final AesGcmSensitivePropertyProvider secondProvider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_256);
+ assertThrows(SensitivePropertyProtectionException.class, () -> secondProvider.unprotect(protectedPropertyValue, PROPERTY_CONTEXT));
+ }
+
+ @Test
+ public void testUnprotectMinLengthRequired() {
+ final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
+
+ assertThrows(IllegalArgumentException.class, () -> provider.unprotect(HEXADECIMAL_KEY_32, PROPERTY_CONTEXT));
+ }
+}
diff --git a/nifi-commons/nifi-property-protection-factory/pom.xml b/nifi-commons/nifi-property-protection-factory/pom.xml
new file mode 100644
index 0000000..2753074
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-factory/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-property-protection-factory</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-shared</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-aws</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-azure</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-cipher</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-gcp</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-hashicorp</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-utils</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-properties</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-utils</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.11.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
similarity index 74%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
index 1064329..4500ff1 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java
@@ -47,9 +47,9 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertiesProtector.class);
- private T protectedProperties;
+ private final T protectedProperties;
- private Map<String, SensitivePropertyProvider> localProviderCache = new HashMap<>();
+ private final Map<String, SensitivePropertyProvider> localProviderCache = new HashMap<>();
/**
* Creates an instance containing the provided {@link ProtectedProperties}.
@@ -188,11 +188,6 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
}
@Override
- public Set<String> getProtectionSchemes() {
- return new HashSet<>(getProtectedPropertyKeys().values());
- }
-
- @Override
public boolean isPropertySensitive(final String key) {
// If the explicit check for ADDITIONAL_SENSITIVE_PROPERTIES_KEY is not here, this will loop infinitely
return key != null && !key.equals(getAdditionalSensitivePropertiesKeysName()) && getSensitivePropertyKeys().contains(key.trim());
@@ -243,17 +238,11 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
}
if (!failedKeys.isEmpty()) {
- if (failedKeys.size() > 1) {
- logger.warn("Combining {} failed keys [{}] into single exception", failedKeys.size(), StringUtils.join(failedKeys, ", "));
- throw new MultipleSensitivePropertyProtectionException("Failed to unprotect keys", failedKeys);
- } else {
- throw new SensitivePropertyProtectionException("Failed to unprotect key " + failedKeys.iterator().next());
- }
+ final String failed = failedKeys.size() == 1 ? failedKeys.iterator().next() : StringUtils.join(failedKeys, ", ");
+ throw new SensitivePropertyProtectionException(String.format("Failed unprotected properties: %s", failed));
}
- final U unprotected = protectedProperties.createApplicationProperties(rawProperties);
-
- return unprotected;
+ return protectedProperties.createApplicationProperties(rawProperties);
} else {
logger.debug("No protected properties");
return protectedProperties.getApplicationProperties();
@@ -262,50 +251,19 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
@Override
public void addSensitivePropertyProvider(final SensitivePropertyProvider sensitivePropertyProvider) {
- Objects.requireNonNull(sensitivePropertyProvider, "Cannot add null SensitivePropertyProvider");
- if (sensitivePropertyProvider == null) {
- throw new IllegalArgumentException("Cannot add null SensitivePropertyProvider");
- }
+ Objects.requireNonNull(sensitivePropertyProvider, "Provider required");
- if (getSensitivePropertyProviders().containsKey(sensitivePropertyProvider.getIdentifierKey())) {
- throw new UnsupportedOperationException("Cannot overwrite existing sensitive property provider registered for " + sensitivePropertyProvider.getIdentifierKey());
+ final String identifierKey = sensitivePropertyProvider.getIdentifierKey();
+ if (localProviderCache.containsKey(identifierKey)) {
+ throw new UnsupportedOperationException(String.format("Sensitive Property Provider Identifier [%s] override not supported", identifierKey));
}
- getSensitivePropertyProviders().put(sensitivePropertyProvider.getIdentifierKey(), sensitivePropertyProvider);
+ localProviderCache.put(identifierKey, sensitivePropertyProvider);
}
@Override
public String toString() {
- final Set<String> providers = getSensitivePropertyProviders().keySet();
- return new StringBuilder("ApplicationPropertiesProtector instance with ")
- .append(size()).append(" properties (")
- .append(getProtectedPropertyKeys().size())
- .append(" protected) and ")
- .append(providers.size())
- .append(" sensitive property providers: ")
- .append(StringUtils.join(providers, ", "))
- .toString();
- }
-
- @Override
- public Map<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
- if (localProviderCache == null) {
- localProviderCache = new HashMap<>();
- }
-
- return localProviderCache;
- }
-
- private SensitivePropertyProvider getSensitivePropertyProvider(final String protectionScheme) {
- if (isProviderAvailable(protectionScheme)) {
- return getSensitivePropertyProviders().get(protectionScheme);
- } else {
- throw new SensitivePropertyProtectionException("No provider available for " + protectionScheme);
- }
- }
-
- private boolean isProviderAvailable(final String protectionScheme) {
- return getSensitivePropertyProviders().containsKey(protectionScheme);
+ return String.format("%s Properties [%d]", getClass().getSimpleName(), getPropertyKeys().size());
}
/**
@@ -318,23 +276,25 @@ public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U
private String unprotectValue(final String key, final String retrievedValue) {
// Checks if the key is sensitive and marked as protected
if (isPropertyProtected(key)) {
- final String protectionScheme = getProperty(getProtectionKey(key));
-
- // No provider registered for this scheme
- if (!isProviderAvailable(protectionScheme)) {
- throw new IllegalStateException(String.format("No provider available for " + key));
- }
+ final String protectionSchemePath = getProperty(getProtectionKey(key));
try {
- final SensitivePropertyProvider sensitivePropertyProvider = getSensitivePropertyProvider(protectionScheme);
- return sensitivePropertyProvider.unprotect(retrievedValue, ProtectedPropertyContext.defaultContext(key));
- } catch (SensitivePropertyProtectionException e) {
- logger.error("Error unprotecting value for " + key, e);
- throw e;
- } catch (IllegalArgumentException e) {
- throw new SensitivePropertyProtectionException("Error unprotecting value for " + key, e);
+ final SensitivePropertyProvider provider = findProvider(protectionSchemePath);
+ return provider.unprotect(retrievedValue, ProtectedPropertyContext.defaultContext(key));
+ } catch (final RuntimeException e) {
+ throw new SensitivePropertyProtectionException(String.format("Property [%s] unprotect failed", key), e);
}
}
return retrievedValue;
}
+
+ private SensitivePropertyProvider findProvider(final String protectionSchemePath) {
+ Objects.requireNonNull(protectionSchemePath, "Protection Scheme Path required");
+ return localProviderCache.entrySet()
+ .stream()
+ .filter(entry -> protectionSchemePath.startsWith(entry.getKey()))
+ .findFirst()
+ .map(Map.Entry::getValue)
+ .orElseThrow(() -> new UnsupportedOperationException(String.format("Protection Scheme Path [%s] Provider not found", protectionSchemePath)));
+ }
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
index c2045d7..46bfbff 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java
@@ -16,6 +16,8 @@
*/
package org.apache.nifi.properties;
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
+
import java.util.function.Supplier;
/**
@@ -35,7 +37,7 @@ public class SensitivePropertyProviderFactoryAware {
protected String decryptValue(final String cipherText, final String protectionScheme, final String propertyName, final String groupIdentifier) throws SensitivePropertyProtectionException {
final SensitivePropertyProviderFactory sensitivePropertyProviderFactory = getSensitivePropertyProviderFactory();
final ProtectedPropertyContext protectedPropertyContext = sensitivePropertyProviderFactory.getPropertyContext(groupIdentifier, propertyName);
- return sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.fromIdentifier(protectionScheme))
+ return sensitivePropertyProviderFactory.getProvider(new StandardProtectionScheme(protectionScheme))
.unprotect(cipherText, protectedPropertyContext);
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
similarity index 55%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
index f9a9e6c..74ca100 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
@@ -19,6 +19,7 @@ package org.apache.nifi.properties;
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
import com.azure.security.keyvault.secrets.SecretClient;
import com.google.cloud.kms.v1.KeyManagementServiceClient;
+
import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
import org.apache.nifi.properties.configuration.AwsKmsClientProvider;
import org.apache.nifi.properties.configuration.AwsSecretsManagerClientProvider;
@@ -26,8 +27,10 @@ import org.apache.nifi.properties.configuration.AzureCryptographyClientProvider;
import org.apache.nifi.properties.configuration.AzureSecretClientProvider;
import org.apache.nifi.properties.configuration.ClientProvider;
import org.apache.nifi.properties.configuration.GoogleKeyManagementServiceClientProvider;
+import org.apache.nifi.properties.scheme.ProtectionScheme;
import org.apache.nifi.util.NiFiBootstrapUtils;
import org.apache.nifi.util.StringUtils;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.services.kms.KmsClient;
@@ -37,6 +40,7 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -45,12 +49,26 @@ import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+/**
+ * Standard implementation of Sensitive Property Provider Factory with custom initialization for each provider
+ */
public class StandardSensitivePropertyProviderFactory implements SensitivePropertyProviderFactory {
private static final Logger logger = LoggerFactory.getLogger(StandardSensitivePropertyProviderFactory.class);
+ private static final List<Class<? extends SensitivePropertyProvider>> PROVIDER_CLASSES = Arrays.asList(
+ AesGcmSensitivePropertyProvider.class,
+ AwsKmsSensitivePropertyProvider.class,
+ AwsSecretsManagerSensitivePropertyProvider.class,
+ AzureKeyVaultKeySensitivePropertyProvider.class,
+ AzureKeyVaultSecretSensitivePropertyProvider.class,
+ GcpKmsSensitivePropertyProvider.class,
+ HashiCorpVaultKeyValueSensitivePropertyProvider.class,
+ HashiCorpVaultTransitSensitivePropertyProvider.class
+ );
+
private final Optional<String> keyHex;
private final Supplier<BootstrapProperties> bootstrapPropertiesSupplier;
- private final Map<PropertyProtectionScheme, SensitivePropertyProvider> providerMap;
+ private final Map<Class<? extends SensitivePropertyProvider>, SensitivePropertyProvider> providers;
private Map<String, Pattern> customPropertyContextMap;
/**
@@ -88,19 +106,61 @@ public class StandardSensitivePropertyProviderFactory implements SensitiveProper
private StandardSensitivePropertyProviderFactory(final String keyHex, final Supplier<BootstrapProperties> bootstrapPropertiesSupplier) {
this.keyHex = Optional.ofNullable(keyHex);
this.bootstrapPropertiesSupplier = bootstrapPropertiesSupplier == null ? () -> null : bootstrapPropertiesSupplier;
- this.providerMap = new HashMap<>();
+ this.providers = new HashMap<>();
this.customPropertyContextMap = null;
}
+ /**
+ * Get Provider where Protection Scheme path starts with a prefix mapped to the Provider Class
+ *
+ * @param protectionScheme Protection Scheme requested
+ * @return Sensitive Property Provider
+ * @throws SensitivePropertyProtectionException Thrown when provider path not found for requested scheme
+ */
+ @Override
+ public SensitivePropertyProvider getProvider(final ProtectionScheme protectionScheme) throws SensitivePropertyProtectionException {
+ final String path = Objects.requireNonNull(protectionScheme, "Protection Scheme required").getPath();
+ final Collection<SensitivePropertyProvider> supportedProviders = getSupportedProviders();
+ return supportedProviders.stream()
+ .filter(provider -> provider.getIdentifierKey().startsWith(path))
+ .findFirst()
+ .orElseThrow(() -> new SensitivePropertyProtectionException(String.format("Protection Scheme [%s] not found", path)));
+ }
+
+ /**
+ * Get Supported Sensitive Property Providers instantiates providers as needed and checks supported status
+ *
+ * @return Supported Sensitive Property Providers
+ */
+ @Override
+ public Collection<SensitivePropertyProvider> getSupportedProviders() {
+ return PROVIDER_CLASSES
+ .stream()
+ .map(this::getProvider)
+ .filter(SensitivePropertyProvider::isSupported)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public ProtectedPropertyContext getPropertyContext(final String groupIdentifier, final String propertyName) {
+ if (customPropertyContextMap == null) {
+ populateCustomPropertyContextMap();
+ }
+ final String contextName = customPropertyContextMap.entrySet().stream()
+ .filter(entry -> entry.getValue().matcher(groupIdentifier).find())
+ .map(Map.Entry::getKey)
+ .findFirst()
+ .orElse(null);
+ return ProtectedPropertyContext.contextFor(propertyName, contextName);
+ }
+
private void populateCustomPropertyContextMap() {
final BootstrapProperties bootstrapProperties = getBootstrapProperties();
customPropertyContextMap = new HashMap<>();
final String contextMappingKeyPrefix = BootstrapPropertyKey.CONTEXT_MAPPING_PREFIX.getKey();
bootstrapProperties.getPropertyKeys().stream()
.filter(k -> k.contains(contextMappingKeyPrefix))
- .forEach(k -> {
- customPropertyContextMap.put(StringUtils.substringAfter(k, contextMappingKeyPrefix), Pattern.compile(bootstrapProperties.getProperty(k)));
- });
+ .forEach(k -> customPropertyContextMap.put(StringUtils.substringAfter(k, contextMappingKeyPrefix), Pattern.compile(bootstrapProperties.getProperty(k))));
}
private String getKeyHex() {
@@ -108,11 +168,6 @@ public class StandardSensitivePropertyProviderFactory implements SensitiveProper
.orElseThrow(() -> new SensitivePropertyProtectionException("Could not read root key from bootstrap.conf")));
}
- /**
- * Returns the configured bootstrap properties, or the default bootstrap.conf properties if
- * not provided.
- * @return The bootstrap.conf properties
- */
private BootstrapProperties getBootstrapProperties() {
return Optional.ofNullable(bootstrapPropertiesSupplier.get()).orElseGet(() -> {
try {
@@ -124,81 +179,54 @@ public class StandardSensitivePropertyProviderFactory implements SensitiveProper
});
}
- @Override
- public SensitivePropertyProvider getProvider(final PropertyProtectionScheme protectionScheme) throws SensitivePropertyProtectionException {
- Objects.requireNonNull(protectionScheme, "Protection scheme is required");
- // Only look up the secret key, which can perform a disk read, if this provider actually requires one
- final String keyHex = protectionScheme.requiresSecretKey() ? getKeyHex() : null;
- switch (protectionScheme) {
- case AES_GCM:
- return providerMap.computeIfAbsent(protectionScheme, s -> new AESSensitivePropertyProvider(keyHex));
- case AWS_KMS:
- return providerMap.computeIfAbsent(protectionScheme, s -> {
- final AwsKmsClientProvider clientProvider = new AwsKmsClientProvider();
- final Properties clientProperties = getClientProperties(clientProvider);
- final Optional<KmsClient> kmsClient = clientProvider.getClient(clientProperties);
- return new AwsKmsSensitivePropertyProvider(kmsClient.orElse(null), clientProperties);
- });
- case AWS_SECRETSMANAGER:
- return providerMap.computeIfAbsent(protectionScheme, s -> {
- final AwsSecretsManagerClientProvider clientProvider = new AwsSecretsManagerClientProvider();
- final Properties clientProperties = getClientProperties(clientProvider);
- final Optional<SecretsManagerClient> secretsManagerClient = clientProvider.getClient(clientProperties);
- return new AwsSecretsManagerSensitivePropertyProvider(secretsManagerClient.orElse(null));
- });
- case AZURE_KEYVAULT_KEY:
- return providerMap.computeIfAbsent(protectionScheme, s -> {
- final AzureCryptographyClientProvider clientProvider = new AzureCryptographyClientProvider();
- final Properties clientProperties = getClientProperties(clientProvider);
- final Optional<CryptographyClient> cryptographyClient = clientProvider.getClient(clientProperties);
- return new AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient.orElse(null), clientProperties);
- });
- case AZURE_KEYVAULT_SECRET:
- return providerMap.computeIfAbsent(protectionScheme, s -> {
- final AzureSecretClientProvider clientProvider = new AzureSecretClientProvider();
- final Properties clientProperties = getClientProperties(clientProvider);
- final Optional<SecretClient> secretClient = clientProvider.getClient(clientProperties);
- return new AzureKeyVaultSecretSensitivePropertyProvider(secretClient.orElse(null));
- });
- case GCP_KMS:
- return providerMap.computeIfAbsent(protectionScheme, s -> {
- final GoogleKeyManagementServiceClientProvider clientProvider = new GoogleKeyManagementServiceClientProvider();
- final Properties clientProperties = getClientProperties(clientProvider);
- final Optional<KeyManagementServiceClient> keyManagementServiceClient = clientProvider.getClient(clientProperties);
- return new GcpKmsSensitivePropertyProvider(keyManagementServiceClient.orElse(null), clientProperties);
- });
- case HASHICORP_VAULT_TRANSIT:
- return providerMap.computeIfAbsent(protectionScheme, s -> new HashiCorpVaultTransitSensitivePropertyProvider(getBootstrapProperties()));
- case HASHICORP_VAULT_KV:
- return providerMap.computeIfAbsent(protectionScheme, s -> new HashiCorpVaultKeyValueSensitivePropertyProvider(getBootstrapProperties()));
- default:
- throw new SensitivePropertyProtectionException("Unsupported protection scheme " + protectionScheme);
- }
+ private <T> Properties getClientProperties(final ClientProvider<T> clientProvider) {
+ final Optional<Properties> clientProperties = clientProvider.getClientProperties(getBootstrapProperties());
+ return clientProperties.orElse(null);
}
- @Override
- public Collection<SensitivePropertyProvider> getSupportedSensitivePropertyProviders() {
- return Arrays.stream(PropertyProtectionScheme.values())
- .map(this::getProvider)
- .filter(SensitivePropertyProvider::isSupported)
- .collect(Collectors.toList());
- }
+ private SensitivePropertyProvider getProvider(final Class<? extends SensitivePropertyProvider> providerClass) throws SensitivePropertyProtectionException {
+ SensitivePropertyProvider provider = providers.get(providerClass);
+ if (provider == null) {
+ if (AesGcmSensitivePropertyProvider.class.equals(providerClass)) {
+ final String hexadecimalKey = getKeyHex();
+ provider = new AesGcmSensitivePropertyProvider(hexadecimalKey);
+ } else if (AwsKmsSensitivePropertyProvider.class.equals(providerClass)) {
+ final AwsKmsClientProvider clientProvider = new AwsKmsClientProvider();
+ final Properties clientProperties = getClientProperties(clientProvider);
+ final Optional<KmsClient> kmsClient = clientProvider.getClient(clientProperties);
+ provider = new AwsKmsSensitivePropertyProvider(kmsClient.orElse(null), clientProperties);
+ } else if (AwsSecretsManagerSensitivePropertyProvider.class.equals(providerClass)) {
+ final AwsSecretsManagerClientProvider clientProvider = new AwsSecretsManagerClientProvider();
+ final Properties clientProperties = getClientProperties(clientProvider);
+ final Optional<SecretsManagerClient> secretsManagerClient = clientProvider.getClient(clientProperties);
+ provider = new AwsSecretsManagerSensitivePropertyProvider(secretsManagerClient.orElse(null));
+ } else if (AzureKeyVaultKeySensitivePropertyProvider.class.equals(providerClass)) {
+ final AzureCryptographyClientProvider clientProvider = new AzureCryptographyClientProvider();
+ final Properties clientProperties = getClientProperties(clientProvider);
+ final Optional<CryptographyClient> cryptographyClient = clientProvider.getClient(clientProperties);
+ provider = new AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient.orElse(null), clientProperties);
+ } else if (AzureKeyVaultSecretSensitivePropertyProvider.class.equals(providerClass)) {
+ final AzureSecretClientProvider clientProvider = new AzureSecretClientProvider();
+ final Properties clientProperties = getClientProperties(clientProvider);
+ final Optional<SecretClient> secretClient = clientProvider.getClient(clientProperties);
+ provider = new AzureKeyVaultSecretSensitivePropertyProvider(secretClient.orElse(null));
+ } else if (GcpKmsSensitivePropertyProvider.class.equals(providerClass)) {
+ final GoogleKeyManagementServiceClientProvider clientProvider = new GoogleKeyManagementServiceClientProvider();
+ final Properties clientProperties = getClientProperties(clientProvider);
+ final Optional<KeyManagementServiceClient> keyManagementServiceClient = clientProvider.getClient(clientProperties);
+ provider = new GcpKmsSensitivePropertyProvider(keyManagementServiceClient.orElse(null), clientProperties);
+ } else if (HashiCorpVaultKeyValueSensitivePropertyProvider.class.equals(providerClass)) {
+ provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(getBootstrapProperties());
+ } else if (HashiCorpVaultTransitSensitivePropertyProvider.class.equals(providerClass)) {
+ provider = new HashiCorpVaultTransitSensitivePropertyProvider(getBootstrapProperties());
+ }
+ }
- @Override
- public ProtectedPropertyContext getPropertyContext(final String groupIdentifier, final String propertyName) {
- if (customPropertyContextMap == null) {
- populateCustomPropertyContextMap();
+ if (provider == null) {
+ throw new UnsupportedOperationException(String.format("Provider class not supported [%s]", providerClass));
}
- final String contextName = customPropertyContextMap.entrySet().stream()
- .filter(entry -> entry.getValue().matcher(groupIdentifier).find())
- .map(Map.Entry::getKey)
- .findFirst()
- .orElse(null);
- return ProtectedPropertyContext.contextFor(propertyName, contextName);
- }
- private <T> Properties getClientProperties(final ClientProvider<T> clientProvider) {
- final Optional<Properties> clientProperties = clientProvider.getClientProperties(getBootstrapProperties());
- return clientProperties.orElse(null);
+ providers.put(providerClass, provider);
+ return provider;
}
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/PropertyProtectionScheme.java
similarity index 51%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
rename to nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/PropertyProtectionScheme.java
index bc48e45..51764e1 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/PropertyProtectionScheme.java
@@ -14,26 +14,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.properties.configuration;
-
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import org.apache.nifi.properties.BootstrapProperties;
+package org.apache.nifi.properties.scheme;
/**
- * Abstract Microsoft Azure Client Provider
+ * Property Protection Schemes supported as arguments for encryption commands should not have direct references
*/
-public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
- public AzureClientProvider() {
- super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
+enum PropertyProtectionScheme implements ProtectionScheme {
+ AES_GCM("aes/gcm"),
+
+ AWS_SECRETSMANAGER("aws/secretsmanager"),
+
+ AWS_KMS("aws/kms"),
+
+ AZURE_KEYVAULT_KEY("azure/keyvault/key"),
+
+ AZURE_KEYVAULT_SECRET("azure/keyvault/secret"),
+
+ GCP_KMS("gcp/kms"),
+
+ HASHICORP_VAULT_KV("hashicorp/vault/kv"),
+
+ HASHICORP_VAULT_TRANSIT("hashicorp/vault/transit");
+
+ PropertyProtectionScheme(final String path) {
+ this.path = path;
}
- /**
- * Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
- *
- * @return Token Credential
- */
- protected TokenCredential getDefaultTokenCredential() {
- return new DefaultAzureCredentialBuilder().build();
+ private final String path;
+
+ @Override
+ public String getPath() {
+ return path;
}
}
diff --git a/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolver.java b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolver.java
new file mode 100644
index 0000000..0c797b3
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-factory/src/main/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolver.java
@@ -0,0 +1,50 @@
+/*
+ * 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.properties.scheme;
+
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Standard implementation of Protection Scheme Resolver using Property Protection Scheme enumeration
+ */
+public class StandardProtectionSchemeResolver implements ProtectionSchemeResolver {
+ /**
+ * Get Protection Scheme based on scheme matching one the supported Protection Property Scheme enumerated values
+ *
+ * @param scheme Scheme name required
+ * @return Protection Scheme
+ */
+ @Override
+ public ProtectionScheme getProtectionScheme(final String scheme) {
+ Objects.requireNonNull(scheme, "Scheme required");
+ return Arrays.stream(PropertyProtectionScheme.values())
+ .filter(propertyProtectionScheme -> propertyProtectionScheme.name().equals(scheme))
+ .findFirst()
+ .orElseThrow(() -> new SensitivePropertyProtectionException(String.format("Protection Scheme [%s] not supported", scheme)));
+ }
+
+ public List<String> getSupportedProtectionSchemes() {
+ return Arrays.stream(PropertyProtectionScheme.values())
+ .map(PropertyProtectionScheme::name)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
similarity index 52%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
rename to nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
index c7aa609..a17cd43 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
+++ b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
@@ -17,131 +17,88 @@
package org.apache.nifi.properties;
import org.apache.commons.io.FilenameUtils;
+import org.apache.nifi.properties.scheme.ProtectionScheme;
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
import org.apache.nifi.util.NiFiProperties;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.mockito.internal.util.io.IOUtil;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
-import java.security.Security;
+import java.util.Collection;
import java.util.Properties;
-import java.util.function.Supplier;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class StandardSensitivePropertyProviderFactoryTest {
- private static final String AES_GCM_128 = "aes/gcm/128";
private SensitivePropertyProviderFactory factory;
private static final String BOOTSTRAP_KEY_HEX = "0123456789ABCDEFFEDCBA9876543210";
- private static final String AD_HOC_KEY_HEX = "123456789ABCDEFFEDCBA98765432101";
+
+ private static final ProtectionScheme AES_GCM = new StandardProtectionScheme("aes/gcm");
+ private static final ProtectionScheme AES_GCM_128 = new StandardProtectionScheme("aes/gcm/128");
+ private static final ProtectionScheme HASHICORP_VAULT_TRANSIT = new StandardProtectionScheme("hashicorp/vault/transit/testing");
+ private static final ProtectionScheme HASHICORP_VAULT_KV = new StandardProtectionScheme("hashicorp/vault/kv/testing");
private static Path tempConfDir;
private static Path bootstrapConf;
private static Path hashicorpVaultBootstrapConf;
private static Path nifiProperties;
+ private static Path azureKeyVaultConf;
private static String defaultBootstrapContents;
- private static NiFiProperties niFiProperties;
-
@BeforeAll
public static void initOnce() throws IOException {
- Security.addProvider(new BouncyCastleProvider());
tempConfDir = Files.createTempDirectory("conf");
bootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
+ azureKeyVaultConf = Files.createTempFile("bootstrap-azure-keyvault", ".conf").toAbsolutePath();
hashicorpVaultBootstrapConf = Files.createTempFile("bootstrap-hashicorp-vault", ".conf").toAbsolutePath();
nifiProperties = Files.createTempFile("nifi", ".properties").toAbsolutePath();
nifiProperties = Files.move(nifiProperties, tempConfDir.resolve("nifi.properties"));
- defaultBootstrapContents = String.format("%s=%s\n%s=%s",
+ defaultBootstrapContents = String.format("%s=%s\n%s=%s\n%s=%s",
"nifi.bootstrap.sensitive.key", BOOTSTRAP_KEY_HEX,
+ "nifi.bootstrap.protection.azure.keyvault.conf", FilenameUtils.separatorsToUnix(azureKeyVaultConf.toString()),
"nifi.bootstrap.protection.hashicorp.vault.conf", FilenameUtils.separatorsToUnix(hashicorpVaultBootstrapConf.toString()));
bootstrapConf = writeDefaultBootstrapConf();
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, FilenameUtils.separatorsToUnix(nifiProperties.toString()));
}
- private static Path writeDefaultBootstrapConf() throws IOException {
- return writeBootstrapConf(defaultBootstrapContents);
- }
-
- private static Path writeBootstrapConf(final String contents) throws IOException {
- final Path tempBootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
- final Path bootstrapConf = Files.move(tempBootstrapConf, tempConfDir.resolve("bootstrap.conf"), StandardCopyOption.REPLACE_EXISTING);
-
- IOUtil.writeText(contents, bootstrapConf.toFile());
- return bootstrapConf;
- }
-
@AfterAll
public static void tearDownOnce() throws IOException {
Files.deleteIfExists(bootstrapConf);
+ Files.deleteIfExists(azureKeyVaultConf);
Files.deleteIfExists(hashicorpVaultBootstrapConf);
Files.deleteIfExists(nifiProperties);
Files.deleteIfExists(tempConfDir);
System.clearProperty(NiFiProperties.PROPERTIES_FILE_PATH);
}
- /**
- * Configures the factory using the default bootstrap location.
- */
- private void configureDefaultFactory() {
+ @BeforeEach
+ public void setFactory() {
factory = StandardSensitivePropertyProviderFactory.withDefaults();
}
- /**
- * Configures the factory using an ad hoc key hex.
- */
- private void configureAdHocKeyFactory() {
- factory = StandardSensitivePropertyProviderFactory.withKey(AD_HOC_KEY_HEX);
- }
-
- /**
- * Configures the factory using an ad hoc key hex and bootstrap.conf properties. The key should override
- * the on in the bootstrap.conf.
- */
- private void configureAdHocKeyAndPropertiesFactory() throws IOException {
- factory = StandardSensitivePropertyProviderFactory.withKeyAndBootstrapSupplier(AD_HOC_KEY_HEX, mockBootstrapProperties());
- }
-
- private Supplier<BootstrapProperties> mockBootstrapProperties() throws IOException {
- final Properties bootstrapProperties = new Properties();
- try (final InputStream inputStream = Files.newInputStream(bootstrapConf)) {
- bootstrapProperties.load(inputStream);
- return () -> new BootstrapProperties("nifi", bootstrapProperties, bootstrapConf);
- }
- }
-
- private void configureHashicorpVault(final Properties properties) throws IOException {
- try (OutputStream out = new FileOutputStream(hashicorpVaultBootstrapConf.toFile())) {
- properties.store(out, "HashiCorpVault test");
- }
- }
-
@Test
public void testGetPropertyContextNotConfigured() {
- configureDefaultFactory();
assertEquals("default/prop", factory.getPropertyContext("ldap-provider", "prop").getContextKey());
}
@Test
public void testGetPropertyContext() throws IOException {
- configureDefaultFactory();
writeBootstrapConf(defaultBootstrapContents + "\n" +
"nifi.bootstrap.protection.context.mapping.ldap=ldap-.*");
try {
@@ -153,80 +110,97 @@ public class StandardSensitivePropertyProviderFactoryTest {
}
@Test
- public void testHashicorpVaultTransit() throws IOException {
- configureDefaultFactory();
+ public void testGetSupportedProviders() {
+ final Collection<SensitivePropertyProvider> providers = factory.getSupportedProviders();
+ assertFalse(providers.isEmpty());
+
+ final boolean aesProviderFound = providers.stream()
+ .anyMatch(provider -> provider instanceof AesGcmSensitivePropertyProvider);
+ assertTrue(aesProviderFound);
+ }
+
+ @Test
+ public void testAzureKeyVaultSecret() throws IOException {
final Properties properties = new Properties();
- properties.put("vault.transit.path", "nifi-transit");
- configureHashicorpVault(properties);
+ properties.put("azure.keyvault.uri", "https://testing.vault.azure.net");
+ configureAzureKeyVault(properties);
- factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
+ final SensitivePropertyProvider provider = factory.getProvider(new StandardProtectionScheme("azure/keyvault/secret"));
+ assertTrue(provider.isSupported());
+ assertEquals(AzureKeyVaultSecretSensitivePropertyProvider.class, provider.getClass());
}
@Test
- public void testHashicorpVaultTransitSupported() throws IOException {
- configureDefaultFactory();
+ public void testHashiCorpVaultKeyVaultSupported() throws IOException {
final Properties properties = new Properties();
- properties.put("vault.transit.path", "nifi-transit");
+ properties.put("vault.kv.path", "testing");
properties.put("vault.uri", "http://localhost:8200");
properties.put("vault.token", "test-token");
configureHashicorpVault(properties);
- SensitivePropertyProvider spp = factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
- assertTrue(spp.isSupported());
+ final SensitivePropertyProvider provider = factory.getProvider(HASHICORP_VAULT_KV);
+ assertTrue(provider.isSupported());
+ assertEquals(HashiCorpVaultKeyValueSensitivePropertyProvider.class, provider.getClass());
+ }
- properties.remove("vault.uri");
+ @Test
+ public void testHashiCorpVaultTransitSupported() throws IOException {
+ final Properties properties = new Properties();
+ properties.put("vault.transit.path", "testing");
+ properties.put("vault.uri", "http://localhost:8200");
+ properties.put("vault.token", "test-token");
configureHashicorpVault(properties);
- configureDefaultFactory();
- spp = factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
- assertFalse(spp.isSupported());
- properties.put("vault.uri", "http://localhost:8200");
- properties.remove("vault.transit.path");
- spp = factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
- assertFalse(spp.isSupported());
+ final SensitivePropertyProvider provider = factory.getProvider(HASHICORP_VAULT_TRANSIT);
+ assertTrue(provider.isSupported());
+ assertEquals(HashiCorpVaultTransitSensitivePropertyProvider.class, provider.getClass());
}
@Test
- public void testHashicorpVaultTransitInvalidCharacters() throws IOException {
- configureDefaultFactory();
+ public void testHashiCorpVaultTransitExceptionWhenMissingProperties() throws IOException {
final Properties properties = new Properties();
- properties.put("vault.transit.path", "invalid/characters");
+ properties.put("vault.uri", "http://localhost:8200");
configureHashicorpVault(properties);
- assertThrows(SensitivePropertyProtectionException.class, () -> factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT));
+ assertThrows(SensitivePropertyProtectionException.class, () -> factory.getProvider(HASHICORP_VAULT_TRANSIT));
}
@Test
- public void testAesGcm() throws IOException {
- configureDefaultFactory();
- final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext("propertyName");
-
- final SensitivePropertyProvider spp = factory.getProvider(PropertyProtectionScheme.AES_GCM);
- assertNotNull(spp);
- assertTrue(spp.isSupported());
-
- final String cleartext = "test";
- assertEquals(cleartext, spp.unprotect(spp.protect(cleartext, context), context));
- assertNotEquals(cleartext, spp.protect(cleartext, context));
- assertEquals(AES_GCM_128, spp.getIdentifierKey());
-
- // Key is now different
- configureAdHocKeyFactory();
- final SensitivePropertyProvider sppAdHocKey = factory.getProvider(PropertyProtectionScheme.AES_GCM);
- assertNotNull(sppAdHocKey);
- assertTrue(sppAdHocKey.isSupported());
- assertEquals(AES_GCM_128, sppAdHocKey.getIdentifierKey());
-
- assertNotEquals(spp.protect(cleartext, context), sppAdHocKey.protect(cleartext, context));
- assertEquals(cleartext, sppAdHocKey.unprotect(sppAdHocKey.protect(cleartext, context), context));
-
- // This should use the same keyHex as the second one
- configureAdHocKeyAndPropertiesFactory();
- final SensitivePropertyProvider sppKeyProperties = factory.getProvider(PropertyProtectionScheme.AES_GCM);
- assertNotNull(sppKeyProperties);
- assertTrue(sppKeyProperties.isSupported());
- assertEquals(AES_GCM_128, sppKeyProperties.getIdentifierKey());
-
- assertEquals(cleartext, sppKeyProperties.unprotect(sppKeyProperties.protect(cleartext, context), context));
+ public void testAesGcmWithoutKeySizeSupported() {
+ final SensitivePropertyProvider provider = factory.getProvider(AES_GCM);
+ assertEquals(AesGcmSensitivePropertyProvider.class, provider.getClass());
+ assertTrue(provider.isSupported());
+ }
+
+ @Test
+ public void testAesGcm128Supported() {
+ final SensitivePropertyProvider provider = factory.getProvider(AES_GCM_128);
+ assertEquals(AesGcmSensitivePropertyProvider.class, provider.getClass());
+ assertTrue(provider.isSupported());
+ }
+
+
+ private static Path writeDefaultBootstrapConf() throws IOException {
+ return writeBootstrapConf(defaultBootstrapContents);
+ }
+
+ private static Path writeBootstrapConf(final String contents) throws IOException {
+ final Path tempBootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
+ final Path bootstrapConf = Files.move(tempBootstrapConf, tempConfDir.resolve("bootstrap.conf"), StandardCopyOption.REPLACE_EXISTING);
+
+ Files.write(bootstrapConf, contents.getBytes(StandardCharsets.UTF_8));
+ return bootstrapConf;
+ }
+
+ private void configureHashicorpVault(final Properties properties) throws IOException {
+ try (OutputStream out = new FileOutputStream(hashicorpVaultBootstrapConf.toFile())) {
+ properties.store(out, hashicorpVaultBootstrapConf.toString());
+ }
+ }
+
+ private void configureAzureKeyVault(final Properties properties) throws IOException {
+ try (OutputStream out = new FileOutputStream(azureKeyVaultConf.toFile())) {
+ properties.store(out, azureKeyVaultConf.toString());
+ }
}
}
diff --git a/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolverTest.java b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolverTest.java
new file mode 100644
index 0000000..9cfc499
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-factory/src/test/java/org/apache/nifi/properties/scheme/StandardProtectionSchemeResolverTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.properties.scheme;
+
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StandardProtectionSchemeResolverTest {
+ private static final String AES_GCM = "AES_GCM";
+
+ private static final String AES_GCM_PATH = "aes/gcm";
+
+ private static final String UNKNOWN = "UNKNOWN";
+
+ private StandardProtectionSchemeResolver resolver;
+
+ @BeforeEach
+ public void setResolver() {
+ resolver = new StandardProtectionSchemeResolver();
+ }
+
+ @Test
+ public void getProtectionSchemeAesGcmFound() {
+ final ProtectionScheme protectionScheme = resolver.getProtectionScheme(AES_GCM);
+ assertNotNull(protectionScheme);
+ assertEquals(AES_GCM_PATH, protectionScheme.getPath());
+ }
+
+ @Test
+ public void getProtectionSchemeUnknownNotFound() {
+ final SensitivePropertyProtectionException exception = assertThrows(SensitivePropertyProtectionException.class, () -> resolver.getProtectionScheme(UNKNOWN));
+ assertTrue(exception.getMessage().contains(UNKNOWN));
+ }
+}
diff --git a/nifi-commons/nifi-property-protection-gcp/pom.xml b/nifi-commons/nifi-property-protection-gcp/pom.xml
new file mode 100644
index 0000000..dd05400
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-gcp/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-property-protection-gcp</artifactId>
+ <properties>
+ <gcp.sdk.version>24.1.2</gcp.sdk.version>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.cloud</groupId>
+ <artifactId>libraries-bom</artifactId>
+ <version>${gcp.sdk.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-shared</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.12.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.cloud</groupId>
+ <artifactId>google-cloud-kms</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
similarity index 95%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
index c7b275d..af76dc8 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
@@ -37,10 +37,17 @@ public class GcpKmsSensitivePropertyProvider extends ClientBasedEncodedSensitive
protected static final String KEYRING_PROPERTY = "gcp.kms.keyring";
protected static final String KEY_PROPERTY = "gcp.kms.key";
+ private static final String SCHEME_BASE_PATH = "gcp/kms";
+
private CryptoKeyName cryptoKeyName;
GcpKmsSensitivePropertyProvider(final KeyManagementServiceClient keyManagementServiceClient, final Properties properties) {
- super(PropertyProtectionScheme.GCP_KMS, keyManagementServiceClient, properties);
+ super(keyManagementServiceClient, properties);
+ }
+
+ @Override
+ public String getIdentifierKey() {
+ return SCHEME_BASE_PATH;
}
/**
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java b/nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
rename to nifi-commons/nifi-property-protection-gcp/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java b/nifi-commons/nifi-property-protection-gcp/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
rename to nifi-commons/nifi-property-protection-gcp/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
diff --git a/nifi-commons/nifi-property-protection-hashicorp/pom.xml b/nifi-commons/nifi-property-protection-hashicorp/pom.xml
new file mode 100644
index 0000000..5abaaca
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-hashicorp/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-property-protection-hashicorp</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-vault-utils</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.12.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
similarity index 89%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
index f992ccd..6ccb457 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/AbstractHashiCorpVaultSensitivePropertyProvider.java
@@ -26,7 +26,7 @@ import org.springframework.core.env.PropertySource;
import java.io.IOException;
import java.nio.file.Paths;
-public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends AbstractSensitivePropertyProvider {
+public abstract class AbstractHashiCorpVaultSensitivePropertyProvider implements SensitivePropertyProvider {
private static final String VAULT_PREFIX = "vault";
private final String path;
@@ -34,8 +34,6 @@ public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends Ab
private final BootstrapProperties vaultBootstrapProperties;
AbstractHashiCorpVaultSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
- super(bootstrapProperties);
-
final String vaultBootstrapConfFilename = bootstrapProperties
.getProperty(BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF).orElse(null);
vaultBootstrapProperties = getVaultBootstrapProperties(vaultBootstrapConfFilename);
@@ -87,7 +85,7 @@ public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends Ab
protected HashiCorpVaultCommunicationService getVaultCommunicationService() {
if (vaultCommunicationService == null) {
- throw new SensitivePropertyProtectionException(getIdentifierKey() + " protection scheme is not fully configured in hashicorp-vault-bootstrap.conf");
+ throw new SensitivePropertyProtectionException("Vault Protection Scheme missing required properties");
}
return vaultCommunicationService;
}
@@ -113,19 +111,14 @@ public abstract class AbstractHashiCorpVaultSensitivePropertyProvider extends Ab
}
/**
- * Returns the key used to identify the provider implementation in {@code nifi.properties},
- * in the format 'vault/{secretsEngine}/{secretsEnginePath}'.
- *
- * @return the key to persist in the sibling property
- */
- @Override
- public String getIdentifierKey() {
- return getProtectionScheme().getIdentifier(path);
- }
-
- /**
* No cleanup necessary
*/
@Override
public void cleanUp() { }
+
+ protected void requireNotBlank(final String value) {
+ if (value == null || value.isEmpty()) {
+ throw new IllegalArgumentException("Property value is null or empty");
+ }
+ }
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
similarity index 78%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
index d373a5a..f846490 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProvider.java
@@ -16,17 +16,17 @@
*/
package org.apache.nifi.properties;
-import org.apache.commons.lang3.StringUtils;
-
import java.util.Objects;
/**
- * Uses the HashiCorp Vault Key/Value (unversioned) Secrets Engine to store sensitive values.
+ * Uses the HashiCorp Vault Key/Value version 1 Secrets Engine to store sensitive values.
*/
public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHashiCorpVaultSensitivePropertyProvider {
private static final String KEY_VALUE_PATH = "vault.kv.path";
+ private static final String IDENTIFIER_KEY_FORMAT = "hashicorp/vault/kv/%s";
+
HashiCorpVaultKeyValueSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
super(bootstrapProperties);
}
@@ -36,20 +36,12 @@ public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHas
if (vaultBootstrapProperties == null) {
return null;
}
- final String kvPath = vaultBootstrapProperties.getProperty(KEY_VALUE_PATH);
- // Validate transit path
- try {
- PropertyProtectionScheme.fromIdentifier(getProtectionScheme().getIdentifier(kvPath));
- } catch (IllegalArgumentException e) {
- throw new SensitivePropertyProtectionException(String.format("%s [%s] contains unsupported characters", KEY_VALUE_PATH, kvPath), e);
- }
-
- return kvPath;
+ return vaultBootstrapProperties.getProperty(KEY_VALUE_PATH);
}
@Override
- protected PropertyProtectionScheme getProtectionScheme() {
- return PropertyProtectionScheme.HASHICORP_VAULT_KV;
+ public String getIdentifierKey() {
+ return String.format(IDENTIFIER_KEY_FORMAT, getPath());
}
/**
@@ -62,9 +54,7 @@ public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHas
*/
@Override
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(unprotectedValue)) {
- throw new IllegalArgumentException("Cannot protect an empty value");
- }
+ requireNotBlank(unprotectedValue);
Objects.requireNonNull(context, "Context is required to protect a value");
getVaultCommunicationService().writeKeyValueSecret(getPath(), context.getContextKey(), unprotectedValue);
@@ -77,7 +67,7 @@ public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHas
* @param protectedValue The value read from {@code nifi.properties} file. Ignored in this provider.
* @param context The property context, from which the Vault secret name is pulled
* @return the raw value to be used by the application
- * @throws SensitivePropertyProtectionException if there is an error retrieving the scret
+ * @throws SensitivePropertyProtectionException if there is an error retrieving the secret
*/
@Override
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
similarity index 75%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
index 452ee14..9b8cc4b 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/main/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProvider.java
@@ -16,8 +16,6 @@
*/
package org.apache.nifi.properties;
-import org.apache.commons.lang3.StringUtils;
-
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -28,6 +26,8 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
private static final Charset PROPERTY_CHARSET = StandardCharsets.UTF_8;
private static final String TRANSIT_PATH = "vault.transit.path";
+ private static final String IDENTIFIER_KEY_FORMAT = "hashicorp/vault/transit/%s";
+
HashiCorpVaultTransitSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
super(bootstrapProperties);
}
@@ -37,20 +37,12 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
if (vaultBootstrapProperties == null) {
return null;
}
- final String transitPath = vaultBootstrapProperties.getProperty(TRANSIT_PATH);
- // Validate transit path
- try {
- PropertyProtectionScheme.fromIdentifier(getProtectionScheme().getIdentifier(transitPath));
- } catch (IllegalArgumentException e) {
- throw new SensitivePropertyProtectionException(String.format("%s [%s] contains unsupported characters", TRANSIT_PATH, transitPath), e);
- }
-
- return transitPath;
+ return vaultBootstrapProperties.getProperty(TRANSIT_PATH);
}
@Override
- protected PropertyProtectionScheme getProtectionScheme() {
- return PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT;
+ public String getIdentifierKey() {
+ return String.format(IDENTIFIER_KEY_FORMAT, getPath());
}
/**
@@ -63,10 +55,7 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
*/
@Override
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(unprotectedValue)) {
- throw new IllegalArgumentException("Cannot encrypt an empty value");
- }
-
+ requireNotBlank(unprotectedValue);
return getVaultCommunicationService().encrypt(getPath(), unprotectedValue.getBytes(PROPERTY_CHARSET));
}
@@ -80,10 +69,7 @@ public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHash
*/
@Override
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(protectedValue)) {
- throw new IllegalArgumentException("Cannot decrypt an empty value");
- }
-
+ requireNotBlank(protectedValue);
return new String(getVaultCommunicationService().decrypt(getPath(), protectedValue), PROPERTY_CHARSET);
}
}
diff --git a/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..d421de8
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultKeyValueSensitivePropertyProviderTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.properties;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.eq;
+
+@ExtendWith(MockitoExtension.class)
+public class HashiCorpVaultKeyValueSensitivePropertyProviderTest {
+ private static final String IDENTIFIER_KEY = "hashicorp/vault/kv/null";
+
+ @Mock
+ private BootstrapProperties bootstrapProperties;
+
+ @Test
+ public void testGetIdentifierKeyPropertiesNotFound() {
+ when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+ final HashiCorpVaultKeyValueSensitivePropertyProvider provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(bootstrapProperties);
+
+ final String identifierKey = provider.getIdentifierKey();
+
+ assertEquals(IDENTIFIER_KEY, identifierKey);
+ }
+
+ @Test
+ public void testIsSupportedPropertiesNotFound() {
+ when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+ final HashiCorpVaultKeyValueSensitivePropertyProvider provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(bootstrapProperties);
+
+ assertFalse(provider.isSupported());
+ }
+}
diff --git a/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProviderTest.java b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..c446046
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-hashicorp/src/test/java/org/apache/nifi/properties/HashiCorpVaultTransitSensitivePropertyProviderTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.properties;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class HashiCorpVaultTransitSensitivePropertyProviderTest {
+ private static final String IDENTIFIER_KEY = "hashicorp/vault/transit/null";
+
+ @Mock
+ private BootstrapProperties bootstrapProperties;
+
+ @Test
+ public void testGetIdentifierKeyPropertiesNotFound() {
+ when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+ final HashiCorpVaultTransitSensitivePropertyProvider provider = new HashiCorpVaultTransitSensitivePropertyProvider(bootstrapProperties);
+
+ final String identifierKey = provider.getIdentifierKey();
+
+ assertEquals(IDENTIFIER_KEY, identifierKey);
+ }
+
+ @Test
+ public void testIsSupportedPropertiesNotFound() {
+ when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
+ final HashiCorpVaultTransitSensitivePropertyProvider provider = new HashiCorpVaultTransitSensitivePropertyProvider(bootstrapProperties);
+
+ assertFalse(provider.isSupported());
+ }
+}
diff --git a/nifi-commons/nifi-property-protection-shared/pom.xml b/nifi-commons/nifi-property-protection-shared/pom.xml
new file mode 100644
index 0000000..cddf0a8
--- /dev/null
+++ b/nifi-commons/nifi-property-protection-shared/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nifi-property-protection-shared</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
similarity index 92%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
index 6887378..c5753b3 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
@@ -33,10 +33,8 @@ public abstract class ClientBasedEncodedSensitivePropertyProvider<T> extends Enc
private final Properties properties;
- public ClientBasedEncodedSensitivePropertyProvider(final PropertyProtectionScheme propertyProtectionScheme,
- final T client,
+ public ClientBasedEncodedSensitivePropertyProvider(final T client,
final Properties properties) {
- super(propertyProtectionScheme);
this.client = client;
this.properties = properties;
validate(client);
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
similarity index 83%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
index b288402..1a1a217 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
+++ b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
@@ -31,32 +31,6 @@ public abstract class EncodedSensitivePropertyProvider implements SensitivePrope
private static final Base64.Decoder DECODER = Base64.getDecoder();
- private final PropertyProtectionScheme propertyProtectionScheme;
-
- public EncodedSensitivePropertyProvider(final PropertyProtectionScheme propertyProtectionScheme) {
- this.propertyProtectionScheme = Objects.requireNonNull(propertyProtectionScheme, "Scheme Required");
- }
-
- /**
- * Get Property Protection Scheme Name
- *
- * @return Property Protection Scheme Name
- */
- @Override
- public String getName() {
- return propertyProtectionScheme.getName();
- }
-
- /**
- * Get Identifier Key based on Property Protection Scheme
- *
- * @return Property Protection Scheme Identifier
- */
- @Override
- public String getIdentifierKey() {
- return propertyProtectionScheme.getIdentifier();
- }
-
/**
* Protect property value and return Base64-encoded representation of encrypted bytes
*
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
similarity index 94%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
index b3bfbd7..ac8c82a 100644
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
+++ b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
@@ -16,7 +16,6 @@
*/
package org.apache.nifi.properties.configuration;
-import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.properties.BootstrapProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -67,7 +66,7 @@ public abstract class BootstrapPropertiesClientProvider<T> implements ClientProv
public Optional<Properties> getClientProperties(final BootstrapProperties bootstrapProperties) {
Objects.requireNonNull(bootstrapProperties, "Bootstrap Properties required");
final String clientBootstrapPropertiesPath = bootstrapProperties.getProperty(bootstrapPropertyKey).orElse(null);
- if (StringUtils.isBlank(clientBootstrapPropertiesPath)) {
+ if (clientBootstrapPropertiesPath == null || clientBootstrapPropertiesPath.isEmpty()) {
logger.debug("Client Properties [{}] not configured", bootstrapPropertyKey);
return Optional.empty();
} else {
@@ -106,8 +105,9 @@ public abstract class BootstrapPropertiesClientProvider<T> implements ClientProv
protected abstract Set<String> getRequiredPropertyNames();
private boolean isMissingProperties(final Properties clientProperties) {
- return clientProperties == null || getRequiredPropertyNames().stream().anyMatch(propertyName ->
- StringUtils.isBlank(clientProperties.getProperty(propertyName))
- );
+ return clientProperties == null || getRequiredPropertyNames().stream().anyMatch(propertyName -> {
+ final String property = clientProperties.getProperty(propertyName);
+ return property == null || property.isEmpty();
+ });
}
}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java b/nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
similarity index 100%
rename from nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
rename to nifi-commons/nifi-property-protection-shared/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
deleted file mode 100644
index 543b166..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java
+++ /dev/null
@@ -1,268 +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.properties;
-
-import org.apache.commons.lang3.StringUtils;
-import org.bouncycastle.util.encoders.Base64;
-import org.bouncycastle.util.encoders.DecoderException;
-import org.bouncycastle.util.encoders.EncoderException;
-import org.bouncycastle.util.encoders.Hex;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class AESSensitivePropertyProvider extends AbstractSensitivePropertyProvider {
- private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProvider.class);
-
- private static final String IMPLEMENTATION_NAME = "AES Sensitive Property Provider";
- private static final String ALGORITHM = "AES/GCM/NoPadding";
- private static final String PROVIDER = "BC";
- private static final String DELIMITER = "||"; // "|" is not a valid Base64 character, so ensured not to be present in cipher text
- private static final int IV_LENGTH = 12;
- private static final int MIN_CIPHER_TEXT_LENGTH = IV_LENGTH * 4 / 3 + DELIMITER.length() + 1;
-
- private final Cipher cipher;
- private final SecretKey key;
- private final int keySize;
-
- AESSensitivePropertyProvider(final byte[] keyHex) {
- this(keyHex == null ? "" : Hex.toHexString(keyHex));
- }
-
- AESSensitivePropertyProvider(final String keyHex) {
- super(null);
-
- byte[] keyBytes = validateKey(keyHex);
-
- try {
- this.cipher = Cipher.getInstance(ALGORITHM, PROVIDER);
- // Only store the key if the cipher was initialized successfully
- this.key = new SecretKeySpec(keyBytes, "AES");
- this.keySize = getKeySize(Hex.toHexString(this.key.getEncoded()));
- } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) {
- logger.error("Encountered an error initializing the {}: {}", IMPLEMENTATION_NAME, e.getMessage());
- throw new SensitivePropertyProtectionException("Error initializing the protection cipher", e);
- }
- }
-
- @Override
- protected PropertyProtectionScheme getProtectionScheme() {
- return PropertyProtectionScheme.AES_GCM;
- }
-
- @Override
- public boolean isSupported() {
- return true; // AES protection is always supported
- }
-
- private byte[] validateKey(String keyHex) {
- if (keyHex == null || StringUtils.isBlank(keyHex)) {
- throw new SensitivePropertyProtectionException("The key cannot be empty");
- }
- keyHex = formatHexKey(keyHex);
- if (!isHexKeyValid(keyHex)) {
- throw new SensitivePropertyProtectionException("The key must be a valid hexadecimal key");
- }
- byte[] key = Hex.decode(keyHex);
- final List<Integer> validKeyLengths = getValidKeyLengths();
- if (!validKeyLengths.contains(key.length * 8)) {
- List<String> validKeyLengthsAsStrings = validKeyLengths.stream().map(i -> Integer.toString(i)).collect(Collectors.toList());
- throw new SensitivePropertyProtectionException("The key (" + key.length * 8 + " bits) must be a valid length: " + StringUtils.join(validKeyLengthsAsStrings, ", "));
- }
- return key;
- }
-
- private static String formatHexKey(String input) {
- if (input == null || StringUtils.isBlank(input)) {
- return "";
- }
- return input.replaceAll("[^0-9a-fA-F]", "").toLowerCase();
- }
-
- private static boolean isHexKeyValid(String key) {
- if (key == null || StringUtils.isBlank(key)) {
- return false;
- }
- // Key length is in "nibbles" (i.e. one hex char = 4 bits)
- return getValidKeyLengths().contains(key.length() * 4) && key.matches("^[0-9a-fA-F]*$");
- }
-
- private static List<Integer> getValidKeyLengths() {
- List<Integer> validLengths = new ArrayList<>();
- validLengths.add(128);
-
- try {
- if (Cipher.getMaxAllowedKeyLength("AES") > 128) {
- validLengths.add(192);
- validLengths.add(256);
- } else {
- logger.warn("JCE Unlimited Strength Cryptography Jurisdiction policies are not available, so the max key length is 128 bits");
- }
- } catch (NoSuchAlgorithmException e) {
- logger.warn("Encountered an error determining the max key length", e);
- }
-
- return validLengths;
- }
-
- /**
- * Returns the key used to identify the provider implementation in {@code nifi.properties}.
- *
- * @return the key to persist in the sibling property
- */
- @Override
- public String getIdentifierKey() {
- return getProtectionScheme().getIdentifier(String.valueOf(keySize));
- }
-
- private static int getKeySize(final String key) {
- // A key in hexadecimal format has one char per nibble (4 bits)
- return StringUtils.isBlank(key) ? 0 : formatHexKey(key).length() * 4;
- }
-
- /**
- * Returns the encrypted cipher text.
- *
- * @param unprotectedValue the sensitive value
- * @param context The property context, unused in this provider
- * @return the value to persist in the {@code nifi.properties} file
- * @throws SensitivePropertyProtectionException if there is an exception encrypting the value
- */
- @Override
- public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(unprotectedValue)) {
- throw new IllegalArgumentException("Cannot encrypt an empty value");
- }
-
- // Generate IV
- byte[] iv = generateIV();
- if (iv.length < IV_LENGTH) {
- throw new IllegalArgumentException("The IV (" + iv.length + " bytes) must be at least " + IV_LENGTH + " bytes");
- }
-
- try {
- // Initialize cipher for encryption
- cipher.init(Cipher.ENCRYPT_MODE, this.key, new IvParameterSpec(iv));
-
- byte[] plainBytes = unprotectedValue.getBytes(StandardCharsets.UTF_8);
- byte[] cipherBytes = cipher.doFinal(plainBytes);
- logger.debug(getName() + " encrypted a sensitive value successfully");
- return base64Encode(iv) + DELIMITER + base64Encode(cipherBytes);
- // return Base64.toBase64String(iv) + DELIMITER + Base64.toBase64String(cipherBytes);
- } catch (BadPaddingException | IllegalBlockSizeException | EncoderException | InvalidAlgorithmParameterException | InvalidKeyException e) {
- final String msg = "Error encrypting a protected value";
- logger.error(msg, e);
- throw new SensitivePropertyProtectionException(msg, e);
- }
- }
-
- private String base64Encode(final byte[] input) {
- return Base64.toBase64String(input).replaceAll("=", "");
- }
-
- /**
- * Generates a new random IV of 12 bytes using {@link java.security.SecureRandom}.
- *
- * @return the IV
- */
- private byte[] generateIV() {
- final byte[] iv = new byte[IV_LENGTH];
- new SecureRandom().nextBytes(iv);
- return iv;
- }
-
- /**
- * Returns the decrypted plaintext.
- *
- * @param protectedValue the cipher text read from the {@code nifi.properties} file
- * @param context The property context, unused in this provider
- * @return the raw value to be used by the application
- * @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
- */
- @Override
- public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (protectedValue == null || protectedValue.trim().length() < MIN_CIPHER_TEXT_LENGTH) {
- throw new IllegalArgumentException("Cannot decrypt a cipher text shorter than " + MIN_CIPHER_TEXT_LENGTH + " chars");
- }
-
- if (!protectedValue.contains(DELIMITER)) {
- throw new IllegalArgumentException("The cipher text does not contain the delimiter " + DELIMITER + " -- it should be of the form Base64(IV) || Base64(cipherText)");
- }
- final String trimmedProtectedValue = protectedValue.trim();
-
- final String armoredIV = trimmedProtectedValue.substring(0, trimmedProtectedValue.indexOf(DELIMITER));
- final byte[] iv = Base64.decode(armoredIV);
- if (iv.length < IV_LENGTH) {
- throw new IllegalArgumentException(String.format("The IV (%s bytes) must be at least %s bytes", iv.length, IV_LENGTH));
- }
-
- String armoredCipherText = trimmedProtectedValue.substring(trimmedProtectedValue.indexOf(DELIMITER) + 2);
-
- // Restore the = padding if necessary to reconstitute the GCM MAC check
- if (armoredCipherText.length() % 4 != 0) {
- final int paddedLength = armoredCipherText.length() + 4 - (armoredCipherText.length() % 4);
- armoredCipherText = StringUtils.rightPad(armoredCipherText, paddedLength, '=');
- }
-
- try {
- final byte[] cipherBytes = Base64.decode(armoredCipherText);
-
- cipher.init(Cipher.DECRYPT_MODE, this.key, new IvParameterSpec(iv));
- final byte[] plainBytes = cipher.doFinal(cipherBytes);
- logger.debug(getName() + " decrypted a sensitive value successfully");
- return new String(plainBytes, StandardCharsets.UTF_8);
- } catch (BadPaddingException | IllegalBlockSizeException | DecoderException | InvalidAlgorithmParameterException | InvalidKeyException e) {
- final String msg = "Error decrypting a protected value";
- logger.error(msg, e);
- throw new SensitivePropertyProtectionException(msg, e);
- }
- }
-
- public static int getIvLength() {
- return IV_LENGTH;
- }
-
- public static int getMinCipherTextLength() {
- return MIN_CIPHER_TEXT_LENGTH;
- }
-
- public static String getDelimiter() {
- return DELIMITER;
- }
-
- /**
- * No cleanup necessary
- */
- @Override
- public void cleanUp() { }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java
deleted file mode 100644
index e163747..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java
+++ /dev/null
@@ -1,50 +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.properties;
-
-public abstract class AbstractSensitivePropertyProvider implements SensitivePropertyProvider {
- private final BootstrapProperties bootstrapProperties;
-
- public AbstractSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
- this.bootstrapProperties = bootstrapProperties;
- }
-
- protected BootstrapProperties getBootstrapProperties() {
- return bootstrapProperties;
- }
-
- /**
- * Return the appropriate PropertyProtectionScheme for this provider.
- * @return The PropertyProtectionScheme
- */
- protected abstract PropertyProtectionScheme getProtectionScheme();
-
- @Override
- public String getName() {
- return getProtectionScheme().getName();
- }
-
- /**
- * Default implementation to return the protection scheme identifier, with no args to populate the identifier key.
- * Concrete classes may choose to override this in order to fill in the identifier with specific args.
- * @return The identifier key
- */
- @Override
- public String getIdentifierKey() {
- return getProtectionScheme().getIdentifier();
- }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java
deleted file mode 100644
index 3b6f3cd..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java
+++ /dev/null
@@ -1,128 +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.properties;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-
-public class MultipleSensitivePropertyProtectionException extends SensitivePropertyProtectionException {
-
- private Set<String> failedKeys;
-
- /**
- * Constructs a new throwable with {@code null} as its detail message.
- * The cause is not initialized, and may subsequently be initialized by a
- * call to {@link #initCause}.
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- */
- public MultipleSensitivePropertyProtectionException() {
- }
-
- /**
- * Constructs a new throwable with the specified detail message. The
- * cause is not initialized, and may subsequently be initialized by
- * a call to {@link #initCause}.
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- *
- * @param message the detail message. The detail message is saved for
- * later retrieval by the {@link #getMessage()} method.
- */
- public MultipleSensitivePropertyProtectionException(String message) {
- super(message);
- }
-
- /**
- * Constructs a new throwable with the specified detail message and
- * cause. <p>Note that the detail message associated with
- * {@code cause} is <i>not</i> automatically incorporated in
- * this throwable's detail message.
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- *
- * @param message the detail message (which is saved for later retrieval
- * by the {@link #getMessage()} method).
- * @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A {@code null} value is
- * permitted, and indicates that the cause is nonexistent or
- * unknown.)
- * @since 1.4
- */
- public MultipleSensitivePropertyProtectionException(String message, Throwable cause) {
- super(message, cause);
- }
-
- /**
- * Constructs a new throwable with the specified cause and a detail
- * message of {@code (cause==null ? null : cause.toString())} (which
- * typically contains the class and detail message of {@code cause}).
- * This constructor is useful for throwables that are little more than
- * wrappers for other throwables (for example, PrivilegedActionException).
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- *
- * @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A {@code null} value is
- * permitted, and indicates that the cause is nonexistent or
- * unknown.)
- * @since 1.4
- */
- public MultipleSensitivePropertyProtectionException(Throwable cause) {
- super(cause);
- }
-
- /**
- * Constructs a new exception with the provided message and a unique set of the keys that caused the error.
- *
- * @param message the message
- * @param failedKeys any failed keys
- */
- public MultipleSensitivePropertyProtectionException(String message, Collection<String> failedKeys) {
- this(message, failedKeys, null);
- }
-
- /**
- * Constructs a new exception with the provided message and a unique set of the keys that caused the error.
- *
- * @param message the message
- * @param failedKeys any failed keys
- * @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A {@code null} value is
- * permitted, and indicates that the cause is nonexistent or
- * unknown.)
- */
- public MultipleSensitivePropertyProtectionException(String message, Collection<String> failedKeys, Throwable cause) {
- super(message, cause);
- this.failedKeys = new HashSet<>(failedKeys);
- }
-
- public Set<String> getFailedKeys() {
- return this.failedKeys;
- }
-
- @Override
- public String toString() {
- return "SensitivePropertyProtectionException for [" + StringUtils.join(this.failedKeys, ", ") + "]: " + getLocalizedMessage();
- }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
deleted file mode 100644
index 4a85799..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
+++ /dev/null
@@ -1,86 +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.properties;
-
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * A scheme for protecting sensitive properties. Each scheme is intended to be backed by an implementation of
- * SensitivePropertyProvider.
- */
-public enum PropertyProtectionScheme {
- AES_GCM("aes/gcm/(128|192|256)", "aes/gcm/%s", "AES Sensitive Property Provider", true),
- AWS_SECRETSMANAGER("aws/secretsmanager", "aws/secretsmanager", "AWS Secrets Manager Sensitive Property Provider", false),
- AWS_KMS("aws/kms", "aws/kms", "AWS KMS Sensitive Property Provider", false),
- AZURE_KEYVAULT_KEY("azure/keyvault/key", "azure/keyvault/key", "Azure Key Vault Key Sensitive Property Provider", false),
- AZURE_KEYVAULT_SECRET("azure/keyvault/secret", "azure/keyvault/secret", "Azure Key Vault Secret Sensitive Property Provider", false),
- GCP_KMS("gcp/kms", "gcp/kms", "GCP Cloud KMS Sensitive Property Provider", false),
- HASHICORP_VAULT_KV("hashicorp/vault/kv/[a-zA-Z0-9_-]+", "hashicorp/vault/kv/%s", "HashiCorp Vault Key/Value Engine Sensitive Property Provider", false),
- HASHICORP_VAULT_TRANSIT("hashicorp/vault/transit/[a-zA-Z0-9_-]+", "hashicorp/vault/transit/%s", "HashiCorp Vault Transit Engine Sensitive Property Provider", false);
-
- PropertyProtectionScheme(final String identifierPattern, final String identifierFormat, final String name, final boolean requiresSecretKey) {
- this.identifierPattern = identifierPattern;
- this.identifierFormat = identifierFormat;
- this.name = name;
- this.requiresSecretKey = requiresSecretKey;
- }
-
- private final String identifierFormat;
- private final String identifierPattern;
- private final String name;
- private final boolean requiresSecretKey;
-
- /**
- * Returns a the identifier of the PropertyProtectionScheme.
- * @param args scheme-specific arguments used to fill in the formatted identifierPattern
- * @return The identifier of the PropertyProtectionScheme
- */
- public String getIdentifier(final String... args) {
- return String.format(identifierFormat, args);
- }
-
- /**
- * Returns whether this scheme requires a secret key.
- * @return True if this scheme requires a secret key
- */
- public boolean requiresSecretKey() {
- return requiresSecretKey;
- }
-
- /**
- * Returns the name of the PropertyProtectionScheme.
- * @return The name
- */
- public String getName() {
- return name;
- }
-
- /**
- * Returns the PropertyProtectionScheme matching the provided name.
- * @param identifier The unique PropertyProtectionScheme identifier
- * @return The matching PropertyProtectionScheme
- * @throws IllegalArgumentException If the name was not recognized
- */
- public static PropertyProtectionScheme fromIdentifier(final String identifier) {
- Objects.requireNonNull(identifier, "Identifier must be specified");
- return Arrays.stream(PropertyProtectionScheme.values())
- .filter(scheme -> identifier.matches(scheme.identifierPattern))
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException("Unrecognized protection scheme :" + identifier));
- }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
deleted file mode 100644
index 2870c2a..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java
+++ /dev/null
@@ -1,91 +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.properties;
-
-public class SensitivePropertyProtectionException extends RuntimeException {
- /**
- * Constructs a new throwable with {@code null} as its detail message.
- * The cause is not initialized, and may subsequently be initialized by a
- * call to {@link #initCause}.
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- */
- public SensitivePropertyProtectionException() {
- }
-
- /**
- * Constructs a new throwable with the specified detail message. The
- * cause is not initialized, and may subsequently be initialized by
- * a call to {@link #initCause}.
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- *
- * @param message the detail message. The detail message is saved for
- * later retrieval by the {@link #getMessage()} method.
- */
- public SensitivePropertyProtectionException(String message) {
- super(message);
- }
-
- /**
- * Constructs a new throwable with the specified detail message and
- * cause. <p>Note that the detail message associated with
- * {@code cause} is <i>not</i> automatically incorporated in
- * this throwable's detail message.
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- *
- * @param message the detail message (which is saved for later retrieval
- * by the {@link #getMessage()} method).
- * @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A {@code null} value is
- * permitted, and indicates that the cause is nonexistent or
- * unknown.)
- * @since 1.4
- */
- public SensitivePropertyProtectionException(String message, Throwable cause) {
- super(message, cause);
- }
-
- /**
- * Constructs a new throwable with the specified cause and a detail
- * message of {@code (cause==null ? null : cause.toString())} (which
- * typically contains the class and detail message of {@code cause}).
- * This constructor is useful for throwables that are little more than
- * wrappers for other throwables (for example, PrivilegedActionException).
- * <p>
- * <p>The {@link #fillInStackTrace()} method is called to initialize
- * the stack trace data in the newly created throwable.
- *
- * @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A {@code null} value is
- * permitted, and indicates that the cause is nonexistent or
- * unknown.)
- * @since 1.4
- */
- public SensitivePropertyProtectionException(Throwable cause) {
- super(cause);
- }
-
- @Override
- public String toString() {
- return "SensitivePropertyProtectionException: " + getLocalizedMessage();
- }
-}
diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy b/nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy
deleted file mode 100644
index fe838c3..0000000
--- a/nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy
+++ /dev/null
@@ -1,496 +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.properties
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.BeforeAll
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.crypto.Cipher
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.nio.charset.StandardCharsets
-import java.security.SecureRandom
-import java.security.Security
-
-import static org.junit.Assume.assumeTrue
-
-class AESSensitivePropertyProviderTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProviderTest.class)
-
- private static final String KEY_128_HEX = "0123456789ABCDEFFEDCBA9876543210"
- private static final String KEY_256_HEX = KEY_128_HEX * 2
- private static final int IV_LENGTH = AESSensitivePropertyProvider.getIvLength()
-
- private static final List<Integer> KEY_SIZES = getAvailableKeySizes()
-
- private static final SecureRandom secureRandom = new SecureRandom()
-
- private static final ProtectedPropertyContext PROPERTY_CONTEXT = ProtectedPropertyContext.defaultContext("propertyName")
-
- private static final Base64.Encoder encoder = Base64.encoder
- private static final Base64.Decoder decoder = Base64.decoder
-
- @BeforeAll
- static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @BeforeEach
- void setUp() throws Exception {
-
- }
-
- @AfterEach
- void tearDown() throws Exception {
-
- }
-
- private static Cipher getCipher(boolean encrypt = true, int keySize = 256, byte[] iv = [0x00] * IV_LENGTH) {
- Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding")
- String key = getKeyOfSize(keySize)
- cipher.init((encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE) as int, new SecretKeySpec(Hex.decode(key), "AES"), new IvParameterSpec(iv))
- logger.setup("Initialized a cipher in ${encrypt ? "encrypt" : "decrypt"} mode with a key of length ${keySize} bits")
- cipher
- }
-
- private static String getKeyOfSize(int keySize = 256) {
- switch (keySize) {
- case 128:
- return KEY_128_HEX
- case 192:
- case 256:
- if (Cipher.getMaxAllowedKeyLength("AES") < keySize) {
- throw new IllegalArgumentException("The JCE unlimited strength cryptographic jurisdiction policies are not installed, so the max key size is 128 bits")
- }
- return KEY_256_HEX[0..<keySize.intdiv(4)]
- default:
- throw new IllegalArgumentException("Key size ${keySize} bits is not valid")
- }
- }
-
- private static List<Integer> getAvailableKeySizes() {
- if (Cipher.getMaxAllowedKeyLength("AES") > 128) {
- [128, 192, 256]
- } else {
- [128]
- }
- }
-
- private static String manipulateString(String input, int start = 0, int end = input?.length()) {
- if ((input[start..end] as List).unique().size() == 1) {
- throw new IllegalArgumentException("Can't manipulate a String where the entire range is identical [${input[start..end]}]")
- }
- List shuffled = input[start..end] as List
- Collections.shuffle(shuffled)
- String reconstituted = input[0..<start] + shuffled.join() + input[end + 1..-1]
- return reconstituted != input ? reconstituted : manipulateString(input, start, end)
- }
-
- @Test
- void testShouldThrowExceptionOnInitializationWithoutBouncyCastle() throws Exception {
- // Arrange
- try {
- Security.removeProvider(new BouncyCastleProvider().getName())
-
- // Act
- def msg = shouldFail(SensitivePropertyProtectionException) {
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(KEY_128_HEX))
- logger.error("This should not be reached")
- }
-
- // Assert
- assert msg =~ "Error initializing the protection cipher"
- } finally {
- Security.addProvider(new BouncyCastleProvider())
- }
- }
-
- // TODO: testShouldGetName()
-
- @Test
- void testShouldProtectValue() throws Exception {
- final String PLAINTEXT = "This is a plaintext value"
-
- // Act
- Map<Integer, String> CIPHER_TEXTS = KEY_SIZES.collectEntries { int keySize ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- [(keySize): spp.protect(PLAINTEXT, PROPERTY_CONTEXT)]
- }
- CIPHER_TEXTS.each { ks, ct -> logger.info("Encrypted for ${ks} length key: ${ct}") }
-
- // Assert
-
- // The IV generation is part of #protect, so the expected cipher text values must be generated after #protect has run
- Map<Integer, Cipher> decryptionCiphers = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
- // The 12 byte IV is the first 16 Base64-encoded characters of the "complete" cipher text
- byte[] iv = decoder.decode(cipherText[0..<16])
- [(keySize): getCipher(false, keySize, iv)]
- }
- Map<Integer, String> plaintexts = decryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
- String cipherTextWithoutIVAndDelimiter = CIPHER_TEXTS[e.key][18..-1]
- String plaintext = new String(e.value.doFinal(decoder.decode(cipherTextWithoutIVAndDelimiter)), StandardCharsets.UTF_8)
- [(e.key): plaintext]
- }
- CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
-
- assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
- }
-
- @Test
- void testShouldHandleProtectEmptyValue() throws Exception {
- final List<String> EMPTY_PLAINTEXTS = ["", " ", null]
-
- // Act
- KEY_SIZES.collectEntries { int keySize ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- EMPTY_PLAINTEXTS.each { String emptyPlaintext ->
- def msg = shouldFail(IllegalArgumentException) {
- spp.protect(emptyPlaintext, PROPERTY_CONTEXT)
- }
- logger.expected("${msg} for keySize ${keySize} and plaintext [${emptyPlaintext}]")
-
- // Assert
- assert msg == "Cannot encrypt an empty value"
- }
- }
- }
-
- @Test
- void testShouldUnprotectValue() throws Exception {
- // Arrange
- final String PLAINTEXT = "This is a plaintext value"
-
- Map<Integer, Cipher> encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
- byte[] iv = new byte[IV_LENGTH]
- secureRandom.nextBytes(iv)
- [(keySize): getCipher(true, keySize, iv)]
- }
-
- Map<Integer, String> CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
- String iv = encoder.encodeToString(e.value.getIV())
- String cipherText = encoder.encodeToString(e.value.doFinal(PLAINTEXT.getBytes(StandardCharsets.UTF_8)))
- [(e.key): "${iv}||${cipherText}"]
- }
- CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
-
- // Act
- Map<Integer, String> plaintexts = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- [(keySize): spp.unprotect(cipherText, PROPERTY_CONTEXT)]
- }
- plaintexts.each { ks, pt -> logger.info("Decrypted for ${ks} length key: ${pt}") }
-
- // Assert
- assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
- }
-
- /**
- * Tests inputs where the entire String is empty/blank space/{@code null}.
- *
- * @throws Exception
- */
- @Test
- void testShouldHandleUnprotectEmptyValue() throws Exception {
- // Arrange
- final List<String> EMPTY_CIPHER_TEXTS = ["", " ", null]
-
- // Act
- KEY_SIZES.each { int keySize ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- EMPTY_CIPHER_TEXTS.each { String emptyCipherText ->
- def msg = shouldFail(IllegalArgumentException) {
- spp.unprotect(emptyCipherText, PROPERTY_CONTEXT)
- }
- logger.expected("${msg} for keySize ${keySize} and cipher text [${emptyCipherText}]")
-
- // Assert
- assert msg == "Cannot decrypt a cipher text shorter than ${AESSensitivePropertyProvider.minCipherTextLength} chars".toString()
- }
- }
- }
-
- @Test
- void testShouldUnprotectValueWithWhitespace() throws Exception {
- // Arrange
- final String PLAINTEXT = "This is a plaintext value"
-
- Map<Integer, Cipher> encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
- byte[] iv = new byte[IV_LENGTH]
- secureRandom.nextBytes(iv)
- [(keySize): getCipher(true, keySize, iv)]
- }
-
- Map<Integer, String> CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
- String iv = encoder.encodeToString(e.value.getIV())
- String cipherText = encoder.encodeToString(e.value.doFinal(PLAINTEXT.getBytes(StandardCharsets.UTF_8)))
- [(e.key): "${iv}||${cipherText}"]
- }
- CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
-
- // Act
- Map<Integer, String> plaintexts = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- [(keySize): spp.unprotect("\t" + cipherText + "\n", PROPERTY_CONTEXT)]
- }
- plaintexts.each { ks, pt -> logger.info("Decrypted for ${ks} length key: ${pt}") }
-
- // Assert
- assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
- }
-
- @Test
- void testShouldHandleUnprotectMalformedValue() throws Exception {
- // Arrange
- final String PLAINTEXT = "This is a plaintext value"
-
- // Act
- KEY_SIZES.each { int keySize ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- String cipherText = spp.protect(PLAINTEXT, PROPERTY_CONTEXT)
- // Swap two characters in the cipher text
- final String MALFORMED_CIPHER_TEXT = manipulateString(cipherText, 25, 28)
- logger.info("Manipulated ${cipherText} to\n${MALFORMED_CIPHER_TEXT.padLeft(163)}")
-
- def msg = shouldFail(SensitivePropertyProtectionException) {
- spp.unprotect(MALFORMED_CIPHER_TEXT, PROPERTY_CONTEXT)
- }
- logger.expected("${msg} for keySize ${keySize} and cipher text [${MALFORMED_CIPHER_TEXT}]")
-
- // Assert
- assert msg == "Error decrypting a protected value"
- }
- }
-
- @Test
- void testShouldHandleUnprotectMissingIV() throws Exception {
- // Arrange
- final String PLAINTEXT = "This is a plaintext value"
-
- // Act
- KEY_SIZES.each { int keySize ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- String cipherText = spp.protect(PLAINTEXT, PROPERTY_CONTEXT)
-
- // Remove the IV from the "complete" cipher text
- final String MISSING_IV_CIPHER_TEXT = cipherText[18..-1]
- logger.info("Manipulated ${cipherText} to\n${MISSING_IV_CIPHER_TEXT.padLeft(172)}")
-
- def msg = shouldFail(IllegalArgumentException) {
- spp.unprotect(MISSING_IV_CIPHER_TEXT, PROPERTY_CONTEXT)
- }
- logger.expected("${msg} for keySize ${keySize} and cipher text [${MISSING_IV_CIPHER_TEXT}]")
-
- // Remove the IV from the "complete" cipher text but keep the delimiter
- final String MISSING_IV_CIPHER_TEXT_WITH_DELIMITER = cipherText[16..-1]
- logger.info("Manipulated ${cipherText} to\n${MISSING_IV_CIPHER_TEXT_WITH_DELIMITER.padLeft(172)}")
-
- def msgWithDelimiter = shouldFail(IllegalArgumentException) {
- spp.unprotect(MISSING_IV_CIPHER_TEXT_WITH_DELIMITER, PROPERTY_CONTEXT)
- }
- logger.expected("${msgWithDelimiter} for keySize ${keySize} and cipher text [${MISSING_IV_CIPHER_TEXT_WITH_DELIMITER}]")
-
- // Assert
- assert msg == "The cipher text does not contain the delimiter || -- it should be of the form Base64(IV) || Base64(cipherText)"
-
- // Assert
- assert msgWithDelimiter == "The IV (0 bytes) must be at least 12 bytes"
- }
- }
-
- /**
- * Tests inputs which have a valid IV and delimiter but no "cipher text".
- *
- * @throws Exception
- */
- @Test
- void testShouldHandleUnprotectEmptyCipherText() throws Exception {
- // Arrange
- final String IV_AND_DELIMITER = "${encoder.encodeToString("Bad IV value".getBytes(StandardCharsets.UTF_8))}||"
- logger.info("IV and delimiter: ${IV_AND_DELIMITER}")
-
- final List<String> EMPTY_CIPHER_TEXTS = ["", " ", "\n"].collect { "${IV_AND_DELIMITER}${it}" }
-
- // Act
- KEY_SIZES.each { int keySize ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- EMPTY_CIPHER_TEXTS.each { String emptyCipherText ->
- def msg = shouldFail(IllegalArgumentException) {
- spp.unprotect(emptyCipherText, PROPERTY_CONTEXT)
- }
- logger.expected("${msg} for keySize ${keySize} and cipher text [${emptyCipherText}]")
-
- // Assert
- assert msg == "Cannot decrypt a cipher text shorter than ${AESSensitivePropertyProvider.minCipherTextLength} chars".toString()
- }
- }
- }
-
- @Test
- void testShouldHandleUnprotectMalformedIV() throws Exception {
- // Arrange
- final String PLAINTEXT = "This is a plaintext value"
-
- // Act
- KEY_SIZES.each { int keySize ->
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
- logger.info("Initialized ${spp.name} with key size ${keySize}")
- String cipherText = spp.protect(PLAINTEXT, PROPERTY_CONTEXT)
- // Swap two characters in the IV
- final String MALFORMED_IV_CIPHER_TEXT = manipulateString(cipherText, 8, 11)
- logger.info("Manipulated ${cipherText} to\n${MALFORMED_IV_CIPHER_TEXT.padLeft(163)}")
-
- def msg = shouldFail(SensitivePropertyProtectionException) {
- spp.unprotect(MALFORMED_IV_CIPHER_TEXT, PROPERTY_CONTEXT)
- }
- logger.expected("${msg} for keySize ${keySize} and cipher text [${MALFORMED_IV_CIPHER_TEXT}]")
-
- // Assert
- assert msg == "Error decrypting a protected value"
- }
- }
-
- @Test
- void testShouldGetIdentifierKeyWithDifferentMaxKeyLengths() throws Exception {
- // Arrange
- def keys = getAvailableKeySizes().collectEntries { int keySize ->
- [(keySize): getKeyOfSize(keySize)]
- }
- logger.info("Keys: ${keys}")
-
- // Act
- keys.each { int size, String key ->
- String identifierKey = new AESSensitivePropertyProvider(key).getIdentifierKey()
- logger.info("Identifier key: ${identifierKey} for size ${size}")
-
- // Assert
- assert identifierKey =~ /aes\/gcm\/${size}/
- }
- }
-
- @Test
- void testShouldNotAllowEmptyKey() throws Exception {
- // Arrange
- final String INVALID_KEY = ""
-
- // Act
- def msg = shouldFail(SensitivePropertyProtectionException) {
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(INVALID_KEY)
- }
-
- // Assert
- assert msg == "The key cannot be empty"
- }
-
- @Test
- void testShouldNotAllowIncorrectlySizedKey() throws Exception {
- // Arrange
- final String INVALID_KEY = "Z" * 31
-
- // Act
- def msg = shouldFail(SensitivePropertyProtectionException) {
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(INVALID_KEY)
- }
-
- // Assert
- assert msg == "The key must be a valid hexadecimal key"
- }
-
- @Test
- void testShouldNotAllowInvalidKey() throws Exception {
- // Arrange
- final String INVALID_KEY = "Z" * 32
-
- // Act
- def msg = shouldFail(SensitivePropertyProtectionException) {
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(INVALID_KEY)
- }
-
- // Assert
- assert msg == "The key must be a valid hexadecimal key"
- }
-
- /**
- * This test is to ensure internal consistency and allow for encrypting value for various property files
- */
- @Test
- void testShouldEncryptArbitraryValues() {
- // Arrange
- def values = ["thisIsABadPassword", "thisIsABadSensitiveKeyPassword", "thisIsABadKeystorePassword", "thisIsABadKeyPassword", "thisIsABadTruststorePassword", "This is an encrypted banner message"]
-
- String key = "2C576A9585DB862F5ECBEE5B4FFFCCA1" //getKeyOfSize(128)
- // key = "0" * 64
-
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(key)
-
- // Act
- def encryptedValues = values.collect { String v ->
- def encryptedValue = spp.protect(v, PROPERTY_CONTEXT)
- logger.info("${v} -> ${encryptedValue}")
- def (String iv, String cipherText) = encryptedValue.tokenize("||")
- logger.info("Normal Base64 encoding would be ${encoder.encodeToString(decoder.decode(iv))}||${encoder.encodeToString(decoder.decode(cipherText))}")
- encryptedValue
- }
-
- // Assert
- assert values == encryptedValues.collect { spp.unprotect(it, PROPERTY_CONTEXT) }
- }
-
- /**
- * This test is to ensure external compatibility in case someone encodes the encrypted value with Base64 and does not remove the padding
- */
- @Test
- void testShouldDecryptPaddedValue() {
- // Arrange
- assumeTrue("JCE unlimited strength crypto policy must be installed for this test", Cipher.getMaxAllowedKeyLength("AES") > 128)
-
- final String EXPECTED_VALUE = getKeyOfSize(256) // "thisIsABadKeyPassword"
- String cipherText = "aYDkDKys1ENr3gp+||sTBPpMlIvHcOLTGZlfWct8r9RY8BuDlDkoaYmGJ/9m9af9tZIVzcnDwvYQAaIKxRGF7vI2yrY7Xd6x9GTDnWGiGiRXlaP458BBMMgfzH2O8"
- String unpaddedCipherText = cipherText.replaceAll("=", "")
-
- String key = "AAAABBBBCCCCDDDDEEEEFFFF00001111" * 2 // getKeyOfSize(256)
-
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(key)
-
- // Act
- String rawValue = spp.unprotect(cipherText, PROPERTY_CONTEXT)
- logger.info("Decrypted ${cipherText} to ${rawValue}")
- String rawUnpaddedValue = spp.unprotect(unpaddedCipherText, PROPERTY_CONTEXT)
- logger.info("Decrypted ${unpaddedCipherText} to ${rawUnpaddedValue}")
-
- // Assert
- assert rawValue == EXPECTED_VALUE
- assert rawUnpaddedValue == EXPECTED_VALUE
- }
-}
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index fb16c31..c073576 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -37,7 +37,14 @@
<module>nifi-property-encryptor</module>
<module>nifi-property-utils</module>
<module>nifi-properties</module>
- <module>nifi-sensitive-property-provider</module>
+ <module>nifi-property-protection-api</module>
+ <module>nifi-property-protection-aws</module>
+ <module>nifi-property-protection-azure</module>
+ <module>nifi-property-protection-cipher</module>
+ <module>nifi-property-protection-factory</module>
+ <module>nifi-property-protection-gcp</module>
+ <module>nifi-property-protection-hashicorp</module>
+ <module>nifi-property-protection-shared</module>
<module>nifi-record</module>
<module>nifi-record-path</module>
<module>nifi-repository-encryption</module>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml
index 56a7175..f7690cb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml
@@ -48,7 +48,11 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-sensitive-property-provider</artifactId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-factory</artifactId>
</dependency>
</dependencies>
<build>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java
index 7547e71..77db7fb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java
@@ -170,13 +170,13 @@ public class NiFiPropertiesLoader {
if (protectedNiFiProperties.hasProtectedKeys()) {
Security.addProvider(new BouncyCastleProvider());
getSensitivePropertyProviderFactory()
- .getSupportedSensitivePropertyProviders()
+ .getSupportedProviders()
.forEach(protectedNiFiProperties::addSensitivePropertyProvider);
}
NiFiProperties props = protectedNiFiProperties.getUnprotectedProperties();
if (protectedNiFiProperties.hasProtectedKeys()) {
getSensitivePropertyProviderFactory()
- .getSupportedSensitivePropertyProviders()
+ .getSupportedProviders()
.forEach(SensitivePropertyProvider::cleanUp);
}
return props;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
index cf565b6..09b1f92 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java
@@ -16,7 +16,6 @@
*/
package org.apache.nifi.properties;
-import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -186,11 +185,6 @@ class ProtectedNiFiProperties extends NiFiProperties implements ProtectedPropert
}
@Override
- public Set<String> getProtectionSchemes() {
- return propertyProtectionDelegate.getProtectionSchemes();
- }
-
- @Override
public boolean isPropertySensitive(final String key) {
return propertyProtectionDelegate.isPropertySensitive(key);
}
@@ -210,11 +204,6 @@ class ProtectedNiFiProperties extends NiFiProperties implements ProtectedPropert
propertyProtectionDelegate.addSensitivePropertyProvider(sensitivePropertyProvider);
}
- @Override
- public Map<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
- return propertyProtectionDelegate.getSensitivePropertyProviders();
- }
-
/**
* Returns the number of properties that are marked as protected in the provided {@link NiFiProperties} instance without requiring external creation of a {@link ProtectedNiFiProperties} instance.
*
@@ -237,14 +226,6 @@ class ProtectedNiFiProperties extends NiFiProperties implements ProtectedPropert
@Override
public String toString() {
- final Set<String> providers = getSensitivePropertyProviders().keySet();
- return new StringBuilder("ProtectedNiFiProperties instance with ")
- .append(size()).append(" properties (")
- .append(getProtectedPropertyKeys().size())
- .append(" protected) and ")
- .append(providers.size())
- .append(" sensitive property providers: ")
- .append(StringUtils.join(providers, ", "))
- .toString();
+ return String.format("%s Size [%d]", getClass().getSimpleName(), size());
}
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy
deleted file mode 100644
index f6faea5..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy
+++ /dev/null
@@ -1,897 +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.properties
-
-import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.util.NiFiProperties
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.AfterClass
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiPropertiesGroovyTest.class)
-
- final def DEFAULT_SENSITIVE_PROPERTIES = [
- "nifi.sensitive.props.key",
- "nifi.security.keystorePasswd",
- "nifi.security.keyPasswd",
- "nifi.security.truststorePasswd",
- "nifi.provenance.repository.encryption.key",
- "nifi.provenance.repository.encryption.key.provider.password",
- "nifi.flowfile.repository.encryption.key.provider.password",
- "nifi.content.repository.encryption.key.provider.password",
- "nifi.repository.encryption.key.provider.keystore.password"
- ]
-
- final def COMMON_ADDITIONAL_SENSITIVE_PROPERTIES = [
- "nifi.sensitive.props.algorithm",
- "nifi.kerberos.service.principal",
- "nifi.kerberos.krb5.file",
- "nifi.kerberos.keytab.location"
- ]
-
- private static final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210" * 2
-
- private static String originalPropertiesPath = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)
-
- private static SensitivePropertyProviderFactory sensitivePropertyProviderFactory =
- StandardSensitivePropertyProviderFactory.withKey(KEY_HEX)
-
- @BeforeClass
- static void setUpOnce() throws Exception {
- Assume.assumeTrue("Test only runs on *nix", !SystemUtils.IS_OS_WINDOWS)
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @Before
- void setUp() throws Exception {
- }
-
- @After
- void tearDown() throws Exception {
- }
-
- @AfterClass
- static void tearDownOnce() {
- if (originalPropertiesPath) {
- System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, originalPropertiesPath)
- }
- }
-
- private static ProtectedNiFiProperties loadFromFile(String propertiesFilePath) {
- String filePath
- try {
- filePath = ProtectedNiFiPropertiesGroovyTest.class.getResource(propertiesFilePath).toURI().getPath()
- } catch (URISyntaxException ex) {
- throw new RuntimeException("Cannot load properties file due to " + ex.getLocalizedMessage(), ex)
- }
-
- File file = new File(filePath)
-
- if (file == null || !file.exists() || !file.canRead()) {
- String path = (file == null ? "missing file" : file.getAbsolutePath())
- logger.error("Cannot read from '{}' -- file is missing or not readable", path)
- throw new IllegalArgumentException("NiFi properties file missing or unreadable")
- }
-
- Properties rawProperties = new Properties()
-
- InputStream inStream = null
- try {
- inStream = new BufferedInputStream(new FileInputStream(file))
- rawProperties.load(inStream)
- logger.info("Loaded {} properties from {}", rawProperties.size(), file.getAbsolutePath())
-
- ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
-
- // If it has protected keys, inject the SPP
- if (protectedNiFiProperties.hasProtectedKeys()) {
- protectedNiFiProperties.addSensitivePropertyProvider(sensitivePropertyProviderFactory
- .getProvider(PropertyProtectionScheme.AES_GCM))
- }
-
- return protectedNiFiProperties
- } catch (final Exception ex) {
- logger.error("Cannot load properties file due to " + ex.getLocalizedMessage())
- throw new RuntimeException("Cannot load properties file due to " + ex.getLocalizedMessage(), ex)
- } finally {
- if (null != inStream) {
- try {
- inStream.close()
- } catch (final Exception ex) {
- /**
- * do nothing *
- */
- }
- }
- }
- }
-
- @Test
- void testConstructorShouldCreateNewInstance() throws Exception {
- // Arrange
-
- // Act
- NiFiProperties niFiProperties = new NiFiProperties()
- logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
-
- // Assert
- assert niFiProperties.size() == 0
- assert niFiProperties.getPropertyKeys() == [] as Set
- }
-
- @Test
- void testConstructorShouldAcceptRawProperties() throws Exception {
- // Arrange
- Properties rawProperties = new Properties()
- rawProperties.setProperty("key", "value")
- logger.info("rawProperties has ${rawProperties.size()} properties: ${rawProperties.stringPropertyNames()}")
- assert rawProperties.size() == 1
-
- // Act
- NiFiProperties niFiProperties = new NiFiProperties(rawProperties)
- logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
-
- // Assert
- assert niFiProperties.size() == 1
- assert niFiProperties.getPropertyKeys() == ["key"] as Set
- }
-
- @Test
- void testConstructorShouldAcceptNiFiProperties() throws Exception {
- // Arrange
- Properties rawProperties = new Properties()
- rawProperties.setProperty("key", "value")
- rawProperties.setProperty("key.protected", "value2")
- NiFiProperties niFiProperties = new NiFiProperties(rawProperties)
- logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
- assert niFiProperties.size() == 2
-
- // Act
- ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(niFiProperties)
- logger.info("protectedNiFiProperties has ${protectedNiFiProperties.size()} properties: ${protectedNiFiProperties.getPropertyKeys()}")
-
- // Assert
- def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
- assert allKeys == ["key", "key.protected"] as Set
- assert allKeys.size() == niFiProperties.size()
-
- }
-
- @Test
- void testShouldAllowMultipleInstances() throws Exception {
- // Arrange
- Properties rawProperties = new Properties()
- rawProperties.setProperty("key", "value")
- logger.info("rawProperties has ${rawProperties.size()} properties: ${rawProperties.stringPropertyNames()}")
- assert rawProperties.size() == 1
-
- // Act
- NiFiProperties niFiProperties = new NiFiProperties(rawProperties)
- logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
- NiFiProperties emptyProperties = new NiFiProperties()
- logger.info("emptyProperties has ${emptyProperties.size()} properties: ${emptyProperties.getPropertyKeys()}")
-
- // Assert
- assert niFiProperties.size() == 1
- assert niFiProperties.getPropertyKeys() == ["key"] as Set
-
- assert emptyProperties.size() == 0
- assert emptyProperties.getPropertyKeys() == [] as Set
- }
-
- @Test
- void testShouldDetectIfPropertyIsSensitive() throws Exception {
- // Arrange
- final String INSENSITIVE_PROPERTY_KEY = "nifi.ui.banner.text"
- final String SENSITIVE_PROPERTY_KEY = "nifi.security.keystorePasswd"
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
-
- // Act
- boolean bannerIsSensitive = properties.isPropertySensitive(INSENSITIVE_PROPERTY_KEY)
- logger.info("${INSENSITIVE_PROPERTY_KEY} is ${bannerIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- boolean passwordIsSensitive = properties.isPropertySensitive(SENSITIVE_PROPERTY_KEY)
- logger.info("${SENSITIVE_PROPERTY_KEY} is ${passwordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
-
- // Assert
- assert !bannerIsSensitive
- assert passwordIsSensitive
- }
-
- @Test
- void testShouldGetDefaultSensitiveProperties() throws Exception {
- // Arrange
- logger.expected("${DEFAULT_SENSITIVE_PROPERTIES.size()} default sensitive properties: ${DEFAULT_SENSITIVE_PROPERTIES.join(", ")}")
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
-
- // Act
- List defaultSensitiveProperties = properties.getSensitivePropertyKeys()
- logger.info("${defaultSensitiveProperties.size()} default sensitive properties: ${defaultSensitiveProperties.join(", ")}")
-
- // Assert
- assert defaultSensitiveProperties.size() == DEFAULT_SENSITIVE_PROPERTIES.size()
- assert defaultSensitiveProperties.containsAll(DEFAULT_SENSITIVE_PROPERTIES)
- }
-
- @Test
- void testShouldGetAdditionalSensitiveProperties() throws Exception {
- // Arrange
- def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.ui.banner.text", "nifi.version"]
- logger.expected("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_additional_sensitive_keys.properties")
-
- // Act
- List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
- logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
-
- // Assert
- assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
- assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
- }
-
- // TODO: Add negative tests (fuzz additional.keys property, etc.)
-
- @Test
- void testGetAdditionalSensitivePropertiesShouldNotIncludeSelf() throws Exception {
- // Arrange
- def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.ui.banner.text", "nifi.version"]
- logger.expected("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_additional_sensitive_keys.properties")
-
- // Act
- List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
- logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
-
- // Assert
- assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
- assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
- }
-
- /**
- * In the default (no protection enabled) scenario, a call to retrieve a sensitive property should return the raw value transparently.
- * @throws Exception
- */
- @Test
- void testShouldGetUnprotectedValueOfSensitiveProperty() throws Exception {
- // Arrange
- final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
- final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected.properties")
-
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- String retrievedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-
- // Assert
- assert retrievedKeystorePassword == EXPECTED_KEYSTORE_PASSWORD
- assert isSensitive
- assert !isProtected
- }
-
- /**
- * In the default (no protection enabled) scenario, a call to retrieve a sensitive property (which is empty) should return the raw value transparently.
- * @throws Exception
- */
- @Test
- void testShouldGetEmptyUnprotectedValueOfSensitiveProperty() throws Exception {
- // Arrange
- final String TRUSTSTORE_PASSWORD_KEY = "nifi.security.truststorePasswd"
- final String EXPECTED_TRUSTSTORE_PASSWORD = ""
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected.properties")
-
- boolean isSensitive = properties.isPropertySensitive(TRUSTSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(TRUSTSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedTruststorePassword = unprotectedProperties.getProperty(TRUSTSTORE_PASSWORD_KEY)
- logger.info("${TRUSTSTORE_PASSWORD_KEY}: ${retrievedTruststorePassword}")
-
- // Assert
- assert retrievedTruststorePassword == EXPECTED_TRUSTSTORE_PASSWORD
- assert isSensitive
- assert !isProtected
- }
-
- /**
- * The new model no longer needs to maintain the protected state -- it is used as a wrapper/decorator during load to unprotect the sensitive properties and then return an instance of raw properties.
- *
- * @throws Exception
- */
- @Test
- void testShouldGetUnprotectedValueOfSensitivePropertyWhenProtected() throws Exception {
- // Arrange
- final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
- final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-
- // Assert
- assert retrievedKeystorePassword == EXPECTED_KEYSTORE_PASSWORD
- assert isSensitive
- assert isProtected
- }
-
- /**
- * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is protected with an unknown protection scheme.
- * @throws Exception
- */
- @Test
- void testGetValueOfSensitivePropertyShouldFailOnUnknownProtectionScheme() throws Exception {
- // Arrange
- final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-
- // Raw properties
- Properties rawProperties = new Properties()
- rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_unknown.properties").newInputStream())
- final String RAW_KEYSTORE_PASSWORD = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${RAW_KEYSTORE_PASSWORD}")
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_unknown.properties")
-
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
-
- // While the value is "protected", the scheme is not registered, so throw an exception
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- def msg = shouldFail(IllegalStateException) {
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
- }
-
- // Assert
- assert msg == "No provider available for nifi.sensitive.props.key"
- assert isSensitive
- assert isProtected
- }
-
- /**
- * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is unable to be unprotected due to a malformed value.
- * @throws Exception
- */
- @Test
- void testGetValueOfSensitivePropertyShouldHandleSingleMalformedValue() throws Exception {
- // Arrange
- final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-
- // Raw properties
- Properties rawProperties = new Properties()
- rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes_single_malformed.properties").newInputStream())
- final String RAW_KEYSTORE_PASSWORD = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${RAW_KEYSTORE_PASSWORD}")
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes_single_malformed.properties")
-
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- def msg = shouldFail(SensitivePropertyProtectionException) {
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
- }
- logger.expected(msg)
-
- // Assert
- assert msg =~ "Failed to unprotect key ${KEYSTORE_PASSWORD_KEY}"
- assert isSensitive
- assert isProtected
- }
-
- /**
- * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is unable to be unprotected due to a malformed value.
- * @throws Exception
- */
- @Test
- void testGetValueOfSensitivePropertyShouldHandleMultipleMalformedValues() throws Exception {
- // Arrange
-
- // Raw properties
- Properties rawProperties = new Properties()
- rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes_multiple_malformed.properties").newInputStream())
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes_multiple_malformed.properties")
-
- // Iterate over the protected keys and track the ones that fail to decrypt
- SensitivePropertyProvider spp = sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.AES_GCM)
- Set<String> malformedKeys = properties.getProtectedPropertyKeys()
- .findAll { String key, String scheme -> scheme == spp.identifierKey }
- .keySet().collect { String key ->
- try {
- String rawValue = spp.unprotect(properties.getProperty(key), ProtectedPropertyContext.defaultContext(key))
- return
- } catch (SensitivePropertyProtectionException e) {
- logger.expected("Caught a malformed value for ${key}")
- return key
- }
- }
-
- logger.expected("Malformed keys: ${malformedKeys.join(", ")}")
-
- // Act
- def e = groovy.test.GroovyAssert.shouldFail(SensitivePropertyProtectionException) {
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- }
- logger.expected(e.getMessage())
-
- // Assert
- assert e instanceof MultipleSensitivePropertyProtectionException
- assert e.getMessage() =~ "Failed to unprotect keys"
- assert e.getFailedKeys() == malformedKeys
-
- }
-
- /**
- * In the default (no protection enabled) scenario, a call to retrieve a sensitive property (which is empty) should return the raw value transparently.
- * @throws Exception
- */
- @Test
- void testShouldGetEmptyUnprotectedValueOfSensitivePropertyWithDefault() throws Exception {
- // Arrange
- final String TRUSTSTORE_PASSWORD_KEY = "nifi.security.truststorePasswd"
- final String EXPECTED_TRUSTSTORE_PASSWORD = ""
- final String DEFAULT_VALUE = "defaultValue"
-
- // Raw properties
- Properties rawProperties = new Properties()
- rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_unprotected.properties").newInputStream())
- final String RAW_TRUSTSTORE_PASSWORD = rawProperties.getProperty(TRUSTSTORE_PASSWORD_KEY)
- logger.info("Raw value for ${TRUSTSTORE_PASSWORD_KEY}: ${RAW_TRUSTSTORE_PASSWORD}")
- assert RAW_TRUSTSTORE_PASSWORD == EXPECTED_TRUSTSTORE_PASSWORD
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected.properties")
-
- boolean isSensitive = properties.isPropertySensitive(TRUSTSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(TRUSTSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- String retrievedTruststorePassword = properties.getProperty(TRUSTSTORE_PASSWORD_KEY, DEFAULT_VALUE)
- logger.info("${TRUSTSTORE_PASSWORD_KEY}: ${retrievedTruststorePassword}")
-
- // Assert
- assert retrievedTruststorePassword == DEFAULT_VALUE
- assert isSensitive
- assert !isProtected
- }
-
- /**
- * In the protection enabled scenario, a call to retrieve a sensitive property should return the raw value transparently.
- * @throws Exception
- */
- @Test
- void testShouldGetUnprotectedValueOfSensitivePropertyWhenProtectedWithDefault() throws Exception {
- // Arrange
- final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
- final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
- final String DEFAULT_VALUE = "defaultValue"
-
- // Raw properties
- Properties rawProperties = new Properties()
- rawProperties.load(new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes.properties").newInputStream())
- final String RAW_KEYSTORE_PASSWORD = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${RAW_KEYSTORE_PASSWORD}")
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY, DEFAULT_VALUE)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
-
- // Assert
- assert retrievedKeystorePassword == EXPECTED_KEYSTORE_PASSWORD
- assert isSensitive
- assert isProtected
- }
-
- // TODO: Test getProtected with multiple providers
-
- /**
- * In the protection enabled scenario, a call to retrieve a sensitive property should fail if the internal cache of providers is empty.
- * @throws Exception
- */
- @Test
- void testGetValueOfSensitivePropertyShouldFailOnInvalidatedInternalCache() throws Exception {
- // Arrange
- final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
- final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword"
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
- final String RAW_PASSWORD = properties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("Read raw value from properties: ${RAW_PASSWORD}")
-
- // Overwrite the internal cache
- properties.getSensitivePropertyProviders().clear()
-
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- def msg = shouldFail(IllegalStateException) {
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
- }
-
- // Assert
- assert msg == "No provider available for nifi.sensitive.props.key"
- assert isSensitive
- assert isProtected
- }
-
- @Test
- void testShouldDetectIfPropertyIsProtected() throws Exception {
- // Arrange
- final String UNPROTECTED_PROPERTY_KEY = "nifi.security.truststorePasswd"
- final String PROTECTED_PROPERTY_KEY = "nifi.security.keystorePasswd"
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
- // Act
- boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(UNPROTECTED_PROPERTY_KEY)
- boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(UNPROTECTED_PROPERTY_KEY)
- logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
- boolean protectedPasswordIsSensitive = properties.isPropertySensitive(PROTECTED_PROPERTY_KEY)
- boolean protectedPasswordIsProtected = properties.isPropertyProtected(PROTECTED_PROPERTY_KEY)
- logger.info("${PROTECTED_PROPERTY_KEY} is ${protectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- logger.info("${PROTECTED_PROPERTY_KEY} is ${protectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
-
- // Assert
- assert unprotectedPasswordIsSensitive
- assert !unprotectedPasswordIsProtected
-
- assert protectedPasswordIsSensitive
- assert protectedPasswordIsProtected
- }
-
- @Test
- void testShouldDetectIfPropertyWithEmptyProtectionSchemeIsProtected() throws Exception {
- // Arrange
- final String UNPROTECTED_PROPERTY_KEY = "nifi.sensitive.props.key"
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_unprotected_extra_line.properties")
-
- // Act
- boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(UNPROTECTED_PROPERTY_KEY)
- boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(UNPROTECTED_PROPERTY_KEY)
- logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- logger.info("${UNPROTECTED_PROPERTY_KEY} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
-
- // Assert
- assert unprotectedPasswordIsSensitive
- assert !unprotectedPasswordIsProtected
- }
-
- private static double getPercentOfSensitivePropertiesProtected(final ProtectedNiFiProperties properties) {
- return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100);
- }
-
- @Test
- void testShouldGetPercentageOfSensitivePropertiesProtected_0() throws Exception {
- // Arrange
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
-
- logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
- logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
- // Act
- double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
- logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
- // Assert
- assert percentProtected == 0.0
- }
-
- @Test
- void testShouldGetPercentageOfSensitivePropertiesProtected_75() throws Exception {
- // Arrange
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes.properties")
-
- logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
- logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
- // Act
- double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
- logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
- // Assert
- assert percentProtected == 75.0
- }
-
- @Test
- void testShouldGetPercentageOfSensitivePropertiesProtected_100() throws Exception {
- // Arrange
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_all_sensitive_properties_protected_aes.properties")
-
- logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
- logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
- // Act
- double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
- logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
- // Assert
- assert percentProtected == 100.0
- }
-
- @Test
- void testInstanceWithNoProtectedPropertiesShouldNotLoadSPP() throws Exception {
- // Arrange
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.properties")
- assert properties.getSensitivePropertyProviders().isEmpty()
-
- logger.info("Has protected properties: ${properties.hasProtectedKeys()}")
- assert !properties.hasProtectedKeys()
-
- // Act
- Map localCache = properties.getSensitivePropertyProviders()
- logger.info("Internal cache ${localCache} has ${localCache.size()} providers loaded")
-
- // Assert
- assert localCache.isEmpty()
- }
-
- @Test
- void testShouldAddSensitivePropertyProvider() throws Exception {
- // Arrange
- ProtectedNiFiProperties properties = new ProtectedNiFiProperties()
- assert properties.getSensitivePropertyProviders().isEmpty()
-
- SensitivePropertyProvider mockProvider =
- [unprotect : { String input ->
- logger.mock("Mock call to #unprotect(${input})")
- input.reverse()
- },
- getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
-
- // Act
- properties.addSensitivePropertyProvider(mockProvider)
-
- // Assert
- assert properties.getSensitivePropertyProviders().size() == 1
- }
-
- @Test
- void testShouldNotAddNullSensitivePropertyProvider() throws Exception {
- // Arrange
- ProtectedNiFiProperties properties = new ProtectedNiFiProperties()
- assert properties.getSensitivePropertyProviders().isEmpty()
-
- // Act
- def msg = shouldFail(NullPointerException) {
- properties.addSensitivePropertyProvider(null)
- }
- logger.expected(msg)
-
- // Assert
- assert properties.getSensitivePropertyProviders().size() == 0
- assert msg == "Cannot add null SensitivePropertyProvider"
- }
-
- @Test
- void testShouldNotAllowOverwriteOfProvider() throws Exception {
- // Arrange
- ProtectedNiFiProperties properties = new ProtectedNiFiProperties()
- assert properties.getSensitivePropertyProviders().isEmpty()
-
- SensitivePropertyProvider mockProvider =
- [unprotect : { String input ->
- logger.mock("Mock call to 1#unprotect(${input})")
- input.reverse()
- },
- getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
- properties.addSensitivePropertyProvider(mockProvider)
- assert properties.getSensitivePropertyProviders().size() == 1
-
- SensitivePropertyProvider mockProvider2 =
- [unprotect : { String input ->
- logger.mock("Mock call to 2#unprotect(${input})")
- input.reverse()
- },
- getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
-
- // Act
- def msg = shouldFail(UnsupportedOperationException) {
- properties.addSensitivePropertyProvider(mockProvider2)
- }
- logger.expected(msg)
-
- // Assert
- assert msg == "Cannot overwrite existing sensitive property provider registered for mockProvider"
- assert properties.getSensitivePropertyProviders().size() == 1
- }
-
- @Test
- void testGetUnprotectedPropertiesShouldReturnInternalInstanceWhenNoneProtected() {
- // Arrange
- String noProtectedPropertiesPath = "/conf/nifi.properties"
- ProtectedNiFiProperties protectedNiFiProperties = loadFromFile(noProtectedPropertiesPath)
- logger.info("Loaded ${protectedNiFiProperties.size()} properties from ${noProtectedPropertiesPath}")
-
- int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
- logger.info("Hash code of internal instance: ${hashCode}")
-
- // Act
- NiFiProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
- logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
-
- // Assert
- assert unprotectedNiFiProperties.size() == protectedNiFiProperties.size()
- assert unprotectedNiFiProperties.getPropertyKeys().every {
- !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
- }
- logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
- assert unprotectedNiFiProperties.hashCode() == hashCode
- }
-
- @Test
- void testGetUnprotectedPropertiesShouldDecryptProtectedProperties() {
- // Arrange
- String noProtectedPropertiesPath = "/conf/nifi_with_sensitive_properties_protected_aes.properties"
- ProtectedNiFiProperties protectedNiFiProperties = loadFromFile(noProtectedPropertiesPath)
- logger.info("Loaded ${protectedNiFiProperties.size()} properties from ${noProtectedPropertiesPath}")
-
- int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
- int protectionSchemeCount = protectedNiFiProperties
- .getPropertyKeys().findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
- .size()
- int expectedUnprotectedPropertyCount = protectedNiFiProperties.size() - protectionSchemeCount
-
- String protectedProps = protectedNiFiProperties
- .getProtectedPropertyKeys()
- .collectEntries {
- [(it.key): protectedNiFiProperties.getProperty(it.key)]
- }.entrySet()
- .join("\n")
-
- logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
- logger.info("Protected properties: \n${protectedProps}")
-
- logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
-
- int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
- logger.info("Hash code of internal instance: ${hashCode}")
-
- // Act
- NiFiProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
- logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
-
- // Assert
- assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
- assert unprotectedNiFiProperties.getPropertyKeys().every {
- !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
- }
- logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
- assert unprotectedNiFiProperties.hashCode() != hashCode
- }
-
- @Test
- void testShouldCalculateSize() {
- // Arrange
- Properties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
- ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
- logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.keySet().join(", ")}")
-
- // Act
- int protectedSize = protectedNiFiProperties.size()
- logger.info("Protected properties (${protectedNiFiProperties.size()}): ${protectedNiFiProperties.getPropertyKeys().join(", ")}")
-
- // Assert
- assert protectedSize == rawProperties.size() - 1
- }
-
- @Test
- void testGetPropertyKeysShouldMatchSize() {
- // Arrange
- Properties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
- ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
- logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.keySet().join(", ")}")
-
- // Act
- def filteredKeys = protectedNiFiProperties.getPropertyKeys()
- logger.info("Protected properties (${protectedNiFiProperties.size()}): ${filteredKeys.join(", ")}")
-
- // Assert
- assert protectedNiFiProperties.size() == rawProperties.size() - 1
- assert filteredKeys == rawProperties.keySet() - "key.protected"
- }
-
- @Test
- void testShouldGetPropertyKeysIncludingProtectionSchemes() {
- // Arrange
- Properties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
- ProtectedNiFiProperties protectedNiFiProperties = new ProtectedNiFiProperties(rawProperties)
- logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.keySet().join(", ")}")
-
- // Act
- def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
- logger.info("Protected properties with schemes (${allKeys.size()}): ${allKeys.join(", ")}")
-
- // Assert
- assert allKeys.size() == rawProperties.size()
- assert allKeys == rawProperties.keySet()
- }
-
- @Test
- void testShouldThrowExceptionWhenImproperValueGiven() throws Exception {
- // Arrange
- final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd"
-
- ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes_improper_delimiter_value.properties")
-
- // Act
- def msg = shouldFail(SensitivePropertyProtectionException) {
- NiFiProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- }
- logger.expected(msg)
-
- // Assert
- assert msg =~ "Failed to unprotect key ${KEYSTORE_PASSWORD_KEY}"
- assert msg =~ "The cipher text does not contain the delimiter ||"
- }
-
- // TODO: Add tests for protectPlainProperties
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/pom.xml
index ea14106..c7a673d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/pom.xml
@@ -147,7 +147,12 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-sensitive-property-provider</artifactId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-factory</artifactId>
<version>1.16.0-SNAPSHOT</version>
</dependency>
<dependency>
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml b/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml
index 3854a34..73436c8 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml
+++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/pom.xml
@@ -406,6 +406,11 @@
<version>2.0.0-M24</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
</dependencies>
<profiles>
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java
index 6c78a32..3eddf9b 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java
@@ -17,10 +17,10 @@
package org.apache.nifi.registry.security.authentication;
import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.properties.PropertyProtectionScheme;
import org.apache.nifi.properties.SensitivePropertyProtectionException;
import org.apache.nifi.properties.SensitivePropertyProvider;
import org.apache.nifi.properties.SensitivePropertyProviderFactory;
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
import org.apache.nifi.registry.extension.ExtensionManager;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.apache.nifi.registry.security.authentication.annotation.IdentityProviderContext;
@@ -28,8 +28,6 @@ import org.apache.nifi.registry.security.authentication.generated.IdentityProvid
import org.apache.nifi.registry.security.authentication.generated.Property;
import org.apache.nifi.registry.security.authentication.generated.Provider;
import org.apache.nifi.registry.security.util.XmlUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -57,8 +55,6 @@ import java.util.Map;
@Configuration
public class IdentityProviderFactory implements IdentityProviderLookup, DisposableBean {
-
- private static final Logger logger = LoggerFactory.getLogger(IdentityProviderFactory.class);
private static final String LOGIN_IDENTITY_PROVIDERS_XSD = "/identity-providers.xsd";
private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.security.authentication.generated";
private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext();
@@ -71,9 +67,9 @@ public class IdentityProviderFactory implements IdentityProviderLookup, Disposab
}
}
- private NiFiRegistryProperties properties;
- private ExtensionManager extensionManager;
- private SensitivePropertyProviderFactory sensitivePropertyProviderFactory;
+ private final NiFiRegistryProperties properties;
+ private final ExtensionManager extensionManager;
+ private final SensitivePropertyProviderFactory sensitivePropertyProviderFactory;
private IdentityProvider identityProvider;
private final Map<String, IdentityProvider> identityProviders = new HashMap<>();
@@ -136,10 +132,8 @@ public class IdentityProviderFactory implements IdentityProviderLookup, Disposab
}
@Override
- public void destroy() throws Exception {
- if (identityProviders != null) {
- identityProviders.entrySet().stream().forEach(e -> e.getValue().preDestruction());
- }
+ public void destroy() {
+ identityProviders.forEach((key, value) -> value.preDestruction());
}
private IdentityProviders loadLoginIdentityProvidersConfiguration() throws Exception {
@@ -279,7 +273,7 @@ public class IdentityProviderFactory implements IdentityProviderLookup, Disposab
"detected and configured during the bootstrap startup sequence. Contact the system administrator.");
}
try {
- final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.fromIdentifier(encryptionScheme));
+ final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(new StandardProtectionScheme(encryptionScheme));
return sensitivePropertyProvider.unprotect(cipherText, sensitivePropertyProviderFactory.getPropertyContext(groupIdentifier, propertyName));
} catch (final IllegalArgumentException e) {
throw new SensitivePropertyProtectionException(String.format("Identity Provider configuration XML was protected using %s, which is not supported. " +
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
index 5704c4a..738f003 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
@@ -18,10 +18,10 @@ package org.apache.nifi.registry.security.authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
-import org.apache.nifi.properties.PropertyProtectionScheme;
import org.apache.nifi.properties.SensitivePropertyProtectionException;
import org.apache.nifi.properties.SensitivePropertyProvider;
import org.apache.nifi.properties.SensitivePropertyProviderFactory;
+import org.apache.nifi.properties.scheme.StandardProtectionScheme;
import org.apache.nifi.registry.extension.ExtensionClassLoader;
import org.apache.nifi.registry.extension.ExtensionCloseable;
import org.apache.nifi.registry.extension.ExtensionManager;
@@ -517,7 +517,7 @@ public class AuthorizerFactory implements UserGroupProviderLookup, AccessPolicyP
"detected and configured during the bootstrap startup sequence. Contact the system administrator.");
}
try {
- final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.fromIdentifier(encryptionScheme));
+ final SensitivePropertyProvider sensitivePropertyProvider = sensitivePropertyProviderFactory.getProvider(new StandardProtectionScheme(encryptionScheme));
return sensitivePropertyProvider.unprotect(cipherText, sensitivePropertyProviderFactory.getPropertyContext(groupIdentifier, propertyName));
} catch (final IllegalArgumentException e) {
throw new SensitivePropertyProtectionException(String.format("Authorizer configuration XML was protected using %s, which is not supported. " +
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml b/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml
index f1aacd6..52e9c9b 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml
@@ -68,7 +68,7 @@
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
- <artifactId>nifi-sensitive-property-provider</artifactId>
+ <artifactId>nifi-property-protection-factory</artifactId>
<version>1.16.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java
index a05c0f7..c5f5a75 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoader.java
@@ -35,10 +35,6 @@ public class NiFiRegistryPropertiesLoader {
private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryPropertiesLoader.class);
- private static final String APPLICATION_PATH = "nifi.registry";
-
- private static final String RELATIVE_PATH = "conf/nifi-registry.properties";
-
private String keyHex;
// Future enhancement: allow for external registration of new providers
@@ -111,8 +107,7 @@ public class NiFiRegistryPropertiesLoader {
rawProperties.load(reader);
final NiFiRegistryProperties innerProperties = new NiFiRegistryProperties(rawProperties);
logger.info("Loaded {} properties from {}", rawProperties.size(), file.getAbsolutePath());
- ProtectedNiFiRegistryProperties protectedNiFiRegistryProperties = new ProtectedNiFiRegistryProperties(innerProperties);
- return protectedNiFiRegistryProperties;
+ return new ProtectedNiFiRegistryProperties(innerProperties);
} catch (final IOException ioe) {
logger.error("Cannot load properties file due to " + ioe.getLocalizedMessage());
throw new RuntimeException("Cannot load properties file due to " + ioe.getLocalizedMessage(), ioe);
@@ -132,7 +127,7 @@ public class NiFiRegistryPropertiesLoader {
if (protectedNiFiProperties.hasProtectedKeys()) {
Security.addProvider(new BouncyCastleProvider());
getSensitivePropertyProviderFactory()
- .getSupportedSensitivePropertyProviders()
+ .getSupportedProviders()
.forEach(protectedNiFiProperties::addSensitivePropertyProvider);
}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java
index 57e1af2..234e724 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/ProtectedNiFiRegistryProperties.java
@@ -16,7 +16,6 @@
*/
package org.apache.nifi.registry.properties;
-import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.properties.ApplicationPropertiesProtector;
import org.apache.nifi.properties.ProtectedProperties;
import org.apache.nifi.properties.SensitivePropertyProtectionException;
@@ -175,11 +174,6 @@ class ProtectedNiFiRegistryProperties extends NiFiRegistryProperties implements
}
@Override
- public Set<String> getProtectionSchemes() {
- return propertyProtectionDelegate.getProtectionSchemes();
- }
-
- @Override
public boolean isPropertySensitive(final String key) {
return propertyProtectionDelegate.isPropertySensitive(key);
}
@@ -200,20 +194,7 @@ class ProtectedNiFiRegistryProperties extends NiFiRegistryProperties implements
}
@Override
- public Map<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
- return propertyProtectionDelegate.getSensitivePropertyProviders();
- }
-
- @Override
public String toString() {
- final Set<String> providers = getSensitivePropertyProviders().keySet();
- return new StringBuilder("ProtectedNiFiRegistryProperties instance with ")
- .append(size()).append(" properties (")
- .append(getProtectedPropertyKeys().size())
- .append(" protected) and ")
- .append(providers.size())
- .append(" sensitive property providers: ")
- .append(StringUtils.join(providers, ", "))
- .toString();
+ return String.format("%s Size [%d]", getClass().getSimpleName(), size());
}
}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
index 1361a27..18869d5 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
@@ -17,31 +17,18 @@
package org.apache.nifi.registry.properties
import org.apache.nifi.properties.ApplicationPropertiesProtector
-import org.apache.nifi.properties.MultipleSensitivePropertyProtectionException
-import org.apache.nifi.properties.PropertyProtectionScheme
-import org.apache.nifi.properties.ProtectedPropertyContext
import org.apache.nifi.properties.SensitivePropertyProtectionException
import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
-import org.junit.AfterClass
-import org.junit.Assume
-import org.junit.Before
-import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
import javax.crypto.Cipher
-import java.security.Security
@RunWith(JUnit4.class)
class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiRegistryPropertiesGroovyTest.class)
-
private static final String KEYSTORE_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD
private static final String KEY_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_KEY_PASSWD
private static final String TRUSTSTORE_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD
@@ -56,27 +43,6 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
private static final String KEY_HEX_256 = KEY_HEX_128 * 2
private static final String KEY_HEX = Cipher.getMaxAllowedKeyLength("AES") < 256 ? KEY_HEX_128 : KEY_HEX_256
- @BeforeClass
- static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @Before
- void setUp() throws Exception {
- }
-
- @After
- void tearDown() throws Exception {
- }
-
- @AfterClass
- static void tearDownOnce() {
- }
-
private static ProtectedNiFiRegistryProperties loadFromResourceFile(String propertiesFilePath) {
return loadFromResourceFile(propertiesFilePath, KEY_HEX)
}
@@ -85,8 +51,6 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
File file = fileForResource(propertiesFilePath)
if (file == null || !file.exists() || !file.canRead()) {
- String path = (file == null ? "missing file" : file.getAbsolutePath())
- logger.error("Cannot read from '{}' -- file is missing or not readable", path)
throw new IllegalArgumentException("NiFi Registry properties file missing or unreadable")
}
@@ -96,19 +60,17 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
final Properties props = new Properties()
props.load(reader)
NiFiRegistryProperties properties = new NiFiRegistryProperties(props)
- logger.info("Loaded {} properties from {}", properties.size(), file.getAbsolutePath())
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(properties)
// If it has protected keys, inject the SPP
if (protectedNiFiProperties.hasProtectedKeys()) {
protectedNiFiProperties.addSensitivePropertyProvider(StandardSensitivePropertyProviderFactory.withKey(keyHex)
- .getProvider(PropertyProtectionScheme.AES_GCM))
+ .getProvider(new StandardProtectionScheme("aes/gcm")))
}
return protectedNiFiProperties
} catch (final Exception ex) {
- logger.error("Cannot load properties file due to " + ex.getLocalizedMessage())
throw new RuntimeException("Cannot load properties file due to " +
ex.getLocalizedMessage(), ex)
}
@@ -131,69 +93,46 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
@Test
void testShouldDetectIfPropertyIsSensitive() throws Exception {
- // Arrange
final String INSENSITIVE_PROPERTY_KEY = "nifi.registry.web.http.port"
final String SENSITIVE_PROPERTY_KEY = "nifi.registry.security.keystorePasswd"
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
- // Act
boolean bannerIsSensitive = properties.isPropertySensitive(INSENSITIVE_PROPERTY_KEY)
- logger.info("${INSENSITIVE_PROPERTY_KEY} is ${bannerIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
boolean passwordIsSensitive = properties.isPropertySensitive(SENSITIVE_PROPERTY_KEY)
- logger.info("${SENSITIVE_PROPERTY_KEY} is ${passwordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- // Assert
assert !bannerIsSensitive
assert passwordIsSensitive
}
@Test
void testShouldGetDefaultSensitiveProperties() throws Exception {
- // Arrange
- logger.info("${DEFAULT_SENSITIVE_PROPERTIES.size()} default sensitive properties: ${DEFAULT_SENSITIVE_PROPERTIES.join(", ")}")
-
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
- // Act
List defaultSensitiveProperties = properties.getSensitivePropertyKeys()
- logger.info("${defaultSensitiveProperties.size()} default sensitive properties: ${defaultSensitiveProperties.join(", ")}")
- // Assert
assert defaultSensitiveProperties.size() == DEFAULT_SENSITIVE_PROPERTIES.size()
assert defaultSensitiveProperties.containsAll(DEFAULT_SENSITIVE_PROPERTIES)
}
@Test
void testShouldGetAdditionalSensitiveProperties() throws Exception {
- // Arrange
def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.registry.web.http.port", "nifi.registry.web.http.host"]
- logger.info("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
-
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_additional_sensitive_keys.properties")
-
- // Act
List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
- logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
- // Assert
assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
}
@Test
void testGetAdditionalSensitivePropertiesShouldNotIncludeSelf() throws Exception {
- // Arrange
def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.registry.web.http.port", "nifi.registry.web.http.host"]
- logger.info("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_additional_sensitive_keys.properties")
- // Act
List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
- logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
- // Assert
assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
}
@@ -204,20 +143,15 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
*/
@Test
void testShouldGetUnprotectedValueOfSensitiveProperty() throws Exception {
- // Arrange
final String expectedKeystorePassword = "thisIsABadKeystorePassword"
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected.properties")
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
- // Act
String retrievedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
- // Assert
assert retrievedKeystorePassword == expectedKeystorePassword
assert isSensitive
assert !isProtected
@@ -229,21 +163,16 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
*/
@Test
void testShouldGetEmptyUnprotectedValueOfSensitiveProperty() throws Exception {
- // Arrange
final String expectedTruststorePassword = ""
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected.properties")
boolean isSensitive = properties.isPropertySensitive(TRUSTSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(TRUSTSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
- // Act
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedTruststorePassword = unprotectedProperties.getProperty(TRUSTSTORE_PASSWORD_KEY)
- logger.info("${TRUSTSTORE_PASSWORD_KEY}: ${retrievedTruststorePassword}")
- // Assert
assert retrievedTruststorePassword == expectedTruststorePassword
assert isSensitive
assert !isProtected
@@ -256,21 +185,16 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
*/
@Test
void testShouldGetUnprotectedValueOfSensitivePropertyWhenProtected() throws Exception {
- // Arrange
final String expectedKeystorePassword = "thisIsABadPassword"
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
- // Act
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
- // Assert
assert retrievedKeystorePassword == expectedKeystorePassword
assert isSensitive
assert isProtected
@@ -282,33 +206,17 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
*/
@Test
void testGetValueOfSensitivePropertyShouldHandleUnknownProtectionScheme() throws Exception {
- // Arrange
-
- // Raw properties
Properties rawProperties = new Properties()
rawProperties.load(new FileReader(fileForResource("/conf/nifi-registry.with_sensitive_props_protected_unknown.properties")))
- final String expectedKeystorePassword = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${expectedKeystorePassword}")
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_unknown.properties")
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
+ assert properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
+ assert properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- // While the value is "protected", the scheme is not registered
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- def msg = shouldFail(IllegalStateException) {
- NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+ shouldFail(SensitivePropertyProtectionException) {
+ properties.getUnprotectedProperties()
}
-
- // Assert
- assert msg == "No provider available for nifi.registry.security.keyPasswd"
- assert isSensitive
- assert isProtected
}
/**
@@ -317,22 +225,14 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
*/
@Test
void testGetValueOfSensitivePropertyShouldHandleSingleMalformedValue() throws Exception {
- // Arrange
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_single_malformed.properties", KEY_HEX_128)
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
- // Act
- def msg = shouldFail(SensitivePropertyProtectionException) {
- NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+ shouldFail(SensitivePropertyProtectionException) {
+ properties.getUnprotectedProperties()
}
- logger.info(msg)
- // Assert
- assert msg =~ "Failed to unprotect key ${KEYSTORE_PASSWORD_KEY}"
assert isSensitive
assert isProtected
}
@@ -343,97 +243,27 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
*/
@Test
void testGetValueOfSensitivePropertyShouldHandleMultipleMalformedValues() throws Exception {
- // Arrange
-
- // Raw properties
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_multiple_malformed.properties", KEY_HEX_128)
- // Iterate over the protected keys and track the ones that fail to decrypt
- SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX_128)
- .getProvider(PropertyProtectionScheme.AES_GCM)
- Set<String> malformedKeys = properties.getProtectedPropertyKeys()
- .findAll { String key, String scheme -> scheme == spp.identifierKey }
- .keySet()
- .findAll { String key ->
- try {
- spp.unprotect(properties.getProperty(key), ProtectedPropertyContext.defaultContext(key))
- return false
- } catch (SensitivePropertyProtectionException e) {
- logger.expected("Caught a malformed value for ${key}")
- return true
- }
- }
-
- logger.expected("Malformed keys: ${malformedKeys.join(", ")}")
-
- // Act
- def e = groovy.test.GroovyAssert.shouldFail(SensitivePropertyProtectionException) {
- NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
- }
- logger.expected(e.getMessage())
-
- // Assert
- assert e instanceof MultipleSensitivePropertyProtectionException
- assert e.getMessage() =~ "Failed to unprotect keys"
- assert e.getFailedKeys() == malformedKeys
-
- }
-
- /**
- * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the internal cache of providers is empty.
- * @throws Exception
- */
- @Test
- void testGetValueOfSensitivePropertyShouldHandleInvalidatedInternalCache() throws Exception {
- // Arrange
- ProtectedNiFiRegistryProperties properties =
- loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
- final String expectedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("Read raw value from properties: ${expectedKeystorePassword}")
-
- // Overwrite the internal cache
- properties.getSensitivePropertyProviders().clear()
-
- boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
- boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
- logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
-
- // Act
- def msg = shouldFail(IllegalStateException) {
- NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+ groovy.test.GroovyAssert.shouldFail(SensitivePropertyProtectionException) {
+ properties.getUnprotectedProperties()
}
-
- // Assert
- assert msg == "No provider available for nifi.registry.security.keyPasswd"
- assert isSensitive
- assert isProtected
}
@Test
void testShouldDetectIfPropertyIsProtected() throws Exception {
- // Arrange
final String unprotectedPropertyKey = TRUSTSTORE_PASSWORD_KEY
final String protectedPropertyKey = KEYSTORE_PASSWORD_KEY
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
- // Act
boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(unprotectedPropertyKey)
boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(unprotectedPropertyKey)
- logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
boolean protectedPasswordIsSensitive = properties.isPropertySensitive(protectedPropertyKey)
boolean protectedPasswordIsProtected = properties.isPropertyProtected(protectedPropertyKey)
- logger.info("${protectedPropertyKey} is ${protectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- logger.info("${protectedPropertyKey} is ${protectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
- // Assert
assert unprotectedPasswordIsSensitive
assert !unprotectedPasswordIsProtected
@@ -443,315 +273,165 @@ class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
@Test
void testShouldDetectIfPropertyWithEmptyProtectionSchemeIsProtected() throws Exception {
- // Arrange
final String unprotectedPropertyKey = KEY_PASSWORD_KEY
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected_extra_line.properties")
- // Act
boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(unprotectedPropertyKey)
boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(unprotectedPropertyKey)
- logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
- logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
- // Assert
assert unprotectedPasswordIsSensitive
assert !unprotectedPasswordIsProtected
}
@Test
void testShouldGetPercentageOfSensitivePropertiesProtected_0() throws Exception {
- // Arrange
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
-
- logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
- logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
- // Act
double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
- logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
- // Assert
- assert percentProtected == 0.0
+ assert percentProtected == 0.0D
}
private static double getPercentOfSensitivePropertiesProtected(final ProtectedNiFiRegistryProperties properties) {
- return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100);
+ return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100)
}
@Test
void testShouldGetPercentageOfSensitivePropertiesProtected_75() throws Exception {
- // Arrange
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
- logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
- logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
- // Act
double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
- logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
- // Assert
- assert percentProtected == 67.0
+ assert percentProtected == 67.0D
}
@Test
void testShouldGetPercentageOfSensitivePropertiesProtected_100() throws Exception {
- // Arrange
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_fully_protected_aes_128.properties", KEY_HEX_128)
- logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
- logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
-
- // Act
double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
- logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
-
- // Assert
- assert percentProtected == 100.0
- }
-
- @Test
- void testInstanceWithNoProtectedPropertiesShouldNotLoadSPP() throws Exception {
- // Arrange
- ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
- assert properties.getSensitivePropertyProviders().isEmpty()
-
- logger.info("Has protected properties: ${properties.hasProtectedKeys()}")
- assert !properties.hasProtectedKeys()
-
- // Act
- Map localCache = properties.getSensitivePropertyProviders()
- logger.info("Internal cache ${localCache} has ${localCache.size()} providers loaded")
- // Assert
- assert localCache.isEmpty()
+ assert percentProtected == 100.0D
}
@Test
void testShouldAddSensitivePropertyProvider() throws Exception {
- // Arrange
ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
- assert properties.getSensitivePropertyProviders().isEmpty()
SensitivePropertyProvider mockProvider =
[unprotect : { String input ->
- logger.mock("Mock call to #unprotect(${input})")
input.reverse()
},
getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
- // Act
properties.addSensitivePropertyProvider(mockProvider)
-
- // Assert
- assert properties.getSensitivePropertyProviders().size() == 1
- }
-
- @Test
- void testShouldNotAddNullSensitivePropertyProvider() throws Exception {
- // Arrange
- ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
- assert properties.getSensitivePropertyProviders().isEmpty()
-
- // Act
- def msg = shouldFail(NullPointerException) {
- properties.addSensitivePropertyProvider(null)
- }
- logger.info(msg)
-
- // Assert
- assert properties.getSensitivePropertyProviders().size() == 0
- assert msg == "Cannot add null SensitivePropertyProvider"
}
@Test
void testShouldNotAllowOverwriteOfProvider() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
- assert properties.getSensitivePropertyProviders().isEmpty()
SensitivePropertyProvider mockProvider =
[unprotect : { String input ->
- logger.mock("Mock call to 1#unprotect(${input})")
input.reverse()
},
getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
properties.addSensitivePropertyProvider(mockProvider)
- assert properties.getSensitivePropertyProviders().size() == 1
SensitivePropertyProvider mockProvider2 =
[unprotect : { String input ->
- logger.mock("Mock call to 2#unprotect(${input})")
input.reverse()
},
getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
- // Act
- def msg = shouldFail(UnsupportedOperationException) {
+ shouldFail(UnsupportedOperationException) {
properties.addSensitivePropertyProvider(mockProvider2)
}
- logger.info(msg)
-
- // Assert
- assert msg == "Cannot overwrite existing sensitive property provider registered for mockProvider"
- assert properties.getSensitivePropertyProviders().size() == 1
}
@Test
void testGetUnprotectedPropertiesShouldReturnInternalInstanceWhenNoneProtected() {
- // Arrange
ProtectedNiFiRegistryProperties protectedNiFiProperties = loadFromResourceFile("/conf/nifi-registry.properties")
- logger.info("Loaded ${protectedNiFiProperties.size()} properties from conf/nifi.properties")
int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
- logger.info("Hash code of internal instance: ${hashCode}")
- // Act
NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
- logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
- // Assert
assert unprotectedNiFiProperties.size() == protectedNiFiProperties.size()
assert unprotectedNiFiProperties.getPropertyKeys().every {
!unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
- logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() == hashCode
}
@Test
void testGetUnprotectedPropertiesShouldDecryptProtectedProperties() {
- // Arrange
ProtectedNiFiRegistryProperties protectedNiFiProperties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
- int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
- int protectionSchemeCount = protectedNiFiProperties
- .getPropertyKeysIncludingProtectionSchemes()
- .findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
- .size()
int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
- String protectedProps = protectedNiFiProperties
- .getProtectedPropertyKeys()
- .collectEntries {
- [(it.key): protectedNiFiProperties.getProperty(it.key)]
- }.entrySet()
- .join("\n")
-
- logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
- logger.info("Protected properties: \n${protectedProps}")
-
- logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
-
int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
- logger.info("Hash code of internal instance: ${hashCode}")
- // Act
NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
- logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
- // Assert
assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
assert unprotectedNiFiProperties.getPropertyKeys().every {
!unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
- logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() != hashCode
}
@Test
void testGetUnprotectedPropertiesShouldDecryptProtectedPropertiesWith256Bit() {
- // Arrange
- Assume.assumeTrue("JCE unlimited strength crypto policy must be installed for this test", Cipher.getMaxAllowedKeyLength("AES") > 128)
ProtectedNiFiRegistryProperties protectedNiFiProperties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_256.properties", KEY_HEX_256)
- int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
- int protectionSchemeCount = protectedNiFiProperties
- .getPropertyKeysIncludingProtectionSchemes()
- .findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
- .size()
int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
- String protectedProps = protectedNiFiProperties
- .getProtectedPropertyKeys()
- .collectEntries {
- [(it.key): protectedNiFiProperties.getProperty(it.key)]
- }.entrySet()
- .join("\n")
-
- logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
- logger.info("Protected properties: \n${protectedProps}")
-
- logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
-
int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
- logger.info("Hash code of internal instance: ${hashCode}")
- // Act
NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
- logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
- // Assert
assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
assert unprotectedNiFiProperties.getPropertyKeys().every {
!unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
- logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() != hashCode
}
@Test
void testShouldCalculateSize() {
- // Arrange
Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
- logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
- // Act
int protectedSize = protectedNiFiProperties.size()
- logger.info("Protected properties (${protectedNiFiProperties.size()}): " +
- "${protectedNiFiProperties.getPropertyKeys().join(", ")}")
-
- // Assert
assert protectedSize == rawProperties.size() - 1
}
@Test
void testGetPropertyKeysShouldMatchSize() {
- // Arrange
Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
- logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
- // Act
def filteredKeys = protectedNiFiProperties.getPropertyKeys()
- logger.info("Protected properties (${protectedNiFiProperties.size()}): ${filteredKeys.join(", ")}")
- // Assert
assert protectedNiFiProperties.size() == rawProperties.size() - 1
assert filteredKeys == rawProperties.getPropertyKeys() - "key.protected"
}
@Test
void testShouldGetPropertyKeysIncludingProtectionSchemes() {
- // Arrange
Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
- logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
- // Act
def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
- logger.info("Protected properties with schemes (${allKeys.size()}): ${allKeys.join(", ")}")
- // Assert
assert allKeys.size() == rawProperties.size()
assert allKeys == rawProperties.getPropertyKeys()
}
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
index 988b596..9085751 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
@@ -35,6 +35,16 @@
<version>1.16.0-SNAPSHOT</version>
</dependency>
<dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-api</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-protection-factory</artifactId>
+ <version>1.16.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-properties</artifactId>
<version>1.16.0-SNAPSHOT</version>
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
index 4fdfbf0..3a76ba3 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/properties/ConfigEncryptionTool.groovy
@@ -31,6 +31,9 @@ import org.apache.nifi.encrypt.PropertyEncryptor
import org.apache.nifi.encrypt.PropertyEncryptorFactory
import org.apache.nifi.flow.encryptor.FlowEncryptor
import org.apache.nifi.flow.encryptor.StandardFlowEncryptor
+import org.apache.nifi.properties.scheme.ProtectionScheme
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException
import org.apache.nifi.toolkit.tls.commandLine.ExitCode
import org.apache.nifi.util.NiFiBootstrapUtils
@@ -71,10 +74,10 @@ class ConfigEncryptionTool {
public static flowXmlPath
public String outputFlowXmlPath
- static final PropertyProtectionScheme DEFAULT_PROTECTION_SCHEME = PropertyProtectionScheme.AES_GCM
+ static final ProtectionScheme DEFAULT_PROTECTION_SCHEME = new StandardProtectionScheme("aes/gcm")
- private PropertyProtectionScheme protectionScheme = DEFAULT_PROTECTION_SCHEME
- private PropertyProtectionScheme migrationProtectionScheme = DEFAULT_PROTECTION_SCHEME
+ private ProtectionScheme protectionScheme = DEFAULT_PROTECTION_SCHEME
+ private ProtectionScheme migrationProtectionScheme = DEFAULT_PROTECTION_SCHEME
private String keyHex
private String migrationKeyHex
private String password
@@ -128,8 +131,8 @@ class ConfigEncryptionTool {
private static final String NEW_FLOW_PROVIDER_ARG = "newFlowProvider"
private static final String TRANSLATE_CLI_ARG = "translateCli"
- private static final String PROTECTION_SCHEME_DESC = String.format("Selects the protection scheme for encrypted properties. " +
- "Valid values are: [%s] (default is %s)", PropertyProtectionScheme.values().join(", "), DEFAULT_PROTECTION_SCHEME.name())
+ private static final StandardProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+ private static final String PROTECTION_SCHEME_DESC = String.format("Selects the protection scheme for encrypted properties. Default is AES_GCM. Valid values: %s", PROTECTION_SCHEME_RESOLVER.supportedProtectionSchemes)
// Static holder to avoid re-generating the options object multiple times in an invocation
private static Options staticOptions
@@ -245,7 +248,7 @@ class ConfigEncryptionTool {
options.addOption(Option.builder("S").longOpt(PROTECTION_SCHEME_ARG).hasArg(true).argName("protectionScheme").desc(PROTECTION_SCHEME_DESC).build())
options.addOption(Option.builder("k").longOpt(KEY_ARG).hasArg(true).argName("keyhex").desc("The raw hexadecimal key to use to encrypt the sensitive properties").build())
options.addOption(Option.builder("e").longOpt(KEY_MIGRATION_ARG).hasArg(true).argName("keyhex").desc("The old raw hexadecimal key to use during key migration").build())
- options.addOption(Option.builder("H").longOpt(PROTECTION_SCHEME_MIGRATION_ARG).hasArg(true).argName("protectionScheme").desc("The old protection scheme to use during encryption migration (see --protectionScheme for possible values). Default is " + DEFAULT_PROTECTION_SCHEME.name()).build())
+ options.addOption(Option.builder("H").longOpt(PROTECTION_SCHEME_MIGRATION_ARG).hasArg(true).argName("protectionScheme").desc("The old protection scheme to use during encryption migration (see --protectionScheme for possible values). Default is AES_GCM").build())
options.addOption(Option.builder("p").longOpt(PASSWORD_ARG).hasArg(true).argName("password").desc("The password from which to derive the key to use to encrypt the sensitive properties").build())
options.addOption(Option.builder("w").longOpt(PASSWORD_MIGRATION_ARG).hasArg(true).argName("password").desc("The old password from which to derive the key during migration").build())
options.addOption(Option.builder("r").longOpt(USE_KEY_ARG).hasArg(false).desc("If provided, the secure console will prompt for the raw key value in hexadecimal form").build())
@@ -326,7 +329,7 @@ class ConfigEncryptionTool {
}
if (commandLine.hasOption(PROTECTION_SCHEME_ARG)) {
- protectionScheme = PropertyProtectionScheme.valueOf(commandLine.getOptionValue(PROTECTION_SCHEME_ARG))
+ protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(commandLine.getOptionValue(PROTECTION_SCHEME_ARG))
}
// If translating nifi.properties to CLI format, none of the remaining parsing is necessary
@@ -428,22 +431,20 @@ class ConfigEncryptionTool {
logger.info("Key migration mode activated")
}
if (commandLine.hasOption(PROTECTION_SCHEME_MIGRATION_ARG)) {
- migrationProtectionScheme = PropertyProtectionScheme.valueOf(commandLine.getOptionValue(PROTECTION_SCHEME_MIGRATION_ARG))
+ migrationProtectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(commandLine.getOptionValue(PROTECTION_SCHEME_MIGRATION_ARG))
}
- if (migrationProtectionScheme.requiresSecretKey()) {
- if (commandLine.hasOption(PASSWORD_MIGRATION_ARG)) {
- usingPasswordMigration = true
- if (commandLine.hasOption(KEY_MIGRATION_ARG)) {
- printUsageAndThrow("Only one of '-w'/'--${PASSWORD_MIGRATION_ARG}' and '-e'/'--${KEY_MIGRATION_ARG}' can be used", ExitCode.INVALID_ARGS)
- } else {
- migrationPassword = commandLine.getOptionValue(PASSWORD_MIGRATION_ARG)
- }
+ if (commandLine.hasOption(PASSWORD_MIGRATION_ARG)) {
+ usingPasswordMigration = true
+ if (commandLine.hasOption(KEY_MIGRATION_ARG)) {
+ printUsageAndThrow("Only one of '-w'/'--${PASSWORD_MIGRATION_ARG}' and '-e'/'--${KEY_MIGRATION_ARG}' can be used", ExitCode.INVALID_ARGS)
} else {
- migrationKeyHex = commandLine.getOptionValue(KEY_MIGRATION_ARG)
- // Use the "migration password" value if the migration key hex is absent
- usingPasswordMigration = !migrationKeyHex
+ migrationPassword = commandLine.getOptionValue(PASSWORD_MIGRATION_ARG)
}
+ } else {
+ migrationKeyHex = commandLine.getOptionValue(KEY_MIGRATION_ARG)
+ // Use the "migration password" value if the migration key hex is absent
+ usingPasswordMigration = !migrationKeyHex
}
} else {
if (commandLine.hasOption(PASSWORD_MIGRATION_ARG) || commandLine.hasOption(KEY_MIGRATION_ARG)) {
@@ -451,25 +452,23 @@ class ConfigEncryptionTool {
}
}
- if (protectionScheme.requiresSecretKey()) {
- if (commandLine.hasOption(PASSWORD_ARG)) {
- usingPassword = true
- if (commandLine.hasOption(KEY_ARG)) {
- printUsageAndThrow("Only one of '-p'/'--${PASSWORD_ARG}' and '-k'/'--${KEY_ARG}' can be used", ExitCode.INVALID_ARGS)
- } else {
- password = commandLine.getOptionValue(PASSWORD_ARG)
- }
+ if (commandLine.hasOption(PASSWORD_ARG)) {
+ usingPassword = true
+ if (commandLine.hasOption(KEY_ARG)) {
+ printUsageAndThrow("Only one of '-p'/'--${PASSWORD_ARG}' and '-k'/'--${KEY_ARG}' can be used", ExitCode.INVALID_ARGS)
} else {
- keyHex = commandLine.getOptionValue(KEY_ARG)
- usingPassword = !keyHex
+ password = commandLine.getOptionValue(PASSWORD_ARG)
}
+ } else {
+ keyHex = commandLine.getOptionValue(KEY_ARG)
+ usingPassword = !keyHex
+ }
- if (commandLine.hasOption(USE_KEY_ARG)) {
- if (keyHex || password) {
- logger.warn("If the key or password is provided in the arguments, '-r'/'--${USE_KEY_ARG}' is ignored")
- } else {
- usingPassword = false
- }
+ if (commandLine.hasOption(USE_KEY_ARG)) {
+ if (keyHex || password) {
+ logger.warn("If the key or password is provided in the arguments, '-r'/'--${USE_KEY_ARG}' is ignored")
+ } else {
+ usingPassword = false
}
}
@@ -575,16 +574,9 @@ class ConfigEncryptionTool {
*
* @param rawKey the unprocessed key input
* @return the formatted hex string in uppercase
- * @throws KeyException if the key is not a valid length after parsing
*/
- private static String parseKey(String rawKey) throws KeyException {
+ private static String parseKey(String rawKey) {
String hexKey = rawKey.replaceAll("[^0-9a-fA-F]", "")
- def validKeyLengths = getValidKeyLengths()
- List<Integer> validHexCharLengths = validKeyLengths.collect {it / 4 }
- if (!validKeyLengths.contains(hexKey.size() * 4)) {
- throw new KeyException("The key (${hexKey.size()} hex chars) must be of length ${validKeyLengths} " +
- "bits (${validHexCharLengths} hex characters)")
- }
hexKey.toUpperCase()
}
@@ -597,9 +589,8 @@ class ConfigEncryptionTool {
Cipher.getMaxAllowedKeyLength("AES") > 128 ? [128, 192, 256] : [128]
}
- private NiFiPropertiesLoader getNiFiPropertiesLoader(final String keyHex) {
- return protectionScheme.requiresSecretKey() || migrationProtectionScheme.requiresSecretKey()
- ? NiFiPropertiesLoader.withKey(keyHex) : new NiFiPropertiesLoader()
+ private static NiFiPropertiesLoader getNiFiPropertiesLoader(final String keyHex) {
+ keyHex == null ? new NiFiPropertiesLoader() : NiFiPropertiesLoader.withKey(keyHex)
}
/**
@@ -695,6 +686,7 @@ class ConfigEncryptionTool {
try {
return new GZIPInputStream(new FileInputStream(filePath))
} catch (ZipException e) {
+ logger.debug("GZIP Compression not found: {}", e.getMessage())
return new FileInputStream(filePath)
} catch (RuntimeException e) {
if (isVerbose) {
@@ -742,7 +734,7 @@ class ConfigEncryptionTool {
loadFlowXml(outputFlowXmlPath)
}
- private OutputStream getFlowOutputStream(File outputFlowXmlPath, boolean isFileGZipped) {
+ private static OutputStream getFlowOutputStream(File outputFlowXmlPath, boolean isFileGZipped) {
OutputStream flowOutputStream = new FileOutputStream(outputFlowXmlPath)
if(isFileGZipped) {
flowOutputStream = new GZIPOutputStream(flowOutputStream)
@@ -751,7 +743,7 @@ class ConfigEncryptionTool {
}
// Create a temporary output file we can write the stream to
- private Path getTemporaryFlowXmlFile(String originalOutputFlowXmlPath) {
+ private static Path getTemporaryFlowXmlFile(String originalOutputFlowXmlPath) {
String outputFilename = Paths.get(originalOutputFlowXmlPath).getFileName().toString()
String migratedFileName = "migrated-${outputFilename}"
Paths.get(originalOutputFlowXmlPath).resolveSibling(migratedFileName)
@@ -782,7 +774,7 @@ class ConfigEncryptionTool {
passwords.each { password ->
final SensitivePropertyProvider sensitivePropertyProvider = providerFactory
- .getProvider(PropertyProtectionScheme.fromIdentifier((String) password.@encryption))
+ .getProvider(new StandardProtectionScheme((String) password.@encryption))
if (isVerbose) {
logger.info("Attempting to decrypt ${password.text()} using protection scheme ${password.@encryption}")
}
@@ -798,6 +790,9 @@ class ConfigEncryptionTool {
logger.info("Updated XML content: ${updatedXml}")
updatedXml
} catch (Exception e) {
+ if (isVerbose) {
+ logger.error("Processing XML failed", e)
+ }
printUsageAndThrow("Cannot decrypt login identity providers XML content", ExitCode.SERVICE_ERROR)
}
}
@@ -830,7 +825,7 @@ class ConfigEncryptionTool {
logger.info("Attempting to decrypt ${password.text()} using protection scheme ${password.@encryption}")
}
final SensitivePropertyProvider sensitivePropertyProvider = providerFactory
- .getProvider(PropertyProtectionScheme.fromIdentifier((String) password.@encryption))
+ .getProvider(new StandardProtectionScheme((String) password.@encryption))
final ProtectedPropertyContext context = getContext(providerFactory, (String) password.@name, groupIdentifier)
String decryptedValue = sensitivePropertyProvider.unprotect((String) password.text().trim(), context)
password.replaceNode {
@@ -845,15 +840,18 @@ class ConfigEncryptionTool {
}
updatedXml
} catch (Exception e) {
+ if (isVerbose) {
+ logger.error("Processor Authorizers failed", e)
+ }
printUsageAndThrow("Cannot decrypt authorizers XML content", ExitCode.SERVICE_ERROR)
}
}
- ProtectedPropertyContext getContext(final SensitivePropertyProviderFactory providerFactory, final String propertyName, final String groupIdentifier) {
- providerFactory.getPropertyContext(groupIdentifier, propertyName);
+ static ProtectedPropertyContext getContext(final SensitivePropertyProviderFactory providerFactory, final String propertyName, final String groupIdentifier) {
+ providerFactory.getPropertyContext(groupIdentifier, propertyName)
}
- String encryptLoginIdentityProviders(final String plainXml, final String newKeyHex = keyHex, final PropertyProtectionScheme newProtectionScheme = protectionScheme) {
+ String encryptLoginIdentityProviders(final String plainXml, final String newKeyHex = keyHex) {
final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(newKeyHex)
// TODO: Switch to XmlParser & XmlNodePrinter to maintain "empty" element structure
@@ -872,11 +870,11 @@ class ConfigEncryptionTool {
}
return plainXml
}
- final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(newProtectionScheme)
+ final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(protectionScheme)
passwords.each { password ->
if (isVerbose) {
- logger.info("Attempting to encrypt ${password.name()} using protection scheme ${sensitivePropertyProvider.identifierKey}")
+ logger.info("Attempting to encrypt ${password.name()} using protection scheme ${protectionScheme}")
}
final ProtectedPropertyContext context = getContext(providerFactory, (String) password.@name, groupIdentifier)
String encryptedValue = sensitivePropertyProvider.protect((String) password.text().trim(), context)
@@ -897,7 +895,7 @@ class ConfigEncryptionTool {
}
}
- String encryptAuthorizers(final String plainXml, final String newKeyHex = keyHex, final PropertyProtectionScheme newProtectionScheme = protectionScheme) {
+ String encryptAuthorizers(final String plainXml, final String newKeyHex = keyHex) {
final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(newKeyHex)
// TODO: Switch to XmlParser & XmlNodePrinter to maintain "empty" element structure
@@ -920,11 +918,11 @@ class ConfigEncryptionTool {
}
return plainXml
}
- final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(newProtectionScheme)
+ final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(protectionScheme)
passwords.each { password ->
if (isVerbose) {
- logger.info("Attempting to encrypt ${password.name()} using protection scheme ${sensitivePropertyProvider.identifierKey}")
+ logger.info("Attempting to encrypt ${password.name()} using protection scheme ${protectionScheme}")
}
final ProtectedPropertyContext context = getContext(providerFactory, (String) password.@name, groupIdentifier)
String encryptedValue = sensitivePropertyProvider.protect((String) password.text().trim(), context)
@@ -984,7 +982,7 @@ class ConfigEncryptionTool {
// Add the encrypted value
encryptedProperties.setProperty(key, protectedValue)
- logger.info("Protected ${key} with ${spp.getIdentifierKey()} -> \t${protectedValue}")
+ logger.info("Protected ${key} with ${protectionScheme} -> \t${protectedValue}")
// Add the protection key ("x.y.z.protected" -> "aes/gcm/{128,256}")
String protectionKey = ApplicationPropertiesProtector.getProtectionKey(key)
@@ -1239,7 +1237,7 @@ class ConfigEncryptionTool {
}
} catch (SAXException e) {
logger.error("No provider element with class {} found in XML content; " +
- "the file could be empty or the element may be missing or commented out", LDAP_PROVIDER_CLASS)
+ "the file could be empty or the element may be missing or commented out: {}", LDAP_PROVIDER_CLASS, e.getMessage())
return fileContents.split("\n")
}
}
@@ -1259,7 +1257,7 @@ class ConfigEncryptionTool {
}
} catch (SAXException e) {
logger.error("No provider element with class {} found in XML content; " +
- "the file could be empty or the element may be missing or commented out", LDAP_USER_GROUP_PROVIDER_CLASS)
+ "the file could be empty or the element may be missing or commented out: {}", LDAP_USER_GROUP_PROVIDER_CLASS, e.getMessage())
return fileContents.split("\n")
}
}
@@ -1287,7 +1285,7 @@ class ConfigEncryptionTool {
// Generate a 128 bit salt
byte[] salt = generateScryptSaltForKeyDerivation()
- int keyLengthInBytes = getValidKeyLengths().max() / 8
+ int keyLengthInBytes = (int) (getValidKeyLengths().max() / 8)
byte[] derivedKeyBytes = SCrypt.generate(password.getBytes(StandardCharsets.UTF_8), salt, SCRYPT_N, SCRYPT_R, SCRYPT_P, keyLengthInBytes)
Hex.encodeHexString(derivedKeyBytes).toUpperCase()
}
@@ -1322,6 +1320,7 @@ class ConfigEncryptionTool {
def nfp = getNiFiPropertiesLoader(keyHex).readProtectedPropertiesFromDisk(new File(niFiPropertiesPath))
return nfp.hasProtectedKeys()
} catch (SensitivePropertyProtectionException | IOException e) {
+ logger.debug("Read Protected Properties failed {}", e.getMessage())
return true
}
} else {
@@ -1397,21 +1396,18 @@ class ConfigEncryptionTool {
tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS)
}
- if (tool.migration && tool.migrationProtectionScheme.requiresSecretKey()) {
+ if (tool.migration) {
String migrationKeyHex = tool.getMigrationKey()
-
- if (!migrationKeyHex) {
- tool.printUsageAndThrow("Original hex key must be provided for migration", ExitCode.INVALID_ARGS)
- }
-
- try {
- // Validate the length and format
- tool.migrationKeyHex = parseKey(migrationKeyHex)
- } catch (KeyException e) {
- if (tool.isVerbose) {
- logger.error("Encountered an error", e)
+ if (migrationKeyHex) {
+ try {
+ // Validate the length and format
+ tool.migrationKeyHex = parseKey(migrationKeyHex)
+ } catch (KeyException e) {
+ if (tool.isVerbose) {
+ logger.error("Encountered an error", e)
+ }
+ tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS)
}
- tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS)
}
}
}
@@ -1422,6 +1418,7 @@ class ConfigEncryptionTool {
try {
tool.niFiProperties = tool.loadNiFiProperties(existingKeyHex)
} catch (Exception e) {
+ logger.error("Load Properties failed", e)
tool.printUsageAndThrow("Cannot migrate key if no previous encryption occurred", ExitCode.ERROR_READING_NIFI_PROPERTIES)
}
}
@@ -1430,6 +1427,7 @@ class ConfigEncryptionTool {
try {
tool.loginIdentityProviders = tool.loadLoginIdentityProviders(existingKeyHex)
} catch (Exception e) {
+ logger.error("Load Login Identify Providers failed", e)
tool.printUsageAndThrow("Cannot migrate key if no previous encryption occurred", ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS)
}
tool.loginIdentityProviders = tool.encryptLoginIdentityProviders(tool.loginIdentityProviders)
@@ -1439,6 +1437,7 @@ class ConfigEncryptionTool {
try {
tool.authorizers = tool.loadAuthorizers(existingKeyHex)
} catch (Exception e) {
+ logger.error("Load Authorizers failed", e)
tool.printUsageAndThrow("Cannot migrate key if no previous encryption occurred", ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS)
}
tool.authorizers = tool.encryptAuthorizers(tool.authorizers)
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
index 1fbf8e7..888a0c2 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/DecryptMode.groovy
@@ -20,9 +20,11 @@ import groovy.cli.commons.CliBuilder
import groovy.cli.commons.OptionAccessor
import org.apache.commons.cli.HelpFormatter
import org.apache.nifi.properties.ConfigEncryptionTool
-import org.apache.nifi.properties.PropertyProtectionScheme
+import org.apache.nifi.properties.scheme.ProtectionScheme
+import org.apache.nifi.properties.scheme.ProtectionSchemeResolver
import org.apache.nifi.properties.SensitivePropertyProvider
import org.apache.nifi.properties.SensitivePropertyProviderFactory
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
import org.apache.nifi.toolkit.encryptconfig.util.PropertiesEncryptor
@@ -36,6 +38,8 @@ class DecryptMode implements ToolMode {
private static final Logger logger = LoggerFactory.getLogger(DecryptMode.class)
+ private static final ProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+
static enum FileType {
properties,
xml
@@ -209,7 +213,7 @@ class DecryptMode implements ToolMode {
OptionAccessor rawOptions
Configuration.KeySource keySource
- PropertyProtectionScheme protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
+ ProtectionScheme protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
String key
SensitivePropertyProvider decryptionProvider
SensitivePropertyProviderFactory providerFactory
@@ -233,12 +237,8 @@ class DecryptMode implements ToolMode {
determineProtectionScheme()
determineBootstrapProperties()
- if (protectionScheme.requiresSecretKey()) {
- determineKey()
- if (!key) {
- throw new RuntimeException("Failed to configure tool, could not determine key.")
- }
- }
+ determineKey()
+
providerFactory = StandardSensitivePropertyProviderFactory
.withKeyAndBootstrapSupplier(key, ConfigEncryptionTool.getBootstrapSupplier(inputBootstrapPath))
decryptionProvider = providerFactory.getProvider(protectionScheme)
@@ -286,7 +286,7 @@ class DecryptMode implements ToolMode {
private void determineProtectionScheme() {
if (rawOptions.S) {
- protectionScheme = PropertyProtectionScheme.valueOf(rawOptions.S)
+ protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(rawOptions.S)
}
}
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
index 4fc52aa..36248cd 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptMode.groovy
@@ -17,7 +17,8 @@
package org.apache.nifi.toolkit.encryptconfig
import groovy.cli.commons.CliBuilder
-import org.apache.nifi.properties.PropertyProtectionScheme
+import org.apache.nifi.properties.scheme.ProtectionSchemeResolver
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
import org.apache.nifi.toolkit.encryptconfig.util.ToolUtilities
@@ -31,6 +32,8 @@ class NiFiRegistryDecryptMode extends DecryptMode {
private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryDecryptMode.class)
+ private static final ProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+
CliBuilder cli
boolean verboseEnabled
@@ -73,7 +76,7 @@ class NiFiRegistryDecryptMode extends DecryptMode {
config.fileType = FileType.properties // disables auto-detection, which is still experimental
if (options.S) {
- config.protectionScheme = PropertyProtectionScheme.valueOf((String) options.S)
+ config.protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme((String) options.S)
}
// one of [-p, -k, -b]
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
index a4f8c90..3f76f59 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryMode.groovy
@@ -22,11 +22,11 @@ import org.apache.commons.cli.HelpFormatter
import org.apache.commons.cli.Options
import org.apache.nifi.properties.BootstrapProperties
import org.apache.nifi.properties.ConfigEncryptionTool
-import org.apache.nifi.properties.PropertyProtectionScheme
-import org.apache.nifi.properties.ProtectedPropertyContext
+import org.apache.nifi.properties.scheme.ProtectionScheme
import org.apache.nifi.properties.SensitivePropertyProtectionException
import org.apache.nifi.properties.SensitivePropertyProvider
import org.apache.nifi.properties.SensitivePropertyProviderFactory
+import org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.apache.nifi.registry.properties.util.NiFiRegistryBootstrapUtils
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
@@ -44,6 +44,8 @@ class NiFiRegistryMode implements ToolMode {
private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryMode.class)
+ private static final StandardProtectionSchemeResolver PROTECTION_SCHEME_RESOLVER = new StandardProtectionSchemeResolver()
+
CliBuilder cli
boolean verboseEnabled
@@ -59,7 +61,7 @@ class NiFiRegistryMode implements ToolMode {
try {
NiFiRegistryBootstrapUtils.loadBootstrapProperties(bootstrapConfPath)
} catch (final IOException e) {
- throw new SensitivePropertyProtectionException(e.getCause(), e)
+ throw new SensitivePropertyProtectionException("Loading Bootstrap Properties failed", e)
}
}
}
@@ -210,7 +212,7 @@ class NiFiRegistryMode implements ToolMode {
cli.S(longOpt: 'protectionScheme',
args: 1,
argName: 'protectionScheme',
- "Selects the protection scheme for encrypted properties. Valid values are: [${PropertyProtectionScheme.values().join(", ")}] (default is ${ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME.name()})")
+ String.format("Selects the protection scheme for encrypted properties. Default is AES_GCM. Valid values: %s", PROTECTION_SCHEME_RESOLVER.supportedProtectionSchemes))
// Options for the old password or key, if running the tool to migrate keys
cli._(longOpt: 'oldPassword',
@@ -224,7 +226,7 @@ class NiFiRegistryMode implements ToolMode {
cli.H(longOpt: 'oldProtectionScheme',
args: 1,
argName: 'protectionScheme',
- "The old protection scheme to use during encryption migration (see --protectionScheme for possible values). Default is ${ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME.name()}.")
+ "The old protection scheme to use during encryption migration (see --protectionScheme for possible values). Default is AES_GCM.")
// Options for output bootstrap.conf file
cli.b(longOpt: 'bootstrapConf',
@@ -278,9 +280,9 @@ class NiFiRegistryMode implements ToolMode {
boolean usingPassword
boolean usingBootstrapKey
- PropertyProtectionScheme protectionScheme
+ ProtectionScheme protectionScheme
String encryptionKey
- PropertyProtectionScheme oldProtectionScheme
+ ProtectionScheme oldProtectionScheme
String decryptionKey
SensitivePropertyProvider encryptionProvider
@@ -371,7 +373,7 @@ class NiFiRegistryMode implements ToolMode {
private void determineProtectionScheme() {
if (rawOptions.S) {
- protectionScheme = PropertyProtectionScheme.valueOf(rawOptions.S)
+ protectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(rawOptions.S)
} else {
protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
}
@@ -379,7 +381,7 @@ class NiFiRegistryMode implements ToolMode {
private void determineOldProtectionScheme() {
if (rawOptions.H) {
- oldProtectionScheme = PropertyProtectionScheme.valueOf(rawOptions.H)
+ oldProtectionScheme = PROTECTION_SCHEME_RESOLVER.getProtectionScheme(rawOptions.H)
} else {
oldProtectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
}
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy
index d43f826..c5d374a 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/PropertiesEncryptor.groovy
@@ -89,17 +89,6 @@ class PropertiesEncryptor {
"Usually this means a decryption password / key was not provided to the tool.")
}
- String supportedDecryptionScheme = decryptionProvider.getIdentifierKey()
- if (supportedDecryptionScheme) {
- propertiesToDecrypt.entrySet().each { entry ->
- if (!supportedDecryptionScheme.equals(entry.getValue())) {
- throw new IllegalStateException("Decryption capability not supported by this tool. " +
- "This tool supports ${supportedDecryptionScheme}, but this properties file contains " +
- "${entry.getKey()} protected by ${entry.getValue()}")
- }
- }
- }
-
Properties unprotectedProperties = new Properties()
for (String propertyName : properties.stringPropertyNames()) {
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy
index c35f129..40a7c03 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/XmlEncryptor.groovy
@@ -83,17 +83,11 @@ abstract class XmlEncryptor {
throw new IllegalStateException("Input XML is encrypted, but decryption capability is not enabled. " +
"Usually this means a decryption password / key was not provided to the tool.")
}
- String supportedDecryptionScheme = decryptionProvider.getIdentifierKey()
logger.debug("Found ${encryptedNodes.size()} encrypted XML elements. Will attempt to decrypt using the provided decryption key.")
encryptedNodes.each { node ->
logger.debug("Attempting to decrypt ${node.text()}")
- if (node.@encryption != supportedDecryptionScheme) {
- throw new IllegalStateException("Decryption capability not supported by this tool. " +
- "This tool supports ${supportedDecryptionScheme}, but this xml file contains " +
- "${node.toString()} protected by ${node.@encryption}")
- }
String groupIdentifier = (String) node.parent().identifier
String propertyName = (String) node.@name
String decryptedValue = decryptionProvider.unprotect(node.text().trim(), providerFactory.getPropertyContext(groupIdentifier, propertyName))
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
index 6821565..90a801b 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
@@ -93,8 +93,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
private final String PASSWORD_PROP_REGEX = "<property[^>]* name=\".* Password\""
private static final EncryptionMethod DEFAULT_ENCRYPTION_METHOD = EncryptionMethod.MD5_256AES
- private static final String DEFAULT_ALGORITHM = DEFAULT_ENCRYPTION_METHOD.algorithm
- private static final String DEFAULT_PROVIDER = DEFAULT_ENCRYPTION_METHOD.provider
private static final String WFXCTR = ConfigEncryptionTool.WRAPPED_FLOW_XML_CIPHER_TEXT_REGEX
private final String DEFAULT_LEGACY_SENSITIVE_PROPS_KEY = "nififtw!"
@@ -129,16 +127,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
keyHex?.size() * 4
}
- private static void printProperties(NiFiProperties properties) {
- if (!(properties instanceof ProtectedNiFiProperties)) {
- properties = new ProtectedNiFiProperties(properties)
- }
-
- (properties as ProtectedNiFiProperties).getPropertyKeysIncludingProtectionSchemes().sort().each { String key ->
- logger.info("${key}\t\t${properties.getProperty(key)}")
- }
- }
-
/**
* OS-agnostic method for setting file permissions. On POSIX-compliant systems, accurately sets the provided permissions. On Windows, sets the corresponding permissions for the file owner only.
*
@@ -624,36 +612,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
@Test
- void testParseKeyShouldThrowExceptionForInvalidKeys() {
- // Arrange
- List<String> keyValues = [
- "0123 4567",
- "non-hex-chars",
- KEY_HEX[0..<-1],
- "&ITD SF^FI&&%SDIF"
- ]
-
- def validKeyLengths = ConfigEncryptionTool.getValidKeyLengths()
- def bitLengths = validKeyLengths.collect { it / 4 }
- String secondHalf = /\[${validKeyLengths.join(", ")}\] bits / +
- /\(\[${bitLengths.join(", ")}\]/ + / hex characters\)/.toString()
-
- // Act
- keyValues.each { String key ->
- logger.info("Reading key: [${key}]")
- def msg = shouldFail(KeyException) {
- String parsedKey = ConfigEncryptionTool.parseKey(key)
- logger.info("Parsed key: [${parsedKey}]")
- }
- logger.expected(msg)
- int trimmedKeySize = key.replaceAll("[^0-9a-fA-F]", "").size()
-
- // Assert
- assert msg =~ "The key \\(${trimmedKeySize} hex chars\\) must be of length ${secondHalf}"
- }
- }
-
- @Test
void testShouldActuallyDeriveKeyFromPassword() {
// Arrange
logger.info("Using password: [${PASSWORD}]")
@@ -770,16 +728,9 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
String[] args = ["-n", workingFile.path, "-k", KEY_HEX]
tool.parse(args)
- // Act
- def msg = shouldFail(IOException) {
+ shouldFail(IOException) {
tool.loadNiFiProperties()
- logger.info("Read nifi.properties")
}
- logger.expected(msg)
-
- // Assert
- assert msg == "Cannot load NiFiProperties from [${workingFile.path}]".toString()
-
workingFile.deleteOnExit()
}
@@ -874,11 +825,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
"${ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX}="
]
- // Act
List<String> updatedLines = tool.updateBootstrapContentsWithKey(originalLines.clone() as List<String>)
- logger.info("Updated bootstrap.conf lines: ${updatedLines}")
- // Assert
assert updatedLines.size() == originalLines.size() + 1
assert updatedLines.first() == ConfigEncryptionTool.BOOTSTRAP_KEY_COMMENT
assert updatedLines.last() == EXPECTED_KEY_LINE
@@ -940,40 +888,19 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
@Test
void testShouldEncryptNiFiPropertiesWithEmptyProtectionScheme() {
- // Arrange
String originalNiFiPropertiesPath = "src/test/resources/nifi_with_sensitive_properties_unprotected_and_empty_protection_schemes.properties"
- File originalFile = new File(originalNiFiPropertiesPath)
- List<String> originalLines = originalFile.readLines()
- logger.info("Read ${originalLines.size()} lines from ${originalNiFiPropertiesPath}")
- logger.info("\n" + originalLines[0..3].join("\n") + "...")
-
NiFiProperties plainProperties = NiFiPropertiesLoader.withKey(KEY_HEX).load(originalNiFiPropertiesPath)
- logger.info("Loaded NiFiProperties from ${originalNiFiPropertiesPath}")
-
ProtectedNiFiProperties protectedWrapper = new ProtectedNiFiProperties(plainProperties)
- logger.info("Loaded ${plainProperties.size()} properties")
- logger.info("There are ${protectedWrapper.getSensitivePropertyKeys().size()} sensitive properties")
-
ConfigEncryptionTool tool = new ConfigEncryptionTool(keyHex: KEY_HEX)
final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
int protectedPropertyCount = protectedWrapper.getProtectedPropertyKeys().size()
- logger.info("Counted ${protectedPropertyCount} protected keys")
assert protectedPropertyCount < protectedWrapper.getSensitivePropertyKeys().size()
-
- // Act
NiFiProperties encryptedProperties = tool.encryptSensitiveProperties(plainProperties)
- // Assert
ProtectedNiFiProperties encryptedWrapper = new ProtectedNiFiProperties(encryptedProperties)
- encryptedWrapper.getProtectedPropertyKeys().every { String key, String protectionScheme ->
- logger.info("${key} is protected by ${protectionScheme}")
- assert protectionScheme == spp.identifierKey
- }
-
- printProperties(encryptedWrapper)
assert encryptedWrapper.getProtectedPropertyKeys().size() == encryptedWrapper.getSensitivePropertyKeys().findAll {
encryptedWrapper.getProperty(it)
@@ -1037,48 +964,26 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
@Test
void testShouldSerializeNiFiPropertiesAndPreserveFormatWithExistingProtectionSchemes() {
- // Arrange
String originalNiFiPropertiesPath = "src/test/resources/nifi_with_few_sensitive_properties_protected_aes.properties"
File originalFile = new File(originalNiFiPropertiesPath)
List<String> originalLines = originalFile.readLines()
- logger.info("Read ${originalLines.size()} lines from ${originalNiFiPropertiesPath}")
- logger.info("\n" + originalLines[0..3].join("\n") + "...")
ProtectedNiFiProperties protectedProperties = NiFiPropertiesLoader.withKey(KEY_HEX).readProtectedPropertiesFromDisk(new File(originalNiFiPropertiesPath))
- logger.info("Loaded NiFiProperties from ${originalNiFiPropertiesPath}")
-
- logger.info("Loaded ${protectedProperties.getPropertyKeys().size()} properties")
- logger.info("There are ${protectedProperties.getSensitivePropertyKeys().size()} sensitive properties")
- logger.info("There are ${protectedProperties.getProtectedPropertyKeys().size()} protected properties")
int originalProtectedPropertyCount = protectedProperties.getProtectedPropertyKeys().size()
protectedProperties.addSensitivePropertyProvider(DEFAULT_PROVIDER_FACTORY.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME))
NiFiProperties encryptedProperties = protectedProperties.getApplicationProperties()
int protectedPropertyCount = ProtectedNiFiProperties.countProtectedProperties(encryptedProperties)
- logger.info("Counted ${protectedPropertyCount} protected keys")
int protectedCountChange = protectedPropertyCount - originalProtectedPropertyCount
- logger.info("Expected line count change: ${protectedCountChange}")
-
- // Act
List<String> lines = ConfigEncryptionTool.serializeNiFiPropertiesAndPreserveFormat(protectedProperties, originalFile)
- logger.info("Serialized NiFiProperties to ${lines.size()} lines")
- lines.eachWithIndex { String entry, int i ->
- logger.debug("${(i + 1).toString().padLeft(3)}: ${entry}")
- }
- // Assert
-
- // Added n new lines for the encrypted properties
assert lines.size() == originalLines.size() + protectedCountChange
protectedProperties.getPropertyKeys().every { String key ->
assert lines.contains("${key}=${protectedProperties.getProperty(key)}".toString())
}
-
- logger.info("Updated nifi.properties:")
- logger.info("\n" * 2 + lines.join("\n"))
}
@Test
@@ -1299,13 +1204,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
})
- // Act
ConfigEncryptionTool.main(args)
- logger.info("Invoked #main with ${args.join(" ")}")
-
- // Assert
-
- // Assertions defined above
}
@Test
@@ -1381,13 +1280,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
})
- // Act
ConfigEncryptionTool.main(args)
- logger.info("Invoked #main with ${args.join(" ")}")
-
- // Assert
-
- // Assertions defined above
}
@Test
@@ -1475,12 +1368,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
})
- logger.info("Invoked #main second time with ${args.join(" ")}")
ConfigEncryptionTool.main(args)
-
- // Assert
-
- // Assertions defined above
}
/**
@@ -1595,12 +1483,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
})
- logger.info("Migrating key (${scenario}) with ${localArgs.join(" ")}")
ConfigEncryptionTool.main(localArgs as String[])
-
- // Assert
-
- // Assertions defined above
}
/**
@@ -1608,58 +1491,34 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
*/
@Test
void testShouldMigrateFromPasswordToPassword() {
- // Arrange
String scenario = "password to password"
def args = ["-w", PASSWORD, "-p", PASSWORD.reverse()]
- // Act
performKeyMigration(scenario, args, PASSWORD, PASSWORD.reverse())
-
- // Assert
-
- // Assertions in common method above
}
@Test
void testShouldMigrateFromPasswordToKey() {
- // Arrange
String scenario = "password to key"
def args = ["-w", PASSWORD, "-k", KEY_HEX]
- // Act
performKeyMigration(scenario, args, PASSWORD, "", "", KEY_HEX)
-
- // Assert
-
- // Assertions in common method above
}
@Test
void testShouldMigrateFromKeyToPassword() {
- // Arrange
String scenario = "key to password"
def args = ["-e", PASSWORD_KEY_HEX, "-p", PASSWORD.reverse()]
- // Act
performKeyMigration(scenario, args, "", PASSWORD.reverse(), PASSWORD_KEY_HEX, "")
-
- // Assert
-
- // Assertions in common method above
}
@Test
void testShouldMigrateFromKeyToKey() {
- // Arrange
String scenario = "key to key"
def args = ["-e", PASSWORD_KEY_HEX, "-k", KEY_HEX]
- // Act
performKeyMigration(scenario, args, "", "", PASSWORD_KEY_HEX, KEY_HEX)
-
- // Assert
-
- // Assertions in common method above
}
@Test
@@ -2424,13 +2283,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
})
- // Act
ConfigEncryptionTool.main(args)
- logger.info("Invoked #main with ${args.join(" ")}")
-
- // Assert
-
- // Assertions defined above
}
@Test
@@ -3555,11 +3408,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
*/
@Test
void testShouldPerformFullOperationOnFlowXmlWithSameSensitivePropsKey() {
- // Arrange
exit.expectSystemExitWithStatus(0)
- File tmpDir = setupTmpDir()
-
File emptyKeyFile = new File("src/test/resources/bootstrap_with_empty_root_key.conf")
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
bootstrapFile.delete()
@@ -3569,7 +3419,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
String originalKeyLine = originalBootstrapLines.find {
it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
}
- logger.info("Original key line from bootstrap.conf: ${originalKeyLine}")
assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
@@ -3594,10 +3443,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
final int CIPHER_TEXT_COUNT = originalFlowCipherTexts.size()
NiFiProperties inputProperties = new NiFiPropertiesLoader().load(workingNiFiPropertiesFile)
- logger.info("Loaded ${inputProperties.size()} properties from input file")
ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
- logger.info("Original sensitive values: ${originalSensitiveValues}")
String newFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY
@@ -3606,8 +3453,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines()
- logger.info("Updated nifi.properties:")
- logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
// Check that the output values for everything is the same including the sensitive props key
NiFiProperties updatedProperties = new NiFiPropertiesLoader().readProtectedPropertiesFromDisk(workingNiFiPropertiesFile)
@@ -3621,7 +3466,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
String updatedKeyLine = updatedBootstrapLines.find {
it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
}
- logger.info("Updated key line: ${updatedKeyLine}")
assert updatedKeyLine == EXPECTED_KEY_LINE
assert originalBootstrapLines.size() == updatedBootstrapLines.size()
@@ -3635,20 +3479,12 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
assert migratedFlowXmlContent != ORIGINAL_FLOW_XML_CONTENT
// Verify that the cipher texts decrypt correctly
- logger.info("Original flow.xml.gz cipher texts: ${originalFlowCipherTexts}")
def migratedFlowCipherTexts = findFieldsInStream(migratedFlowXmlContent, WFXCTR)
- logger.info("Updated flow.xml.gz cipher texts: ${migratedFlowCipherTexts}")
assert migratedFlowCipherTexts.size() == CIPHER_TEXT_COUNT
}
})
- // Act
ConfigEncryptionTool.main(args)
- logger.info("Invoked #main with ${args.join(" ")}")
-
- // Assert
-
- // Assertions defined above
}
/**
@@ -4381,37 +4217,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
String[] args = ["-n", inputPropertiesFile.path, "-b", bootstrapFile.path, "-c"]
- exit.checkAssertionAfterwards(new Assertion() {
- void checkAssertion() {
- final String standardOutput = systemOutRule.getLog()
- List<String> lines = standardOutput.split("\n")
-
- // The SystemRule log also includes STDERR, so truncate after 9 lines
- def stdoutLines = lines[0..<EXPECTED_CLI_OUTPUT.size()]
- logger.info("STDOUT:\n\t${stdoutLines.join("\n\t")}")
-
- // Split the output into lines and create a map of the keys and values
- def parsedCli = stdoutLines.collectEntries { String line ->
- def components = line.split("=", 2)
- components.size() > 1 ? [(components[0]): components[1]] : [(components[0]): ""]
- }
-
- assert parsedCli.size() == EXPECTED_CLI_OUTPUT.size()
- assert EXPECTED_CLI_OUTPUT.every { String k, String v -> parsedCli.get(k) == v }
-
- // Clean up
- bootstrapFile.deleteOnExit()
- tmpDir.deleteOnExit()
- }
- })
-
- // Act
ConfigEncryptionTool.main(args)
- logger.info("Invoked #main with ${args.join(" ")}")
-
- // Assert
-
- // Assertions defined above
}
@Test
@@ -4720,14 +4526,9 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
invalidOpts.each { String invalid ->
tool = new ConfigEncryptionTool()
def args = (invalid + " -c").split(" ")
- logger.info("Testing with ${args}")
- def msg = shouldFail(CommandLineParseException) {
+ shouldFail(CommandLineParseException) {
tool.parse(args as String[])
}
-
- // Assert
- assert msg == "When '-c'/'--translateCli' is specified, only '-h', '-v', and '-n'/'-b' with the relevant files are allowed"
- assert systemOutRule.getLog().contains("usage: org.apache.nifi.properties.ConfigEncryptionTool [")
}
}
@@ -4744,14 +4545,9 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Act
invalidOpts.each { String invalid ->
def args = (invalid + " -c").split(" ")
- logger.info("Testing with ${args}")
- def msg = shouldFail(CommandLineParseException) {
+ shouldFail(CommandLineParseException) {
tool.parse(args as String[])
}
-
- // Assert
- assert msg == "When '-c'/'--translateCli' is specified, '-n'/'--niFiProperties' is required (and '-b'/'--bootstrapConf' is required if the properties are encrypted)"
- assert systemOutRule.getLog().contains("usage: org.apache.nifi.properties.ConfigEncryptionTool [")
}
}
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy
index 72c3a0f..74b2151 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/EncryptConfigMainTest.groovy
@@ -18,9 +18,9 @@ package org.apache.nifi.toolkit.encryptconfig
import org.apache.nifi.properties.NiFiPropertiesLoader
-import org.apache.nifi.properties.PropertyProtectionScheme
import org.apache.nifi.properties.ProtectedPropertyContext
import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
import org.apache.nifi.util.NiFiProperties
import org.bouncycastle.jce.provider.BouncyCastleProvider
@@ -185,7 +185,7 @@ class EncryptConfigMainTest extends GroovyTestCase {
"-v"]
SensitivePropertyProvider spp = org.apache.nifi.properties.StandardSensitivePropertyProviderFactory.withKey(TestUtil.KEY_HEX)
- .getProvider(PropertyProtectionScheme.AES_GCM)
+ .getProvider(new StandardProtectionScheme("aes/gcm"))
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptModeSpec.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptModeSpec.groovy
deleted file mode 100644
index e8f86f4..0000000
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/NiFiRegistryDecryptModeSpec.groovy
+++ /dev/null
@@ -1,117 +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.toolkit.encryptconfig
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.Assume
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import spock.lang.Specification
-
-import java.security.Security
-
-import static org.apache.nifi.toolkit.encryptconfig.TestUtil.*
-
-class NiFiRegistryDecryptModeSpec extends Specification {
- private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryDecryptModeSpec.class)
-
- ByteArrayOutputStream toolStdOutContent
- PrintStream origSystemOut
-
- // runs before every feature method
- def setup() {
- origSystemOut = System.out
- toolStdOutContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(toolStdOutContent));
- }
-
- // runs after every feature method
- def cleanup() {
- toolStdOutContent.flush()
- System.setOut(origSystemOut);
- toolStdOutContent.close()
- }
-
- // runs before the first feature method
- def setupSpec() {
- Security.addProvider(new BouncyCastleProvider())
- setupTmpDir()
- }
-
- // runs after the last feature method
- def cleanupSpec() {
- cleanupTmpDir()
- }
-
- def "decrypt protected nifi-registry.properties file using -k"() {
-
- setup:
- NiFiRegistryDecryptMode tool = new NiFiRegistryDecryptMode()
- def inRegistryProperties1 = copyFileToTempFile(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, "nifi-registry.properties")
- File outRegistryProperties1 = generateTmpFile()
-
- when: "run with args: -k <key> -r <file>"
- tool.run("-k ${KEY_HEX_128} -r ${inRegistryProperties1}".split(" "))
- toolStdOutContent.flush()
- outRegistryProperties1.text = toolStdOutContent.toString()
- then: "decrypted properties file was printed to std out"
- assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_UNPROTECTED, outRegistryProperties1.getAbsolutePath(), true)
- and: "input properties file is still encrypted"
- assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, inRegistryProperties1, true)
-
- }
-
- def "decrypt protected nifi-registry.properties file using -p [256-bit]"() {
-
- Assume.assumeTrue("Test only runs when unlimited strength crypto is available", isUnlimitedStrengthCryptoAvailable())
-
- setup:
- NiFiRegistryDecryptMode tool = new NiFiRegistryDecryptMode()
- def inRegistryProperties1 = copyFileToTempFile(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_PASSWORD_256, "nifi-registry.properties")
- File outRegistryProperties1 = generateTmpFile()
-
- when: "run with args: -p <password> -r <file>"
- tool.run("-p ${PASSWORD} -r ${inRegistryProperties1}".split(" "))
- toolStdOutContent.flush()
- outRegistryProperties1.text = toolStdOutContent.toString()
- then: "decrypted properties file was printed to std out"
- assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_UNPROTECTED, outRegistryProperties1.getAbsolutePath(), true)
- and: "input properties file is still encrypted"
- assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_PASSWORD_256, inRegistryProperties1, true)
-
- }
-
- def "decrypt protected nifi-registry.properties file using -b"() {
-
- setup:
- NiFiRegistryDecryptMode tool = new NiFiRegistryDecryptMode()
- def inRegistryProperties = copyFileToTempFile(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, "nifi-registry.properties")
- def inBootstrap = copyFileToTempFile(RESOURCE_REGISTRY_BOOTSTRAP_KEY_128)
- File outRegistryProperties = generateTmpFile()
-
- when: "run with args: -b <file> -r <file>"
- tool.run("-b ${inBootstrap} -r ${inRegistryProperties}".split(" "))
- toolStdOutContent.flush()
- outRegistryProperties.text = toolStdOutContent.toString()
- then: "decrypted properties file was printed to std out"
- assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_UNPROTECTED, outRegistryProperties.getAbsolutePath(), true)
- and: "input properties file is still encrypted"
- assertPropertiesFilesAreEqual(RESOURCE_REGISTRY_PROPERTIES_POPULATED_PROTECTED_KEY_128, inRegistryProperties, true)
-
- }
-
-}
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy
index 12f6e3a..349e7ad 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy
@@ -17,8 +17,8 @@
package org.apache.nifi.toolkit.encryptconfig
import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.properties.PropertyProtectionScheme
import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.scheme.StandardProtectionScheme
import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryAuthorizersXmlEncryptor
import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryIdentityProvidersXmlEncryptor
@@ -312,7 +312,7 @@ class TestUtil {
assert populatedSensitiveProperties.size() == protectedSensitiveProperties.size()
SensitivePropertyProvider spp = org.apache.nifi.properties.StandardSensitivePropertyProviderFactory.withKey(expectedKey)
- .getProvider(PropertyProtectionScheme.AES_GCM)
+ .getProvider(new StandardProtectionScheme("aes/gcm"))
protectedSensitiveProperties.each {
String value = it.text()