diff --git a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/util/SecureNiFiConfigUtil.java b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/util/SecureNiFiConfigUtil.java
index 10d825f97e..bb1abeee92 100644
--- a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/util/SecureNiFiConfigUtil.java
+++ b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/util/SecureNiFiConfigUtil.java
@@ -96,7 +96,7 @@ public class SecureNiFiConfigUtil {
* @throws IOException can be thrown when writing keystores to disk
* @throws RuntimeException indicates a security exception while generating keystores
*/
- public static void configureSecureNiFiProperties(String nifiPropertiesFilename, Logger cmdLogger) throws IOException, RuntimeException {
+ public static void configureSecureNiFiProperties(final String nifiPropertiesFilename, final Logger cmdLogger) throws IOException, RuntimeException {
final File propertiesFile = new File(nifiPropertiesFilename);
final Properties nifiProperties = loadProperties(propertiesFile);
@@ -111,7 +111,7 @@ public class SecureNiFiConfigUtil {
boolean truststoreExists = fileExists(truststorePath);
if (!keystoreExists && !truststoreExists) {
- TlsConfiguration tlsConfiguration = null;
+ TlsConfiguration tlsConfiguration;
cmdLogger.info("Generating Self-Signed Certificate: Expires on {}", LocalDate.now().plus(CERT_DURATION_DAYS, ChronoUnit.DAYS));
try {
String[] subjectAlternativeNames = getSubjectAlternativeNames(nifiProperties, cmdLogger);
diff --git a/nifi-commons/nifi-properties/pom.xml b/nifi-commons/nifi-properties/pom.xml
index fbcfd9db34..5a81dad807 100644
--- a/nifi-commons/nifi-properties/pom.xml
+++ b/nifi-commons/nifi-properties/pom.xml
@@ -15,6 +15,14 @@
-->
4.0.0
+
+
+ org.apache.nifi
+ nifi-property-utils
+ 1.14.0-SNAPSHOT
+ compile
+
+ org.apache.nifinifi-commons
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProviderFactory.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiBootstrapPropertiesLoader.java
similarity index 60%
rename from nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProviderFactory.java
rename to nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiBootstrapPropertiesLoader.java
index c9d4313e81..da0e889895 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProviderFactory.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiBootstrapPropertiesLoader.java
@@ -14,10 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.registry.properties;
+package org.apache.nifi.util;
-public interface SensitivePropertyProviderFactory {
+import org.apache.nifi.properties.AbstractBootstrapPropertiesLoader;
- SensitivePropertyProvider getProvider();
+public class NiFiBootstrapPropertiesLoader extends AbstractBootstrapPropertiesLoader {
+ @Override
+ protected String getApplicationPrefix() {
+ return "nifi";
+ }
+ @Override
+ protected String getApplicationPropertiesFilename() {
+ return "nifi.properties";
+ }
+
+ @Override
+ protected String getApplicationPropertiesFilePathSystemProperty() {
+ return NiFiProperties.PROPERTIES_FILE_PATH;
+ }
}
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiBootstrapUtils.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiBootstrapUtils.java
new file mode 100644
index 0000000000..13605786cb
--- /dev/null
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiBootstrapUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.util;
+
+import org.apache.nifi.properties.AbstractBootstrapPropertiesLoader;
+import org.apache.nifi.properties.BootstrapProperties;
+
+import java.io.IOException;
+
+/**
+ * Encapsulates utility methods for dealing with bootstrap.conf or nifi.properties.
+ */
+public class NiFiBootstrapUtils {
+ private static final AbstractBootstrapPropertiesLoader BOOTSTRAP_PROPERTIES_LOADER = new NiFiBootstrapPropertiesLoader();
+
+ /**
+ * Returns the key (if any) used to encrypt sensitive properties, extracted from
+ * {@code $NIFI_HOME/conf/bootstrap.conf}.
+ *
+ * @return the key in hexadecimal format
+ * @throws IOException if the file is not readable
+ */
+ public static String extractKeyFromBootstrapFile() throws IOException {
+ return BOOTSTRAP_PROPERTIES_LOADER.extractKeyFromBootstrapFile();
+ }
+
+ /**
+ * Loads the default bootstrap.conf file into a BootstrapProperties object.
+ * @return The default bootstrap.conf as a BootstrapProperties object
+ * @throws IOException If the file is not readable
+ */
+ public static BootstrapProperties loadBootstrapProperties() throws IOException {
+ return loadBootstrapProperties(null);
+ }
+
+ /**
+ * Loads the bootstrap.conf file into a BootstrapProperties object.
+ * @param bootstrapPath the path to the bootstrap file
+ * @return The bootstrap.conf as a BootstrapProperties object
+ * @throws IOException If the file is not readable
+ */
+ public static BootstrapProperties loadBootstrapProperties(final String bootstrapPath) throws IOException {
+ return BOOTSTRAP_PROPERTIES_LOADER.loadBootstrapProperties(bootstrapPath);
+ }
+
+ /**
+ * Returns the key (if any) used to encrypt sensitive properties, extracted from
+ * {@code $NIFI_HOME/conf/bootstrap.conf}.
+ *
+ * @param bootstrapPath the path to the bootstrap file (if null, returns the sensitive key
+ * found in $NIFI_HOME/conf/bootstrap.conf)
+ * @return the key in hexadecimal format
+ * @throws IOException if the file is not readable
+ */
+ public static String extractKeyFromBootstrapFile(final String bootstrapPath) throws IOException {
+ return BOOTSTRAP_PROPERTIES_LOADER.extractKeyFromBootstrapFile(bootstrapPath);
+ }
+
+ /**
+ * Returns the default file path to {@code $NIFI_HOME/conf/nifi.properties}. If the system
+ * property nifi.properties.file.path is not set, it will be set to the relative conf/nifi.properties
+ *
+ * @return the path to the nifi.properties file
+ */
+ public static String getDefaultApplicationPropertiesFilePath() {
+ return BOOTSTRAP_PROPERTIES_LOADER.getDefaultApplicationPropertiesFilePath();
+ }
+}
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 371cc58669..fdf473ec3e 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.util;
+import org.apache.nifi.properties.ApplicationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,7 +47,7 @@ import java.util.stream.Stream;
* this class or passing it along. Its use should be refactored and minimized
* over time.
*/
-public abstract class NiFiProperties {
+public class NiFiProperties extends ApplicationProperties {
private static final Logger logger = LoggerFactory.getLogger(NiFiProperties.class);
// core properties
@@ -392,20 +393,17 @@ public abstract class NiFiProperties {
public static final int DEFAULT_COMPONENT_STATUS_REPOSITORY_PERSIST_COMPONENT_DAYS = 3;
public static final String DEFAULT_COMPONENT_STATUS_REPOSITORY_PERSIST_LOCATION = "./status_repository";
- /**
- * Retrieves the property value for the given property key.
- *
- * @param key the key of property value to lookup
- * @return value of property at given key or null if not found
- */
- public abstract String getProperty(String key);
+ public NiFiProperties() {
+ this(Collections.EMPTY_MAP);
+ }
- /**
- * Retrieves all known property keys.
- *
- * @return all known property keys
- */
- public abstract Set getPropertyKeys();
+ public NiFiProperties(final Map props) {
+ super(props);
+ }
+
+ public NiFiProperties(final Properties props) {
+ super(props);
+ }
// getters for core properties //
public File getFlowConfigurationFile() {
@@ -1591,14 +1589,10 @@ public abstract class NiFiProperties {
}
public boolean isTlsConfigurationPresent() {
- return StringUtils.isNotBlank(getProperty(NiFiProperties.SECURITY_KEYSTORE))
- && getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD) != null
- && StringUtils.isNotBlank(getProperty(NiFiProperties.SECURITY_TRUSTSTORE))
- && getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD) != null;
- }
-
- public int size() {
- return getPropertyKeys().size();
+ return StringUtils.isNotBlank(getProperty(SECURITY_KEYSTORE))
+ && getProperty(SECURITY_KEYSTORE_PASSWD) != null
+ && StringUtils.isNotBlank(getProperty(SECURITY_TRUSTSTORE))
+ && getProperty(SECURITY_TRUSTSTORE_PASSWD) != null;
}
public String getFlowFileRepoEncryptionKeyId() {
@@ -2026,6 +2020,11 @@ public abstract class NiFiProperties {
public Set getPropertyKeys() {
return properties.stringPropertyNames();
}
+
+ @Override
+ public int size() {
+ return getPropertyKeys().size();
+ }
};
}
@@ -2077,4 +2076,9 @@ public abstract class NiFiProperties {
}
// Other properties to validate...
}
+
+ @Override
+ public String toString() {
+ return "NiFiProperties instance with " + size() + " properties";
+ }
}
diff --git a/nifi-commons/nifi-property-utils/pom.xml b/nifi-commons/nifi-property-utils/pom.xml
new file mode 100644
index 0000000000..6ef57b2048
--- /dev/null
+++ b/nifi-commons/nifi-property-utils/pom.xml
@@ -0,0 +1,24 @@
+
+
+
+ 4.0.0
+
+ org.apache.nifi
+ nifi-commons
+ 1.14.0-SNAPSHOT
+
+ nifi-property-utils
+
diff --git a/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/AbstractBootstrapPropertiesLoader.java b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/AbstractBootstrapPropertiesLoader.java
new file mode 100644
index 0000000000..cbeea84980
--- /dev/null
+++ b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/AbstractBootstrapPropertiesLoader.java
@@ -0,0 +1,162 @@
+/*
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+/**
+ * An abstract base class for an application-specific BootstrapProperties loader.
+ */
+public abstract class AbstractBootstrapPropertiesLoader {
+ private static final Logger logger = LoggerFactory.getLogger(AbstractBootstrapPropertiesLoader.class);
+
+ private static final String RELATIVE_APPLICATION_PROPERTIES_PATTERN = "conf/%s";
+ private static final String BOOTSTRAP_CONF = "bootstrap.conf";
+
+ /**
+ * Return the property prefix used in the bootstrap.conf file for this application.
+ * @return the property prefix
+ */
+ protected abstract String getApplicationPrefix();
+
+ /**
+ * Return the name of the main application properties file (e.g., nifi.properties). This will be
+ * used to determine the default location of the application properties file.
+ * @return The name of the application properties file
+ */
+ protected abstract String getApplicationPropertiesFilename();
+
+ /**
+ * Return the system property name that should specify the file path of the main
+ * application properties file.
+ * @return The system property name that should provide the file path of the main application
+ * properties file
+ */
+ protected abstract String getApplicationPropertiesFilePathSystemProperty();
+
+ /**
+ * Returns the key (if any) used to encrypt sensitive properties, extracted from
+ * {@code $APPLICATION_HOME/conf/bootstrap.conf}.
+ *
+ * @return the key in hexadecimal format
+ * @throws IOException if the file is not readable
+ */
+ public String extractKeyFromBootstrapFile() throws IOException {
+ return extractKeyFromBootstrapFile(null);
+ }
+
+ /**
+ * Loads the bootstrap.conf file into a BootstrapProperties object.
+ * @param bootstrapPath the path to the bootstrap file
+ * @return The bootstrap.conf as a BootstrapProperties object
+ * @throws IOException If the file is not readable
+ */
+ public BootstrapProperties loadBootstrapProperties(final String bootstrapPath) throws IOException {
+ final Properties properties = new Properties();
+ final Path bootstrapFilePath = getBootstrapFile(bootstrapPath).toPath();
+ try (final InputStream bootstrapInput = Files.newInputStream(bootstrapFilePath)) {
+ properties.load(bootstrapInput);
+ return new BootstrapProperties(getApplicationPrefix(), properties, bootstrapFilePath);
+ } catch (final IOException e) {
+ logger.error("Cannot read from bootstrap.conf file at {}", bootstrapFilePath);
+ throw new IOException("Cannot read from bootstrap.conf", e);
+ }
+ }
+
+ /**
+ * Returns the key (if any) used to encrypt sensitive properties, extracted from
+ * {@code $APPLICATION_HOME/conf/bootstrap.conf}.
+ *
+ * @param bootstrapPath the path to the bootstrap file (if null, returns the sensitive key
+ * found in $APPLICATION_HOME/conf/bootstrap.conf)
+ * @return the key in hexadecimal format
+ * @throws IOException if the file is not readable
+ */
+ public String extractKeyFromBootstrapFile(final String bootstrapPath) throws IOException {
+ final BootstrapProperties bootstrapProperties = loadBootstrapProperties(bootstrapPath);
+
+ return bootstrapProperties.getBootstrapSensitiveKey().orElseGet(() -> {
+ logger.warn("No encryption key present in the bootstrap.conf file at {}", bootstrapProperties.getConfigFilePath());
+ return "";
+ });
+ }
+
+ /**
+ * Returns the file for bootstrap.conf.
+ *
+ * @param bootstrapPath the path to the bootstrap file (defaults to $APPLICATION_HOME/conf/bootstrap.conf
+ * if null)
+ * @return the {@code $APPLICATION_HOME/conf/bootstrap.conf} file
+ * @throws IOException if the directory containing the file is not readable
+ */
+ private File getBootstrapFile(final String bootstrapPath) throws IOException {
+ final File expectedBootstrapFile;
+ if (bootstrapPath == null) {
+ // Guess at location of bootstrap.conf file from nifi.properties file
+ final String defaultApplicationPropertiesFilePath = getDefaultApplicationPropertiesFilePath();
+ final File propertiesFile = new File(defaultApplicationPropertiesFilePath);
+ final File confDir = new File(propertiesFile.getParent());
+ if (confDir.exists() && confDir.canRead()) {
+ expectedBootstrapFile = new File(confDir, BOOTSTRAP_CONF);
+ } else {
+ logger.error("Cannot read from bootstrap.conf file at {} -- conf/ directory is missing or permissions are incorrect", confDir.getAbsolutePath());
+ throw new IOException("Cannot read from bootstrap.conf");
+ }
+ } else {
+ expectedBootstrapFile = new File(bootstrapPath);
+ }
+
+ if (expectedBootstrapFile.exists() && expectedBootstrapFile.canRead()) {
+ return expectedBootstrapFile;
+ } else {
+ logger.error("Cannot read from bootstrap.conf file at {} -- file is missing or permissions are incorrect", expectedBootstrapFile.getAbsolutePath());
+ throw new IOException("Cannot read from bootstrap.conf");
+ }
+ }
+
+ /**
+ * Returns the default file path to {@code $APPLICATION_HOME/conf/$APPLICATION.properties}. If the system
+ * property provided by {@code AbstractBootstrapPropertiesLoader#getApplicationPropertiesFilePathSystemProperty()}
+ * is not set, it will be set to the relative path provided by
+ * {@code AbstractBootstrapPropertiesLoader#getRelativeApplicationPropertiesFilePath()}.
+ *
+ * @return the path to the application properties file
+ */
+ public String getDefaultApplicationPropertiesFilePath() {
+ final String systemPropertyName = getApplicationPropertiesFilePathSystemProperty();
+ final String defaultRelativePath = String.format(RELATIVE_APPLICATION_PROPERTIES_PATTERN, getApplicationPropertiesFilename());
+
+ String systemPath = System.getProperty(systemPropertyName);
+
+ if (systemPath == null || systemPath.trim().isEmpty()) {
+ logger.warn("The system property {} is not set, so it is being set to '{}'", systemPropertyName, defaultRelativePath);
+ System.setProperty(systemPropertyName, defaultRelativePath);
+ systemPath = defaultRelativePath;
+ }
+
+ logger.info("Determined default application properties path to be '{}'", systemPath);
+ return systemPath;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ApplicationProperties.java
similarity index 65%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
rename to nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ApplicationProperties.java
index c800b3ad38..16bbee1dd8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java
+++ b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ApplicationProperties.java
@@ -16,8 +16,18 @@
*/
package org.apache.nifi.properties;
-public interface SensitivePropertyProviderFactory {
+import java.util.Map;
+import java.util.Properties;
- SensitivePropertyProvider getProvider();
+/**
+ * A tagging class that represents the main configuration properties for an application (e.g. NiFi or NiFi Registry).
+ */
+public class ApplicationProperties extends StandardReadableProperties {
+ public ApplicationProperties(Properties properties) {
+ super(properties);
+ }
+ public ApplicationProperties(Map properties) {
+ super(properties);
+ }
}
diff --git a/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/BootstrapProperties.java b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/BootstrapProperties.java
new file mode 100644
index 0000000000..4713e278fd
--- /dev/null
+++ b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/BootstrapProperties.java
@@ -0,0 +1,86 @@
+/*
+ * 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.nio.file.Path;
+import java.util.Enumeration;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * Properties representing bootstrap.conf.
+ */
+public class BootstrapProperties extends StandardReadableProperties {
+ private static final String PROPERTY_KEY_FORMAT = "%s.%s";
+ private static final String BOOTSTRAP_SENSITIVE_KEY = "bootstrap.sensitive.key";
+
+ private final String propertyPrefix;
+ private final Path configFilePath;
+
+ public BootstrapProperties(final String propertyPrefix, final Properties properties, final Path configFilePath) {
+ super(new Properties());
+
+ Objects.requireNonNull(properties, "Properties are required");
+ this.propertyPrefix = Objects.requireNonNull(propertyPrefix, "Property prefix is required");
+ this.configFilePath = configFilePath;
+
+ this.filterProperties(properties);
+
+ }
+
+ /**
+ * Returns the path to the bootstrap config file.
+ * @return The path to the file
+ */
+ public Path getConfigFilePath() {
+ return configFilePath;
+ }
+
+ /**
+ * Includes only the properties starting with the propertyPrefix.
+ * @param properties Unfiltered properties
+ */
+ private void filterProperties(final Properties properties) {
+ getRawProperties().clear();
+ final Properties filteredProperties = new Properties();
+ for(final Enumeration
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/AESSensitivePropertyProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/AESSensitivePropertyProvider.java
deleted file mode 100644
index b7d1d2e23f..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/AESSensitivePropertyProvider.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.properties;
-
-import org.apache.commons.lang3.StringUtils;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-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.security.Security;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class AESSensitivePropertyProvider implements SensitivePropertyProvider {
- private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProvider.class);
-
- private static final String IMPLEMENTATION_NAME = "AES Sensitive Property Provider";
- private static final String IMPLEMENTATION_KEY = "aes/gcm/";
- 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 Cipher cipher;
- private final SecretKey key;
-
- public AESSensitivePropertyProvider(String keyHex) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {
- byte[] key = validateKey(keyHex);
-
- try {
- Security.addProvider(new BouncyCastleProvider());
- cipher = Cipher.getInstance(ALGORITHM, PROVIDER);
- // Only store the key if the cipher was initialized successfully
- this.key = new SecretKeySpec(key, "AES");
- } 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);
- }
- }
-
- 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 validKeyLengths = getValidKeyLengths();
- if (!validKeyLengths.contains(key.length * 8)) {
- List 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;
- }
-
- public AESSensitivePropertyProvider(byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {
- this(key == null ? "" : Hex.toHexString(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 getValidKeyLengths() {
- List 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 name of the underlying implementation.
- *
- * @return the name of this sensitive property provider
- */
- @Override
- public String getName() {
- return IMPLEMENTATION_NAME;
- }
-
- /**
- * 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 IMPLEMENTATION_KEY + getKeySize(Hex.toHexString(key.getEncoded()));
- }
-
- private int getKeySize(String key) {
- if (StringUtils.isBlank(key)) {
- return 0;
- } else {
- // A key in hexadecimal format has one char per nibble (4 bits)
- return formatHexKey(key).length() * 4;
- }
- }
-
- /**
- * Returns the encrypted cipher text.
- *
- * @param unprotectedValue the sensitive value
- * @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(String unprotectedValue) throws SensitivePropertyProtectionException {
- if (unprotectedValue == null || unprotectedValue.trim().length() == 0) {
- 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.info(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(byte[] input) {
- return Base64.toBase64String(input).replaceAll("=", "");
- }
-
- /**
- * Generates a new random IV of 12 bytes using {@link SecureRandom}.
- *
- * @return the IV
- */
- private byte[] generateIV() {
- 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
- * @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(String protectedValue) 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)");
- }
-
- protectedValue = protectedValue.trim();
-
- final String IV_B64 = protectedValue.substring(0, protectedValue.indexOf(DELIMITER));
- byte[] iv = Base64.decode(IV_B64);
- if (iv.length < IV_LENGTH) {
- throw new IllegalArgumentException("The IV (" + iv.length + " bytes) must be at least " + IV_LENGTH + " bytes");
- }
-
- String CIPHERTEXT_B64 = protectedValue.substring(protectedValue.indexOf(DELIMITER) + 2);
-
- // Restore the = padding if necessary to reconstitute the GCM MAC check
- if (CIPHERTEXT_B64.length() % 4 != 0) {
- final int paddedLength = CIPHERTEXT_B64.length() + 4 - (CIPHERTEXT_B64.length() % 4);
- CIPHERTEXT_B64 = StringUtils.rightPad(CIPHERTEXT_B64, paddedLength, '=');
- }
-
- try {
- byte[] cipherBytes = Base64.decode(CIPHERTEXT_B64);
-
- cipher.init(Cipher.DECRYPT_MODE, this.key, new IvParameterSpec(iv));
- 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;
- }
-}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/AESSensitivePropertyProviderFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/AESSensitivePropertyProviderFactory.java
deleted file mode 100644
index 5c24a73c63..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/AESSensitivePropertyProviderFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.properties;
-
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.crypto.NoSuchPaddingException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-
-public class AESSensitivePropertyProviderFactory implements SensitivePropertyProviderFactory {
- private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProviderFactory.class);
-
- private String keyHex;
-
- public AESSensitivePropertyProviderFactory(String keyHex) {
- this.keyHex = keyHex;
- }
-
- public SensitivePropertyProvider getProvider() throws SensitivePropertyProtectionException {
- try {
- if (keyHex != null && !StringUtils.isBlank(keyHex)) {
- return new AESSensitivePropertyProvider(keyHex);
- } else {
- throw new SensitivePropertyProtectionException("The provider factory cannot generate providers without a key");
- }
- } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) {
- String msg = "Error creating AES Sensitive Property Provider";
- logger.warn(msg, e);
- throw new SensitivePropertyProtectionException(msg, e);
- }
- }
-
- @Override
- public String toString() {
- return "SensitivePropertyProviderFactory for creating AESSensitivePropertyProviders";
- }
-}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/MultipleSensitivePropertyProtectionException.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/MultipleSensitivePropertyProtectionException.java
deleted file mode 100644
index df4047feac..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/MultipleSensitivePropertyProtectionException.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.properties;
-
-import org.apache.commons.lang3.StringUtils;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-public class MultipleSensitivePropertyProtectionException extends SensitivePropertyProtectionException {
-
- private Set 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}.
- *
- *
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}.
- *
- *
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.
Note that the detail message associated with
- * {@code cause} is not automatically incorporated in
- * this throwable's detail message.
- *
- *
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).
- *
- *
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 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 failedKeys, Throwable cause) {
- super(message, cause);
- this.failedKeys = new HashSet<>(failedKeys);
- }
-
- public Set getFailedKeys() {
- return this.failedKeys;
- }
-
- @Override
- public String toString() {
- return "SensitivePropertyProtectionException for [" + StringUtils.join(this.failedKeys, ", ") + "]: " + getLocalizedMessage();
- }
-}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
index 48b90e5318..3f8ec6bb94 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
@@ -17,13 +17,14 @@
package org.apache.nifi.registry.properties;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.properties.ApplicationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Enumeration;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -31,11 +32,20 @@ import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
-public class NiFiRegistryProperties extends Properties {
+public class NiFiRegistryProperties extends ApplicationProperties {
private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryProperties.class);
+ public static final String NIFI_REGISTRY_PROPERTIES_FILE_PATH_PROPERTY = "nifi.registry.properties.file.path";
+ public static final String NIFI_REGISTRY_BOOTSTRAP_FILE_PATH_PROPERTY = "nifi.registry.bootstrap.config.file.path";
+ public static final String NIFI_REGISTRY_BOOTSTRAP_DOCS_DIR_PROPERTY = "nifi.registry.bootstrap.config.docs.dir";
+
+ public static final String RELATIVE_BOOTSTRAP_FILE_LOCATION = "conf/bootstrap.conf";
+ public static final String RELATIVE_PROPERTIES_FILE_LOCATION = "conf/nifi-registry.properties";
+ public static final String RELATIVE_DOCS_LOCATION = "docs";
+
// Keys
+ public static final String PROPERTIES_FILE_PATH = "nifi.registry.properties.file.path";
public static final String WEB_WAR_DIR = "nifi.registry.web.war.directory";
public static final String WEB_HTTP_PORT = "nifi.registry.web.http.port";
public static final String WEB_HTTP_HOST = "nifi.registry.web.http.host";
@@ -119,11 +129,15 @@ public class NiFiRegistryProperties extends Properties {
public static final String DEFAULT_SECURITY_USER_OIDC_READ_TIMEOUT = "5 secs";
public NiFiRegistryProperties() {
- super();
+ this(Collections.EMPTY_MAP);
}
- public NiFiRegistryProperties(Map props) {
- this.putAll(props);
+ public NiFiRegistryProperties(final Map props) {
+ super(props);
+ }
+
+ public NiFiRegistryProperties(final Properties props) {
+ super(props);
}
public int getWebThreads() {
@@ -297,7 +311,7 @@ public class NiFiRegistryProperties extends Properties {
public Set getExtensionsDirs() {
final Set extensionDirs = new HashSet<>();
- stringPropertyNames().stream().filter(key -> key.startsWith(EXTENSION_DIR_PREFIX)).forEach(key -> extensionDirs.add(getProperty(key)));
+ getPropertyKeys().stream().filter(key -> key.startsWith(EXTENSION_DIR_PREFIX)).forEach(key -> extensionDirs.add(getProperty(key)));
return extensionDirs;
}
@@ -305,21 +319,6 @@ public class NiFiRegistryProperties extends Properties {
return Boolean.parseBoolean(getPropertyAsTrimmedString(REVISIONS_ENABLED));
}
- /**
- * Retrieves all known property keys.
- *
- * @return all known property keys
- */
- public Set getPropertyKeys() {
- Set propertyNames = new HashSet<>();
- Enumeration e = this.propertyNames();
- for (; e.hasMoreElements(); ){
- propertyNames.add((String) e.nextElement());
- }
-
- return propertyNames;
- }
-
// Helper functions for common ways of interpreting property values
private String getPropertyAsTrimmedString(String key) {
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 5ceffd1754..be9e282db7 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
@@ -16,25 +16,33 @@
*/
package org.apache.nifi.registry.properties;
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+import org.apache.nifi.properties.SensitivePropertyProvider;
+import org.apache.nifi.properties.SensitivePropertyProviderFactory;
+import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory;
+import org.apache.nifi.registry.properties.util.NiFiRegistryBootstrapUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.crypto.Cipher;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.util.Properties;
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
- private static SensitivePropertyProviderFactory sensitivePropertyProviderFactory;
+ private SensitivePropertyProviderFactory sensitivePropertyProviderFactory;
/**
* Returns an instance of the loader configured with the key.
@@ -46,7 +54,7 @@ public class NiFiRegistryPropertiesLoader {
* @param keyHex the key used to encrypt any sensitive properties
* @return the configured loader
*/
- public static NiFiRegistryPropertiesLoader withKey(String keyHex) {
+ public static NiFiRegistryPropertiesLoader withKey(final String keyHex) {
NiFiRegistryPropertiesLoader loader = new NiFiRegistryPropertiesLoader();
loader.setKeyHex(keyHex);
return loader;
@@ -54,12 +62,12 @@ public class NiFiRegistryPropertiesLoader {
/**
* Sets the hexadecimal key used to unprotect properties encrypted with
- * {@link AESSensitivePropertyProvider}. If the key has already been set,
+ * {@link SensitivePropertyProvider}. If the key has already been set,
* calling this method will throw a {@link RuntimeException}.
*
* @param keyHex the key in hexadecimal format
*/
- public void setKeyHex(String keyHex) {
+ public void setKeyHex(final String keyHex) {
if (this.keyHex == null || this.keyHex.trim().isEmpty()) {
this.keyHex = keyHex;
} else {
@@ -67,21 +75,18 @@ public class NiFiRegistryPropertiesLoader {
}
}
- private static String getDefaultProviderKey() {
- try {
- return "aes/gcm/" + (Cipher.getMaxAllowedKeyLength("AES") > 128 ? "256" : "128");
- } catch (NoSuchAlgorithmException e) {
- return "aes/gcm/128";
+ private SensitivePropertyProviderFactory getSensitivePropertyProviderFactory() {
+ if (sensitivePropertyProviderFactory == null) {
+ sensitivePropertyProviderFactory = StandardSensitivePropertyProviderFactory
+ .withKeyAndBootstrapSupplier(keyHex, () -> {
+ try {
+ return NiFiRegistryBootstrapUtils.loadBootstrapProperties();
+ } catch (IOException e) {
+ throw new SensitivePropertyProtectionException("Could not load bootstrap.conf for sensitive property provider configuration.", e);
+ }
+ });
}
- }
-
- private void initializeSensitivePropertyProviderFactory() {
- sensitivePropertyProviderFactory = new AESSensitivePropertyProviderFactory(keyHex);
- }
-
- private SensitivePropertyProvider getSensitivePropertyProvider() {
- initializeSensitivePropertyProviderFactory();
- return sensitivePropertyProviderFactory.getProvider();
+ return sensitivePropertyProviderFactory;
}
/**
@@ -100,11 +105,12 @@ public class NiFiRegistryPropertiesLoader {
throw new IllegalArgumentException("NiFi Registry properties file missing or unreadable");
}
- final NiFiRegistryProperties rawProperties = new NiFiRegistryProperties();
+ final Properties rawProperties = new Properties();
try (final FileReader reader = new FileReader(file)) {
rawProperties.load(reader);
+ final NiFiRegistryProperties innerProperties = new NiFiRegistryProperties(rawProperties);
logger.info("Loaded {} properties from {}", rawProperties.size(), file.getAbsolutePath());
- ProtectedNiFiRegistryProperties protectedNiFiRegistryProperties = new ProtectedNiFiRegistryProperties(rawProperties);
+ ProtectedNiFiRegistryProperties protectedNiFiRegistryProperties = new ProtectedNiFiRegistryProperties(innerProperties);
return protectedNiFiRegistryProperties;
} catch (final IOException ioe) {
logger.error("Cannot load properties file due to " + ioe.getLocalizedMessage());
@@ -120,13 +126,16 @@ public class NiFiRegistryPropertiesLoader {
* @param file the File containing the serialized properties
* @return the NiFiProperties instance
*/
- public NiFiRegistryProperties load(File file) {
- ProtectedNiFiRegistryProperties protectedNiFiRegistryProperties = readProtectedPropertiesFromDisk(file);
- if (protectedNiFiRegistryProperties.hasProtectedKeys()) {
- protectedNiFiRegistryProperties.addSensitivePropertyProvider(getSensitivePropertyProvider());
+ public NiFiRegistryProperties load(final File file) {
+ final ProtectedNiFiRegistryProperties protectedNiFiProperties = readProtectedPropertiesFromDisk(file);
+ if (protectedNiFiProperties.hasProtectedKeys()) {
+ Security.addProvider(new BouncyCastleProvider());
+ getSensitivePropertyProviderFactory()
+ .getSupportedSensitivePropertyProviders()
+ .forEach(protectedNiFiProperties::addSensitivePropertyProvider);
}
- return protectedNiFiRegistryProperties.getUnprotectedProperties();
+ return protectedNiFiProperties.getUnprotectedProperties();
}
/**
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 5debc4a1be..57e1af2460 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
@@ -17,31 +17,36 @@
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;
+import org.apache.nifi.properties.SensitivePropertyProtector;
+import org.apache.nifi.properties.SensitivePropertyProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
-import java.util.stream.Collectors;
import static java.util.Arrays.asList;
/**
- * Wrapper class of {@link NiFiRegistryProperties} for intermediate phase when
- * {@link NiFiRegistryPropertiesLoader} loads the raw properties file and performs
- * unprotection activities before returning an instance of {@link NiFiRegistryProperties}.
+ * Decorator class for intermediate phase when {@link NiFiRegistryPropertiesLoader} loads the
+ * raw properties file and performs unprotection activities before returning a clean
+ * implementation of {@link NiFiRegistryProperties}.
+ * This encapsulates the sensitive property access logic from external consumers
+ * of {@code NiFiRegistryProperties}.
*/
-class ProtectedNiFiRegistryProperties {
+class ProtectedNiFiRegistryProperties extends NiFiRegistryProperties implements ProtectedProperties,
+ SensitivePropertyProtector {
private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiRegistryProperties.class);
- private NiFiRegistryProperties properties;
+ private SensitivePropertyProtector propertyProtectionDelegate;
- private Map localProviderCache = new HashMap<>();
+ private NiFiRegistryProperties applicationProperties;
// Additional "sensitive" property key
public static final String ADDITIONAL_SENSITIVE_PROPERTIES_KEY = "nifi.registry.sensitive.props.additional.keys";
@@ -53,32 +58,34 @@ class ProtectedNiFiRegistryProperties {
NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD));
public ProtectedNiFiRegistryProperties() {
- this(null);
+ this(new NiFiRegistryProperties());
}
/**
* Creates an instance containing the provided {@link NiFiRegistryProperties}.
*
- * @param props the NiFiProperties to contain
+ * @param props the NiFiRegistryProperties to contain
*/
- public ProtectedNiFiRegistryProperties(NiFiRegistryProperties props) {
- if (props == null) {
- props = new NiFiRegistryProperties();
- }
- this.properties = props;
- logger.debug("Loaded {} properties (including {} protection schemes) into ProtectedNiFiProperties",
- getPropertyKeysIncludingProtectionSchemes().size(), getProtectedPropertyKeys().size());
+ public ProtectedNiFiRegistryProperties(final NiFiRegistryProperties props) {
+ this.applicationProperties = props;
+ this.propertyProtectionDelegate = new ApplicationPropertiesProtector<>(this);
+ logger.debug("Loaded {} properties (including {} protection schemes) into ProtectedNiFiRegistryProperties", getApplicationProperties()
+ .getPropertyKeys().size(), getProtectedPropertyKeys().size());
}
- /**
- * Retrieves the property value for the given property key.
- *
- * @param key the key of property value to lookup
- * @return value of property at given key or null if not found
- */
- // @Override
- public String getProperty(String key) {
- return getInternalNiFiProperties().getProperty(key);
+ @Override
+ public String getAdditionalSensitivePropertiesKeys() {
+ return getProperty(getAdditionalSensitivePropertiesKeysName());
+ }
+
+ @Override
+ public String getAdditionalSensitivePropertiesKeysName() {
+ return ADDITIONAL_SENSITIVE_PROPERTIES_KEY;
+ }
+
+ @Override
+ public List getDefaultSensitiveProperties() {
+ return DEFAULT_SENSITIVE_PROPERTIES;
}
/**
@@ -89,18 +96,43 @@ class ProtectedNiFiRegistryProperties {
*
* @return the internal properties
*/
- NiFiRegistryProperties getInternalNiFiProperties() {
- if (this.properties == null) {
- this.properties = new NiFiRegistryProperties();
+ @Override
+ public NiFiRegistryProperties getApplicationProperties() {
+ if (this.applicationProperties == null) {
+ this.applicationProperties = new NiFiRegistryProperties();
}
- return this.properties;
+ return this.applicationProperties;
+ }
+
+ @Override
+ public NiFiRegistryProperties createApplicationProperties(final Properties rawProperties) {
+ return new NiFiRegistryProperties(rawProperties);
}
/**
- * Returns the number of properties in the NiFiRegistryProperties,
- * excluding protection scheme properties.
+ * Retrieves the property value for the given property key.
*
+ * @param key the key of property value to lookup
+ * @return value of property at given key or null if not found
+ */
+ @Override
+ public String getProperty(String key) {
+ return getApplicationProperties().getProperty(key);
+ }
+
+ /**
+ * Retrieves all known property keys.
+ *
+ * @return all known property keys
+ */
+ @Override
+ public Set getPropertyKeys() {
+ return propertyProtectionDelegate.getPropertyKeys();
+ }
+
+ /**
+ * Returns the number of properties, excluding protection scheme properties.
*
* Example:
*
@@ -112,359 +144,71 @@ class ProtectedNiFiRegistryProperties {
*
* @return the count of real properties
*/
- int size() {
- return getPropertyKeysExcludingProtectionSchemes().size();
+ @Override
+ public int size() {
+ return propertyProtectionDelegate.size();
}
- /**
- * Returns the complete set of property keys in the NiFiRegistryProperties,
- * including any protection keys (i.e. 'x.y.z.protected').
- *
- * @return the set of property keys
- */
- Set getPropertyKeysIncludingProtectionSchemes() {
- return getInternalNiFiProperties().getPropertyKeys();
+ @Override
+ public Set getPropertyKeysIncludingProtectionSchemes() {
+ return propertyProtectionDelegate.getPropertyKeysIncludingProtectionSchemes();
}
- /**
- * Returns the set of property keys in the NiFiRegistryProperties,
- * excluding any protection keys (i.e. 'x.y.z.protected').
- *
- * @return the set of property keys
- */
- Set getPropertyKeysExcludingProtectionSchemes() {
- Set filteredKeys = getPropertyKeysIncludingProtectionSchemes();
- filteredKeys.removeIf(p -> p.endsWith(".protected"));
- return filteredKeys;
- }
-
- /**
- * Splits a single string containing multiple property keys into a List.
- *
- * Delimited by ',' or ';' and ignores leading and trailing whitespace around delimiter.
- *
- * @param multipleProperties a single String containing multiple properties, i.e.
- * "nifi.registry.property.1; nifi.registry.property.2, nifi.registry.property.3"
- * @return a List containing the split and trimmed properties
- */
- private static List splitMultipleProperties(String multipleProperties) {
- if (multipleProperties == null || multipleProperties.trim().isEmpty()) {
- return new ArrayList<>(0);
- } else {
- List properties = new ArrayList<>(asList(multipleProperties.split("\\s*[,;]\\s*")));
- for (int i = 0; i < properties.size(); i++) {
- properties.set(i, properties.get(i).trim());
- }
- return properties;
- }
- }
-
- /**
- * Returns a list of the keys identifying "sensitive" properties.
- *
- * There is a default list, and additional keys can be provided in the
- * {@code nifi.registry.sensitive.props.additional.keys} property in {@code nifi-registry.properties}.
- *
- * @return the list of sensitive property keys
- */
+ @Override
public List getSensitivePropertyKeys() {
- String additionalPropertiesString = getProperty(ADDITIONAL_SENSITIVE_PROPERTIES_KEY);
- if (additionalPropertiesString == null || additionalPropertiesString.trim().isEmpty()) {
- return DEFAULT_SENSITIVE_PROPERTIES;
- } else {
- List additionalProperties = splitMultipleProperties(additionalPropertiesString);
- /* Remove this key if it was accidentally provided as a sensitive key
- * because we cannot protect it and read from it
- */
- if (additionalProperties.contains(ADDITIONAL_SENSITIVE_PROPERTIES_KEY)) {
- logger.warn("The key '{}' contains itself. This is poor practice and should be removed", ADDITIONAL_SENSITIVE_PROPERTIES_KEY);
- additionalProperties.remove(ADDITIONAL_SENSITIVE_PROPERTIES_KEY);
- }
- additionalProperties.addAll(DEFAULT_SENSITIVE_PROPERTIES);
- return additionalProperties;
- }
+ return propertyProtectionDelegate.getSensitivePropertyKeys();
}
- /**
- * Returns a list of the keys identifying "sensitive" properties. There is a default list,
- * and additional keys can be provided in the {@code nifi.sensitive.props.additional.keys} property in {@code nifi.properties}.
- *
- * @return the list of sensitive property keys
- */
+ @Override
public List getPopulatedSensitivePropertyKeys() {
- List allSensitiveKeys = getSensitivePropertyKeys();
- return allSensitiveKeys.stream().filter(k -> StringUtils.isNotBlank(getProperty(k))).collect(Collectors.toList());
+ return propertyProtectionDelegate.getPopulatedSensitivePropertyKeys();
}
- /**
- * Returns true if any sensitive keys are protected.
- *
- * @return true if any key is protected; false otherwise
- */
+ @Override
public boolean hasProtectedKeys() {
- List sensitiveKeys = getSensitivePropertyKeys();
- for (String k : sensitiveKeys) {
- if (isPropertyProtected(k)) {
- return true;
- }
- }
- return false;
+ return propertyProtectionDelegate.hasProtectedKeys();
}
- /**
- * 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.
- *
- * @return the Map of protected property keys and the protection identifier for each
- */
+ @Override
public Map getProtectedPropertyKeys() {
- List sensitiveKeys = getSensitivePropertyKeys();
-
- Map traditionalProtectedProperties = new HashMap<>();
- for (String key : sensitiveKeys) {
- String protection = getProperty(getProtectionKey(key));
- if (StringUtils.isNotBlank(protection) && StringUtils.isNotBlank(getProperty(key))) {
- traditionalProtectedProperties.put(key, protection);
- }
- }
-
- return traditionalProtectedProperties;
+ return propertyProtectionDelegate.getProtectedPropertyKeys();
}
- /**
- * Returns the unique set of all protection schemes currently in use for this instance.
- *
- * @return the set of protection schemes
- */
+ @Override
public Set getProtectionSchemes() {
- return new HashSet<>(getProtectedPropertyKeys().values());
+ return propertyProtectionDelegate.getProtectionSchemes();
}
- /**
- * Returns a percentage of the total number of populated properties marked as sensitive that are currently protected.
- *
- * @return the percent of sensitive properties marked as protected
- */
- public int getPercentOfSensitivePropertiesProtected() {
- return (int) Math.round(getProtectedPropertyKeys().size() / ((double) getPopulatedSensitivePropertyKeys().size()) * 100);
+ @Override
+ public boolean isPropertySensitive(final String key) {
+ return propertyProtectionDelegate.isPropertySensitive(key);
}
- /**
- * Returns true if the property identified by this key is considered sensitive in this instance of {@code NiFiProperties}.
- * Some properties are sensitive by default, while others can be specified by
- * {@link ProtectedNiFiRegistryProperties#ADDITIONAL_SENSITIVE_PROPERTIES_KEY}.
- *
- * @param key the key
- * @return true if it is sensitive
- * @see ProtectedNiFiRegistryProperties#getSensitivePropertyKeys()
- */
- public boolean isPropertySensitive(String key) {
- // If the explicit check for ADDITIONAL_SENSITIVE_PROPERTIES_KEY is not here, this could loop infinitely
- return key != null && !key.equals(ADDITIONAL_SENSITIVE_PROPERTIES_KEY) && getSensitivePropertyKeys().contains(key.trim());
+ @Override
+ public boolean isPropertyProtected(final String key) {
+ return propertyProtectionDelegate.isPropertyProtected(key);
}
- /**
- * Returns true if the property identified by this key is considered protected in this instance of {@code NiFiProperties}.
- * The property value is protected if the key is sensitive and the sibling key of key.protected is present.
- *
- * @param key the key
- * @return true if it is currently marked as protected
- * @see ProtectedNiFiRegistryProperties#getSensitivePropertyKeys()
- */
- public boolean isPropertyProtected(String key) {
- return key != null && isPropertySensitive(key) && !StringUtils.isBlank(getProperty(getProtectionKey(key)));
- }
-
- /**
- * Returns the sibling property key which specifies the protection scheme for this key.
- *
- * nifi.registry.sensitive.key -> nifi.sensitive.key.protected
- *
- * @param key the key identifying the sensitive property
- * @return the key identifying the protection scheme for the sensitive property
- */
- public static String getProtectionKey(String key) {
- if (key == null || key.isEmpty()) {
- throw new IllegalArgumentException("Cannot find protection key for null key");
- }
-
- return key + ".protected";
- }
-
- /**
- * Returns the unprotected {@link NiFiRegistryProperties} instance. If none of the
- * properties loaded are marked as protected, it will simply pass through the
- * internal instance. If any are protected, it will drop the protection scheme keys
- * and translate each protected value (encrypted, HSM-retrieved, etc.) into the raw
- * value and store it under the original key.
- *
- * If any property fails to unprotect, it will save that key and continue. After
- * attempting all properties, it will throw an exception containing all failed
- * properties. This is necessary because the order is not enforced, so all failed
- * properties should be gathered together.
- *
- * @return the NiFiRegistryProperties instance with all raw values
- * @throws SensitivePropertyProtectionException if there is a problem unprotecting one or more keys
- */
+ @Override
public NiFiRegistryProperties getUnprotectedProperties() throws SensitivePropertyProtectionException {
- if (hasProtectedKeys()) {
- logger.debug("There are {} protected properties of {} sensitive properties ({}%)",
- getProtectedPropertyKeys().size(),
- getPopulatedSensitivePropertyKeys().size(),
- getPercentOfSensitivePropertiesProtected());
-
- NiFiRegistryProperties unprotectedProperties = new NiFiRegistryProperties();
-
- Set failedKeys = new HashSet<>();
-
- for (String key : getPropertyKeysExcludingProtectionSchemes()) {
- /* Three kinds of keys
- * 1. protection schemes -- skip
- * 2. protected keys -- unprotect and copy
- * 3. normal keys -- copy over
- */
- if (key.endsWith(".protected")) {
- // Do nothing
- } else if (isPropertyProtected(key)) {
- try {
- unprotectedProperties.setProperty(key, unprotectValue(key, getProperty(key)));
- } catch (SensitivePropertyProtectionException e) {
- logger.warn("Failed to unprotect '{}'", key, e);
- failedKeys.add(key);
- }
- } else {
- unprotectedProperties.setProperty(key, getProperty(key));
- }
- }
-
- 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());
- }
- }
-
- return unprotectedProperties;
- } else {
- logger.debug("No protected properties");
- return getInternalNiFiProperties();
- }
+ return propertyProtectionDelegate.getUnprotectedProperties();
}
- /**
- * Registers a new {@link SensitivePropertyProvider}. This method will throw a {@link UnsupportedOperationException} if a provider is already registered for the protection scheme.
- *
- * @param sensitivePropertyProvider the provider
- */
- void addSensitivePropertyProvider(SensitivePropertyProvider sensitivePropertyProvider) {
- if (sensitivePropertyProvider == null) {
- throw new IllegalArgumentException("Cannot add null SensitivePropertyProvider");
- }
-
- if (getSensitivePropertyProviders().containsKey(sensitivePropertyProvider.getIdentifierKey())) {
- throw new UnsupportedOperationException("Cannot overwrite existing sensitive property provider registered for " + sensitivePropertyProvider.getIdentifierKey());
- }
-
- getSensitivePropertyProviders().put(sensitivePropertyProvider.getIdentifierKey(), sensitivePropertyProvider);
+ @Override
+ public void addSensitivePropertyProvider(final SensitivePropertyProvider sensitivePropertyProvider) {
+ propertyProtectionDelegate.addSensitivePropertyProvider(sensitivePropertyProvider);
}
- private String getDefaultProtectionScheme() {
- if (!getSensitivePropertyProviders().isEmpty()) {
- List schemes = new ArrayList<>(getSensitivePropertyProviders().keySet());
- Collections.sort(schemes);
- return schemes.get(0);
- } else {
- throw new IllegalStateException("No registered protection schemes");
- }
- }
-
- /**
- * Returns a new instance of {@link NiFiRegistryProperties} with all populated sensitive values protected by the default protection scheme.
- *
- * Plain non-sensitive values are copied directly.
- *
- * @return the protected properties in a {@link NiFiRegistryProperties} object
- * @throws IllegalStateException if no protection schemes are registered
- */
- NiFiRegistryProperties protectPlainProperties() {
- try {
- return protectPlainProperties(getDefaultProtectionScheme());
- } catch (IllegalStateException e) {
- final String msg = "Cannot protect properties with default scheme if no protection schemes are registered";
- logger.warn(msg);
- throw new IllegalStateException(msg, e);
- }
- }
-
- /**
- * Returns a new instance of {@link NiFiRegistryProperties} with all populated sensitive values protected by the provided protection scheme.
- *
- * Plain non-sensitive values are copied directly.
- *
- * @param protectionScheme the identifier key of the {@link SensitivePropertyProvider} to use
- * @return the protected properties in a {@link NiFiRegistryProperties} object
- */
- NiFiRegistryProperties protectPlainProperties(String protectionScheme) {
- SensitivePropertyProvider spp = getSensitivePropertyProvider(protectionScheme);
-
- NiFiRegistryProperties protectedProperties = new NiFiRegistryProperties();
-
- // Copy over the plain keys
- Set plainKeys = getPropertyKeysExcludingProtectionSchemes();
- plainKeys.removeAll(getSensitivePropertyKeys());
- for (String key : plainKeys) {
- protectedProperties.setProperty(key, getInternalNiFiProperties().getProperty(key));
- }
-
- // Add the protected keys and the protection schemes
- for (String key : getSensitivePropertyKeys()) {
- final String plainValue = getProperty(key);
- if (plainValue != null && !plainValue.trim().isEmpty()) {
- final String protectedValue = spp.protect(plainValue);
- protectedProperties.setProperty(key, protectedValue);
- protectedProperties.setProperty(getProtectionKey(key), protectionScheme);
- }
- }
-
- return protectedProperties;
- }
-
- /**
- * Returns the number of properties that are marked as protected in the provided {@link NiFiRegistryProperties} instance
- * without requiring external creation of a {@link ProtectedNiFiRegistryProperties} instance.
- *
- * @param plainProperties the instance to count protected properties
- * @return the number of protected properties
- */
- public static int countProtectedProperties(NiFiRegistryProperties plainProperties) {
- return new ProtectedNiFiRegistryProperties(plainProperties).getProtectedPropertyKeys().size();
- }
-
- /**
- * Returns the number of properties that are marked as sensitive in the provided {@link NiFiRegistryProperties} instance
- * without requiring external creation of a {@link ProtectedNiFiRegistryProperties} instance.
- *
- * @param plainProperties the instance to count sensitive properties
- * @return the number of sensitive properties
- */
- public static int countSensitiveProperties(NiFiRegistryProperties plainProperties) {
- return new ProtectedNiFiRegistryProperties(plainProperties).getSensitivePropertyKeys().size();
+ @Override
+ public Map getSensitivePropertyProviders() {
+ return propertyProtectionDelegate.getSensitivePropertyProviders();
}
@Override
public String toString() {
final Set providers = getSensitivePropertyProviders().keySet();
- return new StringBuilder("ProtectedNiFiProperties instance with ")
- .append(getPropertyKeysIncludingProtectionSchemes().size())
- .append(" properties (")
+ return new StringBuilder("ProtectedNiFiRegistryProperties instance with ")
+ .append(size()).append(" properties (")
.append(getProtectedPropertyKeys().size())
.append(" protected) and ")
.append(providers.size())
@@ -472,57 +216,4 @@ class ProtectedNiFiRegistryProperties {
.append(StringUtils.join(providers, ", "))
.toString();
}
-
- /**
- * Returns the local provider cache (null-safe) as a Map of protection schemes -> implementations.
- *
- * @return the map
- */
- private Map getSensitivePropertyProviders() {
- if (localProviderCache == null) {
- localProviderCache = new HashMap<>();
- }
-
- return localProviderCache;
- }
-
- private SensitivePropertyProvider getSensitivePropertyProvider(String protectionScheme) {
- if (isProviderAvailable(protectionScheme)) {
- return getSensitivePropertyProviders().get(protectionScheme);
- } else {
- throw new SensitivePropertyProtectionException("No provider available for " + protectionScheme);
- }
- }
-
- private boolean isProviderAvailable(String protectionScheme) {
- return getSensitivePropertyProviders().containsKey(protectionScheme);
- }
-
- /**
- * If the value is protected, unprotects it and returns it. If not, returns the original value.
- *
- * @param key the retrieved property key
- * @param retrievedValue the retrieved property value
- * @return the unprotected value
- */
- private String unprotectValue(String key, 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, so just return the value
- if (!isProviderAvailable(protectionScheme)) {
- logger.warn("No provider available for {} so passing the protected {} value back", protectionScheme, key);
- return retrievedValue;
- }
-
- try {
- SensitivePropertyProvider sensitivePropertyProvider = getSensitivePropertyProvider(protectionScheme);
- return sensitivePropertyProvider.unprotect(retrievedValue);
- } catch (SensitivePropertyProtectionException e) {
- throw new SensitivePropertyProtectionException("Error unprotecting value for " + key, e.getCause());
- }
- }
- return retrievedValue;
- }
}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProtectionException.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProtectionException.java
deleted file mode 100644
index 2ffa9022f8..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProtectionException.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.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}.
- *
- *
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}.
- *
- *
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.
Note that the detail message associated with
- * {@code cause} is not automatically incorporated in
- * this throwable's detail message.
- *
- *
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.)
- */
- 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).
- *
- *
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.)
- */
- public SensitivePropertyProtectionException(Throwable cause) {
- super(cause);
- }
-
- @Override
- public String toString() {
- return "SensitivePropertyProtectionException: " + getLocalizedMessage();
- }
-}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProvider.java
deleted file mode 100644
index c0dd43c6b9..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/SensitivePropertyProvider.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.properties;
-
-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}.
- *
- * @return the key to persist in the sibling property
- */
- String getIdentifierKey();
-
- /**
- * Returns the "protected" form of this value. This is a form which can safely be persisted in the {@code nifi.properties} file without compromising the value.
- * An encryption-based provider would return a cipher text, while a remote-lookup provider could return a unique ID to retrieve the secured value.
- *
- * @param unprotectedValue the sensitive value
- * @return the value to persist in the {@code nifi.properties} file
- */
- String protect(String unprotectedValue) throws SensitivePropertyProtectionException;
-
- /**
- * Returns the "unprotected" form of this value. This is the raw sensitive value which is used by the application logic.
- * An encryption-based provider would decrypt a cipher text and return the plaintext, while a remote-lookup provider could retrieve the secured value.
- *
- * @param protectedValue the protected value read from the {@code nifi.properties} file
- * @return the raw value to be used by the application
- */
- String unprotect(String protectedValue) throws SensitivePropertyProtectionException;
-}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapPropertiesLoader.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapPropertiesLoader.java
new file mode 100644
index 0000000000..1a8be9c012
--- /dev/null
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapPropertiesLoader.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.properties.util;
+
+import org.apache.nifi.properties.AbstractBootstrapPropertiesLoader;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+
+public class NiFiRegistryBootstrapPropertiesLoader extends AbstractBootstrapPropertiesLoader {
+ @Override
+ protected String getApplicationPrefix() {
+ return "nifi.registry";
+ }
+
+ @Override
+ protected String getApplicationPropertiesFilename() {
+ return "nifi-registry.properties";
+ }
+
+ @Override
+ protected String getApplicationPropertiesFilePathSystemProperty() {
+ return NiFiRegistryProperties.PROPERTIES_FILE_PATH;
+ }
+}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapUtils.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapUtils.java
new file mode 100644
index 0000000000..8494915ef2
--- /dev/null
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.properties.util;
+
+import org.apache.nifi.properties.AbstractBootstrapPropertiesLoader;
+import org.apache.nifi.properties.BootstrapProperties;
+
+import java.io.IOException;
+
+/**
+ * Encapsulates utility methods for dealing with bootstrap.conf or nifi-registry.properties.
+ */
+public class NiFiRegistryBootstrapUtils {
+ private static final AbstractBootstrapPropertiesLoader BOOTSTRAP_PROPERTIES_LOADER = new NiFiRegistryBootstrapPropertiesLoader();
+
+ /**
+ * Returns the key (if any) used to encrypt sensitive properties, extracted from
+ * {@code $NIFI_REGISTRY_HOME/conf/bootstrap.conf}.
+ *
+ * @return the key in hexadecimal format
+ * @throws IOException if the file is not readable
+ */
+ public static String extractKeyFromBootstrapFile() throws IOException {
+ return BOOTSTRAP_PROPERTIES_LOADER.extractKeyFromBootstrapFile();
+ }
+
+ /**
+ * Loads the default bootstrap.conf file into a BootstrapProperties object.
+ * @return The default bootstrap.conf as a BootstrapProperties object
+ * @throws IOException If the file is not readable
+ */
+ public static BootstrapProperties loadBootstrapProperties() throws IOException {
+ return loadBootstrapProperties(null);
+ }
+
+ /**
+ * Loads the bootstrap.conf file into a BootstrapProperties object.
+ * @param bootstrapPath the path to the bootstrap file
+ * @return The bootstrap.conf as a BootstrapProperties object
+ * @throws IOException If the file is not readable
+ */
+ public static BootstrapProperties loadBootstrapProperties(final String bootstrapPath) throws IOException {
+ return BOOTSTRAP_PROPERTIES_LOADER.loadBootstrapProperties(bootstrapPath);
+ }
+
+ /**
+ * Returns the key (if any) used to encrypt sensitive properties, extracted from
+ * {@code $NIFI_REGISTRY_HOME/conf/bootstrap.conf}.
+ *
+ * @param bootstrapPath the path to the bootstrap file (if null, returns the sensitive key
+ * found in $NIFI_REGISTRY_HOME/conf/bootstrap.conf)
+ * @return the key in hexadecimal format
+ * @throws IOException if the file is not readable
+ */
+ public static String extractKeyFromBootstrapFile(final String bootstrapPath) throws IOException {
+ return BOOTSTRAP_PROPERTIES_LOADER.extractKeyFromBootstrapFile(bootstrapPath);
+ }
+
+ /**
+ * Returns the default file path to {@code $NIFI_REGISTRY_HOME/conf/nifi-registry.properties}. If the system
+ * property nifi-registry.properties.file.path is not set, it will be set to the relative conf/nifi-registry.properties
+ *
+ * @return the path to the nifi-registry.properties file
+ */
+ public static String getDefaultApplicationPropertiesFilePath() {
+ return BOOTSTRAP_PROPERTIES_LOADER.getDefaultApplicationPropertiesFilePath();
+ }
+}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/security/crypto/BootstrapFileCryptoKeyProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/security/crypto/BootstrapFileCryptoKeyProvider.java
index 191b5e28bf..c277a9c9b1 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/security/crypto/BootstrapFileCryptoKeyProvider.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/security/crypto/BootstrapFileCryptoKeyProvider.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.registry.security.crypto;
+import org.apache.nifi.registry.properties.util.NiFiRegistryBootstrapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -62,7 +63,7 @@ public class BootstrapFileCryptoKeyProvider implements CryptoKeyProvider {
@Override
public String getKey() throws MissingCryptoKeyException {
try {
- return CryptoKeyLoader.extractKeyFromBootstrapFile(this.bootstrapFile);
+ return NiFiRegistryBootstrapUtils.extractKeyFromBootstrapFile(this.bootstrapFile);
} catch (IOException ioe) {
final String errMsg = "Loading the master crypto key from bootstrap file '" + bootstrapFile + "' failed due to IOException.";
logger.warn(errMsg);
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/security/crypto/CryptoKeyLoader.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/security/crypto/CryptoKeyLoader.java
deleted file mode 100644
index d828773f34..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/security/crypto/CryptoKeyLoader.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.security.crypto;
-
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-public class CryptoKeyLoader {
-
- private static final Logger logger = LoggerFactory.getLogger(CryptoKeyLoader.class);
-
- private static final String BOOTSTRAP_KEY_PREFIX = "nifi.registry.bootstrap.sensitive.key=";
-
- /**
- * Returns the key (if any) used to encrypt sensitive properties.
- * The key extracted from the bootstrap.conf file at the specified location.
- *
- * @param bootstrapPath the path to the bootstrap file
- * @return the key in hexadecimal format, or {@link CryptoKeyProvider#EMPTY_KEY} if the key is null or empty
- * @throws IOException if the file is not readable
- */
- public static String extractKeyFromBootstrapFile(String bootstrapPath) throws IOException {
- File bootstrapFile;
- if (StringUtils.isBlank(bootstrapPath)) {
- logger.error("Cannot read from bootstrap.conf file to extract encryption key; location not specified");
- throw new IOException("Cannot read from bootstrap.conf without file location");
- } else {
- bootstrapFile = new File(bootstrapPath);
- }
-
- String keyValue;
- if (bootstrapFile.exists() && bootstrapFile.canRead()) {
- try (Stream stream = Files.lines(Paths.get(bootstrapFile.getAbsolutePath()))) {
- Optional keyLine = stream.filter(l -> l.startsWith(BOOTSTRAP_KEY_PREFIX)).findFirst();
- if (keyLine.isPresent()) {
- keyValue = keyLine.get().split("=", 2)[1];
- keyValue = checkHexKey(keyValue);
- } else {
- keyValue = CryptoKeyProvider.EMPTY_KEY;
- }
- } catch (IOException e) {
- logger.error("Cannot read from bootstrap.conf file at {} to extract encryption key", bootstrapFile.getAbsolutePath());
- throw new IOException("Cannot read from bootstrap.conf", e);
- }
- } else {
- logger.error("Cannot read from bootstrap.conf file at {} to extract encryption key -- file is missing or permissions are incorrect", bootstrapFile.getAbsolutePath());
- throw new IOException("Cannot read from bootstrap.conf");
- }
-
- if (CryptoKeyProvider.EMPTY_KEY.equals(keyValue)) {
- logger.info("No encryption key present in the bootstrap.conf file at {}", bootstrapFile.getAbsolutePath());
- }
-
- return keyValue;
- }
-
- private static String checkHexKey(String input) {
- if (input == null || input.trim().isEmpty()) {
- logger.debug("Checking the hex key value that was loaded determined the key is empty.");
- return CryptoKeyProvider.EMPTY_KEY;
- }
- return input;
- }
-
-}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/AESSensitivePropertyProviderFactoryTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/AESSensitivePropertyProviderFactoryTest.groovy
deleted file mode 100644
index 0d1d5e29be..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/AESSensitivePropertyProviderFactoryTest.groovy
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.properties
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.*
-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 AESSensitivePropertyProviderFactoryTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProviderFactoryTest.class)
-
- private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
- private static final String KEY_HEX_256 = KEY_HEX_128 * 2
-
- @BeforeClass
- public static void setUpOnce() throws Exception {
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @Before
- public void setUp() throws Exception {
- }
-
- @After
- public void tearDown() throws Exception {
- }
-
- @Test
- public void testShouldGetProviderWithKey() throws Exception {
- // Arrange
- SensitivePropertyProviderFactory factory = new AESSensitivePropertyProviderFactory(KEY_HEX_128)
-
- // Act
- SensitivePropertyProvider provider = factory.getProvider()
-
- // Assert
- assert provider instanceof AESSensitivePropertyProvider
- assert provider.@key
- assert provider.@cipher
- }
-
- @Test
- public void testShouldGetProviderWith256BitKey() throws Exception {
- // Arrange
- Assume.assumeTrue("JCE unlimited strength crypto policy must be installed for this test", Cipher.getMaxAllowedKeyLength("AES") > 128)
- SensitivePropertyProviderFactory factory = new AESSensitivePropertyProviderFactory(KEY_HEX_256)
-
- // Act
- SensitivePropertyProvider provider = factory.getProvider()
-
- // Assert
- assert provider instanceof AESSensitivePropertyProvider
- assert provider.@key
- assert provider.@cipher
- }
-}
\ No newline at end of file
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/AESSensitivePropertyProviderTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/AESSensitivePropertyProviderTest.groovy
deleted file mode 100644
index bad659feeb..0000000000
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/AESSensitivePropertyProviderTest.groovy
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.registry.properties
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.util.encoders.Hex
-import org.junit.*
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.crypto.Cipher
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import java.nio.charset.StandardCharsets
-import java.security.SecureRandom
-import java.security.Security
-
-@RunWith(JUnit4.class)
-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 KEY_SIZES = getAvailableKeySizes()
-
- private static final SecureRandom secureRandom = new SecureRandom()
-
- private static final Base64.Encoder encoder = Base64.encoder
- private static final Base64.Decoder decoder = Base64.decoder
-
- @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 {
-
- }
-
- 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.. 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.. 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)]
- }
- 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 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 plaintexts = decryptionCiphers.collectEntries { Map.Entry 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 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)
- }
- 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 encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
- byte[] iv = new byte[IV_LENGTH]
- secureRandom.nextBytes(iv)
- [(keySize): getCipher(true, keySize, iv)]
- }
-
- Map CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry 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 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)]
- }
- 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 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)
- }
- 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 encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
- byte[] iv = new byte[IV_LENGTH]
- secureRandom.nextBytes(iv)
- [(keySize): getCipher(true, keySize, iv)]
- }
-
- Map CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry 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 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")]
- }
- 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)
- // 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)
- }
- 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)
-
- // 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)
- }
- 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)
- }
- 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 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)
- }
- 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)
- // 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)
- }
- 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", "nififtw!"]
-
- 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)
- 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) }
- }
-
- /**
- * This test is to ensure external compatibility in case someone encodes the encrypted value with Base64 and does not remove the padding
- */
- @Test
- void testShouldDecryptPaddedValueWith256BitKey() {
- // Arrange
- Assume.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)
- logger.info("Decrypted ${cipherText} to ${rawValue}")
- String rawUnpaddedValue = spp.unprotect(unpaddedCipherText)
- logger.info("Decrypted ${unpaddedCipherText} to ${rawUnpaddedValue}")
-
- // Assert
- assert rawValue == EXPECTED_VALUE
- assert rawUnpaddedValue == EXPECTED_VALUE
- }
-}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesGroovyTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesGroovyTest.groovy
index c7a0a4d388..92480d0fd7 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesGroovyTest.groovy
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesGroovyTest.groovy
@@ -52,8 +52,8 @@ class NiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
try {
filePath = NiFiRegistryPropertiesGroovyTest.class.getResource(propertiesFilePath).toURI().getPath()
} catch (URISyntaxException ex) {
- throw new RuntimeException("Cannot load properties file due to "
- + ex.getLocalizedMessage(), ex)
+ throw new RuntimeException("Cannot load properties file due to " +
+ ex.getLocalizedMessage(), ex)
}
NiFiRegistryProperties properties = new NiFiRegistryProperties()
@@ -66,8 +66,8 @@ class NiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
return properties
} 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)
+ throw new RuntimeException("Cannot load properties file due to " +
+ ex.getLocalizedMessage(), ex)
}
}
@@ -106,8 +106,9 @@ class NiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
// Arrange
// Act
- NiFiRegistryProperties properties = new NiFiRegistryProperties()
- properties.setProperty("key", "value")
+ Properties props = new Properties()
+ props.setProperty("key", "value")
+ NiFiRegistryProperties properties = new NiFiRegistryProperties(props)
logger.info("niFiProperties has ${properties.size()} properties: ${properties.getPropertyKeys()}")
NiFiRegistryProperties emptyProperties = new NiFiRegistryProperties()
logger.info("emptyProperties has ${emptyProperties.size()} properties: ${emptyProperties.getPropertyKeys()}")
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoaderGroovyTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoaderGroovyTest.groovy
index 58c8087d5b..5fe7eed017 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoaderGroovyTest.groovy
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/NiFiRegistryPropertiesLoaderGroovyTest.groovy
@@ -17,7 +17,6 @@
package org.apache.nifi.registry.properties
import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
@@ -46,7 +45,7 @@ class NiFiRegistryPropertiesLoaderGroovyTest extends GroovyTestCase {
private static final String PASSWORD_KEY_HEX_128 = "2C576A9585DB862F5ECBEE5B4FFFCCA1"
@BeforeClass
- public static void setUpOnce() throws Exception {
+ static void setUpOnce() throws Exception {
Security.addProvider(new BouncyCastleProvider())
logger.metaClass.methodMissing = { String name, args ->
@@ -55,21 +54,15 @@ class NiFiRegistryPropertiesLoaderGroovyTest extends GroovyTestCase {
}
@Before
- public void setUp() throws Exception {
- }
-
- @After
- public void tearDown() throws Exception {
- // Clear the sensitive property providers between runs
- NiFiRegistryPropertiesLoader.@sensitivePropertyProviderFactory = null
+ void setUp() throws Exception {
}
@AfterClass
- public static void tearDownOnce() {
+ static void tearDownOnce() {
}
@Test
- public void testConstructorShouldCreateNewInstance() throws Exception {
+ void testConstructorShouldCreateNewInstance() throws Exception {
// Arrange
// Act
@@ -103,31 +96,6 @@ class NiFiRegistryPropertiesLoaderGroovyTest extends GroovyTestCase {
assert !propertiesLoader2.@keyHex
}
- @Test
- public void testShouldGetDefaultProviderKey() throws Exception {
- // Arrange
- final String expectedProviderKey = "aes/gcm/${Cipher.getMaxAllowedKeyLength("AES") > 128 ? 256 : 128}"
- logger.info("Expected provider key: ${expectedProviderKey}")
-
- // Act
- String defaultKey = NiFiRegistryPropertiesLoader.getDefaultProviderKey()
- logger.info("Default key: ${defaultKey}")
- // Assert
- assert defaultKey == expectedProviderKey
- }
-
- @Test
- public void testShouldInitializeSensitivePropertyProviderFactory() throws Exception {
- // Arrange
- NiFiRegistryPropertiesLoader propertiesLoader = new NiFiRegistryPropertiesLoader()
-
- // Act
- propertiesLoader.initializeSensitivePropertyProviderFactory()
-
- // Assert
- assert propertiesLoader.@sensitivePropertyProviderFactory
- }
-
@Test
public void testShouldLoadUnprotectedPropertiesFromFile() throws Exception {
// Arrange
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiPropertiesGroovyTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
similarity index 87%
rename from nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiPropertiesGroovyTest.groovy
rename to nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
index 86c7fb4021..b1bb526487 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiPropertiesGroovyTest.groovy
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/ProtectedNiFiRegistryPropertiesGroovyTest.groovy
@@ -16,6 +16,12 @@
*/
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.SensitivePropertyProtectionException
+import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.After
import org.junit.AfterClass
@@ -32,8 +38,8 @@ import javax.crypto.Cipher
import java.security.Security
@RunWith(JUnit4.class)
-class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiPropertiesGroovyTest.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
@@ -83,39 +89,40 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
throw new IllegalArgumentException("NiFi Registry properties file missing or unreadable")
}
- NiFiRegistryProperties properties = new NiFiRegistryProperties()
FileReader reader = new FileReader(file)
try {
- properties.load(reader)
+ 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(new AESSensitivePropertyProvider(keyHex))
+ protectedNiFiProperties.addSensitivePropertyProvider(StandardSensitivePropertyProviderFactory.withKey(keyHex)
+ .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)
+ throw new RuntimeException("Cannot load properties file due to " +
+ ex.getLocalizedMessage(), ex)
}
}
private static File fileForResource(String resourcePath) {
String filePath
try {
- URL resourceURL = ProtectedNiFiPropertiesGroovyTest.class.getResource(resourcePath)
+ URL resourceURL = ProtectedNiFiRegistryPropertiesGroovyTest.class.getResource(resourcePath)
if (!resourceURL) {
throw new RuntimeException("File ${resourcePath} not found in class resources, cannot load.")
}
filePath = resourceURL.toURI().getPath()
} catch (URISyntaxException ex) {
- throw new RuntimeException("Cannot load resource file due to "
- + ex.getLocalizedMessage(), ex)
+ throw new RuntimeException("Cannot load resource file due to " + ex.getLocalizedMessage(), ex)
}
File file = new File(filePath)
return file
@@ -287,16 +294,18 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
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 treat it as raw
+ // 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
- NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+ def msg = shouldFail(IllegalStateException) {
+ NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
+ String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
+ logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+ }
// Assert
- assert retrievedKeystorePassword == expectedKeystorePassword
+ assert msg == "No provider available for nifi.registry.security.keyPasswd"
assert isSensitive
assert isProtected
}
@@ -340,7 +349,8 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
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 = new AESSensitivePropertyProvider(KEY_HEX_128)
+ SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX_128)
+ .getProvider(PropertyProtectionScheme.AES_GCM)
Set malformedKeys = properties.getProtectedPropertyKeys()
.findAll { String key, String scheme -> scheme == spp.identifierKey }
.keySet()
@@ -384,19 +394,21 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
logger.info("Read raw value from properties: ${expectedKeystorePassword}")
// Overwrite the internal cache
- properties.localProviderCache = [:]
+ 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
- NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
- String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
- logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+ def msg = shouldFail(IllegalStateException) {
+ NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
+ String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
+ logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
+ }
// Assert
- assert retrievedKeystorePassword == expectedKeystorePassword
+ assert msg == "No provider available for nifi.registry.security.keyPasswd"
assert isSensitive
assert isProtected
}
@@ -455,13 +467,17 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
// Act
- double percentProtected = properties.getPercentOfSensitivePropertiesProtected()
+ double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
// Assert
assert percentProtected == 0.0
}
+ private static double getPercentOfSensitivePropertiesProtected(final ProtectedNiFiRegistryProperties properties) {
+ return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100);
+ }
+
@Test
void testShouldGetPercentageOfSensitivePropertiesProtected_75() throws Exception {
// Arrange
@@ -472,7 +488,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
// Act
- double percentProtected = properties.getPercentOfSensitivePropertiesProtected()
+ double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
// Assert
@@ -489,7 +505,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
// Act
- double percentProtected = properties.getPercentOfSensitivePropertiesProtected()
+ double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
// Assert
@@ -500,13 +516,13 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
void testInstanceWithNoProtectedPropertiesShouldNotLoadSPP() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
- assert properties.@localProviderCache?.isEmpty()
+ assert properties.getSensitivePropertyProviders().isEmpty()
logger.info("Has protected properties: ${properties.hasProtectedKeys()}")
assert !properties.hasProtectedKeys()
// Act
- Map localCache = properties.@localProviderCache
+ Map localCache = properties.getSensitivePropertyProviders()
logger.info("Internal cache ${localCache} has ${localCache.size()} providers loaded")
// Assert
@@ -537,10 +553,10 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
void testShouldNotAddNullSensitivePropertyProvider() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
- assert properties.@localProviderCache?.isEmpty()
+ assert properties.getSensitivePropertyProviders().isEmpty()
// Act
- def msg = shouldFail(IllegalArgumentException) {
+ def msg = shouldFail(NullPointerException) {
properties.addSensitivePropertyProvider(null)
}
logger.info(msg)
@@ -589,7 +605,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
ProtectedNiFiRegistryProperties protectedNiFiProperties = loadFromResourceFile("/conf/nifi-registry.properties")
logger.info("Loaded ${protectedNiFiProperties.size()} properties from conf/nifi.properties")
- int hashCode = protectedNiFiProperties.internalNiFiProperties.hashCode()
+ int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
logger.info("Hash code of internal instance: ${hashCode}")
// Act
@@ -599,7 +615,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
// Assert
assert unprotectedNiFiProperties.size() == protectedNiFiProperties.size()
assert unprotectedNiFiProperties.getPropertyKeys().every {
- !unprotectedNiFiProperties.getProperty(it).endsWith(".protected")
+ !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() == hashCode
@@ -614,7 +630,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
int protectionSchemeCount = protectedNiFiProperties
.getPropertyKeysIncludingProtectionSchemes()
- .findAll { it.endsWith(".protected") }
+ .findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
.size()
int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
@@ -630,7 +646,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
- int hashCode = protectedNiFiProperties.internalNiFiProperties.hashCode()
+ int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
logger.info("Hash code of internal instance: ${hashCode}")
// Act
@@ -640,7 +656,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
// Assert
assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
assert unprotectedNiFiProperties.getPropertyKeys().every {
- !unprotectedNiFiProperties.getProperty(it).endsWith(".protected")
+ !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() != hashCode
@@ -656,7 +672,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
int protectionSchemeCount = protectedNiFiProperties
.getPropertyKeysIncludingProtectionSchemes()
- .findAll { it.endsWith(".protected") }
+ .findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
.size()
int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
@@ -672,7 +688,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
- int hashCode = protectedNiFiProperties.internalNiFiProperties.hashCode()
+ int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
logger.info("Hash code of internal instance: ${hashCode}")
// Act
@@ -682,7 +698,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
// Assert
assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
assert unprotectedNiFiProperties.getPropertyKeys().every {
- !unprotectedNiFiProperties.getProperty(it).endsWith(".protected")
+ !unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() != hashCode
@@ -691,14 +707,15 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
@Test
void testShouldCalculateSize() {
// Arrange
- NiFiRegistryProperties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as NiFiRegistryProperties
+ 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.keySet().join(", ")}")
+ logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
// Act
int protectedSize = protectedNiFiProperties.size()
logger.info("Protected properties (${protectedNiFiProperties.size()}): " +
- "${protectedNiFiProperties.getPropertyKeysExcludingProtectionSchemes().join(", ")}")
+ "${protectedNiFiProperties.getPropertyKeys().join(", ")}")
// Assert
assert protectedSize == rawProperties.size() - 1
@@ -707,25 +724,27 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
@Test
void testGetPropertyKeysShouldMatchSize() {
// Arrange
- NiFiRegistryProperties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as NiFiRegistryProperties
+ 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.keySet().join(", ")}")
+ logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
// Act
- def filteredKeys = protectedNiFiProperties.getPropertyKeysExcludingProtectionSchemes()
+ 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"
+ assert filteredKeys == rawProperties.getPropertyKeys() - "key.protected"
}
@Test
void testShouldGetPropertyKeysIncludingProtectionSchemes() {
// Arrange
- NiFiRegistryProperties rawProperties = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as NiFiRegistryProperties
+ 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.keySet().join(", ")}")
+ logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
// Act
def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
@@ -733,7 +752,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase {
// Assert
assert allKeys.size() == rawProperties.size()
- assert allKeys == rawProperties.keySet()
+ assert allKeys == rawProperties.getPropertyKeys()
}
}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/security/crypto/CryptoKeyLoaderGroovyTest.groovy b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapUtilsGroovyTest.groovy
similarity index 81%
rename from nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/security/crypto/CryptoKeyLoaderGroovyTest.groovy
rename to nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapUtilsGroovyTest.groovy
index 6d8623ee90..465a122011 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/security/crypto/CryptoKeyLoaderGroovyTest.groovy
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/groovy/org/apache/nifi/registry/properties/util/NiFiRegistryBootstrapUtilsGroovyTest.groovy
@@ -14,10 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.security.crypto
+package org.apache.nifi.registry.properties.util
import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.registry.security.crypto.CryptoKeyLoader
import org.apache.nifi.registry.security.crypto.CryptoKeyProvider
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Assume
@@ -33,9 +32,9 @@ import java.nio.file.attribute.PosixFilePermission
import java.security.Security
@RunWith(JUnit4.class)
-class CryptoKeyLoaderGroovyTest extends GroovyTestCase {
+class NiFiRegistryBootstrapUtilsGroovyTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(CryptoKeyLoaderGroovyTest.class)
+ private static final Logger logger = LoggerFactory.getLogger(NiFiRegistryBootstrapUtilsGroovyTest.class)
private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
private static final String KEY_HEX_256 = KEY_HEX_128 * 2
@@ -57,7 +56,7 @@ class CryptoKeyLoaderGroovyTest extends GroovyTestCase {
final String expectedKey = KEY_HEX_256
// Act
- String key = CryptoKeyLoader.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.conf")
+ String key = NiFiRegistryBootstrapUtils.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.conf")
// Assert
assert key == expectedKey
@@ -68,7 +67,7 @@ class CryptoKeyLoaderGroovyTest extends GroovyTestCase {
// Arrange
// Act
- String key = CryptoKeyLoader.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.with_missing_key_line.conf")
+ String key = NiFiRegistryBootstrapUtils.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.with_missing_key_line.conf")
// Assert
assert key == CryptoKeyProvider.EMPTY_KEY
@@ -79,7 +78,7 @@ class CryptoKeyLoaderGroovyTest extends GroovyTestCase {
// Arrange
// Act
- String key = CryptoKeyLoader.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.with_missing_key.conf")
+ String key = NiFiRegistryBootstrapUtils.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.with_missing_key.conf")
// Assert
assert key == CryptoKeyProvider.EMPTY_KEY
@@ -91,7 +90,7 @@ class CryptoKeyLoaderGroovyTest extends GroovyTestCase {
// Act
def msg = shouldFail(IOException) {
- CryptoKeyLoader.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.missing.conf")
+ NiFiRegistryBootstrapUtils.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.missing.conf")
}
logger.info(msg)
@@ -110,7 +109,7 @@ class CryptoKeyLoaderGroovyTest extends GroovyTestCase {
// Act
def msg = shouldFail(IOException) {
- CryptoKeyLoader.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.unreadable_file_permissions.conf")
+ NiFiRegistryBootstrapUtils.extractKeyFromBootstrapFile("src/test/resources/conf/bootstrap.unreadable_file_permissions.conf")
}
logger.info(msg)
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java b/nifi-registry/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java
index 455ab9d896..0123e39691 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java
@@ -16,10 +16,10 @@
*/
package org.apache.nifi.registry;
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
import org.apache.nifi.registry.jetty.JettyServer;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.apache.nifi.registry.properties.NiFiRegistryPropertiesLoader;
-import org.apache.nifi.registry.properties.SensitivePropertyProtectionException;
import org.apache.nifi.registry.security.crypto.BootstrapFileCryptoKeyProvider;
import org.apache.nifi.registry.security.crypto.CryptoKeyProvider;
import org.apache.nifi.registry.security.crypto.MissingCryptoKeyException;
@@ -42,13 +42,6 @@ public class NiFiRegistry {
public static final String BOOTSTRAP_PORT_PROPERTY = "nifi.registry.bootstrap.listen.port";
- public static final String NIFI_REGISTRY_PROPERTIES_FILE_PATH_PROPERTY = "nifi.registry.properties.file.path";
- public static final String NIFI_REGISTRY_BOOTSTRAP_FILE_PATH_PROPERTY = "nifi.registry.bootstrap.config.file.path";
- public static final String NIFI_REGISTRY_BOOTSTRAP_DOCS_DIR_PROPERTY = "nifi.registry.bootstrap.config.docs.dir";
-
- public static final String RELATIVE_BOOTSTRAP_FILE_LOCATION = "conf/bootstrap.conf";
- public static final String RELATIVE_PROPERTIES_FILE_LOCATION = "conf/nifi-registry.properties";
- public static final String RELATIVE_DOCS_LOCATION = "docs";
private final JettyServer server;
private final BootstrapListener bootstrapListener;
@@ -106,7 +99,8 @@ public class NiFiRegistry {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
- final String docsDir = System.getProperty(NIFI_REGISTRY_BOOTSTRAP_DOCS_DIR_PROPERTY, RELATIVE_DOCS_LOCATION);
+ final String docsDir = System.getProperty(NiFiRegistryProperties.NIFI_REGISTRY_BOOTSTRAP_DOCS_DIR_PROPERTY,
+ NiFiRegistryProperties.RELATIVE_DOCS_LOCATION);
final long startTime = System.nanoTime();
server = new JettyServer(properties, masterKeyProvider, docsDir);
@@ -168,7 +162,8 @@ public class NiFiRegistry {
}
public static CryptoKeyProvider getMasterKeyProvider() {
- final String bootstrapConfigFilePath = System.getProperty(NIFI_REGISTRY_BOOTSTRAP_FILE_PATH_PROPERTY, RELATIVE_BOOTSTRAP_FILE_LOCATION);
+ final String bootstrapConfigFilePath = System.getProperty(NiFiRegistryProperties.NIFI_REGISTRY_BOOTSTRAP_FILE_PATH_PROPERTY,
+ NiFiRegistryProperties.RELATIVE_BOOTSTRAP_FILE_LOCATION);
CryptoKeyProvider masterKeyProvider = new BootstrapFileCryptoKeyProvider(bootstrapConfigFilePath);
LOGGER.info("Read property protection key from {}", bootstrapConfigFilePath);
return masterKeyProvider;
@@ -186,7 +181,8 @@ public class NiFiRegistry {
try {
try {
// Load properties using key. If properties are protected and key missing, throw RuntimeException
- final String nifiRegistryPropertiesFilePath = System.getProperty(NIFI_REGISTRY_PROPERTIES_FILE_PATH_PROPERTY, RELATIVE_PROPERTIES_FILE_LOCATION);
+ final String nifiRegistryPropertiesFilePath = System.getProperty(NiFiRegistryProperties.NIFI_REGISTRY_PROPERTIES_FILE_PATH_PROPERTY,
+ NiFiRegistryProperties.RELATIVE_PROPERTIES_FILE_LOCATION);
final NiFiRegistryProperties properties = NiFiRegistryPropertiesLoader.withKey(key).load(nifiRegistryPropertiesFilePath);
LOGGER.info("Loaded {} properties", properties.size());
return properties;
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java
index 0fe05380a5..7a30d37321 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java
@@ -40,6 +40,7 @@ import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import java.io.FileReader;
import java.io.IOException;
+import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -176,17 +177,17 @@ public abstract class IntegrationTestBase {
* @param propertiesFilePath The location of the properties file
* @return A NiFIRegistryProperties instance based on the properties file contents
*/
- static NiFiRegistryProperties loadNiFiRegistryProperties(String propertiesFilePath) {
- NiFiRegistryProperties properties = new NiFiRegistryProperties();
+ static NiFiRegistryProperties loadNiFiRegistryProperties(final String propertiesFilePath) {
+ final Properties props = new Properties();
try (final FileReader reader = new FileReader(propertiesFilePath)) {
- properties.load(reader);
+ props.load(reader);
+ return new NiFiRegistryProperties(props);
} catch (final IOException ioe) {
throw new RuntimeException("Unable to load properties: " + ioe, ioe);
}
- return properties;
}
- private static Client createClientFromConfig(NiFiRegistryClientConfig registryClientConfig) {
+ private static Client createClientFromConfig(final NiFiRegistryClientConfig registryClientConfig) {
final ClientConfig clientConfig = new ClientConfig();
clientConfig.register(jacksonJaxbJsonProvider());
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
index f6d12483a5..2bbefc3d56 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
@@ -17,6 +17,9 @@
package org.apache.nifi.registry.web.api;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.properties.PropertyProtectionScheme;
+import org.apache.nifi.properties.SensitivePropertyProvider;
+import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory;
import org.apache.nifi.registry.SecureLdapTestApiApplication;
import org.apache.nifi.registry.authorization.AccessPolicy;
import org.apache.nifi.registry.authorization.AccessPolicySummary;
@@ -33,9 +36,7 @@ import org.apache.nifi.registry.client.UserClient;
import org.apache.nifi.registry.client.impl.JerseyNiFiRegistryClient;
import org.apache.nifi.registry.client.impl.request.BearerTokenRequestConfig;
import org.apache.nifi.registry.extension.ExtensionManager;
-import org.apache.nifi.registry.properties.AESSensitivePropertyProvider;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
-import org.apache.nifi.registry.properties.SensitivePropertyProvider;
import org.apache.nifi.registry.revision.entity.RevisionInfo;
import org.apache.nifi.registry.security.authorization.Authorizer;
import org.apache.nifi.registry.security.authorization.AuthorizerFactory;
@@ -140,7 +141,8 @@ public class SecureLdapIT extends IntegrationTestBase {
@Primary
@Bean
public static SensitivePropertyProvider sensitivePropertyProvider() throws Exception {
- return new AESSensitivePropertyProvider(getNiFiRegistryMasterKeyProvider().getKey());
+ return StandardSensitivePropertyProviderFactory.withKey(getNiFiRegistryMasterKeyProvider().getKey())
+ .getProvider(PropertyProtectionScheme.AES_GCM);
}
private static CryptoKeyProvider getNiFiRegistryMasterKeyProvider() {
diff --git a/nifi-registry/nifi-registry-toolkit/nifi-registry-toolkit-persistence/src/main/java/org/apache/nifi/registry/toolkit/persistence/FlowPersistenceProviderMigrator.java b/nifi-registry/nifi-registry-toolkit/nifi-registry-toolkit-persistence/src/main/java/org/apache/nifi/registry/toolkit/persistence/FlowPersistenceProviderMigrator.java
index a510281b81..266d2481e0 100644
--- a/nifi-registry/nifi-registry-toolkit/nifi-registry-toolkit-persistence/src/main/java/org/apache/nifi/registry/toolkit/persistence/FlowPersistenceProviderMigrator.java
+++ b/nifi-registry/nifi-registry-toolkit/nifi-registry-toolkit-persistence/src/main/java/org/apache/nifi/registry/toolkit/persistence/FlowPersistenceProviderMigrator.java
@@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
+import java.util.Properties;
public class FlowPersistenceProviderMigrator {
private static final Logger log = LoggerFactory.getLogger(FlowPersistenceProviderMigrator.class);
@@ -91,12 +92,13 @@ public class FlowPersistenceProviderMigrator {
new FlowPersistenceProviderMigrator().doMigrate(fromMetadataService, fromPersistenceProvider, toPersistenceProvider);
}
- private static NiFiRegistryProperties createToProperties(CommandLine commandLine, NiFiRegistryProperties fromProperties) {
- NiFiRegistryProperties toProperties = new NiFiRegistryProperties();
- for (String propertyKey : fromProperties.getPropertyKeys()) {
- toProperties.setProperty(propertyKey, fromProperties.getProperty(propertyKey));
+ private static NiFiRegistryProperties createToProperties(final CommandLine commandLine, final NiFiRegistryProperties fromProperties) {
+ final Properties props = new Properties();
+ for (final String propertyKey : fromProperties.getPropertyKeys()) {
+ props.setProperty(propertyKey, fromProperties.getProperty(propertyKey));
}
- toProperties.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, commandLine.getOptionValue('t'));
+ props.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, commandLine.getOptionValue('t'));
+ final NiFiRegistryProperties toProperties = new NiFiRegistryProperties(props);
return toProperties;
}
diff --git a/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/nodemanager/NodeManagerTool.groovy b/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/nodemanager/NodeManagerTool.groovy
index de5aeb4427..f9a2f9f25d 100644
--- a/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/nodemanager/NodeManagerTool.groovy
+++ b/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/nodemanager/NodeManagerTool.groovy
@@ -28,6 +28,7 @@ import org.apache.nifi.toolkit.admin.client.ClientFactory
import org.apache.nifi.toolkit.admin.client.NiFiClientFactory
import org.apache.nifi.toolkit.admin.client.NiFiClientUtil
import org.apache.nifi.toolkit.admin.util.AdminUtil
+import org.apache.nifi.util.NiFiBootstrapUtils
import org.apache.nifi.util.NiFiProperties
import org.apache.nifi.util.StringUtils
import org.apache.nifi.web.api.dto.NodeDTO
@@ -271,7 +272,7 @@ public class NodeManagerTool extends AbstractAdminTool {
String nifiConfDir = AdminUtil.getRelativeDirectory(bootstrapProperties.getProperty("conf.dir"), bootstrapConf.getCanonicalFile().getParentFile().getParentFile().getCanonicalPath())
String nifiLibDir = AdminUtil.getRelativeDirectory(bootstrapProperties.getProperty("lib.dir"), bootstrapConf.getCanonicalFile().getParentFile().getParentFile().getCanonicalPath())
String nifiPropertiesFileName = nifiConfDir + File.separator +"nifi.properties"
- final String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile(bootstrapConfFileName)
+ final String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile(bootstrapConfFileName)
final NiFiProperties niFiProperties = NiFiPropertiesLoader.withKey(key).load(nifiPropertiesFileName)
final String operation = commandLine.getOptionValue(OPERATION)
diff --git a/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/notify/NotificationTool.groovy b/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/notify/NotificationTool.groovy
index df538e8061..136bcd88f3 100644
--- a/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/notify/NotificationTool.groovy
+++ b/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/notify/NotificationTool.groovy
@@ -29,6 +29,7 @@ import org.apache.nifi.toolkit.admin.client.ClientFactory
import org.apache.nifi.toolkit.admin.client.NiFiClientFactory
import org.apache.nifi.toolkit.admin.client.NiFiClientUtil
import org.apache.nifi.toolkit.admin.util.AdminUtil
+import org.apache.nifi.util.NiFiBootstrapUtils
import org.apache.nifi.util.NiFiProperties
import org.apache.nifi.web.api.dto.BulletinDTO
import org.apache.nifi.web.api.entity.BulletinEntity
@@ -88,7 +89,7 @@ public class NotificationTool extends AbstractAdminTool {
logger.info("Loading nifi properties for host information")
}
- final String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile(bootstrapConfFile)
+ final String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile(bootstrapConfFile)
final NiFiProperties niFiProperties = NiFiPropertiesLoader.withKey(key).load(nifiPropertiesFile)
final Client client = clientFactory.getClient(niFiProperties,nifiInstallDir)
final String url = NiFiClientUtil.getUrl(niFiProperties,NOTIFICATION_ENDPOINT)
diff --git a/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy b/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
index ddb445fb2d..e469980cf5 100644
--- a/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
+++ b/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
@@ -23,6 +23,7 @@ import org.apache.nifi.properties.NiFiPropertiesLoader
import org.apache.nifi.security.util.CertificateUtils
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine
+import org.apache.nifi.util.NiFiBootstrapUtils
import org.apache.nifi.util.NiFiProperties
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
@@ -90,7 +91,7 @@ class NiFiClientFactorySpec extends Specification {
def bootstrapConfFile = "src/test/resources/notify/conf/bootstrap.conf"
def nifiPropertiesFile = "src/test/resources/notify/conf/nifi-secured.properties"
- def key = NiFiPropertiesLoader.extractKeyFromBootstrapFile(bootstrapConfFile)
+ def key = NiFiBootstrapUtils.extractKeyFromBootstrapFile(bootstrapConfFile)
def NiFiProperties niFiProperties = NiFiPropertiesLoader.withKey(key).load(nifiPropertiesFile)
def clientFactory = new NiFiClientFactory()
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
index 319e2d95bf..606664ab61 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/pom.xml
@@ -34,6 +34,11 @@
nifi-properties1.14.0-SNAPSHOT
+
+ org.apache.nifi.registry
+ nifi-registry-properties
+ 1.14.0-SNAPSHOT
+ org.apache.nifinifi-properties-loader
@@ -168,4 +173,4 @@
-
\ No newline at end of file
+
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 f6f4453ed2..9f48d6c0ba 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,9 +31,10 @@ 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.security.kms.CryptoUtils
+import org.apache.nifi.registry.properties.util.NiFiRegistryBootstrapUtils
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException
import org.apache.nifi.toolkit.tls.commandLine.ExitCode
+import org.apache.nifi.util.NiFiBootstrapUtils
import org.apache.nifi.util.NiFiProperties
import org.apache.nifi.util.console.TextDevice
import org.apache.nifi.util.console.TextDevices
@@ -46,16 +47,17 @@ import org.xml.sax.SAXException
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import java.nio.charset.StandardCharsets
+import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.security.KeyException
import java.security.Security
+import java.util.function.Supplier
import java.util.regex.Matcher
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.util.zip.ZipException
-import java.nio.file.Files
class ConfigEncryptionTool {
private static final Logger logger = LoggerFactory.getLogger(ConfigEncryptionTool.class)
@@ -70,6 +72,10 @@ class ConfigEncryptionTool {
public static flowXmlPath
public String outputFlowXmlPath
+ static final PropertyProtectionScheme DEFAULT_PROTECTION_SCHEME = PropertyProtectionScheme.AES_GCM
+
+ private PropertyProtectionScheme protectionScheme = DEFAULT_PROTECTION_SCHEME
+ private PropertyProtectionScheme migrationProtectionScheme = DEFAULT_PROTECTION_SCHEME
private String keyHex
private String migrationKeyHex
private String password
@@ -77,6 +83,7 @@ class ConfigEncryptionTool {
// This is the raw value used in nifi.sensitive.props.key
private String flowPropertiesPassword
+ private String existingFlowPropertiesPassword
private String newFlowAlgorithm
private String newFlowProvider
@@ -109,9 +116,11 @@ class ConfigEncryptionTool {
private static final String FLOW_XML_ARG = "flowXml"
private static final String OUTPUT_FLOW_XML_ARG = "outputFlowXml"
private static final String KEY_ARG = "key"
+ private static final String PROTECTION_SCHEME_ARG = "protectionScheme"
private static final String PASSWORD_ARG = "password"
private static final String KEY_MIGRATION_ARG = "oldKey"
private static final String PASSWORD_MIGRATION_ARG = "oldPassword"
+ private static final String PROTECTION_SCHEME_MIGRATION_ARG = "oldProtectionScheme"
private static final String USE_KEY_ARG = "useRawKey"
private static final String MIGRATION_ARG = "migrate"
private static final String PROPS_KEY_ARG = "propsKey"
@@ -120,6 +129,9 @@ 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())
+
// Static holder to avoid re-generating the options object multiple times in an invocation
private static Options staticOptions
@@ -230,13 +242,15 @@ class ConfigEncryptionTool {
options.addOption(Option.builder("u").longOpt(OUTPUT_AUTHORIZERS_ARG).hasArg(true).argName("file").desc("The destination authorizers.xml file containing protected config values (will not modify input authorizers.xml)").build())
options.addOption(Option.builder("f").longOpt(FLOW_XML_ARG).hasArg(true).argName("file").desc("The flow.xml.gz file currently protected with old password (will be overwritten unless -g is specified)").build())
options.addOption(Option.builder("g").longOpt(OUTPUT_FLOW_XML_ARG).hasArg(true).argName("file").desc("The destination flow.xml.gz file containing protected config values (will not modify input flow.xml.gz)").build())
- options.addOption(Option.builder("b").longOpt(BOOTSTRAP_CONF_ARG).hasArg(true).argName("file").desc("The bootstrap.conf file to persist root key").build())
+ options.addOption(Option.builder("b").longOpt(BOOTSTRAP_CONF_ARG).hasArg(true).argName("file").desc("The bootstrap.conf file to persist root key and to optionally provide any configuration for the protection scheme.").build())
+ 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("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())
- options.addOption(Option.builder("m").longOpt(MIGRATION_ARG).hasArg(false).desc("If provided, the nifi.properties and/or login-identity-providers.xml sensitive properties will be re-encrypted with a new key").build())
+ options.addOption(Option.builder("m").longOpt(MIGRATION_ARG).hasArg(false).desc("If provided, the nifi.properties and/or login-identity-providers.xml sensitive properties will be re-encrypted with the new scheme").build())
options.addOption(Option.builder("x").longOpt(DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG).hasArg(false).desc("If provided, the properties in flow.xml.gz will be re-encrypted with a new key but the nifi.properties and/or login-identity-providers.xml files will not be modified").build())
options.addOption(Option.builder("s").longOpt(PROPS_KEY_ARG).hasArg(true).argName("password|keyhex").desc("The password or key to use to encrypt the sensitive processor properties in flow.xml.gz").build())
options.addOption(Option.builder("A").longOpt(NEW_FLOW_ALGORITHM_ARG).hasArg(true).argName("algorithm").desc("The algorithm to use to encrypt the sensitive processor properties in flow.xml.gz").build())
@@ -312,6 +326,10 @@ class ConfigEncryptionTool {
}
}
+ if (commandLine.hasOption(PROTECTION_SCHEME_ARG)) {
+ protectionScheme = PropertyProtectionScheme.valueOf(commandLine.getOptionValue(PROTECTION_SCHEME_ARG))
+ }
+
// If translating nifi.properties to CLI format, none of the remaining parsing is necessary
if (translatingCli) {
@@ -410,17 +428,23 @@ class ConfigEncryptionTool {
if (isVerbose) {
logger.info("Key migration mode activated")
}
- 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)
+ if (commandLine.hasOption(PROTECTION_SCHEME_MIGRATION_ARG)) {
+ migrationProtectionScheme = PropertyProtectionScheme.valueOf(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)
+ }
} else {
- migrationPassword = commandLine.getOptionValue(PASSWORD_MIGRATION_ARG)
+ migrationKeyHex = commandLine.getOptionValue(KEY_MIGRATION_ARG)
+ // Use the "migration password" value if the migration key hex is absent
+ usingPasswordMigration = !migrationKeyHex
}
- } 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)) {
@@ -428,23 +452,25 @@ class ConfigEncryptionTool {
}
}
- 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)
+ 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)
+ }
} else {
- password = commandLine.getOptionValue(PASSWORD_ARG)
+ keyHex = commandLine.getOptionValue(KEY_ARG)
+ usingPassword = !keyHex
}
- } 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
+ }
}
}
@@ -555,8 +581,10 @@ class ConfigEncryptionTool {
private static String parseKey(String rawKey) throws KeyException {
String hexKey = rawKey.replaceAll("[^0-9a-fA-F]", "")
def validKeyLengths = getValidKeyLengths()
+ List 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 (${validKeyLengths.collect { it / 4 }} hex characters)")
+ throw new KeyException("The key (${hexKey.size()} hex chars) must be of length ${validKeyLengths} " +
+ "bits (${validHexCharLengths} hex characters)")
}
hexKey.toUpperCase()
}
@@ -570,6 +598,10 @@ class ConfigEncryptionTool {
Cipher.getMaxAllowedKeyLength("AES") > 128 ? [128, 192, 256] : [128]
}
+ private NiFiPropertiesLoader getNiFiPropertiesLoader(final String keyHex) {
+ return protectionScheme.requiresSecretKey() ? NiFiPropertiesLoader.withKey(keyHex) : new NiFiPropertiesLoader()
+ }
+
/**
* Loads the {@link NiFiProperties} instance from the provided file path (restoring the original value of the System property {@code nifi.properties.file.path} after loading this instance).
*
@@ -581,7 +613,7 @@ class ConfigEncryptionTool {
if (niFiPropertiesPath && (niFiPropertiesFile = new File(niFiPropertiesPath)).exists()) {
NiFiProperties properties
try {
- properties = NiFiPropertiesLoader.withKey(existingKeyHex).load(niFiPropertiesFile)
+ properties = getNiFiPropertiesLoader(existingKeyHex).load(niFiPropertiesFile)
logger.info("Loaded NiFiProperties instance with ${properties.size()} properties")
return properties
} catch (RuntimeException e) {
@@ -725,14 +757,18 @@ class ConfigEncryptionTool {
Paths.get(originalOutputFlowXmlPath).resolveSibling(migratedFileName)
}
+ private SensitivePropertyProviderFactory getSensitivePropertyProviderFactory(final String keyHex) {
+ StandardSensitivePropertyProviderFactory.withKeyAndBootstrapSupplier(keyHex, getBootstrapSupplier(bootstrapConfPath))
+ }
+
String decryptLoginIdentityProviders(String encryptedXml, String existingKeyHex = keyHex) {
- AESSensitivePropertyProvider sensitivePropertyProvider = new AESSensitivePropertyProvider(existingKeyHex)
+ final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(existingKeyHex)
try {
def doc = getXmlSlurper().parseText(encryptedXml)
// Find the provider element by class even if it has been renamed
def passwords = doc.provider.find { it.'class' as String == LDAP_PROVIDER_CLASS }.property.findAll {
- it.@name =~ "Password" && it.@encryption =~ "aes/gcm/\\d{3}"
+ it.@name =~ "Password" && it.@encryption != ""
}
if (passwords.isEmpty()) {
@@ -743,8 +779,10 @@ class ConfigEncryptionTool {
}
passwords.each { password ->
+ final SensitivePropertyProvider sensitivePropertyProvider = providerFactory
+ .getProvider(PropertyProtectionScheme.fromIdentifier((String) password.@encryption))
if (isVerbose) {
- logger.info("Attempting to decrypt ${password.text()}")
+ logger.info("Attempting to decrypt ${password.text()} using protection scheme ${password.@encryption}")
}
String decryptedValue = sensitivePropertyProvider.unprotect(password.text().trim())
password.replaceNode {
@@ -762,7 +800,7 @@ class ConfigEncryptionTool {
}
String decryptAuthorizers(String encryptedXml, String existingKeyHex = keyHex) {
- AESSensitivePropertyProvider sensitivePropertyProvider = new AESSensitivePropertyProvider(existingKeyHex)
+ final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(existingKeyHex)
try {
def filename = "authorizers.xml"
@@ -771,7 +809,7 @@ class ConfigEncryptionTool {
def passwords = doc.userGroupProvider.find {
it.'class' as String == LDAP_USER_GROUP_PROVIDER_CLASS
}.property.findAll {
- it.@name =~ "Password" && it.@encryption =~ "aes/gcm/\\d{3}"
+ it.@name =~ "Password" && it.@encryption != ""
}
if (passwords.isEmpty()) {
@@ -784,8 +822,10 @@ class ConfigEncryptionTool {
passwords.each { password ->
// TODO: Capture the raw password, and only display it in the log if the decrypted value is different (to avoid possibly printing an incorrectly provided plaintext password)
if (isVerbose) {
- logger.info("Attempting to decrypt ${password.text()}")
+ logger.info("Attempting to decrypt ${password.text()} using protection scheme ${password.@encryption}")
}
+ final SensitivePropertyProvider sensitivePropertyProvider = providerFactory
+ .getProvider(PropertyProtectionScheme.fromIdentifier((String) password.@encryption))
String decryptedValue = sensitivePropertyProvider.unprotect(password.text().trim())
password.replaceNode {
property(name: password.@name, encryption: "none", decryptedValue)
@@ -803,8 +843,8 @@ class ConfigEncryptionTool {
}
}
- String encryptLoginIdentityProviders(String plainXml, String newKeyHex = keyHex) {
- AESSensitivePropertyProvider sensitivePropertyProvider = new AESSensitivePropertyProvider(newKeyHex)
+ String encryptLoginIdentityProviders(final String plainXml, final String newKeyHex = keyHex, final PropertyProtectionScheme newProtectionScheme = protectionScheme) {
+ final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(newKeyHex)
// TODO: Switch to XmlParser & XmlNodePrinter to maintain "empty" element structure
try {
@@ -822,10 +862,11 @@ class ConfigEncryptionTool {
}
return plainXml
}
+ final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(newProtectionScheme)
passwords.each { password ->
if (isVerbose) {
- logger.info("Attempting to encrypt ${password.name()}")
+ logger.info("Attempting to encrypt ${password.name()} using protection scheme ${sensitivePropertyProvider.identifierKey}")
}
String encryptedValue = sensitivePropertyProvider.protect(password.text().trim())
password.replaceNode {
@@ -845,8 +886,8 @@ class ConfigEncryptionTool {
}
}
- String encryptAuthorizers(String plainXml, String newKeyHex = keyHex) {
- AESSensitivePropertyProvider sensitivePropertyProvider = new AESSensitivePropertyProvider(newKeyHex)
+ String encryptAuthorizers(final String plainXml, final String newKeyHex = keyHex, final PropertyProtectionScheme newProtectionScheme = protectionScheme) {
+ final SensitivePropertyProviderFactory providerFactory = getSensitivePropertyProviderFactory(newKeyHex)
// TODO: Switch to XmlParser & XmlNodePrinter to maintain "empty" element structure
try {
@@ -865,10 +906,11 @@ class ConfigEncryptionTool {
}
return plainXml
}
+ final SensitivePropertyProvider sensitivePropertyProvider = providerFactory.getProvider(newProtectionScheme)
passwords.each { password ->
if (isVerbose) {
- logger.info("Attempting to encrypt ${password.name()}")
+ logger.info("Attempting to encrypt ${password.name()} using protection scheme ${sensitivePropertyProvider.identifierKey}")
}
String encryptedValue = sensitivePropertyProvider.protect(password.text().trim())
password.replaceNode {
@@ -912,7 +954,8 @@ class ConfigEncryptionTool {
// Holder for encrypted properties and protection schemes
Properties encryptedProperties = new Properties()
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(keyHex)
+ final SensitivePropertyProviderFactory sensitivePropertyProviderFactory = getSensitivePropertyProviderFactory(keyHex)
+ final SensitivePropertyProvider spp = sensitivePropertyProviderFactory.getProvider(protectionScheme)
protectedWrapper.addSensitivePropertyProvider(spp)
List keysToSkip = []
@@ -929,7 +972,7 @@ class ConfigEncryptionTool {
logger.info("Protected ${key} with ${spp.getIdentifierKey()} -> \t${protectedValue}")
// Add the protection key ("x.y.z.protected" -> "aes/gcm/{128,256}")
- String protectionKey = protectedWrapper.getProtectionKey(key)
+ String protectionKey = ApplicationPropertiesProtector.getProtectionKey(key)
encryptedProperties.setProperty(protectionKey, spp.getIdentifierKey())
logger.info("Updated protection key ${protectionKey}")
@@ -943,7 +986,7 @@ class ConfigEncryptionTool {
nonSensitiveKeys.each { String key ->
encryptedProperties.setProperty(key, plainProperties.getProperty(key))
}
- NiFiProperties mergedProperties = new StandardNiFiProperties(encryptedProperties)
+ NiFiProperties mergedProperties = new NiFiProperties(encryptedProperties)
logger.info("Final result: ${mergedProperties.size()} keys including ${ProtectedNiFiProperties.countProtectedProperties(mergedProperties)} protected keys")
mergedProperties
@@ -1129,7 +1172,8 @@ class ConfigEncryptionTool {
// Only need to replace the keys that have been protected AND nifi.sensitive.props.key
Map protectedKeys = protectedNiFiProperties.getProtectedPropertyKeys()
if (!protectedKeys.containsKey(NiFiProperties.SENSITIVE_PROPS_KEY)) {
- protectedKeys.put(NiFiProperties.SENSITIVE_PROPS_KEY, protectedNiFiProperties.getProperty(ProtectedNiFiProperties.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)))
+ protectedKeys.put(NiFiProperties.SENSITIVE_PROPS_KEY, protectedNiFiProperties.getProperty(ApplicationPropertiesProtector
+ .getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)))
}
protectedKeys.each { String key, String protectionScheme ->
@@ -1139,8 +1183,8 @@ class ConfigEncryptionTool {
}
// Get the index of the following line (or cap at max)
int p = l + 1 > lines.size() ? lines.size() : l + 1
- String protectionLine = "${protectedNiFiProperties.getProtectionKey(key)}=${protectionScheme ?: ""}"
- if (p < lines.size() && lines.get(p).startsWith("${protectedNiFiProperties.getProtectionKey(key)}=")) {
+ String protectionLine = "${ApplicationPropertiesProtector.getProtectionKey(key)}=${protectionScheme ?: ""}"
+ if (p < lines.size() && lines.get(p).startsWith("${ApplicationPropertiesProtector.getProtectionKey(key)}=")) {
lines.set(p, protectionLine)
} else {
lines.add(p, protectionLine)
@@ -1260,7 +1304,7 @@ class ConfigEncryptionTool {
boolean niFiPropertiesAreEncrypted() {
if (niFiPropertiesPath) {
try {
- def nfp = NiFiPropertiesLoader.withKey(keyHex).readProtectedPropertiesFromDisk(new File(niFiPropertiesPath))
+ def nfp = getNiFiPropertiesLoader(keyHex).readProtectedPropertiesFromDisk(new File(niFiPropertiesPath))
return nfp.hasProtectedKeys()
} catch (SensitivePropertyProtectionException | IOException e) {
return true
@@ -1299,7 +1343,7 @@ class ConfigEncryptionTool {
if (tool.translatingCli) {
if (tool.bootstrapConfPath) {
// Check to see if bootstrap.conf has a root key
- tool.keyHex = CryptoUtils.extractKeyFromBootstrapFile(tool.bootstrapConfPath)
+ tool.keyHex = NiFiBootstrapUtils.extractKeyFromBootstrapFile(tool.bootstrapConfPath)
}
if (!tool.keyHex) {
@@ -1319,7 +1363,7 @@ class ConfigEncryptionTool {
if (!tool.ignorePropertiesFiles || (tool.handlingFlowXml && existingNiFiPropertiesAreEncrypted)) {
// If we are handling the flow.xml.gz and nifi.properties is already encrypted, try getting the key from bootstrap.conf rather than the console
if (tool.ignorePropertiesFiles) {
- tool.keyHex = CryptoUtils.extractKeyFromBootstrapFile(tool.bootstrapConfPath)
+ tool.keyHex = NiFiBootstrapUtils.extractKeyFromBootstrapFile(tool.bootstrapConfPath)
} else {
tool.keyHex = tool.getKey()
}
@@ -1338,7 +1382,7 @@ class ConfigEncryptionTool {
tool.printUsageAndThrow(e.getMessage(), ExitCode.INVALID_ARGS)
}
- if (tool.migration) {
+ if (tool.migration && tool.migrationProtectionScheme.requiresSecretKey()) {
String migrationKeyHex = tool.getMigrationKey()
if (!migrationKeyHex) {
@@ -1397,6 +1441,9 @@ class ConfigEncryptionTool {
}
if (tool.handlingNiFiProperties) {
+ // If the flow password was not set in nifi.properties, use the hard-coded default
+ tool.existingFlowPropertiesPassword = tool.getExistingFlowPassword()
+
tool.niFiProperties = tool.encryptSensitiveProperties(tool.niFiProperties)
}
} catch (CommandLineParseException e) {
@@ -1444,8 +1491,7 @@ class ConfigEncryptionTool {
}
void handleFlowXml(boolean existingNiFiPropertiesAreEncrypted = false) {
- // If the flow password was not set in nifi.properties, use the hard-coded default
- String existingFlowPassword = getExistingFlowPassword()
+ String existingFlowPassword = existingFlowPropertiesPassword ?: getExistingFlowPassword()
// If the new password was not provided in the arguments, read from the console. If that is empty, use the same value (essentially a copy no-op)
String newFlowPassword = flowPropertiesPassword ?: getFlowPassword()
@@ -1481,20 +1527,22 @@ class ConfigEncryptionTool {
rawProperties.put(k, nfp.getProperty(k))
}
- // If the tool is not going to encrypt NiFiProperties and the existing file is already encrypted, encrypt and update the new sensitive props key
- if (!handlingNiFiProperties && existingNiFiPropertiesAreEncrypted) {
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(keyHex)
+ // If the tool is supposed to encrypt NiFiProperties or the existing file is already encrypted, encrypt and update the new sensitive props key
+ if (handlingNiFiProperties || existingNiFiPropertiesAreEncrypted) {
+ final SensitivePropertyProviderFactory sensitivePropertyProviderFactory = getSensitivePropertyProviderFactory(keyHex)
+ SensitivePropertyProvider spp = sensitivePropertyProviderFactory.getProvider(protectionScheme)
String encryptedSPK = spp.protect(newFlowPassword)
rawProperties.put(NiFiProperties.SENSITIVE_PROPS_KEY, encryptedSPK)
// Manually update the protection scheme or it will be lost
- rawProperties.put(ProtectedNiFiProperties.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY), spp.getIdentifierKey())
+ rawProperties.put(ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY), spp.getIdentifierKey())
if (isVerbose) {
logger.info("Tool is not configured to encrypt nifi.properties, but the existing nifi.properties is encrypted and flow.xml.gz was migrated, so manually persisting the new encrypted value to nifi.properties")
}
} else {
rawProperties.put(NiFiProperties.SENSITIVE_PROPS_KEY, newFlowPassword)
+ rawProperties.put(ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY), "")
}
- niFiProperties = new StandardNiFiProperties(rawProperties)
+ niFiProperties = new NiFiProperties(rawProperties)
}
}
@@ -1513,6 +1561,19 @@ class ConfigEncryptionTool {
cliOutput.join("\n")
}
+ static Supplier getBootstrapSupplier(final String bootstrapConfPath) {
+ new Supplier() {
+ @Override
+ BootstrapProperties get() {
+ try {
+ NiFiRegistryBootstrapUtils.loadBootstrapProperties(bootstrapConfPath)
+ } catch (final IOException e) {
+ throw new SensitivePropertyProtectionException(e.getCause(), e)
+ }
+ }
+ }
+ }
+
static String determineBaseUrl(NiFiProperties niFiProperties) {
String protocol = niFiProperties.isHTTPSConfigured() ? "https" : "http"
String host = niFiProperties.isHTTPSConfigured() ? niFiProperties.getProperty(NiFiProperties.WEB_HTTPS_HOST) : niFiProperties.getProperty(NiFiProperties.WEB_HTTP_HOST)
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 b2b901bbaf..52289c781e 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
@@ -19,8 +19,10 @@ package org.apache.nifi.toolkit.encryptconfig
import groovy.cli.commons.CliBuilder
import groovy.cli.commons.OptionAccessor
import org.apache.commons.cli.HelpFormatter
-import org.apache.nifi.properties.AESSensitivePropertyProvider
+import org.apache.nifi.properties.ConfigEncryptionTool
+import org.apache.nifi.properties.PropertyProtectionScheme
import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
import org.apache.nifi.toolkit.encryptconfig.util.PropertiesEncryptor
import org.apache.nifi.toolkit.encryptconfig.util.ToolUtilities
@@ -208,6 +210,7 @@ class DecryptMode implements ToolMode {
OptionAccessor rawOptions
Configuration.KeySource keySource
+ PropertyProtectionScheme protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
String key
SensitivePropertyProvider decryptionProvider
String inputBootstrapPath
@@ -228,11 +231,17 @@ class DecryptMode implements ToolMode {
validateOptions()
determineInputFileFromRemainingArgs()
- determineKey()
- if (!key) {
- throw new RuntimeException("Failed to configure tool, could not determine key.")
+ determineProtectionScheme()
+ determineBootstrapProperties()
+ if (protectionScheme.requiresSecretKey()) {
+ determineKey()
+ if (!key) {
+ throw new RuntimeException("Failed to configure tool, could not determine key.")
+ }
}
- decryptionProvider = new AESSensitivePropertyProvider(key)
+ decryptionProvider = StandardSensitivePropertyProviderFactory
+ .withKeyAndBootstrapSupplier(key, ConfigEncryptionTool.getBootstrapSupplier(inputBootstrapPath))
+ .getProvider(protectionScheme)
if (rawOptions.t) {
fileType = FileType.valueOf(rawOptions.t)
@@ -244,6 +253,12 @@ class DecryptMode implements ToolMode {
}
}
+ private void determineBootstrapProperties() {
+ if (rawOptions.b) {
+ inputBootstrapPath = rawOptions.b
+ }
+ }
+
private void validateOptions() {
String validationFailedMessage = null
@@ -268,6 +283,13 @@ class DecryptMode implements ToolMode {
this.inputFilePath = remainingArgs[0]
}
+ private void determineProtectionScheme() {
+
+ if (rawOptions.S) {
+ protectionScheme = PropertyProtectionScheme.valueOf(rawOptions.S)
+ }
+ }
+
private void determineKey() {
boolean usingPassword = false
@@ -302,7 +324,6 @@ class DecryptMode implements ToolMode {
}
key = ToolUtilities.determineKey(TextDevices.defaultTextDevice(), keyHex, password, usingPassword)
} else if (usingBootstrapKey) {
- inputBootstrapPath = rawOptions.b
logger.debug("Looking in bootstrap conf file ${inputBootstrapPath} for root key for decryption.")
// first, try to treat the bootstrap file as a NiFi bootstrap.conf
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 f30b011f58..919e2b0de2 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,9 @@
package org.apache.nifi.toolkit.encryptconfig
import groovy.cli.commons.CliBuilder
-import org.apache.nifi.properties.AESSensitivePropertyProvider
+import org.apache.nifi.properties.ConfigEncryptionTool
+import org.apache.nifi.properties.PropertyProtectionScheme
+import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
import org.apache.nifi.toolkit.encryptconfig.util.ToolUtilities
import org.slf4j.Logger
@@ -72,6 +74,10 @@ class NiFiRegistryDecryptMode extends DecryptMode {
config.inputFilePath = options.r
config.fileType = FileType.properties // disables auto-detection, which is still experimental
+ if (options.S) {
+ config.protectionScheme = PropertyProtectionScheme.valueOf((String) options.S)
+ }
+
// one of [-p, -k, -b]
String keyHex = null
String password = null
@@ -110,7 +116,9 @@ class NiFiRegistryDecryptMode extends DecryptMode {
}
}
- config.decryptionProvider = new AESSensitivePropertyProvider(config.key)
+ config.decryptionProvider = StandardSensitivePropertyProviderFactory
+ .withKeyAndBootstrapSupplier(config.key, ConfigEncryptionTool.getBootstrapSupplier(config.inputBootstrapPath))
+ .getProvider(config.protectionScheme)
run(config)
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 4a43c21528..3b5bbce4d0 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
@@ -20,8 +20,10 @@ import groovy.cli.commons.CliBuilder
import groovy.cli.commons.OptionAccessor
import org.apache.commons.cli.HelpFormatter
import org.apache.commons.cli.Options
-import org.apache.nifi.properties.AESSensitivePropertyProvider
+import org.apache.nifi.properties.ConfigEncryptionTool
+import org.apache.nifi.properties.PropertyProtectionScheme
import org.apache.nifi.properties.SensitivePropertyProvider
+import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryAuthorizersXmlEncryptor
import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryIdentityProvidersXmlEncryptor
@@ -186,6 +188,10 @@ class NiFiRegistryMode implements ToolMode {
argName: 'keyhex',
optionalArg: true,
'Protect the files using a raw hexadecimal key. If an argument is not provided to this flag, interactive mode will be triggered to prompt the user to enter the key.')
+ 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()})")
// Options for the old password or key, if running the tool to migrate keys
cli._(longOpt: 'oldPassword',
@@ -196,12 +202,16 @@ class NiFiRegistryMode implements ToolMode {
args: 1,
argName: 'keyhex',
'If the input files are already protected using a key, this specifies the raw hexadecimal key so that the files can be unprotected before re-protecting.')
+ 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()}.")
// Options for output bootstrap.conf file
cli.b(longOpt: 'bootstrapConf',
args: 1,
argName: 'file',
- 'The bootstrap.conf file containing no root key or an existing root key. If a new password or key is specified (using -p or -k) and no output bootstrap.conf file is specified, then this file will be overwritten to persist the new master key.')
+ 'The bootstrap.conf file containing no root key or an existing root key, and any other protection scheme configuration properties. If a new password or key is specified (using -p or -k) and no output bootstrap.conf file is specified, then this file will be overwritten to persist the new master key.')
cli.B(longOpt: 'outputBootstrapConf',
args: 1,
argName: 'file',
@@ -249,7 +259,9 @@ class NiFiRegistryMode implements ToolMode {
boolean usingPassword
boolean usingBootstrapKey
+ PropertyProtectionScheme protectionScheme
String encryptionKey
+ PropertyProtectionScheme oldProtectionScheme
String decryptionKey
SensitivePropertyProvider encryptionProvider
@@ -285,32 +297,43 @@ class NiFiRegistryMode implements ToolMode {
// Set input bootstrap.conf path
inputBootstrapPath = rawOptions.b
- // Determine key for encryption (required)
- determineEncryptionKey()
- if (!encryptionKey) {
- throw new RuntimeException("Failed to configure tool, could not determine encryption key. Must provide -p, -k, or -b. If using -b, bootstrap.conf argument must already contain root key.")
- }
- encryptionProvider = new AESSensitivePropertyProvider(encryptionKey)
-
+ determineOldProtectionScheme()
// Determine key for decryption (if migrating)
determineDecryptionKey()
if (!decryptionKey) {
logger.debug("No decryption key specified via options, so if any input files require decryption prior to re-encryption (i.e., migration), this tool will fail.")
}
- decryptionProvider = decryptionKey ? new AESSensitivePropertyProvider(decryptionKey) : null
-
- writingKeyToBootstrap = (usingPassword || usingRawKeyHex || rawOptions.B)
- if (writingKeyToBootstrap) {
- outputBootstrapPath = rawOptions.B ?: inputBootstrapPath
- }
handlingNiFiRegistryProperties = rawOptions.r
if (handlingNiFiRegistryProperties) {
inputNiFiRegistryPropertiesPath = rawOptions.r
outputNiFiRegistryPropertiesPath = rawOptions.R ?: inputNiFiRegistryPropertiesPath
+ }
+
+ determineProtectionScheme()
+
+ // Determine key for encryption (required)
+ determineEncryptionKey()
+ if (!encryptionKey) {
+ throw new RuntimeException("Failed to configure tool, could not determine encryption key. Must provide -p, -k, or -b. If using -b, bootstrap.conf argument must already contain root key.")
+ }
+ encryptionProvider = StandardSensitivePropertyProviderFactory
+ .withKeyAndBootstrapSupplier(encryptionKey, ConfigEncryptionTool.getBootstrapSupplier(inputBootstrapPath))
+ .getProvider(oldProtectionScheme)
+
+ decryptionProvider = decryptionKey ? StandardSensitivePropertyProviderFactory
+ .withKeyAndBootstrapSupplier(decryptionKey, ConfigEncryptionTool.getBootstrapSupplier(inputBootstrapPath))
+ .getProvider(protectionScheme) : null
+
+ if (handlingNiFiRegistryProperties) {
propertiesEncryptor = new NiFiRegistryPropertiesEncryptor(encryptionProvider, decryptionProvider)
}
+ writingKeyToBootstrap = (usingPassword || usingRawKeyHex || rawOptions.B)
+ if (writingKeyToBootstrap) {
+ outputBootstrapPath = rawOptions.B ?: inputBootstrapPath
+ }
+
handlingIdentityProviders = rawOptions.i
if (handlingIdentityProviders) {
inputIdentityProvidersPath = rawOptions.i
@@ -327,6 +350,23 @@ class NiFiRegistryMode implements ToolMode {
}
+ private void determineProtectionScheme() {
+
+ if (rawOptions.S) {
+ protectionScheme = PropertyProtectionScheme.valueOf(rawOptions.S)
+ } else {
+ protectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
+ }
+ }
+ private void determineOldProtectionScheme() {
+
+ if (rawOptions.H) {
+ oldProtectionScheme = PropertyProtectionScheme.valueOf(rawOptions.H)
+ } else {
+ oldProtectionScheme = ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME
+ }
+ }
+
private void validateOptions() {
String validationFailedMessage = null
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiRegistryAuthorizersXmlEncryptor.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiRegistryAuthorizersXmlEncryptor.groovy
index 102ad9e1ee..30639a83e6 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiRegistryAuthorizersXmlEncryptor.groovy
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/main/groovy/org/apache/nifi/toolkit/encryptconfig/util/NiFiRegistryAuthorizersXmlEncryptor.groovy
@@ -42,7 +42,7 @@ class NiFiRegistryAuthorizersXmlEncryptor extends XmlEncryptor {
* .*? -> find everything as needed up until and including occurrence of ''
*/
- NiFiRegistryAuthorizersXmlEncryptor(SensitivePropertyProvider encryptionProvider, SensitivePropertyProvider decryptionProvider) {
+ NiFiRegistryAuthorizersXmlEncryptor(final SensitivePropertyProvider encryptionProvider, final SensitivePropertyProvider decryptionProvider) {
super(encryptionProvider, decryptionProvider)
}
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 acb118b482..57be63b47d 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
@@ -19,11 +19,10 @@ package org.apache.nifi.properties
import org.apache.commons.cli.CommandLine
import org.apache.commons.cli.CommandLineParser
import org.apache.commons.cli.DefaultParser
+import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.SystemUtils
import org.apache.log4j.AppenderSkeleton
import org.apache.log4j.spi.LoggingEvent
-import org.apache.nifi.encrypt.PropertyEncryptor
-import org.apache.nifi.encrypt.PropertyEncryptorFactory
import org.apache.nifi.security.util.EncryptionMethod
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException
import org.apache.nifi.util.NiFiProperties
@@ -34,7 +33,6 @@ import org.junit.After
import org.junit.AfterClass
import org.junit.Assume
import org.junit.BeforeClass
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.contrib.java.lang.system.Assertion
@@ -50,20 +48,13 @@ import org.xmlunit.diff.DefaultNodeMatcher
import org.xmlunit.diff.Diff
import org.xmlunit.diff.ElementSelectors
-import javax.crypto.BadPaddingException
import javax.crypto.Cipher
-import javax.crypto.SecretKey
-import javax.crypto.SecretKeyFactory
-import javax.crypto.spec.PBEKeySpec
-import javax.crypto.spec.PBEParameterSpec
+import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermission
import java.security.KeyException
import java.security.Security
-import org.apache.commons.io.IOUtils
-import java.nio.charset.StandardCharsets
-
@RunWith(JUnit4.class)
class ConfigEncryptionToolTest extends GroovyTestCase {
private static final Logger logger = LoggerFactory.getLogger(ConfigEncryptionToolTest.class)
@@ -82,6 +73,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
public static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
private static final String PASSWORD = "thisIsABadPassword"
private static final String ANOTHER_PASSWORD = "thisIsAnotherBadPassword"
+
+ private static final SensitivePropertyProviderFactory DEFAULT_PROVIDER_FACTORY = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX)
// From ConfigEncryptionTool.deriveKeyFromPassword("thisIsABadPassword")
private static
@@ -1005,12 +998,13 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Loaded ${plainProperties.size()} properties")
logger.info("There are ${protectedWrapper.getSensitivePropertyKeys().size()} sensitive properties")
- SensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
- int protectedPropertyCount = protectedWrapper.protectedPropertyKeys.size()
+ 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()
- ConfigEncryptionTool tool = new ConfigEncryptionTool(keyHex: KEY_HEX)
// Act
NiFiProperties encryptedProperties = tool.encryptSensitiveProperties(plainProperties)
@@ -1035,7 +1029,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
Assume.assumeTrue("Test only runs on *nix because Windows line endings are different", !SystemUtils.IS_OS_WINDOWS)
Properties rawProperties = [key: "value", key2: "value2"] as Properties
- NiFiProperties properties = new StandardNiFiProperties(rawProperties)
+ NiFiProperties properties = new NiFiProperties(rawProperties)
logger.info("Loaded ${properties.size()} properties")
// Act
@@ -1059,7 +1053,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
Assume.assumeTrue("Test only runs on *nix because Windows line endings are different", !SystemUtils.IS_OS_WINDOWS)
Properties rawProperties = [key: "value", key2: "value2"] as Properties
- NiFiProperties properties = new StandardNiFiProperties(rawProperties)
+ NiFiProperties properties = new NiFiProperties(rawProperties)
logger.info("Loaded ${properties.size()} properties")
def currentTimeZone = TimeZone.default
@@ -1084,48 +1078,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Reset current time zone to ${currentTimeZone.displayName} (${currentTimeZone.ID})")
}
- @Test
- void testShouldSerializeNiFiPropertiesAndPreserveFormat() {
- // Arrange
- String originalNiFiPropertiesPath = "src/test/resources/nifi_with_few_sensitive_properties_unprotected.properties"
-
- File originalFile = new File(originalNiFiPropertiesPath)
- List 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")
-
- protectedWrapper.addSensitivePropertyProvider(new AESSensitivePropertyProvider(KEY_HEX))
- NiFiProperties protectedProperties = protectedWrapper.protectPlainProperties()
- int protectedPropertyCount = ProtectedNiFiProperties.countProtectedProperties(protectedProperties)
- logger.info("Counted ${protectedPropertyCount} protected keys")
-
- // Act
- List 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() + protectedPropertyCount
-
- 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
void testShouldSerializeNiFiPropertiesAndPreserveFormatWithExistingProtectionSchemes() {
// Arrange
@@ -1144,8 +1096,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("There are ${protectedProperties.getProtectedPropertyKeys().size()} protected properties")
int originalProtectedPropertyCount = protectedProperties.getProtectedPropertyKeys().size()
- protectedProperties.addSensitivePropertyProvider(new AESSensitivePropertyProvider(KEY_HEX))
- NiFiProperties encryptedProperties = protectedProperties.protectPlainProperties()
+ 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")
@@ -1172,53 +1124,6 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("\n" * 2 + lines.join("\n"))
}
- @Test
- void testShouldSerializeNiFiPropertiesAndPreserveFormatWithNewPropertyAtEOF() {
- // Arrange
- String originalNiFiPropertiesPath = "src/test/resources/nifi_with_few_sensitive_properties_unprotected.properties"
-
- File originalFile = new File(originalNiFiPropertiesPath)
- List 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")
-
- // Set a value for the sensitive property which is the last line in the file
-
- // Groovy access to avoid duplicating entire object to add one value
- (plainProperties as StandardNiFiProperties).@rawProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, "thisIsABadTruststorePassword")
-
- protectedWrapper.addSensitivePropertyProvider(new AESSensitivePropertyProvider(KEY_HEX))
- NiFiProperties protectedProperties = protectedWrapper.protectPlainProperties()
- int protectedPropertyCount = ProtectedNiFiProperties.countProtectedProperties(protectedProperties)
- logger.info("Counted ${protectedPropertyCount} protected keys")
-
- // Act
- List 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() + protectedPropertyCount
-
- 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
void testShouldWriteNiFiProperties() {
// Arrange
@@ -1823,7 +1728,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Sanity check for decryption
String cipherText = "q4r7WIgN0MaxdAKM||SGgdCTPGSFEcuH4RraMYEdeyVbOx93abdWTVSWvh1w+klA"
String EXPECTED_PASSWORD = "thisIsABadPassword"
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX_128)
+ final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX_128).getProvider(tool.protectionScheme)
assert spp.unprotect(cipherText) == EXPECTED_PASSWORD
tool.keyHex = KEY_HEX_128
@@ -1955,7 +1860,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptLoginIdentityProviders(lines.join("\n")).split("\n")
@@ -1993,7 +1898,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptLoginIdentityProviders(lines.join("\n")).split("\n")
@@ -2032,7 +1937,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptLoginIdentityProviders(lines.join("\n")).split("\n")
@@ -2070,7 +1975,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptLoginIdentityProviders(lines.join("\n")).split("\n")
@@ -2109,7 +2014,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Read lines: \n${lines.join("\n")}")
assert lines.findAll { it =~ "ldap-provider" }.empty
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptLoginIdentityProviders(lines.join("\n")).split("\n")
@@ -2437,7 +2342,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
String[] args = ["-l", inputLIPFile.path, "-b", bootstrapFile.path, "-i", outputLIPFile.path, "-k", KEY_HEX, "-v"]
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
@@ -2520,7 +2425,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Migrate from KEY_HEX_128 to PASSWORD_KEY_HEX
String[] args = ["-l", inputLIPFile.path, "-b", bootstrapFile.path, "-i", outputLIPFile.path, "-m", "-e", KEY_HEX_128, "-k", PASSWORD_KEY_HEX, "-v"]
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(PASSWORD_KEY_HEX)
+ final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX)
+ .getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
@@ -2585,7 +2491,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Sanity check for decryption
String cipherText = "q4r7WIgN0MaxdAKM||SGgdCTPGSFEcuH4RraMYEdeyVbOx93abdWTVSWvh1w+klA"
String EXPECTED_PASSWORD = "thisIsABadPassword"
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX_128)
+ final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX_128).getProvider(tool.protectionScheme)
assert spp.unprotect(cipherText) == EXPECTED_PASSWORD
tool.keyHex = KEY_HEX_128
@@ -2717,7 +2623,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n")
@@ -2755,7 +2661,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n")
@@ -2794,7 +2700,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n")
@@ -2832,7 +2738,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def lines = workingFile.readLines()
logger.info("Read lines: \n${lines.join("\n")}")
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n")
@@ -2871,7 +2777,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Read lines: \n${lines.join("\n")}")
assert lines.findAll { it =~ "ldap-user-group-provider" }.empty
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(tool.protectionScheme)
// Act
def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n")
@@ -3163,7 +3069,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
String[] args = ["-a", inputAuthorizersFile.path, "-b", bootstrapFile.path, "-u", outputAuthorizersFile.path, "-k", KEY_HEX, "-v"]
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
@@ -3246,7 +3152,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Migrate from KEY_HEX_128 to PASSWORD_KEY_HEX
String[] args = ["-a", inputAuthorizersFile.path, "-b", bootstrapFile.path, "-u", outputAuthorizersFile.path, "-m", "-e", KEY_HEX_128, "-k", PASSWORD_KEY_HEX, "-v"]
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(PASSWORD_KEY_HEX)
+ final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX)
+ .getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
@@ -3324,7 +3231,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
String[] args = ["-a", inputAuthorizersFile.path, "-b", bootstrapFile.path, "-u", outputAuthorizersFile.path, "-k", KEY_HEX, "-v"]
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
@@ -3434,7 +3341,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
"-k", KEY_HEX,
"-v"]
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX)
+ final SensitivePropertyProvider spp = DEFAULT_PROVIDER_FACTORY.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
@@ -3849,7 +3756,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Original sensitive values: ${originalSensitiveValues}")
- final String SENSITIVE_PROTECTION_KEY = ProtectedNiFiProperties.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
+ final String SENSITIVE_PROTECTION_KEY = ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
ProtectedNiFiProperties encryptedProperties = niFiPropertiesLoader.readProtectedPropertiesFromDisk(workingNiFiPropertiesFile)
def originalEncryptedValues = encryptedProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): encryptedProperties.getProperty(key)] }
logger.info("Original encrypted values: ${originalEncryptedValues}")
@@ -3867,7 +3774,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Updated nifi.properties:")
logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(PASSWORD_KEY_HEX_128)
+ final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX_128)
+ .getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
// Check that the output values for everything is the same except the sensitive props key
NiFiProperties updatedProperties = new NiFiPropertiesLoader().readProtectedPropertiesFromDisk(workingNiFiPropertiesFile)
@@ -3981,7 +3889,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Original sensitive values: ${originalSensitiveValues}")
- final String SENSITIVE_PROTECTION_KEY = ProtectedNiFiProperties.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
+ final String SENSITIVE_PROTECTION_KEY = ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
ProtectedNiFiProperties encryptedProperties = niFiPropertiesLoader.readProtectedPropertiesFromDisk(workingNiFiPropertiesFile)
def originalEncryptedValues = encryptedProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): encryptedProperties.getProperty(key)] }
logger.info("Original encrypted values: ${originalEncryptedValues}")
@@ -3999,7 +3907,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
logger.info("Updated nifi.properties:")
logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(PASSWORD_KEY_HEX_128)
+ final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX_128)
+ .getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
// Check that the output values for everything is the same except the sensitive props key
NiFiProperties updatedProperties = new NiFiPropertiesLoader().readProtectedPropertiesFromDisk(workingNiFiPropertiesFile)
@@ -4115,7 +4024,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
logger.info("Original sensitive values: ${originalSensitiveValues}")
- final String SENSITIVE_PROTECTION_KEY = ProtectedNiFiProperties.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
+ final String SENSITIVE_PROTECTION_KEY = ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
ProtectedNiFiProperties encryptedProperties = niFiPropertiesLoader.readProtectedPropertiesFromDisk(workingNiFiPropertiesFile)
def originalEncryptedValues = encryptedProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): encryptedProperties.getProperty(key)] }
logger.info("Original encrypted values: ${originalEncryptedValues}")
@@ -4126,7 +4035,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
def passwordProgression = [DEFAULT_LEGACY_SENSITIVE_PROPS_KEY] + (0..5).collect { "${FLOW_PASSWORD}${it}" }
// The root key is not changing
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(PASSWORD_KEY_HEX_128)
+ final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX_128)
+ .getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
// Act
passwordProgression.eachWithIndex { String existingFlowPassword, int i ->
@@ -4394,8 +4304,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
assert message == "Encountered an error migrating flow content"
}
- private static StandardNiFiProperties wrapNFP(Map map) {
- new StandardNiFiProperties(
+ private static NiFiProperties wrapNFP(Map map) {
+ new NiFiProperties(
new Properties(map))
}
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 1abc44570e..a33ee264f0 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
@@ -16,8 +16,10 @@
*/
package org.apache.nifi.toolkit.encryptconfig
-import org.apache.nifi.properties.AESSensitivePropertyProvider
+
import org.apache.nifi.properties.NiFiPropertiesLoader
+import org.apache.nifi.properties.PropertyProtectionScheme
+import org.apache.nifi.properties.SensitivePropertyProvider
import org.apache.nifi.toolkit.encryptconfig.util.BootstrapUtil
import org.apache.nifi.util.NiFiProperties
import org.bouncycastle.jce.provider.BouncyCastleProvider
@@ -181,7 +183,8 @@ class EncryptConfigMainTest extends GroovyTestCase {
"-k", TestUtil.KEY_HEX,
"-v"]
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(TestUtil.KEY_HEX)
+ SensitivePropertyProvider spp = org.apache.nifi.properties.StandardSensitivePropertyProviderFactory.withKey(TestUtil.KEY_HEX)
+ .getProvider(PropertyProtectionScheme.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/TestUtil.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/toolkit/encryptconfig/TestUtil.groovy
index 481fd894b5..4c9678e5b6 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
@@ -16,9 +16,9 @@
*/
package org.apache.nifi.toolkit.encryptconfig
-
import org.apache.commons.lang3.SystemUtils
-import org.apache.nifi.properties.AESSensitivePropertyProvider
+import org.apache.nifi.properties.PropertyProtectionScheme
+import org.apache.nifi.properties.SensitivePropertyProvider
import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryAuthorizersXmlEncryptor
import org.apache.nifi.toolkit.encryptconfig.util.NiFiRegistryIdentityProvidersXmlEncryptor
@@ -298,7 +298,8 @@ class TestUtil {
assert populatedSensitiveProperties.size() == protectedSensitiveProperties.size()
- AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(expectedKey)
+ SensitivePropertyProvider spp = org.apache.nifi.properties.StandardSensitivePropertyProviderFactory.withKey(expectedKey)
+ .getProvider(PropertyProtectionScheme.AES_GCM)
protectedSensitiveProperties.each {
String value = it.text()
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/bootstrap.conf b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/bootstrap.conf
index c5bd663984..a2674a69f5 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/bootstrap.conf
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/bootstrap.conf
@@ -69,4 +69,8 @@ notification.max.attempts=5
#nifi.stop.notification.services=email-notification
# Comma-separated list of identifiers that are present in the notification.services.file; which services should be used to notify when NiFi dies?
-#nifi.dead.notification.services=email-notification
\ No newline at end of file
+#nifi.dead.notification.services=email-notification
+
+nifi.sensitive.props.key=AEFHKJADF
+
+nifi.sensitive.props.aws.creds.file=
\ No newline at end of file
diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/login-identity-providers.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/login-identity-providers.xml
index 7e2a3e1c17..cf1e86b9d0 100644
--- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/login-identity-providers.xml
+++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/login-identity-providers.xml
@@ -68,7 +68,7 @@
-
+
diff --git a/pom.xml b/pom.xml
index d11a419898..20b4bde619 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,12 +33,12 @@
nifi-docsnifi-maven-archetypesnifi-external
- nifi-toolkitnifi-dockernifi-system-testsminifinifi-statelessnifi-registry
+ nifi-toolkithttps://nifi.apache.org