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.nifi nifi-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 e = properties.keys() ; e.hasMoreElements(); ) { + final String key = e.nextElement().toString(); + if (key.startsWith(propertyPrefix)) { + filteredProperties.put(key, properties.getProperty(key)); + } + } + getRawProperties().putAll(filteredProperties); + } + + private String getPropertyKey(final String subKey) { + return String.format(PROPERTY_KEY_FORMAT, propertyPrefix, subKey); + } + + /** + * Returns the bootstrap sensitive key. + * @return The bootstrap sensitive key + */ + public Optional getBootstrapSensitiveKey() { + return Optional.ofNullable(getProperty(getPropertyKey(BOOTSTRAP_SENSITIVE_KEY))); + } + + @Override + public String toString() { + return String.format("Bootstrap properties [%s] with prefix [%s]", configFilePath, propertyPrefix); + } +} diff --git a/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ProtectedProperties.java b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ProtectedProperties.java new file mode 100644 index 0000000000..f04b1a812f --- /dev/null +++ b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ProtectedProperties.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import java.util.List; +import java.util.Properties; + +/** + * Represents a protected set of ApplicationProperties, with methods regarding which sensitive properties + * are protected. + * @param The ApplicationProperties type + */ +public interface ProtectedProperties { + + /** + * Additional sensitive properties keys + * @return Additional sensitive properties keys + */ + String getAdditionalSensitivePropertiesKeys(); + + /** + * Returns the name of the property that specifies the additional sensitive properties keys + * @return Name of additional sensitive properties keys + */ + String getAdditionalSensitivePropertiesKeysName(); + + /** + * Additional sensitive properties keys + * @return Additional sensitive properties keys + */ + List getDefaultSensitiveProperties(); + + /** + * Returns the application properties. + * @return The application properties + */ + T getApplicationProperties(); + + /** + * Create a new ApplicationProperties object of the generic type. + * @param rawProperties Plain old properties + * @return The ApplicationProperties + */ + T createApplicationProperties(Properties rawProperties); +} diff --git a/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ReadableProperties.java b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ReadableProperties.java new file mode 100644 index 0000000000..1dd328501d --- /dev/null +++ b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/ReadableProperties.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import java.util.Set; + +/** + * A base interface for providing a readable set of properties. + */ +public interface ReadableProperties { + + /** + * 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 + */ + String getProperty(String key); + + /** + * Retrieves the property value for the given property key. + * + * @param key the key of property value to lookup + * @param defaultValue The default value to use if the property does not exist + * @return value of property at given key or null if not found + */ + String getProperty(String key, String defaultValue); + + /** + * Retrieves all known property keys. + * + * @return all known property keys + */ + Set getPropertyKeys(); + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/StandardNiFiProperties.java b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/StandardReadableProperties.java similarity index 55% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/StandardNiFiProperties.java rename to nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/StandardReadableProperties.java index b7561ed3fe..d23b677885 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/StandardNiFiProperties.java +++ b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/StandardReadableProperties.java @@ -18,42 +18,39 @@ package org.apache.nifi.properties; import java.util.Enumeration; import java.util.HashSet; +import java.util.Map; import java.util.Properties; import java.util.Set; -import org.apache.nifi.util.NiFiProperties; -public class StandardNiFiProperties extends NiFiProperties { +/** + * A Properties-backed implementation of ReadableProperties. + */ +public class StandardReadableProperties implements ReadableProperties { - private Properties rawProperties = new Properties(); + private final Properties rawProperties = new Properties(); - public StandardNiFiProperties() { - this(null); + public StandardReadableProperties(final Properties properties) { + rawProperties.putAll(properties); } - public StandardNiFiProperties(Properties props) { - this.rawProperties = props == null ? new Properties() : props; + public StandardReadableProperties(final Map properties) { + rawProperties.putAll(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) { + public String getProperty(final String key) { return rawProperties.getProperty(key); } - /** - * Retrieves all known property keys. - * - * @return all known property keys - */ + @Override + public String getProperty(final String key, String defaultValue) { + return rawProperties.getProperty(key, defaultValue); + } + @Override public Set getPropertyKeys() { Set propertyNames = new HashSet<>(); - Enumeration e = getRawProperties().propertyNames(); + Enumeration e = rawProperties.propertyNames(); for (; e.hasMoreElements(); ){ propertyNames.add((String) e.nextElement()); } @@ -61,21 +58,15 @@ public class StandardNiFiProperties extends NiFiProperties { return propertyNames; } - Properties getRawProperties() { - if (this.rawProperties == null) { - this.rawProperties = new Properties(); - } - - return this.rawProperties; + protected Properties getRawProperties() { + return rawProperties; } - @Override + /** + * Returns the size of the properties. + * @return The size of the properties (number of keys) + */ public int size() { - return getRawProperties().size(); - } - - @Override - public String toString() { - return "StandardNiFiProperties instance with " + size() + " properties"; + return rawProperties.size(); } } diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java index 6aa7fdb33b..c0b60c85c6 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/kms/CryptoUtils.java @@ -16,6 +16,21 @@ */ package org.apache.nifi.security.kms; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration; +import org.apache.nifi.security.util.EncryptionMethod; +import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider; +import org.apache.nifi.util.NiFiBootstrapUtils; +import org.bouncycastle.util.encoders.DecoderException; +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.SecretKey; +import javax.crypto.spec.SecretKeySpec; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -24,8 +39,6 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; import java.security.KeyManagementException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -34,22 +47,7 @@ import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.regex.Pattern; -import java.util.stream.Stream; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.security.repository.config.RepositoryEncryptionConfiguration; -import org.apache.nifi.security.util.EncryptionMethod; -import org.apache.nifi.security.util.crypto.AESKeyedCipherProvider; -import org.apache.nifi.util.NiFiProperties; -import org.bouncycastle.util.encoders.Hex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class CryptoUtils { private static final Logger logger = LoggerFactory.getLogger(CryptoUtils.class); @@ -60,9 +58,6 @@ public class CryptoUtils { public static final String LEGACY_SKP_FQCN = "org.apache.nifi.provenance.StaticKeyProvider"; public static final String LEGACY_FBKP_FQCN = "org.apache.nifi.provenance.FileBasedKeyProvider"; - private static final String RELATIVE_NIFI_PROPS_PATH = "conf/nifi.properties"; - private static final String BOOTSTRAP_KEY_PREFIX = "nifi.bootstrap.sensitive.key="; - // TODO: Enforce even length private static final Pattern HEX_PATTERN = Pattern.compile("(?i)^[0-9a-f]+$"); @@ -309,87 +304,14 @@ public class CryptoUtils { public static SecretKey getRootKey() throws KeyManagementException { try { // Get the root encryption key from bootstrap.conf - String rootKeyHex = extractKeyFromBootstrapFile(); + String rootKeyHex = NiFiBootstrapUtils.extractKeyFromBootstrapFile(); return new SecretKeySpec(Hex.decode(rootKeyHex), "AES"); - } catch (IOException e) { + } catch (IOException | DecoderException e) { logger.error("Encountered an error: ", e); throw new KeyManagementException(e); } } - /** - * 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 extractKeyFromBootstrapFile(""); - } - - /** - * 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 - * @return the key in hexadecimal format - * @throws IOException if the file is not readable - */ - public static String extractKeyFromBootstrapFile(String bootstrapPath) throws IOException { - File expectedBootstrapFile; - if (StringUtils.isBlank(bootstrapPath)) { - // Guess at location of bootstrap.conf file from nifi.properties file - String defaultNiFiPropertiesPath = getDefaultFilePath(); - File propertiesFile = new File(defaultNiFiPropertiesPath); - 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 {} to extract encryption key -- 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()) { - try (Stream stream = Files.lines(Paths.get(expectedBootstrapFile.getAbsolutePath()))) { - Optional keyLine = stream.filter(l -> l.startsWith(BOOTSTRAP_KEY_PREFIX)).findFirst(); - if (keyLine.isPresent()) { - return keyLine.get().split("=", 2)[1]; - } else { - logger.warn("No encryption key present in the bootstrap.conf file at {}", expectedBootstrapFile.getAbsolutePath()); - return ""; - } - } catch (IOException e) { - logger.error("Cannot read from bootstrap.conf file at {} to extract encryption key", expectedBootstrapFile.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", expectedBootstrapFile.getAbsolutePath()); - throw new IOException("Cannot read from bootstrap.conf"); - } - } - - /** - * Returns the default file path to {@code $NIFI_HOME/conf/nifi.properties}. If the system - * property {@code nifi.properties.file.path} is not set, it will be set to the relative - * path {@code conf/nifi.properties}. - * - * @return the path to the nifi.properties file - */ - public static String getDefaultFilePath() { - String systemPath = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH); - - if (systemPath == null || systemPath.trim().isEmpty()) { - logger.warn("The system variable {} is not set, so it is being set to '{}'", NiFiProperties.PROPERTIES_FILE_PATH, RELATIVE_NIFI_PROPS_PATH); - System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, RELATIVE_NIFI_PROPS_PATH); - systemPath = RELATIVE_NIFI_PROPS_PATH; - } - - logger.info("Determined default nifi.properties path to be '{}'", systemPath); - return systemPath; - } - /** * Returns true if the two parameters are equal. This method is null-safe and evaluates the * equality in constant-time rather than "short-circuiting" on the first inequality. This diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/StandardTlsConfiguration.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/StandardTlsConfiguration.java index 4b731d4bf5..c627e5cb0e 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/StandardTlsConfiguration.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/StandardTlsConfiguration.java @@ -174,42 +174,24 @@ public class StandardTlsConfiguration implements TlsConfiguration { // Static factory method from NiFiProperties /** - * Returns a {@link org.apache.nifi.security.util.TlsConfiguration} instantiated from the relevant {@link NiFiProperties} properties. + * Returns a {@link org.apache.nifi.security.util.TlsConfiguration} instantiated from the relevant NiFi properties. * * @param niFiProperties the NiFi properties * @return a populated TlsConfiguration container object */ - public static TlsConfiguration fromNiFiProperties(NiFiProperties niFiProperties) { - Objects.requireNonNull("The NiFi properties cannot be null"); - - String keystorePath = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE); - String keystorePassword = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD); - String keyPassword = niFiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD); - String keystoreType = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE); - String truststorePath = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE); - String truststorePassword = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD); - String truststoreType = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE); - String protocol = TLS_PROTOCOL_VERSION; - - final StandardTlsConfiguration tlsConfiguration = new StandardTlsConfiguration(keystorePath, keystorePassword, keyPassword, - keystoreType, truststorePath, truststorePassword, - truststoreType, protocol); - if (logger.isDebugEnabled()) { - logger.debug("Instantiating TlsConfiguration from NiFi properties: {}, {}, {}, {}, {}, {}, {}, {}", - keystorePath, tlsConfiguration.getKeystorePasswordForLogging(), tlsConfiguration.getKeyPasswordForLogging(), keystoreType, - truststorePath, tlsConfiguration.getTruststorePasswordForLogging(), truststoreType, protocol); - } - - return tlsConfiguration; + public static TlsConfiguration fromNiFiProperties(final NiFiProperties niFiProperties) { + final Properties properties = new Properties(); + niFiProperties.getPropertyKeys().forEach(key -> properties.setProperty(key, niFiProperties.getProperty(key))); + return fromNiFiProperties(properties); } /** - * Returns a {@link org.apache.nifi.security.util.TlsConfiguration} instantiated from the relevant {@link NiFiProperties} properties. + * Returns a {@link org.apache.nifi.security.util.TlsConfiguration} instantiated from the relevant NiFi properties. * * @param niFiProperties the NiFi properties, as a simple java.util.Properties object * @return a populated TlsConfiguration container object */ - public static TlsConfiguration fromNiFiProperties(Properties niFiProperties) { + public static TlsConfiguration fromNiFiProperties(final Properties niFiProperties) { Objects.requireNonNull("The NiFi properties cannot be null"); String keystorePath = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE); diff --git a/nifi-commons/nifi-sensitive-property-provider/pom.xml b/nifi-commons/nifi-sensitive-property-provider/pom.xml new file mode 100644 index 0000000000..bdfa9bafc5 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + org.apache.nifi + nifi-commons + 1.14.0-SNAPSHOT + + nifi-sensitive-property-provider + + + + org.apache.nifi + nifi-properties + 1.14.0-SNAPSHOT + + + org.bouncycastle + bcprov-jdk15on + + + org.apache.commons + commons-lang3 + 3.12.0 + + + org.apache.nifi + nifi-security-utils + 1.14.0-SNAPSHOT + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.5 + + + add-test-source + generate-test-sources + + add-test-source + + + + src/test/groovy + + + + + + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java similarity index 78% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java rename to nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java index 062e35233c..3999a3a305 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProvider.java @@ -16,6 +16,21 @@ */ package org.apache.nifi.properties; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.DecoderException; +import org.bouncycastle.util.encoders.EncoderException; +import org.bouncycastle.util.encoders.Hex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -25,48 +40,51 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -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 org.apache.commons.lang3.StringUtils; -import org.bouncycastle.util.encoders.Base64; -import org.bouncycastle.util.encoders.DecoderException; -import org.bouncycastle.util.encoders.EncoderException; -import org.bouncycastle.util.encoders.Hex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class AESSensitivePropertyProvider implements SensitivePropertyProvider { +public class AESSensitivePropertyProvider extends AbstractSensitivePropertyProvider { private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProvider.class); private static final String IMPLEMENTATION_NAME = "AES Sensitive Property Provider"; - private static final String 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 Cipher cipher; private final SecretKey key; + private final int keySize; - public AESSensitivePropertyProvider(String keyHex) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { - byte[] key = validateKey(keyHex); + AESSensitivePropertyProvider(final byte[] keyHex) { + this(keyHex == null ? "" : Hex.toHexString(keyHex)); + } + + AESSensitivePropertyProvider(final String keyHex) { + super(null); + + byte[] keyBytes = validateKey(keyHex); try { - cipher = Cipher.getInstance(ALGORITHM, PROVIDER); + this.cipher = Cipher.getInstance(ALGORITHM, PROVIDER); // Only store the key if the cipher was initialized successfully - this.key = new SecretKeySpec(key, "AES"); + this.key = new SecretKeySpec(keyBytes, "AES"); + this.keySize = getKeySize(Hex.toHexString(this.key.getEncoded())); } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) { logger.error("Encountered an error initializing the {}: {}", IMPLEMENTATION_NAME, e.getMessage()); throw new SensitivePropertyProtectionException("Error initializing the protection cipher", e); } } + @Override + protected PropertyProtectionScheme getProtectionScheme() { + return PropertyProtectionScheme.AES_GCM; + } + + @Override + protected boolean isSupported(final BootstrapProperties bootstrapProperties) { + return true; // AES protection is always supported + } + private byte[] validateKey(String keyHex) { if (keyHex == null || StringUtils.isBlank(keyHex)) { throw new SensitivePropertyProtectionException("The key cannot be empty"); @@ -84,10 +102,6 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { 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 ""; @@ -121,16 +135,6 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { 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}. * @@ -138,16 +142,12 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { */ @Override public String getIdentifierKey() { - return IMPLEMENTATION_KEY + getKeySize(Hex.toHexString(key.getEncoded())); + return getProtectionScheme().getIdentifier(String.valueOf(keySize)); } - 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; - } + private static int getKeySize(final String key) { + // A key in hexadecimal format has one char per nibble (4 bits) + return StringUtils.isBlank(key) ? 0 : formatHexKey(key).length() * 4; } /** @@ -158,8 +158,8 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { * @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) { + public String protect(final String unprotectedValue) throws SensitivePropertyProtectionException { + if (StringUtils.isBlank(unprotectedValue)) { throw new IllegalArgumentException("Cannot encrypt an empty value"); } @@ -185,7 +185,7 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { } } - private String base64Encode(byte[] input) { + private String base64Encode(final byte[] input) { return Base64.toBase64String(input).replaceAll("=", ""); } @@ -195,7 +195,7 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { * @return the IV */ private byte[] generateIV() { - byte[] iv = new byte[IV_LENGTH]; + final byte[] iv = new byte[IV_LENGTH]; new SecureRandom().nextBytes(iv); return iv; } @@ -208,7 +208,7 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { * @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text */ @Override - public String unprotect(String protectedValue) throws SensitivePropertyProtectionException { + public String unprotect(final 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"); } @@ -216,28 +216,27 @@ public class AESSensitivePropertyProvider implements SensitivePropertyProvider { if (!protectedValue.contains(DELIMITER)) { throw new IllegalArgumentException("The cipher text does not contain the delimiter " + DELIMITER + " -- it should be of the form Base64(IV) || Base64(cipherText)"); } + final String trimmedProtectedValue = protectedValue.trim(); - protectedValue = protectedValue.trim(); - - final String IV_B64 = protectedValue.substring(0, protectedValue.indexOf(DELIMITER)); - byte[] iv = Base64.decode(IV_B64); + final String armoredIV = trimmedProtectedValue.substring(0, trimmedProtectedValue.indexOf(DELIMITER)); + final byte[] iv = Base64.decode(armoredIV); if (iv.length < IV_LENGTH) { - throw new IllegalArgumentException("The IV (" + iv.length + " bytes) must be at least " + IV_LENGTH + " bytes"); + throw new IllegalArgumentException(String.format("The IV (%s bytes) must be at least %s bytes", iv.length, IV_LENGTH)); } - String CIPHERTEXT_B64 = protectedValue.substring(protectedValue.indexOf(DELIMITER) + 2); + String armoredCipherText = trimmedProtectedValue.substring(trimmedProtectedValue.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, '='); + if (armoredCipherText.length() % 4 != 0) { + final int paddedLength = armoredCipherText.length() + 4 - (armoredCipherText.length() % 4); + armoredCipherText = StringUtils.rightPad(armoredCipherText, paddedLength, '='); } try { - byte[] cipherBytes = Base64.decode(CIPHERTEXT_B64); + final byte[] cipherBytes = Base64.decode(armoredCipherText); cipher.init(Cipher.DECRYPT_MODE, this.key, new IvParameterSpec(iv)); - byte[] plainBytes = cipher.doFinal(cipherBytes); + final byte[] plainBytes = cipher.doFinal(cipherBytes); logger.debug(getName() + " decrypted a sensitive value successfully"); return new String(plainBytes, StandardCharsets.UTF_8); } catch (BadPaddingException | IllegalBlockSizeException | DecoderException | InvalidAlgorithmParameterException | InvalidKeyException e) { diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java new file mode 100644 index 0000000000..b52fb73a41 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AbstractSensitivePropertyProvider.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +public abstract class AbstractSensitivePropertyProvider implements SensitivePropertyProvider { + private final BootstrapProperties bootstrapProperties; + + public AbstractSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) { + this.bootstrapProperties = bootstrapProperties; + } + + protected BootstrapProperties getBootstrapProperties() { + return bootstrapProperties; + } + + /** + * Return the appropriate PropertyProtectionScheme for this provider. + * @return The PropertyProtectionScheme + */ + protected abstract PropertyProtectionScheme getProtectionScheme(); + + /** + * Return true if this SensitivePropertyProvider is supported, given the provided + * Bootstrap properties. + * @param bootstrapProperties The Bootstrap properties + * @return True if this SensitivePropertyProvider is supported + */ + protected abstract boolean isSupported(BootstrapProperties bootstrapProperties); + + @Override + public String getName() { + return getProtectionScheme().getName(); + } + + /** + * Default implementation to return the protection scheme identifier, with no args to populate the identifier key. + * Concrete classes may choose to override this in order to fill in the identifier with specific args. + * @return The identifier key + */ + @Override + public String getIdentifierKey() { + return getProtectionScheme().getIdentifier(); + } + + @Override + public boolean isSupported() { + return isSupported(bootstrapProperties); + } +} diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java new file mode 100644 index 0000000000..3f03a9f098 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ApplicationPropertiesProtector.java @@ -0,0 +1,340 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; + +/** + * Class performing unprotection activities before returning a clean + * implementation of {@link ApplicationProperties}. + * This encapsulates the sensitive property access logic from external consumers + * of {@code ApplicationProperties}. + * + * @param The type of protected application properties + * @param The type of standard application properties that backs the protected application properties + */ +public class ApplicationPropertiesProtector, U extends ApplicationProperties> + implements SensitivePropertyProtector { + public static final String PROTECTED_KEY_SUFFIX = ".protected"; + + private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertiesProtector.class); + + private T protectedProperties; + + private Map localProviderCache = new HashMap<>(); + + /** + * Creates an instance containing the provided {@link ProtectedProperties}. + * + * @param protectedProperties the ProtectedProperties to contain + */ + public ApplicationPropertiesProtector(final T protectedProperties) { + this.protectedProperties = protectedProperties; + logger.debug("Loaded {} properties (including {} protection schemes) into {}", getPropertyKeysIncludingProtectionSchemes().size(), + getProtectedPropertyKeys().size(), this.getClass().getName()); + } + + /** + * Returns the sibling property key which specifies the protection scheme for this key. + *

+ * Example: + *

+ * nifi.sensitive.key=ABCXYZ + * nifi.sensitive.key.protected=aes/gcm/256 + *

+ * nifi.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(final String key) { + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("Cannot find protection key for null key"); + } + + return key + PROTECTED_KEY_SUFFIX; + } + + /** + * Retrieves all known property keys. + * + * @return all known property keys + */ + @Override + public Set getPropertyKeys() { + Set filteredKeys = getPropertyKeysIncludingProtectionSchemes(); + filteredKeys.removeIf(p -> p.endsWith(PROTECTED_KEY_SUFFIX)); + return filteredKeys; + } + + @Override + public int size() { + return getPropertyKeys().size(); + } + + @Override + public Set getPropertyKeysIncludingProtectionSchemes() { + return protectedProperties.getApplicationProperties().getPropertyKeys(); + } + + /** + * 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.property.1; nifi.property.2, nifi.property.3" + * @return a List containing the split and trimmed properties + */ + private static List splitMultipleProperties(final 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; + } + } + + private String getProperty(final String key) { + return protectedProperties.getApplicationProperties().getProperty(key); + } + + private String getAdditionalSensitivePropertiesKeys() { + return getProperty(protectedProperties.getAdditionalSensitivePropertiesKeysName()); + } + + private String getAdditionalSensitivePropertiesKeysName() { + return protectedProperties.getAdditionalSensitivePropertiesKeysName(); + } + + @Override + public List getSensitivePropertyKeys() { + final String additionalPropertiesString = getAdditionalSensitivePropertiesKeys(); + final String additionalPropertiesKeyName = protectedProperties.getAdditionalSensitivePropertiesKeysName(); + if (additionalPropertiesString == null || additionalPropertiesString.trim().isEmpty()) { + return protectedProperties.getDefaultSensitiveProperties(); + } 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(additionalPropertiesKeyName)) { + logger.warn("The key '{}' contains itself. This is poor practice and should be removed", additionalPropertiesKeyName); + additionalProperties.remove(additionalPropertiesKeyName); + } + additionalProperties.addAll(protectedProperties.getDefaultSensitiveProperties()); + return additionalProperties; + } + } + + @Override + public List getPopulatedSensitivePropertyKeys() { + List allSensitiveKeys = getSensitivePropertyKeys(); + return allSensitiveKeys.stream().filter(k -> StringUtils.isNotBlank(getProperty(k))).collect(Collectors.toList()); + } + + @Override + public boolean hasProtectedKeys() { + final List sensitiveKeys = getSensitivePropertyKeys(); + for (String k : sensitiveKeys) { + if (isPropertyProtected(k)) { + return true; + } + } + return false; + } + + @Override + public Map getProtectedPropertyKeys() { + final List sensitiveKeys = getSensitivePropertyKeys(); + + final Map traditionalProtectedProperties = new HashMap<>(); + for (final String key : sensitiveKeys) { + final String protection = getProperty(getProtectionKey(key)); + if (StringUtils.isNotBlank(protection) && StringUtils.isNotBlank(getProperty(key))) { + traditionalProtectedProperties.put(key, protection); + } + } + + return traditionalProtectedProperties; + } + + @Override + public Set getProtectionSchemes() { + return new HashSet<>(getProtectedPropertyKeys().values()); + } + + @Override + public boolean isPropertySensitive(final String key) { + // If the explicit check for ADDITIONAL_SENSITIVE_PROPERTIES_KEY is not here, this will loop infinitely + return key != null && !key.equals(getAdditionalSensitivePropertiesKeysName()) && getSensitivePropertyKeys().contains(key.trim()); + } + + /** + * 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 ApplicationPropertiesProtector#getSensitivePropertyKeys() + */ + @Override + public boolean isPropertyProtected(final String key) { + return key != null && isPropertySensitive(key) && !StringUtils.isBlank(getProperty(getProtectionKey(key))); + } + + @Override + public U getUnprotectedProperties() throws SensitivePropertyProtectionException { + if (hasProtectedKeys()) { + logger.debug("Protected Properties [{}] Sensitive Properties [{}]", + getProtectedPropertyKeys().size(), + getSensitivePropertyKeys().size()); + + final Properties rawProperties = new Properties(); + + final Set failedKeys = new HashSet<>(); + + for (final String key : getPropertyKeys()) { + /* Three kinds of keys + * 1. protection schemes -- skip + * 2. protected keys -- unprotect and copy + * 3. normal keys -- copy over + */ + if (key.endsWith(PROTECTED_KEY_SUFFIX)) { + // Do nothing + } else if (isPropertyProtected(key)) { + try { + rawProperties.setProperty(key, unprotectValue(key, getProperty(key))); + } catch (final SensitivePropertyProtectionException e) { + logger.warn("Failed to unprotect '{}'", key, e); + failedKeys.add(key); + } + } else { + rawProperties.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()); + } + } + + final U unprotected = protectedProperties.createApplicationProperties(rawProperties); + + return unprotected; + } else { + logger.debug("No protected properties"); + return protectedProperties.getApplicationProperties(); + } + } + + @Override + public void addSensitivePropertyProvider(final SensitivePropertyProvider sensitivePropertyProvider) { + Objects.requireNonNull(sensitivePropertyProvider, "Cannot add null 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 String toString() { + final Set providers = getSensitivePropertyProviders().keySet(); + return new StringBuilder("ApplicationPropertiesProtector instance with ") + .append(size()).append(" properties (") + .append(getProtectedPropertyKeys().size()) + .append(" protected) and ") + .append(providers.size()) + .append(" sensitive property providers: ") + .append(StringUtils.join(providers, ", ")) + .toString(); + } + + @Override + public Map getSensitivePropertyProviders() { + if (localProviderCache == null) { + localProviderCache = new HashMap<>(); + } + + return localProviderCache; + } + + private SensitivePropertyProvider getSensitivePropertyProvider(final String protectionScheme) { + if (isProviderAvailable(protectionScheme)) { + return getSensitivePropertyProviders().get(protectionScheme); + } else { + throw new SensitivePropertyProtectionException("No provider available for " + protectionScheme); + } + } + + private boolean isProviderAvailable(final String protectionScheme) { + return getSensitivePropertyProviders().containsKey(protectionScheme); + } + + /** + * 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(final String key, final String retrievedValue) { + // Checks if the key is sensitive and marked as protected + if (isPropertyProtected(key)) { + final String protectionScheme = getProperty(getProtectionKey(key)); + + // No provider registered for this scheme + if (!isProviderAvailable(protectionScheme)) { + throw new IllegalStateException(String.format("No provider available for " + key)); + } + + try { + final SensitivePropertyProvider sensitivePropertyProvider = getSensitivePropertyProvider(protectionScheme); + return sensitivePropertyProvider.unprotect(retrievedValue); + } catch (SensitivePropertyProtectionException e) { + logger.error("Error unprotecting value for " + key, e); + throw e; + } catch (IllegalArgumentException e) { + throw new SensitivePropertyProtectionException("Error unprotecting value for " + key, e); + } + } + return retrievedValue; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java similarity index 100% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java rename to nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/MultipleSensitivePropertyProtectionException.java diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java new file mode 100644 index 0000000000..40174065f2 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import java.util.Arrays; +import java.util.Objects; + +/** + * A scheme for protecting sensitive properties. Each scheme is intended to be backed by an implementation of + * SensitivePropertyProvider. + */ +public enum PropertyProtectionScheme { + AES_GCM("aes/gcm/(128|192|256)", "aes/gcm/%s", "AES Sensitive Property Provider", true); + + PropertyProtectionScheme(final String identifierPattern, final String identifierFormat, final String name, final boolean requiresSecretKey) { + this.identifierPattern = identifierPattern; + this.identifierFormat = identifierFormat; + this.name = name; + this.requiresSecretKey = requiresSecretKey; + } + + private final String identifierFormat; + private final String identifierPattern; + private final String name; + private final boolean requiresSecretKey; + + /** + * Returns a the identifier of the PropertyProtectionScheme. + * @param args scheme-specific arguments used to fill in the formatted identifierPattern + * @return The identifier of the PropertyProtectionScheme + */ + public String getIdentifier(final String... args) { + return String.format(identifierFormat, args); + } + + /** + * Returns whether this scheme requires a secret key. + * @return True if this scheme requires a secret key + */ + public boolean requiresSecretKey() { + return requiresSecretKey; + } + + /** + * Returns the name of the PropertyProtectionScheme. + * @return The name + */ + public String getName() { + return name; + } + + /** + * Returns the PropertyProtectionScheme matching the provided name. + * @param identifier The unique PropertyProtectionScheme identifier + * @return The matching PropertyProtectionScheme + * @throws IllegalArgumentException If the name was not recognized + */ + public static PropertyProtectionScheme fromIdentifier(final String identifier) { + Objects.requireNonNull(identifier, "Identifier must be specified"); + return Arrays.stream(PropertyProtectionScheme.values()) + .filter(scheme -> identifier.matches(scheme.identifierPattern)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unrecognized protection scheme :" + identifier)); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java similarity index 100% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java rename to nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtectionException.java diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java new file mode 100644 index 0000000000..b78dd6d127 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProtector.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Encapsulates methods needed to protect application properties. + * @param The ProtectedProperties type + * @param The ApplicationProperties type + */ +public interface SensitivePropertyProtector, U extends ApplicationProperties> { + + /** + * Returns the number of properties, excluding protection scheme properties. + *

+ * Example: + *

+ * key: E(value, key) + * key.protected: aes/gcm/256 + * key2: value2 + *

+ * would return size 2 + * + * @return the count of real properties + */ + int size(); + + /** + * Retrieves all known property keys. + * + * @return all known property keys + */ + Set getPropertyKeys(); + + /** + * Returns the complete set of property keys, including any protection keys (i.e. 'x.y.z.protected'). + * + * @return the set of property keys + */ + Set getPropertyKeysIncludingProtectionSchemes(); + + /** + * 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 the ApplicationProperties. + * + * @return the list of sensitive property keys + */ + List 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 the ApplicationProperties. + * + * @return the list of sensitive property keys + */ + List getPopulatedSensitivePropertyKeys(); + + /** + * Returns true if any sensitive keys are protected. + * + * @return true if any key is protected; false otherwise + */ + boolean hasProtectedKeys(); + + /** + * Returns the unique set of all protection schemes currently in use for this instance. + * + * @return the set of protection schemes + */ + Set getProtectionSchemes(); + + /** + * Returns a Map of the keys identifying "sensitive" properties that are currently protected and the "protection" key for each. + * This may or may not include all properties marked as sensitive. + * + * @return the Map of protected property keys and the protection identifier for each + */ + Map getProtectedPropertyKeys(); + + /** + * Returns the local provider cache (null-safe) as a Map of protection schemes -> implementations. + * + * @return the map + */ + Map getSensitivePropertyProviders(); + + /** + * Returns true if the property identified by this key is considered sensitive in this instance of {@code ApplicationProperties}. + * Some properties are sensitive by default, while others can be specified by + * {@link ProtectedProperties#getAdditionalSensitivePropertiesKeys()}. + * + * @param key the key + * @return true if it is sensitive + * @see ApplicationPropertiesProtector#getSensitivePropertyKeys() + */ + boolean isPropertySensitive(String key); + + /** + * Returns the unprotected {@link ApplicationProperties} 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 ApplicationProperties instance with all raw values + * @throws SensitivePropertyProtectionException if there is a problem unprotecting one or more keys + */ + boolean isPropertyProtected(String key); + + /** + * Returns the unprotected ApplicationProperties. + * @return The unprotected properties + * @throws SensitivePropertyProtectionException if there is a problem unprotecting one or more keys + */ + U getUnprotectedProperties() throws SensitivePropertyProtectionException; + + /** + * 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); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java similarity index 91% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java rename to nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java index b0c0be2e38..bb26ecf1d6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProvider.java @@ -32,6 +32,13 @@ public interface SensitivePropertyProvider { */ String getIdentifierKey(); + /** + * Returns whether this SensitivePropertyProvider is supported with the current system + * configuration. + * @return Whether this SensitivePropertyProvider is supported + */ + boolean isSupported(); + /** * 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. diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java new file mode 100644 index 0000000000..834e7085af --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactory.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import java.util.Collection; + +public interface SensitivePropertyProviderFactory { + + /** + * Gives the appropriate SensitivePropertyProvider, given a protection scheme. + * @param protectionScheme The protection scheme to use + * @return The appropriate SensitivePropertyProvider + */ + SensitivePropertyProvider getProvider(PropertyProtectionScheme protectionScheme); + + /** + * Returns a collection of all supported sensitive property providers. + * @return The supported sensitive property providers + */ + Collection getSupportedSensitivePropertyProviders(); + +} diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java new file mode 100644 index 0000000000..88bf1d69b9 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/SensitivePropertyProviderFactoryAware.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import java.util.function.Supplier; + +/** + * Provides a default SensitivePropertyProviderFactory to subclasses. + */ +public class SensitivePropertyProviderFactoryAware { + + private SensitivePropertyProviderFactory sensitivePropertyProviderFactory; + + protected SensitivePropertyProviderFactory getSensitivePropertyProviderFactory() throws SensitivePropertyProtectionException { + if (sensitivePropertyProviderFactory == null) { + sensitivePropertyProviderFactory = StandardSensitivePropertyProviderFactory.withDefaults(); + } + return sensitivePropertyProviderFactory; + } + + /** + * Configures and sets the SensitivePropertyProviderFactory. + * @param keyHex An key in hex format, which some providers may use for encryption + * @param bootstrapPropertiesSupplier The bootstrap.conf properties supplier + * @return The configured SensitivePropertyProviderFactory + */ + public SensitivePropertyProviderFactory configureSensitivePropertyProviderFactory(final String keyHex, + final Supplier bootstrapPropertiesSupplier) { + sensitivePropertyProviderFactory = StandardSensitivePropertyProviderFactory.withKeyAndBootstrapSupplier(keyHex, bootstrapPropertiesSupplier); + return sensitivePropertyProviderFactory; + } +} diff --git a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java new file mode 100644 index 0000000000..ef59333200 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import org.apache.nifi.util.NiFiBootstrapUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class StandardSensitivePropertyProviderFactory implements SensitivePropertyProviderFactory { + private static final Logger logger = LoggerFactory.getLogger(StandardSensitivePropertyProviderFactory.class); + + private final Optional keyHex; + private final Supplier bootstrapPropertiesSupplier; + private final Map providerMap; + + /** + * Creates a StandardSensitivePropertyProviderFactory using the default bootstrap.conf location and + * the keyHex extracted from this bootstrap.conf. + */ + public static SensitivePropertyProviderFactory withDefaults() { + return withKeyAndBootstrapSupplier(null, null); + } + + /** + * Creates a StandardSensitivePropertyProviderFactory using only the provided secret key hex. The default + * bootstrap.conf will be used for any providers that may require it, but the provided keyHex will be used instead + * of the one from the default bootstrap.conf. + * @param keyHex The secret key hex for encrypting properties + * @return A StandardSensitivePropertyProviderFactory + */ + public static SensitivePropertyProviderFactory withKey(final String keyHex) { + return new StandardSensitivePropertyProviderFactory(keyHex, null); + } + + /** + * Creates a new StandardSensitivePropertyProviderFactory using a separate keyHex and provided bootstrap.conf. + * The provided keyHex will be used instead of the one from the bootstrap.conf. + * @param keyHex The secret key hex for encrypting properties + * @param bootstrapPropertiesSupplier A supplier for the BootstrapProperties that represent bootstrap.conf. + * If the supplier returns null, the default bootstrap.conf will be used instead. + * @return A StandardSensitivePropertyProviderFactory + */ + public static SensitivePropertyProviderFactory withKeyAndBootstrapSupplier(final String keyHex, + final Supplier bootstrapPropertiesSupplier) { + return new StandardSensitivePropertyProviderFactory(keyHex, bootstrapPropertiesSupplier); + } + + private StandardSensitivePropertyProviderFactory(final String keyHex, final Supplier bootstrapPropertiesSupplier) { + this.keyHex = Optional.ofNullable(keyHex); + this.bootstrapPropertiesSupplier = bootstrapPropertiesSupplier == null ? () -> null : bootstrapPropertiesSupplier; + this.providerMap = new HashMap<>(); + } + + private String getKeyHex() { + return keyHex.orElseGet(() -> getBootstrapProperties().getBootstrapSensitiveKey() + .orElseThrow(() -> new SensitivePropertyProtectionException("Could not read root key from bootstrap.conf"))); + } + + /** + * Returns the configured bootstrap properties, or the default bootstrap.conf properties if + * not provided. + * @return The bootstrap.conf properties + */ + private BootstrapProperties getBootstrapProperties() { + return Optional.ofNullable(bootstrapPropertiesSupplier.get()).orElseGet(() -> { + try { + return NiFiBootstrapUtils.loadBootstrapProperties(); + } catch (final IOException e) { + logger.error("Error extracting root key from bootstrap.conf for login identity provider decryption", e); + throw new SensitivePropertyProtectionException("Could not read root key from bootstrap.conf"); + } + }); + } + + @Override + public SensitivePropertyProvider getProvider(final PropertyProtectionScheme protectionScheme) throws SensitivePropertyProtectionException { + Objects.requireNonNull(protectionScheme, "Protection scheme is required"); + // Only look up the secret key, which can perform a disk read, if this provider actually requires one + final String keyHex = protectionScheme.requiresSecretKey() ? getKeyHex() : null; + switch (protectionScheme) { + case AES_GCM: + return providerMap.computeIfAbsent(protectionScheme, s -> new AESSensitivePropertyProvider(keyHex)); + // Other providers may choose to pass getBootstrapProperties() into the constructor + default: + throw new SensitivePropertyProtectionException("Unsupported protection scheme " + protectionScheme); + } + } + + @Override + public Collection getSupportedSensitivePropertyProviders() { + return Arrays.stream(PropertyProtectionScheme.values()) + .map(this::getProvider) + .filter(SensitivePropertyProvider::isSupported) + .collect(Collectors.toList()); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy b/nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy similarity index 100% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy rename to nifi-commons/nifi-sensitive-property-provider/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderTest.groovy diff --git a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java new file mode 100644 index 0000000000..f36f259c62 --- /dev/null +++ b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.properties; + +import org.apache.nifi.util.NiFiProperties; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.internal.util.io.IOUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Security; +import java.util.Properties; +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class StandardSensitivePropertyProviderFactoryTest { + + private static final String AES_GCM_128 = "aes/gcm/128"; + private SensitivePropertyProviderFactory factory; + + private static final String BOOTSTRAP_KEY_HEX = "0123456789ABCDEFFEDCBA9876543210"; + private static final String AD_HOC_KEY_HEX = "123456789ABCDEFFEDCBA98765432101"; + + private static Path tempConfDir; + private static Path mockBootstrapConf; + private static Path mockNifiProperties; + + private static NiFiProperties niFiProperties; + + @BeforeClass + public static void initOnce() throws IOException { + Security.addProvider(new BouncyCastleProvider()); + tempConfDir = Files.createTempDirectory("conf"); + mockBootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath(); + + mockNifiProperties = Files.createTempFile("nifi", ".properties").toAbsolutePath(); + + mockBootstrapConf = Files.move(mockBootstrapConf, tempConfDir.resolve("bootstrap.conf")); + mockNifiProperties = Files.move(mockNifiProperties, tempConfDir.resolve("nifi.properties")); + + IOUtil.writeText("nifi.bootstrap.sensitive.key=" + BOOTSTRAP_KEY_HEX, mockBootstrapConf.toFile()); + System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, mockNifiProperties.toString()); + + niFiProperties = new NiFiProperties(); + } + + @AfterClass + public static void tearDownOnce() throws IOException { + Files.deleteIfExists(mockBootstrapConf); + Files.deleteIfExists(mockNifiProperties); + Files.deleteIfExists(tempConfDir); + System.clearProperty(NiFiProperties.PROPERTIES_FILE_PATH); + } + + /** + * Configures the factory using the default bootstrap location. + */ + private void configureDefaultFactory() { + factory = StandardSensitivePropertyProviderFactory.withDefaults(); + } + + /** + * Configures the factory using an ad hoc key hex. + */ + private void configureAdHocKeyFactory() { + factory = StandardSensitivePropertyProviderFactory.withKey(AD_HOC_KEY_HEX); + } + + /** + * Configures the factory using an ad hoc key hex and bootstrap.conf properties. The key should override + * the on in the bootstrap.conf. + */ + private void configureAdHocKeyAndPropertiesFactory() throws IOException { + factory = StandardSensitivePropertyProviderFactory.withKeyAndBootstrapSupplier(AD_HOC_KEY_HEX, mockBootstrapProperties()); + } + + private Supplier mockBootstrapProperties() throws IOException { + final Properties bootstrapProperties = new Properties(); + try (final InputStream inputStream = Files.newInputStream(mockBootstrapConf)) { + bootstrapProperties.load(inputStream); + return () -> new BootstrapProperties("nifi", bootstrapProperties, mockBootstrapConf); + } + } + + @Test + public void testAES_GCM() throws IOException { + configureDefaultFactory(); + + final SensitivePropertyProvider spp = factory.getProvider(PropertyProtectionScheme.AES_GCM); + assertNotNull(spp); + assertTrue(spp.isSupported()); + + final String cleartext = "test"; + assertEquals(cleartext, spp.unprotect(spp.protect(cleartext))); + assertNotEquals(cleartext, spp.protect(cleartext)); + assertEquals(AES_GCM_128, spp.getIdentifierKey()); + + // Key is now different + configureAdHocKeyFactory(); + final SensitivePropertyProvider sppAdHocKey = factory.getProvider(PropertyProtectionScheme.AES_GCM); + assertNotNull(sppAdHocKey); + assertTrue(sppAdHocKey.isSupported()); + assertEquals(AES_GCM_128, sppAdHocKey.getIdentifierKey()); + + assertNotEquals(spp.protect(cleartext), sppAdHocKey.protect(cleartext)); + assertEquals(cleartext, sppAdHocKey.unprotect(sppAdHocKey.protect(cleartext))); + + // This should use the same keyHex as the second one + configureAdHocKeyAndPropertiesFactory(); + final SensitivePropertyProvider sppKeyProperties = factory.getProvider(PropertyProtectionScheme.AES_GCM); + assertNotNull(sppKeyProperties); + assertTrue(sppKeyProperties.isSupported()); + assertEquals(AES_GCM_128, sppKeyProperties.getIdentifierKey()); + + assertEquals(cleartext, sppKeyProperties.unprotect(sppKeyProperties.protect(cleartext))); + } +} diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml index 43bcb2f875..368a67a549 100644 --- a/nifi-commons/pom.xml +++ b/nifi-commons/pom.xml @@ -35,7 +35,9 @@ nifi-metrics nifi-parameter nifi-property-encryptor + nifi-property-utils nifi-properties + nifi-sensitive-property-provider nifi-record nifi-record-path nifi-rocksdb-utils diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java index 5006ca541f..0056ef9707 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java @@ -16,8 +16,38 @@ */ package org.apache.nifi.authorization; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.annotation.AuthorizerContext; +import org.apache.nifi.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.authorization.generated.Authorizers; +import org.apache.nifi.authorization.generated.Property; +import org.apache.nifi.bundle.Bundle; +import org.apache.nifi.nar.ExtensionManager; +import org.apache.nifi.properties.PropertyProtectionScheme; +import org.apache.nifi.properties.SensitivePropertyProtectionException; +import org.apache.nifi.properties.SensitivePropertyProviderFactoryAware; +import org.apache.nifi.security.xml.XmlUtils; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.file.classloader.ClassLoaderUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; import java.io.File; -import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -29,51 +59,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.authorization.annotation.AuthorizerContext; -import org.apache.nifi.authorization.exception.AuthorizationAccessException; -import org.apache.nifi.authorization.exception.AuthorizerCreationException; -import org.apache.nifi.authorization.exception.AuthorizerDestructionException; -import org.apache.nifi.authorization.generated.Authorizers; -import org.apache.nifi.authorization.generated.Property; -import org.apache.nifi.bundle.Bundle; -import org.apache.nifi.nar.ExtensionManager; -import org.apache.nifi.properties.AESSensitivePropertyProviderFactory; -import org.apache.nifi.properties.SensitivePropertyProtectionException; -import org.apache.nifi.properties.SensitivePropertyProvider; -import org.apache.nifi.properties.SensitivePropertyProviderFactory; -import org.apache.nifi.security.kms.CryptoUtils; -import org.apache.nifi.security.xml.XmlUtils; -import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.util.file.classloader.ClassLoaderUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.FactoryBean; -import org.xml.sax.SAXException; /** * Factory bean for loading the configured authorizer. */ -public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup { +public class AuthorizerFactoryBean extends SensitivePropertyProviderFactoryAware + implements FactoryBean, DisposableBean, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup { private static final Logger logger = LoggerFactory.getLogger(AuthorizerFactoryBean.class); private static final String AUTHORIZERS_XSD = "/authorizers.xsd"; private static final String JAXB_GENERATED_PATH = "org.apache.nifi.authorization.generated"; private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); - private static SensitivePropertyProviderFactory SENSITIVE_PROPERTY_PROVIDER_FACTORY; - private static SensitivePropertyProvider SENSITIVE_PROPERTY_PROVIDER; + private NiFiProperties properties; /** * Load the JAXBContext. @@ -87,12 +85,15 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserG } private Authorizer authorizer; - private NiFiProperties properties; private ExtensionManager extensionManager; private final Map userGroupProviders = new HashMap<>(); private final Map accessPolicyProviders = new HashMap<>(); private final Map authorizers = new HashMap<>(); + public void setProperties(final NiFiProperties properties) { + this.properties = properties; + } + @Override public UserGroupProvider getUserGroupProvider(String identifier) { return userGroupProviders.get(identifier); @@ -480,26 +481,8 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserG }; } - private String decryptValue(String cipherText, String encryptionScheme) throws SensitivePropertyProtectionException { - initializeSensitivePropertyProvider(encryptionScheme); - return SENSITIVE_PROPERTY_PROVIDER.unprotect(cipherText); - } - - private static void initializeSensitivePropertyProvider(String encryptionScheme) throws SensitivePropertyProtectionException { - if (SENSITIVE_PROPERTY_PROVIDER == null || !SENSITIVE_PROPERTY_PROVIDER.getIdentifierKey().equalsIgnoreCase(encryptionScheme)) { - try { - String keyHex = getRootKey(); - SENSITIVE_PROPERTY_PROVIDER_FACTORY = new AESSensitivePropertyProviderFactory(keyHex); - SENSITIVE_PROPERTY_PROVIDER = SENSITIVE_PROPERTY_PROVIDER_FACTORY.getProvider(); - } catch (IOException e) { - logger.error("Error extracting root key from bootstrap.conf for login identity provider decryption", e); - throw new SensitivePropertyProtectionException("Could not read root key from bootstrap.conf"); - } - } - } - - private static String getRootKey() throws IOException { - return CryptoUtils.extractKeyFromBootstrapFile(); + private String decryptValue(final String cipherText, final String protectionScheme) throws SensitivePropertyProtectionException { + return getSensitivePropertyProviderFactory().getProvider(PropertyProtectionScheme.fromIdentifier(protectionScheme)).unprotect(cipherText); } @Override @@ -555,10 +538,6 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserG } } - public void setProperties(NiFiProperties properties) { - this.properties = properties; - } - public void setExtensionManager(ExtensionManager extensionManager) { this.extensionManager = extensionManager; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/groovy/org/apache/nifi/authorization/AuthorizerFactoryBeanTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/groovy/org/apache/nifi/authorization/AuthorizerFactoryBeanTest.groovy index dccc46cc14..9a548ba774 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/groovy/org/apache/nifi/authorization/AuthorizerFactoryBeanTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/groovy/org/apache/nifi/authorization/AuthorizerFactoryBeanTest.groovy @@ -17,10 +17,7 @@ package org.apache.nifi.authorization import org.apache.nifi.authorization.generated.Property -import org.apache.nifi.properties.AESSensitivePropertyProvider import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.junit.After -import org.junit.AfterClass import org.junit.Before import org.junit.BeforeClass import org.junit.Test @@ -52,8 +49,10 @@ class AuthorizerFactoryBeanTest extends GroovyTestCase { private static final String PASSWORD = "thisIsABadPassword" + private AuthorizerFactoryBean bean + @BeforeClass - public static void setUpOnce() throws Exception { + static void setUpOnce() throws Exception { Security.addProvider(new BouncyCastleProvider()) logger.metaClass.methodMissing = { String name, args -> @@ -61,29 +60,16 @@ class AuthorizerFactoryBeanTest extends GroovyTestCase { } } - @AfterClass - public static void tearDownOnce() throws Exception { - } - @Before - public void setUp() throws Exception { - AuthorizerFactoryBean.SENSITIVE_PROPERTY_PROVIDER = new AESSensitivePropertyProvider(KEY_HEX) - } - - @After - public void tearDown() throws Exception { - AuthorizerFactoryBean.SENSITIVE_PROPERTY_PROVIDER = null - AuthorizerFactoryBean.SENSITIVE_PROPERTY_PROVIDER_FACTORY = null + void setUp() throws Exception { + bean = new AuthorizerFactoryBean() + bean.configureSensitivePropertyProviderFactory(KEY_HEX, null) } private static boolean isUnlimitedStrengthCryptoAvailable() { Cipher.getMaxAllowedKeyLength("AES") > 128 } - private static int getKeyLength(String keyHex = KEY_HEX) { - keyHex?.size() * 4 - } - @Test void testShouldDecryptValue() { // Arrange @@ -91,7 +77,7 @@ class AuthorizerFactoryBeanTest extends GroovyTestCase { logger.info("Cipher text: ${CIPHER_TEXT}") // Act - String decrypted = new AuthorizerFactoryBean().decryptValue(CIPHER_TEXT, ENCRYPTION_SCHEME) + String decrypted = bean.decryptValue(CIPHER_TEXT, ENCRYPTION_SCHEME) logger.info("Decrypted ${CIPHER_TEXT} -> ${decrypted}") // Assert @@ -107,7 +93,6 @@ class AuthorizerFactoryBeanTest extends GroovyTestCase { List properties = [managerPasswordProperty] logger.info("Manager Password property: ${managerPasswordProperty.dump()}") - def bean = new AuthorizerFactoryBean() // Act def context = bean.loadAuthorizerConfiguration(identifier, properties) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactoryTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactoryTest.groovy index f12a69527a..fe690df2ed 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactoryTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-flowfile-repo-serialization/src/test/groovy/org/apache/nifi/controller/repository/EncryptedRepositoryRecordSerdeFactoryTest.groovy @@ -20,7 +20,6 @@ package org.apache.nifi.controller.repository import org.apache.commons.lang3.SystemUtils import org.apache.nifi.controller.repository.claim.ResourceClaimManager import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager -import org.apache.nifi.properties.StandardNiFiProperties import org.apache.nifi.security.kms.EncryptionException import org.apache.nifi.util.NiFiProperties import org.bouncycastle.jce.provider.BouncyCastleProvider @@ -74,7 +73,7 @@ class EncryptedRepositoryRecordSerdeFactoryTest extends GroovyTestCase { (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY) : KEY_1_HEX, (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID) : KEY_ID ] - mockNiFiProperties = new StandardNiFiProperties(new Properties(flowfileEncryptionProps)) + mockNiFiProperties = new NiFiProperties(new Properties(flowfileEncryptionProps)) } @After @@ -124,7 +123,7 @@ class EncryptedRepositoryRecordSerdeFactoryTest extends GroovyTestCase { @Test void testCreateSerDeShouldFailWithUnpopulatedNiFiProperties() { // Arrange - NiFiProperties emptyNiFiProperties = new StandardNiFiProperties(new Properties([:])) + NiFiProperties emptyNiFiProperties = new NiFiProperties(new Properties([:])) // Act def msg = shouldFail(EncryptionException) { @@ -144,7 +143,7 @@ class EncryptedRepositoryRecordSerdeFactoryTest extends GroovyTestCase { (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY) : KEY_1_HEX, (NiFiProperties.FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID) : KEY_ID ] - NiFiProperties invalidNiFiProperties = new StandardNiFiProperties(new Properties(invalidFlowfileEncryptionProps)) + NiFiProperties invalidNiFiProperties = new NiFiProperties(new Properties(invalidFlowfileEncryptionProps)) // Act def msg = shouldFail(EncryptionException) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/flow/PopularVoteFlowElectionFactoryBeanTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/flow/PopularVoteFlowElectionFactoryBeanTest.groovy index 4e7f7f955f..cb3dac8a69 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/flow/PopularVoteFlowElectionFactoryBeanTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/flow/PopularVoteFlowElectionFactoryBeanTest.groovy @@ -18,7 +18,7 @@ package org.apache.nifi.cluster.coordination.flow import org.apache.nifi.encrypt.PropertyEncryptor import org.apache.nifi.encrypt.PropertyEncryptorFactory -import org.apache.nifi.properties.StandardNiFiProperties + import org.apache.nifi.security.util.EncryptionMethod import org.apache.nifi.util.NiFiProperties import org.junit.Before @@ -51,7 +51,7 @@ class PopularVoteFlowElectionFactoryBeanTest extends GroovyTestCase { } NiFiProperties mockProperties(Map defaults = [:]) { - def mockProps = new StandardNiFiProperties(new Properties([ + def mockProps = new NiFiProperties(new Properties([ (NiFiProperties.SENSITIVE_PROPS_ALGORITHM):DEFAULT_ENCRYPTION_METHOD.algorithm, (NiFiProperties.SENSITIVE_PROPS_PROVIDER):DEFAULT_ENCRYPTION_METHOD.provider, ] + defaults)) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy index d288aede7f..248c1a1f7a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy @@ -18,7 +18,7 @@ package org.apache.nifi.cluster.coordination.http.replication.okhttp -import org.apache.nifi.properties.StandardNiFiProperties + import org.apache.nifi.util.NiFiProperties import org.junit.BeforeClass import org.junit.Test @@ -38,13 +38,13 @@ class OkHttpReplicationClientTest extends GroovyTestCase { } } - private static StandardNiFiProperties mockNiFiProperties() { + private static NiFiProperties mockNiFiProperties() { [getClusterNodeConnectionTimeout: { -> "10 ms" }, getClusterNodeReadTimeout : { -> "10 ms" }, getProperty : { String prop -> logger.mock("Requested getProperty(${prop}) -> \"\"") "" - }] as StandardNiFiProperties + }] as NiFiProperties } @Test @@ -150,7 +150,7 @@ class OkHttpReplicationClientTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_HOST) : "localhost", (NiFiProperties.WEB_HTTPS_PORT) : "51552", ] - NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(propsMap)) + NiFiProperties mockNiFiProperties = new NiFiProperties(new Properties(propsMap)) // Act OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties) @@ -173,7 +173,7 @@ class OkHttpReplicationClientTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_HOST) : "localhost", (NiFiProperties.WEB_HTTPS_PORT) : "51552", ] - NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(flowfileEncryptionProps)) + NiFiProperties mockNiFiProperties = new NiFiProperties(new Properties(flowfileEncryptionProps)) // Act OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties) @@ -197,7 +197,7 @@ class OkHttpReplicationClientTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_HOST) : "localhost", (NiFiProperties.WEB_HTTPS_PORT) : "51552", ] - NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(propsMap)) + NiFiProperties mockNiFiProperties = new NiFiProperties(new Properties(propsMap)) // Act OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties) @@ -221,7 +221,7 @@ class OkHttpReplicationClientTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_HOST) : "localhost", (NiFiProperties.WEB_HTTPS_PORT) : "51552", ] - NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(propsMap)) + NiFiProperties mockNiFiProperties = new NiFiProperties(new Properties(propsMap)) // Act OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties) @@ -248,13 +248,13 @@ class OkHttpReplicationClientTest extends GroovyTestCase { ] + propsMap - NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(propsMap)) - NiFiProperties mockTLSNiFiProperties = new StandardNiFiProperties(new Properties(tlsPropsMap)) + NiFiProperties mockNiFiProperties = new NiFiProperties(new Properties(propsMap)) + NiFiProperties mockTLSNiFiProperties = new NiFiProperties(new Properties(tlsPropsMap)) // Remove the keystore password to create an invalid configuration Map invalidTlsPropsMap = tlsPropsMap invalidTlsPropsMap.remove(NiFiProperties.SECURITY_KEYSTORE_PASSWD) - NiFiProperties mockInvalidTLSNiFiProperties = new StandardNiFiProperties(new Properties(invalidTlsPropsMap)) + NiFiProperties mockInvalidTLSNiFiProperties = new NiFiProperties(new Properties(invalidTlsPropsMap)) // Act OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/cluster/ZooKeeperClientConfigTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/cluster/ZooKeeperClientConfigTest.java index ef832c5ca9..f54c10e538 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/cluster/ZooKeeperClientConfigTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/cluster/ZooKeeperClientConfigTest.java @@ -17,18 +17,16 @@ package org.apache.nifi.cluster; import org.apache.nifi.controller.cluster.ZooKeeperClientConfig; -import org.apache.nifi.properties.StandardNiFiProperties; import org.apache.nifi.util.NiFiProperties; +import org.junit.Test; + +import java.util.Properties; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import org.junit.Test; - -import java.util.Properties; - public class ZooKeeperClientConfigTest { private static final String LOCAL_CONNECT_STRING = "local:1234"; @@ -95,7 +93,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING, LOCAL_CONNECT_STRING); properties.setProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, Boolean.TRUE.toString()); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertTrue(zkClientConfig.isClientSecure()); assertEquals(zkClientConfig.getConnectionSocket(), ZooKeeperClientConfig.NETTY_CLIENT_CNXN_SOCKET); } @@ -106,7 +104,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING, LOCAL_CONNECT_STRING); properties.setProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, Boolean.FALSE.toString()); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertFalse(zkClientConfig.isClientSecure()); assertEquals(zkClientConfig.getConnectionSocket(), ZooKeeperClientConfig.NIO_CLIENT_CNXN_SOCKET); } @@ -117,7 +115,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING, LOCAL_CONNECT_STRING); properties.setProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, ""); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertFalse(zkClientConfig.isClientSecure()); assertEquals(zkClientConfig.getConnectionSocket(), ZooKeeperClientConfig.NIO_CLIENT_CNXN_SOCKET); } @@ -128,7 +126,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING, LOCAL_CONNECT_STRING); properties.setProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, " true "); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertTrue(zkClientConfig.isClientSecure()); assertEquals(zkClientConfig.getConnectionSocket(), ZooKeeperClientConfig.NETTY_CLIENT_CNXN_SOCKET); } @@ -138,9 +136,9 @@ public class ZooKeeperClientConfigTest { final Properties properties = new Properties(); properties.setProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING, LOCAL_CONNECT_STRING); properties.setProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, Boolean.TRUE.toString()); - ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertTrue(zkClientConfig.isClientSecure()); assertEquals(zkClientConfig.getConnectionSocket(), ZooKeeperClientConfig.NETTY_CLIENT_CNXN_SOCKET); } @@ -150,7 +148,7 @@ public class ZooKeeperClientConfigTest { final Properties properties = new Properties(); properties.setProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING, LOCAL_CONNECT_STRING); properties.setProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE, "meh"); - ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); } @Test @@ -161,7 +159,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_TYPE, storeType); properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, storeType); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertEquals(storeType, zkClientConfig.getKeyStoreType()); assertEquals(storeType, zkClientConfig.getTrustStoreType()); } @@ -174,7 +172,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_TYPE, storeType); properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, storeType); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); final String expectedStoreType = "JKS"; assertEquals(expectedStoreType, zkClientConfig.getKeyStoreType()); assertEquals(expectedStoreType, zkClientConfig.getTrustStoreType()); @@ -187,7 +185,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_TYPE, ""); properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, ""); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertNull(zkClientConfig.getKeyStoreType()); assertNull(zkClientConfig.getTrustStoreType()); } @@ -199,7 +197,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_TYPE, " "); properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, " "); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertNull(zkClientConfig.getKeyStoreType()); assertNull(zkClientConfig.getTrustStoreType()); } @@ -219,7 +217,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE, ZOOKEEPER_TRUSTSTORE); properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, ZOOKEEPER_STORE_TYPE); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertEquals(ZOOKEEPER_KEYSTORE, zkClientConfig.getKeyStore()); assertEquals(ZOOKEEPER_TRUSTSTORE, zkClientConfig.getTrustStore()); } @@ -233,7 +231,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, DEFAULT_TRUSTSTORE); properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, DEFAULT_STORE_TYPE); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertEquals(DEFAULT_KEYSTORE, zkClientConfig.getKeyStore()); assertEquals(DEFAULT_TRUSTSTORE, zkClientConfig.getTrustStore()); } @@ -251,7 +249,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, DEFAULT_TRUSTSTORE); properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, DEFAULT_STORE_TYPE); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertEquals(ZOOKEEPER_KEYSTORE, zkClientConfig.getKeyStore()); assertEquals(ZOOKEEPER_TRUSTSTORE, zkClientConfig.getTrustStore()); assertEquals(ZOOKEEPER_STORE_TYPE, zkClientConfig.getKeyStoreType()); @@ -271,7 +269,7 @@ public class ZooKeeperClientConfigTest { properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, DEFAULT_TRUSTSTORE); properties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, DEFAULT_STORE_TYPE); - final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new StandardNiFiProperties(properties)); + final ZooKeeperClientConfig zkClientConfig = ZooKeeperClientConfig.createConfig(new NiFiProperties(properties)); assertEquals(DEFAULT_KEYSTORE, zkClientConfig.getKeyStore()); assertEquals(DEFAULT_TRUSTSTORE, zkClientConfig.getTrustStore()); assertEquals(DEFAULT_STORE_TYPE, zkClientConfig.getKeyStoreType()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServer.java index df0a08e36e..ff12c7e255 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/state/server/TestZooKeeperStateServer.java @@ -16,19 +16,15 @@ */ package org.apache.nifi.controller.state.server; -import org.apache.curator.retry.RetryOneTime; -import org.apache.curator.test.InstanceSpec; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; - +import org.apache.curator.retry.RetryOneTime; +import org.apache.curator.test.InstanceSpec; import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.properties.StandardNiFiProperties; - import org.apache.zookeeper.client.FourLetterWordMain; import org.apache.zookeeper.common.X509Exception.SSLContextException; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; - import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -73,7 +69,7 @@ public class TestZooKeeperStateServer { properties.setProperty(NiFiProperties.STATE_MANAGEMENT_ZOOKEEPER_PROPERTIES, zkServerConfig.toString()); properties.setProperty(NiFiProperties.STATE_MANAGEMENT_START_EMBEDDED_ZOOKEEPER, Boolean.TRUE.toString()); - zkServer = ZooKeeperStateServer.create(new StandardNiFiProperties(properties)); + zkServer = ZooKeeperStateServer.create(new NiFiProperties(properties)); if (zkServer != null) zkServer.start(); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/leader/election/ITSecureClientZooKeeperFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/leader/election/ITSecureClientZooKeeperFactory.java index 9e0a1aee47..b0a4f22c24 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/leader/election/ITSecureClientZooKeeperFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/leader/election/ITSecureClientZooKeeperFactory.java @@ -16,22 +16,18 @@ */ package org.apache.nifi.leader.election; -import org.apache.curator.retry.RetryOneTime; -import org.apache.curator.test.InstanceSpec; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; - -import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.properties.StandardNiFiProperties; -import org.apache.nifi.controller.cluster.ZooKeeperClientConfig; +import org.apache.curator.retry.RetryOneTime; +import org.apache.curator.test.InstanceSpec; import org.apache.nifi.controller.cluster.SecureClientZooKeeperFactory; +import org.apache.nifi.controller.cluster.ZooKeeperClientConfig; import org.apache.nifi.security.util.CertificateUtils; - +import org.apache.nifi.util.NiFiProperties; import org.apache.zookeeper.common.ClientX509Util; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZooKeeperServer; - import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -42,7 +38,6 @@ import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.cert.CertificateException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -50,6 +45,7 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; @@ -218,7 +214,7 @@ public class ITSecureClientZooKeeperFactory { properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_TYPE, trustStoreType); properties.setProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_PASSWD, trustStorePassword); - return new StandardNiFiProperties(properties); + return new NiFiProperties(properties); } public static X509Certificate createKeyStore(final String alias, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml index f49c66dfae..ad5058d67d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/pom.xml @@ -46,6 +46,10 @@ org.apache.nifi nifi-security-utils + + org.apache.nifi + nifi-sensitive-property-provider + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProviderFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProviderFactory.java deleted file mode 100644 index 56a1cc0509..0000000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/AESSensitivePropertyProviderFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.properties; - -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import javax.crypto.NoSuchPaddingException; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java index 250227f9d0..480f801da7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/NiFiPropertiesLoader.java @@ -16,6 +16,12 @@ */ package org.apache.nifi.properties; +import org.apache.nifi.util.NiFiBootstrapUtils; +import org.apache.nifi.util.NiFiProperties; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -25,20 +31,13 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Security; import java.util.Base64; import java.util.List; import java.util.Properties; -import java.util.stream.Collectors; import java.util.Set; -import javax.crypto.Cipher; -import org.apache.nifi.security.kms.CryptoUtils; -import org.apache.nifi.util.NiFiProperties; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.stream.Collectors; public class NiFiPropertiesLoader { @@ -49,12 +48,12 @@ public class NiFiPropertiesLoader { private static final String MIGRATION_INSTRUCTIONS = "See Admin Guide section [Updating the Sensitive Properties Key]"; private static final String PROPERTIES_KEY_MESSAGE = String.format("Sensitive Properties Key [%s] not found: %s", NiFiProperties.SENSITIVE_PROPS_KEY, MIGRATION_INSTRUCTIONS); - private final String defaultPropertiesFilePath = CryptoUtils.getDefaultFilePath(); + private final String defaultPropertiesFilePath = NiFiBootstrapUtils.getDefaultApplicationPropertiesFilePath(); private NiFiProperties instance; private String keyHex; // Future enhancement: allow for external registration of new providers - private static SensitivePropertyProviderFactory sensitivePropertyProviderFactory; + private SensitivePropertyProviderFactory sensitivePropertyProviderFactory; public NiFiPropertiesLoader() { } @@ -69,20 +68,20 @@ public class NiFiPropertiesLoader { * @param keyHex the key used to encrypt any sensitive properties * @return the configured loader */ - public static NiFiPropertiesLoader withKey(String keyHex) { - NiFiPropertiesLoader loader = new NiFiPropertiesLoader(); + public static NiFiPropertiesLoader withKey(final String keyHex) { + final NiFiPropertiesLoader loader = new NiFiPropertiesLoader(); loader.setKeyHex(keyHex); return loader; } /** - * Sets the hexadecimal key used to unprotect properties encrypted with - * {@link AESSensitivePropertyProvider}. If the key has already been set, + * Sets the hexadecimal key used to unprotect properties encrypted with a + * {@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 { @@ -101,68 +100,25 @@ public class NiFiPropertiesLoader { * or nifi.properties files */ public static NiFiProperties loadDefaultWithKeyFromBootstrap() throws IOException { - // The nifi.properties file may not be encrypted, so attempt to naively load it first try { + // The default behavior of StandardSensitivePropertiesFactory is to use the key + // from bootstrap.conf if no key is provided return new NiFiPropertiesLoader().loadDefault(); } catch (Exception e) { logger.warn("Encountered an error naively loading the nifi.properties file because one or more properties are protected: {}", e.getLocalizedMessage()); - } - - try { - String keyHex = CryptoUtils.extractKeyFromBootstrapFile(); - return NiFiPropertiesLoader.withKey(keyHex).loadDefault(); - } catch (IOException e) { - logger.error("Encountered an exception loading the default nifi.properties file {} with the key provided in bootstrap.conf", CryptoUtils.getDefaultFilePath(), e); throw e; } } - /** - * 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 - * @deprecated Use {@link CryptoUtils#extractKeyFromBootstrapFile()} instead. - */ - @Deprecated - public static String extractKeyFromBootstrapFile() throws IOException { - // TODO: Replace all existing uses with direct reference to CryptoUtils - return extractKeyFromBootstrapFile(""); - } - - /** - * 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 - * @return the key in hexadecimal format - * @throws IOException if the file is not readable - * @deprecated Use {@link CryptoUtils#extractKeyFromBootstrapFile(String)} instead. - */ - @Deprecated - public static String extractKeyFromBootstrapFile(String bootstrapPath) throws IOException { - // TODO: Replace all existing uses with direct reference to CryptoUtils - return CryptoUtils.extractKeyFromBootstrapFile(bootstrapPath); - } - private NiFiProperties loadDefault() { return load(defaultPropertiesFilePath); } - 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.withKey(keyHex); } - } - - private void initializeSensitivePropertyProviderFactory() { - sensitivePropertyProviderFactory = new AESSensitivePropertyProviderFactory(keyHex); - } - - private SensitivePropertyProvider getSensitivePropertyProvider() { - initializeSensitivePropertyProviderFactory(); - return sensitivePropertyProviderFactory.getProvider(); + return sensitivePropertyProviderFactory; } /** @@ -209,11 +165,13 @@ public class NiFiPropertiesLoader { * @param file the File containing the serialized properties * @return the NiFiProperties instance */ - public NiFiProperties load(File file) { - ProtectedNiFiProperties protectedNiFiProperties = readProtectedPropertiesFromDisk(file); + public NiFiProperties load(final File file) { + final ProtectedNiFiProperties protectedNiFiProperties = readProtectedPropertiesFromDisk(file); if (protectedNiFiProperties.hasProtectedKeys()) { Security.addProvider(new BouncyCastleProvider()); - protectedNiFiProperties.addSensitivePropertyProvider(getSensitivePropertyProvider()); + getSensitivePropertyProviderFactory() + .getSupportedSensitivePropertyProviders() + .forEach(protectedNiFiProperties::addSensitivePropertyProvider); } return protectedNiFiProperties.getUnprotectedProperties(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java index 16fb463f19..368ff038a6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/main/java/org/apache/nifi/properties/ProtectedNiFiProperties.java @@ -16,35 +16,33 @@ */ package org.apache.nifi.properties; -import static java.util.Arrays.asList; - -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 org.apache.commons.lang3.StringUtils; import org.apache.nifi.util.NiFiProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import static java.util.Arrays.asList; + /** * Decorator class for intermediate phase when {@link NiFiPropertiesLoader} loads the * raw properties file and performs unprotection activities before returning a clean - * implementation of {@link NiFiProperties}, likely {@link StandardNiFiProperties}. + * implementation of {@link NiFiProperties}. * This encapsulates the sensitive property access logic from external consumers * of {@code NiFiProperties}. */ -class ProtectedNiFiProperties extends StandardNiFiProperties { +class ProtectedNiFiProperties extends NiFiProperties implements ProtectedProperties, + SensitivePropertyProtector { private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiProperties.class); - private NiFiProperties niFiProperties; + private SensitivePropertyProtector propertyProtectionDelegate; - private Map localProviderCache = new HashMap<>(); + private NiFiProperties applicationProperties; // Additional "sensitive" property key public static final String ADDITIONAL_SENSITIVE_PROPERTIES_KEY = "nifi.sensitive.props.additional.keys"; @@ -54,7 +52,7 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { SECURITY_KEYSTORE_PASSWD, SECURITY_TRUSTSTORE_PASSWD, SENSITIVE_PROPS_KEY, PROVENANCE_REPO_ENCRYPTION_KEY)); public ProtectedNiFiProperties() { - this(new StandardNiFiProperties()); + this(new NiFiProperties()); } /** @@ -62,9 +60,11 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { * * @param props the NiFiProperties to contain */ - public ProtectedNiFiProperties(NiFiProperties props) { - this.niFiProperties = props; - logger.debug("Loaded {} properties (including {} protection schemes) into ProtectedNiFiProperties", getPropertyKeysIncludingProtectionSchemes().size(), getProtectedPropertyKeys().size()); + public ProtectedNiFiProperties(final NiFiProperties props) { + this.applicationProperties = props; + this.propertyProtectionDelegate = new ApplicationPropertiesProtector<>(this); + logger.debug("Loaded {} properties (including {} protection schemes) into ProtectedNiFiProperties", getApplicationProperties() + .getPropertyKeys().size(), getProtectedPropertyKeys().size()); } /** @@ -72,8 +72,44 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { * * @param rawProps the Properties to contain */ - public ProtectedNiFiProperties(Properties rawProps) { - this(new StandardNiFiProperties(rawProps)); + public ProtectedNiFiProperties(final Properties rawProps) { + this(new NiFiProperties(rawProps)); + } + + @Override + public String getAdditionalSensitivePropertiesKeys() { + return getProperty(getAdditionalSensitivePropertiesKeysName()); + } + + @Override + public String getAdditionalSensitivePropertiesKeysName() { + return ADDITIONAL_SENSITIVE_PROPERTIES_KEY; + } + + @Override + public List getDefaultSensitiveProperties() { + return DEFAULT_SENSITIVE_PROPERTIES; + } + + /** + * Returns the internal representation of the {@link NiFiProperties} -- protected + * or not as determined by the current state. No guarantee is made to the + * protection state of these properties. If the internal reference is null, a new + * {@link NiFiProperties} instance is created. + * + * @return the internal properties + */ + public NiFiProperties getApplicationProperties() { + if (this.applicationProperties == null) { + this.applicationProperties = new NiFiProperties(); + } + + return this.applicationProperties; + } + + @Override + public NiFiProperties createApplicationProperties(final Properties rawProperties) { + return new NiFiProperties(rawProperties); } /** @@ -84,7 +120,7 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { */ @Override public String getProperty(String key) { - return getInternalNiFiProperties().getProperty(key); + return getApplicationProperties().getProperty(key); } /** @@ -94,25 +130,7 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { */ @Override public Set getPropertyKeys() { - Set filteredKeys = getPropertyKeysIncludingProtectionSchemes(); - filteredKeys.removeIf(p -> p.endsWith(".protected")); - return filteredKeys; - } - - /** - * Returns the internal representation of the {@link NiFiProperties} -- protected - * or not as determined by the current state. No guarantee is made to the - * protection state of these properties. If the internal reference is null, a new - * {@link StandardNiFiProperties} instance is created. - * - * @return the internal properties - */ - NiFiProperties getInternalNiFiProperties() { - if (this.niFiProperties == null) { - this.niFiProperties = new StandardNiFiProperties(); - } - - return this.niFiProperties; + return propertyProtectionDelegate.getPropertyKeys(); } /** @@ -130,317 +148,62 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { */ @Override public int size() { - return getPropertyKeys().size(); + return propertyProtectionDelegate.size(); } - /** - * Returns the complete set of property keys, 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(); } - /** - * 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.property.1; nifi.property.2, nifi.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.sensitive.props.additional.keys} property in {@code nifi.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(); - - // This is the Java 8 way, but can likely be optimized (and not sure of correctness) - // Map protectedProperties = sensitiveKeys.stream().filter(key -> - // getProperty(getProtectionKey(key)) != null).collect(Collectors.toMap(Function.identity(), key -> - // getProperty(getProtectionKey(key)))); - - // Groovy - // Map groovyProtectedProperties = sensitiveKeys.collectEntries { key -> - // [(key): getProperty(getProtectionKey(key))] }.findAll { k, v -> v } - - // Traditional way - 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 ProtectedNiFiProperties#ADDITIONAL_SENSITIVE_PROPERTIES_KEY}. - * - * @param key the key - * @return true if it is sensitive - * @see ProtectedNiFiProperties#getSensitivePropertyKeys() - */ - public boolean isPropertySensitive(String key) { - // If the explicit check for ADDITIONAL_SENSITIVE_PROPERTIES_KEY is not here, this will 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 ProtectedNiFiProperties#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. - *

- * Example: - *

- * nifi.sensitive.key=ABCXYZ - * nifi.sensitive.key.protected=aes/gcm/256 - *

- * nifi.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 NiFiProperties} 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 NiFiProperties instance with all raw values - * @throws SensitivePropertyProtectionException if there is a problem unprotecting one or more keys - */ + @Override public NiFiProperties getUnprotectedProperties() throws SensitivePropertyProtectionException { - if (hasProtectedKeys()) { - logger.info("There are {} protected properties of {} sensitive properties ({}%)", - getProtectedPropertyKeys().size(), - getSensitivePropertyKeys().size(), - getPercentOfSensitivePropertiesProtected()); - - Properties rawProperties = new Properties(); - - Set failedKeys = new HashSet<>(); - - for (String key : getPropertyKeys()) { - /* 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 { - rawProperties.setProperty(key, unprotectValue(key, getProperty(key))); - } catch (SensitivePropertyProtectionException e) { - logger.warn("Failed to unprotect '{}'", key, e); - failedKeys.add(key); - } - } else { - rawProperties.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()); - } - } - - NiFiProperties unprotected = new StandardNiFiProperties(rawProperties); - - return unprotected; - } 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 NiFiProperties} 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 StandardNiFiProperties} object - * @throws IllegalStateException if no protection schemes are registered - */ - NiFiProperties 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 NiFiProperties} 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 StandardNiFiProperties} object - */ - NiFiProperties protectPlainProperties(String protectionScheme) { - SensitivePropertyProvider spp = getSensitivePropertyProvider(protectionScheme); - - // Make a new holder (settable) - Properties protectedProperties = new Properties(); - - // Copy over the plain keys - Set plainKeys = getPropertyKeys(); - 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 = getInternalNiFiProperties().getProperty(key); - if (plainValue != null && !plainValue.trim().isEmpty()) { - final String protectedValue = spp.protect(plainValue); - protectedProperties.setProperty(key, protectedValue); - protectedProperties.setProperty(getProtectionKey(key), protectionScheme); - } - } - - return new StandardNiFiProperties(protectedProperties); + @Override + public Map getSensitivePropertyProviders() { + return propertyProtectionDelegate.getSensitivePropertyProviders(); } /** @@ -449,7 +212,7 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { * @param plainProperties the instance to count protected properties * @return the number of protected properties */ - public static int countProtectedProperties(NiFiProperties plainProperties) { + public static int countProtectedProperties(final NiFiProperties plainProperties) { return new ProtectedNiFiProperties(plainProperties).getProtectedPropertyKeys().size(); } @@ -459,7 +222,7 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { * @param plainProperties the instance to count sensitive properties * @return the number of sensitive properties */ - public static int countSensitiveProperties(NiFiProperties plainProperties) { + public static int countSensitiveProperties(final NiFiProperties plainProperties) { return new ProtectedNiFiProperties(plainProperties).getSensitivePropertyKeys().size(); } @@ -475,59 +238,4 @@ class ProtectedNiFiProperties extends StandardNiFiProperties { .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()); - } catch (IllegalArgumentException e) { - throw new SensitivePropertyProtectionException("Error unprotecting value for " + key, e); - } - } - return retrievedValue; - } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderFactoryTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderFactoryTest.groovy deleted file mode 100644 index 26352c0edc..0000000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/AESSensitivePropertyProviderFactoryTest.groovy +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.properties - -import org.apache.commons.lang3.SystemUtils -import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.junit.After -import org.junit.Assume -import org.junit.Before -import org.junit.BeforeClass -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import java.security.Security - -@RunWith(JUnit4.class) -class AESSensitivePropertyProviderFactoryTest extends GroovyTestCase { - private static final Logger logger = LoggerFactory.getLogger(AESSensitivePropertyProviderFactoryTest.class) - - private static final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210" * 2 - - @BeforeClass - public static void setUpOnce() throws Exception { - Assume.assumeTrue("Test only runs on *nix", !SystemUtils.IS_OS_WINDOWS) - Security.addProvider(new BouncyCastleProvider()) - - logger.metaClass.methodMissing = { String name, args -> - logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") - } - } - - @Before - public void setUp() throws Exception { - - } - - @After - public void tearDown() throws Exception { - - } - - @Ignore("This is resolved in PR 1216") - @Test - public void testShouldNotGetProviderWithoutKey() throws Exception { - // Arrange - SensitivePropertyProviderFactory factory = new AESSensitivePropertyProviderFactory() - - // Act - def msg = shouldFail(SensitivePropertyProtectionException) { - SensitivePropertyProvider provider = factory.getProvider() - } - logger.expected(msg) - - // Assert - assert msg == "The provider factory cannot generate providers without a key" - } - - @Test - public void testShouldGetProviderWithKey() throws Exception { - // Arrange - SensitivePropertyProviderFactory factory = new AESSensitivePropertyProviderFactory(KEY_HEX) - - // Act - SensitivePropertyProvider provider = factory.getProvider() - - // Assert - assert provider instanceof AESSensitivePropertyProvider - assert provider.@key - assert provider.@cipher - } - - @Ignore("This is resolved in PR 1216") - @Test - public void testGetProviderShouldHandleEmptyKey() throws Exception { - // Arrange - SensitivePropertyProviderFactory factory = new AESSensitivePropertyProviderFactory("") - - // Act - def msg = shouldFail(SensitivePropertyProtectionException) { - SensitivePropertyProvider provider = factory.getProvider() - } - logger.expected(msg) - - // Assert - assert msg == "The provider factory cannot generate providers without a key" - } -} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/StandardNiFiPropertiesGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/NiFiPropertiesGroovyTest.groovy similarity index 92% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/StandardNiFiPropertiesGroovyTest.groovy rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/NiFiPropertiesGroovyTest.groovy index b73ac42e28..485050bd1b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/StandardNiFiPropertiesGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/NiFiPropertiesGroovyTest.groovy @@ -28,8 +28,8 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory @RunWith(JUnit4.class) -class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { - private static final Logger logger = LoggerFactory.getLogger(StandardNiFiPropertiesGroovyTest.class) +class NiFiPropertiesGroovyTest extends GroovyTestCase { + private static final Logger logger = LoggerFactory.getLogger(NiFiPropertiesGroovyTest.class) private static String originalPropertiesPath = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH) private static final String PREK = NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY @@ -63,18 +63,18 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { } } - private static StandardNiFiProperties loadFromFile(String propertiesFilePath) { + private static NiFiProperties loadFromFile(String propertiesFilePath) { String filePath try { - filePath = StandardNiFiPropertiesGroovyTest.class.getResource(propertiesFilePath).toURI().getPath() + filePath = NiFiPropertiesGroovyTest.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) } System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, filePath) - StandardNiFiProperties properties = new StandardNiFiProperties() + NiFiProperties properties = new NiFiProperties() // clear out existing properties for (String prop : properties.stringPropertyNames()) { @@ -86,8 +86,8 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { inStream = new BufferedInputStream(new FileInputStream(filePath)) properties.load(inStream) } catch (final Exception 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) } finally { if (null != inStream) { try { @@ -108,7 +108,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange // Act - NiFiProperties niFiProperties = new StandardNiFiProperties() + NiFiProperties niFiProperties = new NiFiProperties() logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Assert @@ -125,7 +125,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { assert rawProperties.size() == 1 // Act - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Assert @@ -142,9 +142,9 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { assert rawProperties.size() == 1 // Act - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") - NiFiProperties emptyProperties = new StandardNiFiProperties() + NiFiProperties emptyProperties = new NiFiProperties() logger.info("emptyProperties has ${emptyProperties.size()} properties: ${emptyProperties.getPropertyKeys()}") // Assert @@ -163,7 +163,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210" rawProperties.setProperty(PREKID, KEY_ID) rawProperties.setProperty(PREK, KEY_HEX) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -196,7 +196,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty(PREK, KEY_HEX) rawProperties.setProperty("${PREK}.id.${KEY_ID_2}", KEY_HEX_2) rawProperties.setProperty("${PREK}.id.${KEY_ID_3}", KEY_HEX_3) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -236,7 +236,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty(FFREK, "") rawProperties.setProperty("${FFREK}.id.${KEY_ID}", KEY_HEX) rawProperties.setProperty("${FFREK}.id.${KEY_ID_2}", KEY_HEX_2) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -276,7 +276,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // rawProperties.setProperty(FFREK, "") rawProperties.setProperty("${FFREK}.id.${KEY_ID}", KEY_HEX) rawProperties.setProperty("${FFREK}.id.${KEY_ID_2}", KEY_HEX_2) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -318,7 +318,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // rawProperties.setProperty(FFREK, "") rawProperties.setProperty("${FFREK}.${KEY_ID}", KEY_HEX) rawProperties.setProperty("${FFREK}.${KEY_ID_2}", KEY_HEX_2) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -365,7 +365,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty(FFREK, KEY_HEX_DUP) rawProperties.setProperty("${FFREK}.id.${KEY_ID}", KEY_HEX) rawProperties.setProperty("${FFREK}.id.${KEY_ID_2}", KEY_HEX_2) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -412,7 +412,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty("${FFREK}.id.${KEY_ID}", KEY_HEX) rawProperties.setProperty(FFREK, KEY_HEX_DUP) rawProperties.setProperty(FFREKID, KEY_ID_2) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -445,7 +445,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty("${PREK}.id.${KEY_ID}", KEY_HEX) rawProperties.setProperty("${PREK}.id.${KEY_ID_2}", KEY_HEX_2) rawProperties.setProperty("${PREK}.id.${KEY_ID_3}", KEY_HEX_3) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -467,7 +467,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { void testShouldGetProvenanceRepoEncryptionKeysWithNoneDefined() throws Exception { // Arrange Properties rawProperties = new Properties() - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -492,7 +492,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { final String KEY_ID = "arbitraryKeyId" rawProperties.setProperty(PREKID, KEY_ID) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -527,7 +527,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty("${PREK}.id.${KEY_ID_3}", KEY_HEX_3) rawProperties.setProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS, "some.class.provider") rawProperties.setProperty(NiFiProperties.PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION, "some://url") - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -553,7 +553,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { final String KEY_HEX = "0123456789ABCDEFFEDCBA9876543210" rawProperties.setProperty(CREKID, KEY_ID) rawProperties.setProperty(CREK, KEY_HEX) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -586,7 +586,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty(CREK, KEY_HEX) rawProperties.setProperty("${CREK}.id.${KEY_ID_2}", KEY_HEX_2) rawProperties.setProperty("${CREK}.id.${KEY_ID_3}", KEY_HEX_3) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -619,7 +619,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty("${CREK}.id.${KEY_ID}", KEY_HEX) rawProperties.setProperty("${CREK}.id.${KEY_ID_2}", KEY_HEX_2) rawProperties.setProperty("${CREK}.id.${KEY_ID_3}", KEY_HEX_3) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -641,7 +641,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { void testShouldGetContentRepositoryEncryptionKeysWithNoneDefined() throws Exception { // Arrange Properties rawProperties = new Properties() - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -666,7 +666,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { final String KEY_ID = "arbitraryKeyId" rawProperties.setProperty(CREKID, KEY_ID) - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -701,7 +701,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { rawProperties.setProperty("${CREK}.id.${KEY_ID_3}", KEY_HEX_3) rawProperties.setProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS, "some.class.provider") rawProperties.setProperty(NiFiProperties.CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION, "some://url") - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Act @@ -724,7 +724,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String noLeadingSlash = "some/context/path" Properties rawProps = new Properties(["nifi.web.proxy.context.path": noLeadingSlash]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw context path property [${noLeadingSlash}]") // Act @@ -740,7 +740,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String leadingSlash = "/some/context/path" Properties rawProps = new Properties(["nifi.web.proxy.context.path": leadingSlash]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw context path property [${leadingSlash}]") // Act @@ -760,7 +760,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { List paths = [noLeadingSlash, leadingSlash, leadingAndTrailingSlash] String combinedPaths = paths.join(",") Properties rawProps = new Properties(["nifi.web.proxy.context.path": combinedPaths]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw context path property [${noLeadingSlash}]") // Act @@ -780,7 +780,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String leadingSlash = "/some/context/path" Properties rawProps = new Properties(["nifi.web.proxy.context.path": leadingSlash]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw context path property [${leadingSlash}]") // Act @@ -801,7 +801,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { List paths = [noLeadingSlash, leadingSlash, leadingAndTrailingSlash] String combinedPaths = paths.join(",") Properties rawProps = new Properties(["nifi.web.proxy.context.path": combinedPaths]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw context path property [${noLeadingSlash}]") // Act @@ -818,7 +818,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String empty = "" Properties rawProps = new Properties(["nifi.web.proxy.context.path": empty]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw context path property [${empty}]") // Act @@ -834,7 +834,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String extraSpaceHostname = "somehost.com " Properties rawProps = new Properties(["nifi.web.proxy.host": extraSpaceHostname]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw proxy host property [${extraSpaceHostname}]") // Act @@ -851,7 +851,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String hostname = "somehost.com" Properties rawProps = new Properties(["nifi.web.proxy.host": hostname]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw proxy host property [${hostname}]") // Act @@ -872,7 +872,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { List hosts = [extraSpaceHostname, normalHostname, hostnameWithPort, extraSpaceHostnameWithPort] String combinedHosts = hosts.join(",") Properties rawProps = new Properties(["nifi.web.proxy.host": combinedHosts]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw proxy host property [${combinedHosts}]") // Act @@ -893,7 +893,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String normalHostname = "someotherhost.com" Properties rawProps = new Properties(["nifi.web.proxy.host": normalHostname]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw proxy host property [${normalHostname}]") // Act @@ -915,7 +915,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { List hosts = [extraSpaceHostname, normalHostname, hostnameWithPort, extraSpaceHostnameWithPort] String combinedHosts = hosts.join(",") Properties rawProps = new Properties(["nifi.web.proxy.host": combinedHosts]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw proxy host property [${combinedHosts}]") // Act @@ -932,7 +932,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String empty = "" Properties rawProps = new Properties(["nifi.web.proxy.host": empty]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw proxy host property [${empty}]") // Act @@ -948,7 +948,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange String empty = "" Properties rawProps = new Properties(["nifi.web.proxy.host": empty]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with raw proxy host property [${empty}]") // Act @@ -997,7 +997,7 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase { void testWebMaxContentSizeShouldDefaultToEmpty() { // Arrange Properties rawProps = new Properties(["nifi.web.max.content.size": ""]) - NiFiProperties props = new StandardNiFiProperties(rawProps) + NiFiProperties props = new NiFiProperties(rawProps) logger.info("Created a NiFiProperties instance with empty web max content size property") // Act diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/NiFiPropertiesLoaderGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/NiFiPropertiesLoaderGroovyTest.groovy index ef9ff4b07b..b73a97024f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/NiFiPropertiesLoaderGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/NiFiPropertiesLoaderGroovyTest.groovy @@ -17,6 +17,7 @@ package org.apache.nifi.properties import org.apache.commons.lang3.SystemUtils +import org.apache.nifi.util.NiFiBootstrapUtils import org.apache.nifi.util.NiFiProperties import org.apache.nifi.util.file.FileUtils import org.bouncycastle.jce.provider.BouncyCastleProvider @@ -89,7 +90,6 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { // if (ProtectedNiFiProperties.@localProviderCache) { // ProtectedNiFiProperties.@localProviderCache = [:] // } - NiFiPropertiesLoader.@sensitivePropertyProviderFactory = null } @AfterClass @@ -123,31 +123,6 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { assert niFiPropertiesLoader.@keyHex == KEY_HEX } - @Test - void testShouldGetDefaultProviderKey() throws Exception { - // Arrange - final String EXPECTED_PROVIDER_KEY = "aes/gcm/${Cipher.getMaxAllowedKeyLength("AES") > 128 ? 256 : 128}" - logger.info("Expected provider key: ${EXPECTED_PROVIDER_KEY}") - - // Act - String defaultKey = NiFiPropertiesLoader.getDefaultProviderKey() - logger.info("Default key: ${defaultKey}") - // Assert - assert defaultKey == EXPECTED_PROVIDER_KEY - } - - @Test - void testShouldInitializeSensitivePropertyProviderFactory() throws Exception { - // Arrange - NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader() - - // Act - niFiPropertiesLoader.initializeSensitivePropertyProviderFactory() - - // Assert - assert niFiPropertiesLoader.@sensitivePropertyProviderFactory - } - @Test void testShouldLoadUnprotectedPropertiesFromFile() throws Exception { // Arrange @@ -161,7 +136,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { assert niFiProperties.size() > 0 // Ensure it is not a ProtectedNiFiProperties - assert niFiProperties instanceof StandardNiFiProperties + assert !(niFiProperties instanceof ProtectedNiFiProperties) } @Test @@ -180,7 +155,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { assert niFiProperties.size() > 0 // Ensure it is not a ProtectedNiFiProperties - assert niFiProperties instanceof StandardNiFiProperties + assert !(niFiProperties instanceof ProtectedNiFiProperties) } @Test @@ -193,13 +168,13 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { logger.info("Set ${NiFiProperties.PROPERTIES_FILE_PATH} to ${protectedFile.absolutePath}") // Act - def msg = shouldFail(IOException) { + def msg = shouldFail(SensitivePropertyProtectionException) { NiFiProperties niFiProperties = NiFiPropertiesLoader.loadDefaultWithKeyFromBootstrap() } logger.expected(msg) // Assert - assert msg =~ "Cannot read from bootstrap.conf" + assert msg =~ "Could not read root key from bootstrap.conf" } @Test @@ -308,7 +283,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { assert niFiProperties.size() > 0 // Ensure it is not a ProtectedNiFiProperties - assert niFiProperties instanceof StandardNiFiProperties + assert !(niFiProperties instanceof ProtectedNiFiProperties) } @Test @@ -348,7 +323,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { } // Ensure it is not a ProtectedNiFiProperties - assert niFiProperties instanceof StandardNiFiProperties + assert !(niFiProperties instanceof ProtectedNiFiProperties) } @Test @@ -358,7 +333,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath) // Act - String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile() + String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile() // Assert assert key == KEY_HEX @@ -371,7 +346,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath) // Act - String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile() + String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile() // Assert assert key == "" @@ -384,7 +359,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath) // Act - String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile() + String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile() // Assert assert key == "" @@ -398,7 +373,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { // Act def msg = shouldFail(IOException) { - String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile() + String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile() } logger.expected(msg) @@ -419,7 +394,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { // Act def msg = shouldFail(IOException) { - String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile() + String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile() } logger.expected(msg) @@ -444,7 +419,7 @@ class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase { // Act def msg = shouldFail(IOException) { - String key = NiFiPropertiesLoader.extractKeyFromBootstrapFile() + String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile() } logger.expected(msg) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy index 034c6cae1d..f902dbcce4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/ProtectedNiFiPropertiesGroovyTest.groovy @@ -55,6 +55,9 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { private static String originalPropertiesPath = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH) + private static SensitivePropertyProviderFactory sensitivePropertyProviderFactory = + StandardSensitivePropertyProviderFactory.withKey(KEY_HEX) + @BeforeClass static void setUpOnce() throws Exception { Assume.assumeTrue("Test only runs on *nix", !SystemUtils.IS_OS_WINDOWS) @@ -85,8 +88,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { try { filePath = ProtectedNiFiPropertiesGroovyTest.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) } File file = new File(filePath) @@ -109,14 +111,14 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { // If it has protected keys, inject the SPP if (protectedNiFiProperties.hasProtectedKeys()) { - protectedNiFiProperties.addSensitivePropertyProvider(new AESSensitivePropertyProvider(KEY_HEX)) + protectedNiFiProperties.addSensitivePropertyProvider(sensitivePropertyProviderFactory + .getProvider(PropertyProtectionScheme.AES_GCM)) } return protectedNiFiProperties } catch (final Exception ex) { logger.error("Cannot load properties file due to " + ex.getLocalizedMessage()) - throw new RuntimeException("Cannot load properties file due to " - + ex.getLocalizedMessage(), ex) + throw new RuntimeException("Cannot load properties file due to " + ex.getLocalizedMessage(), ex) } finally { if (null != inStream) { try { @@ -135,7 +137,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { // Arrange // Act - NiFiProperties niFiProperties = new StandardNiFiProperties() + NiFiProperties niFiProperties = new NiFiProperties() logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Assert @@ -152,7 +154,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { assert rawProperties.size() == 1 // Act - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") // Assert @@ -166,7 +168,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { Properties rawProperties = new Properties() rawProperties.setProperty("key", "value") rawProperties.setProperty("key.protected", "value2") - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") assert niFiProperties.size() == 2 @@ -190,9 +192,9 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { assert rawProperties.size() == 1 // Act - NiFiProperties niFiProperties = new StandardNiFiProperties(rawProperties) + NiFiProperties niFiProperties = new NiFiProperties(rawProperties) logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}") - NiFiProperties emptyProperties = new StandardNiFiProperties() + NiFiProperties emptyProperties = new NiFiProperties() logger.info("emptyProperties has ${emptyProperties.size()} properties: ${emptyProperties.getPropertyKeys()}") // Assert @@ -360,7 +362,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { * @throws Exception */ @Test - void testGetValueOfSensitivePropertyShouldHandleUnknownProtectionScheme() throws Exception { + void testGetValueOfSensitivePropertyShouldFailOnUnknownProtectionScheme() throws Exception { // Arrange final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd" @@ -375,16 +377,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, so throw an exception logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}") // Act - NiFiProperties unprotectedProperties = properties.getUnprotectedProperties() - String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY) - logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}") + def msg = shouldFail(IllegalStateException) { + NiFiProperties unprotectedProperties = properties.getUnprotectedProperties() + String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY) + logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}") + } // Assert - assert retrievedKeystorePassword == RAW_KEYSTORE_PASSWORD + assert msg == "No provider available for nifi.sensitive.props.key" assert isSensitive assert isProtected } @@ -439,7 +443,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { ProtectedNiFiProperties properties = loadFromFile("/conf/nifi_with_sensitive_properties_protected_aes_multiple_malformed.properties") // Iterate over the protected keys and track the ones that fail to decrypt - SensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + SensitivePropertyProvider spp = sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.AES_GCM) Set malformedKeys = properties.getProtectedPropertyKeys() .findAll { String key, String scheme -> scheme == spp.identifierKey } .keySet().collect { String key -> @@ -538,11 +542,11 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { // TODO: Test getProtected with multiple providers /** - * In the protection enabled scenario, a call to retrieve a sensitive property should handle if the internal cache of providers is empty. + * In the protection enabled scenario, a call to retrieve a sensitive property should fail if the internal cache of providers is empty. * @throws Exception */ @Test - void testGetValueOfSensitivePropertyShouldHandleInvalidatedInternalCache() throws Exception { + void testGetValueOfSensitivePropertyShouldFailOnInvalidatedInternalCache() throws Exception { // Arrange final String KEYSTORE_PASSWORD_KEY = "nifi.security.keystorePasswd" final String EXPECTED_KEYSTORE_PASSWORD = "thisIsABadKeystorePassword" @@ -553,19 +557,21 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { logger.info("Read raw value from properties: ${RAW_PASSWORD}") // 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 - NiFiProperties unprotectedProperties = properties.getUnprotectedProperties() - String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY) - logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}") + def msg = shouldFail(IllegalStateException) { + NiFiProperties unprotectedProperties = properties.getUnprotectedProperties() + String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY) + logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}") + } // Assert - assert retrievedKeystorePassword == RAW_PASSWORD + assert msg == "No provider available for nifi.sensitive.props.key" assert isSensitive assert isProtected } @@ -614,6 +620,10 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { assert !unprotectedPasswordIsProtected } + private static double getPercentOfSensitivePropertiesProtected(final ProtectedNiFiProperties properties) { + return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100); + } + @Test void testShouldGetPercentageOfSensitivePropertiesProtected_0() throws Exception { // Arrange @@ -623,7 +633,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 @@ -639,7 +649,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 @@ -655,7 +665,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 @@ -666,13 +676,13 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { void testInstanceWithNoProtectedPropertiesShouldNotLoadSPP() throws Exception { // Arrange ProtectedNiFiProperties properties = loadFromFile("/conf/nifi.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 @@ -706,7 +716,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { assert properties.getSensitivePropertyProviders().isEmpty() // Act - def msg = shouldFail(IllegalArgumentException) { + def msg = shouldFail(NullPointerException) { properties.addSensitivePropertyProvider(null) } logger.expected(msg) @@ -756,7 +766,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { ProtectedNiFiProperties protectedNiFiProperties = loadFromFile(noProtectedPropertiesPath) logger.info("Loaded ${protectedNiFiProperties.size()} properties from ${noProtectedPropertiesPath}") - int hashCode = protectedNiFiProperties.internalNiFiProperties.hashCode() + int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode() logger.info("Hash code of internal instance: ${hashCode}") // Act @@ -766,7 +776,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 @@ -781,7 +791,7 @@ class ProtectedNiFiPropertiesGroovyTest extends GroovyTestCase { int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size() int protectionSchemeCount = protectedNiFiProperties - .getPropertyKeys().findAll { it.endsWith(".protected") } + .getPropertyKeys().findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) } .size() int expectedUnprotectedPropertyCount = protectedNiFiProperties.size() - protectionSchemeCount @@ -797,7 +807,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 @@ -807,7 +817,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 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/test/groovy/org/apache/nifi/NiFiGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/test/groovy/org/apache/nifi/NiFiGroovyTest.groovy index 69dc5b0412..5dda4e194c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/test/groovy/org/apache/nifi/NiFiGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/test/groovy/org/apache/nifi/NiFiGroovyTest.groovy @@ -18,9 +18,11 @@ package org.apache.nifi import ch.qos.logback.classic.spi.LoggingEvent import ch.qos.logback.core.AppenderBase -import org.apache.nifi.properties.AESSensitivePropertyProvider +import org.apache.nifi.properties.ApplicationPropertiesProtector import org.apache.nifi.properties.NiFiPropertiesLoader -import org.apache.nifi.properties.StandardNiFiProperties +import org.apache.nifi.properties.PropertyProtectionScheme +import org.apache.nifi.properties.SensitivePropertyProvider +import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory import org.apache.nifi.util.NiFiProperties import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.After @@ -34,7 +36,6 @@ import org.slf4j.LoggerFactory import org.slf4j.bridge.SLF4JBridgeHandler import javax.crypto.Cipher -import java.nio.file.Paths import java.security.Security @RunWith(JUnit4.class) @@ -64,7 +65,6 @@ class NiFiGroovyTest extends GroovyTestCase { @After void tearDown() throws Exception { - NiFiPropertiesLoader.@sensitivePropertyProviderFactory = null TestAppender.reset() System.setIn(System.in) } @@ -166,7 +166,7 @@ class NiFiGroovyTest extends GroovyTestCase { System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, testPropertiesPath) def protectedNiFiProperties = new NiFiPropertiesLoader().readProtectedPropertiesFromDisk(new File(testPropertiesPath)) - NiFiProperties unprocessedProperties = protectedNiFiProperties.internalNiFiProperties + NiFiProperties unprocessedProperties = protectedNiFiProperties.getApplicationProperties() def protectedKeys = getProtectedKeys(unprocessedProperties) logger.info("Reading from raw properties file gives protected properties: ${protectedKeys}") @@ -198,29 +198,30 @@ class NiFiGroovyTest extends GroovyTestCase { } private static boolean hasProtectedKeys(NiFiProperties properties) { - properties.getPropertyKeys().any { it.endsWith(".protected") } + properties.getPropertyKeys().any { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) } } private static Map getProtectedPropertyKeys(NiFiProperties properties) { getProtectedKeys(properties).collectEntries { String key -> - [(key): properties.getProperty(key + ".protected")] + [(key): properties.getProperty(key + ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)] } } private static Set getProtectedKeys(NiFiProperties properties) { - properties.getPropertyKeys().findAll { it.endsWith(".protected") }.collect { it - ".protected" } + properties.getPropertyKeys().findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }.collect { it - ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX } } private static NiFiProperties decrypt(NiFiProperties encryptedProperties, String keyHex) { - AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(keyHex) + SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(keyHex) + .getProvider(PropertyProtectionScheme.AES_GCM) def map = encryptedProperties.getPropertyKeys().collectEntries { String key -> - if (encryptedProperties.getProperty(key + ".protected") == spp.getIdentifierKey()) { + if (encryptedProperties.getProperty(key + ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) == spp.getIdentifierKey()) { [(key): spp.unprotect(encryptedProperties.getProperty(key))] - } else if (!key.endsWith(".protected")) { + } else if (!key.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)) { [(key): encryptedProperties.getProperty(key)] } } - new StandardNiFiProperties(map as Properties) + new NiFiProperties(map as Properties) } @Test diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/groovy/org/apache/nifi/remote/StandardPublicPortGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/groovy/org/apache/nifi/remote/StandardPublicPortGroovyTest.groovy index e9e9b6ca3a..3a09a786f5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/groovy/org/apache/nifi/remote/StandardPublicPortGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/groovy/org/apache/nifi/remote/StandardPublicPortGroovyTest.groovy @@ -16,12 +16,10 @@ */ package org.apache.nifi.remote - import org.apache.nifi.authorization.Authorizer import org.apache.nifi.connectable.Connectable import org.apache.nifi.connectable.ConnectableType import org.apache.nifi.controller.ProcessScheduler -import org.apache.nifi.properties.StandardNiFiProperties import org.apache.nifi.remote.protocol.CommunicationsSession import org.apache.nifi.remote.protocol.ServerProtocol import org.apache.nifi.reporting.BulletinRepository @@ -87,7 +85,7 @@ class StandardPublicPortGroovyTest extends GroovyTestCase { logger.mock("getProperty(${prop}) -> ${value}") value }, - ] as StandardNiFiProperties + ] as NiFiProperties StandardPublicPort port = createPublicPort(mockProps) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestHttpRemoteSiteListener.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestHttpRemoteSiteListener.java index fbad70f9d8..099efab42d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestHttpRemoteSiteListener.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestHttpRemoteSiteListener.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.apache.nifi.processor.ProcessSession; -import org.apache.nifi.properties.StandardNiFiProperties; import org.apache.nifi.remote.protocol.FlowFileTransaction; import org.apache.nifi.remote.protocol.HandshakeProperties; import org.apache.nifi.util.NiFiProperties; @@ -40,7 +39,7 @@ public class TestHttpRemoteSiteListener { @Test public void testNormalTransactionProgress() { - HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties()); + HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new NiFiProperties()); String transactionId = transactionManager.createTransaction(); assertTrue("Transaction should be active.", transactionManager.isTransactionActive(transactionId)); @@ -60,7 +59,7 @@ public class TestHttpRemoteSiteListener { @Test public void testDuplicatedTransactionId() { - HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties()); + HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new NiFiProperties()); String transactionId = transactionManager.createTransaction(); assertTrue("Transaction should be active.", transactionManager.isTransactionActive(transactionId)); @@ -79,7 +78,7 @@ public class TestHttpRemoteSiteListener { @Test public void testNoneExistingTransaction() { - HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties()); + HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new NiFiProperties()); String transactionId = "does-not-exist-1"; assertFalse("Transaction should not be active.", transactionManager.isTransactionActive(transactionId)); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestPeerDescriptionModifier.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestPeerDescriptionModifier.java index cf6639f348..122cc3ff28 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestPeerDescriptionModifier.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestPeerDescriptionModifier.java @@ -17,7 +17,6 @@ package org.apache.nifi.remote; import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageException; -import org.apache.nifi.properties.StandardNiFiProperties; import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol; import org.apache.nifi.util.NiFiProperties; import org.junit.Test; @@ -37,7 +36,7 @@ public class TestPeerDescriptionModifier { @Test public void testNoConfiguration() { Properties props = new Properties(); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); final PeerDescriptionModifier modifier = new PeerDescriptionModifier(properties); assertFalse(modifier.isModificationNeeded(SiteToSiteTransportProtocol.RAW)); assertFalse(modifier.isModificationNeeded(SiteToSiteTransportProtocol.HTTP)); @@ -47,7 +46,7 @@ public class TestPeerDescriptionModifier { public void testInvalidNoHostname() { Properties props = new Properties(); props.put("nifi.remote.route.raw.no-host.when", "true"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); try { new PeerDescriptionModifier(properties); fail("Should throw an Exception"); @@ -61,7 +60,7 @@ public class TestPeerDescriptionModifier { Properties props = new Properties(); props.put("nifi.remote.route.raw.no-port.when", "true"); props.put("nifi.remote.route.raw.no-port.hostname", "proxy.example.com"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); try { new PeerDescriptionModifier(properties); fail("Should throw an Exception"); @@ -78,7 +77,7 @@ public class TestPeerDescriptionModifier { props.put("nifi.remote.route.raw.invalid-name.port", "8081"); props.put("nifi.remote.route.raw.invalid-name.secure", "true"); props.put("nifi.remote.route.raw.invalid-name.unsupported", "true"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); try { new PeerDescriptionModifier(properties); fail("Should throw an Exception"); @@ -93,7 +92,7 @@ public class TestPeerDescriptionModifier { public void testInvalidPropertyKeyNoProtocol() { Properties props = new Properties(); props.put("nifi.remote.route.", "true"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); try { new PeerDescriptionModifier(properties); fail("Should throw an Exception"); @@ -108,7 +107,7 @@ public class TestPeerDescriptionModifier { public void testInvalidPropertyKeyNoName() { Properties props = new Properties(); props.put("nifi.remote.route.http.", "true"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); try { new PeerDescriptionModifier(properties); fail("Should throw an Exception"); @@ -125,7 +124,7 @@ public class TestPeerDescriptionModifier { props.put("nifi.remote.route.raw.invalid-el.when", "${nonExistingFunction()}"); props.put("nifi.remote.route.raw.invalid-el.hostname", "proxy.example.com"); props.put("nifi.remote.route.raw.invalid-el.port", "8081"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); final PeerDescriptionModifier modifier = new PeerDescriptionModifier(properties); final PeerDescription source = new PeerDescription("client", 12345, true); @@ -146,7 +145,7 @@ public class TestPeerDescriptionModifier { props.put("nifi.remote.route.raw.no-port.when", "true"); props.put("nifi.remote.route.raw.no-port.hostname", "proxy.example.com"); props.put("nifi.remote.route.raw.no-port.port", "8443"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); final PeerDescriptionModifier modifier = new PeerDescriptionModifier(properties); final PeerDescription source = new PeerDescription("client", 12345, true); @@ -178,7 +177,7 @@ public class TestPeerDescriptionModifier { props.put("nifi.remote.input.socket.port", "8081"); props.put("nifi.remote.input.http.enabled", "true"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); final PeerDescriptionModifier modifier = new PeerDescriptionModifier(properties); // For requests coming from the proxy server, modify target description, @@ -230,7 +229,7 @@ public class TestPeerDescriptionModifier { props.put("nifi.remote.input.socket.port", "8081"); props.put("nifi.remote.input.http.enabled", "true"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); final PeerDescriptionModifier modifier = new PeerDescriptionModifier(properties); // For requests coming from the proxy server, modify target description, @@ -284,7 +283,7 @@ public class TestPeerDescriptionModifier { props.put("nifi.remote.input.http.enabled", "true"); - final NiFiProperties properties = new StandardNiFiProperties(props); + final NiFiProperties properties = new NiFiProperties(props); final PeerDescriptionModifier modifier = new PeerDescriptionModifier(properties); // For requests coming from the proxy server, modify target description, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/protocol/http/TestHttpFlowFileServerProtocol.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/protocol/http/TestHttpFlowFileServerProtocol.java index 529dfe62a9..e63e901f8e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/protocol/http/TestHttpFlowFileServerProtocol.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/protocol/http/TestHttpFlowFileServerProtocol.java @@ -25,7 +25,6 @@ import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.Relationship; -import org.apache.nifi.properties.StandardNiFiProperties; import org.apache.nifi.provenance.ProvenanceEventRecord; import org.apache.nifi.provenance.ProvenanceEventType; import org.apache.nifi.remote.HttpRemoteSiteListener; @@ -109,7 +108,7 @@ public class TestHttpFlowFileServerProtocol { private HttpFlowFileServerProtocol getDefaultHttpFlowFileServerProtocol() { final StandardVersionNegotiator versionNegotiator = new StandardVersionNegotiator(5, 4, 3, 2, 1); - return new StandardHttpFlowFileServerProtocol(versionNegotiator, new StandardNiFiProperties()); + return new StandardHttpFlowFileServerProtocol(versionNegotiator, new NiFiProperties()); } @Test @@ -367,7 +366,7 @@ public class TestHttpFlowFileServerProtocol { sessionState.getFlowFileQueue().offer(flowFile); } - final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties()); + final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(new NiFiProperties()); serverProtocol.handshake(peer); assertTrue(serverProtocol.isHandshakeSuccessful()); @@ -522,7 +521,7 @@ public class TestHttpFlowFileServerProtocol { } private void receiveFlowFiles(final HttpFlowFileServerProtocol serverProtocol, final String transactionId, final Peer peer, final DataPacket ... dataPackets) throws IOException { - final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties()); + final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(new NiFiProperties()); final HttpServerCommunicationsSession commsSession = (HttpServerCommunicationsSession) peer.getCommunicationsSession(); serverProtocol.handshake(peer); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy index 6842e4c3e7..d00ed76faf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy @@ -23,7 +23,6 @@ import org.apache.nifi.nar.ExtensionManagerHolder import org.apache.nifi.nar.ExtensionMapping import org.apache.nifi.nar.SystemBundle import org.apache.nifi.processor.DataUnit -import org.apache.nifi.properties.StandardNiFiProperties import org.apache.nifi.security.util.StandardTlsConfiguration import org.apache.nifi.security.util.TlsConfiguration import org.apache.nifi.security.util.TlsPlatform @@ -50,6 +49,11 @@ import org.junit.contrib.java.lang.system.SystemErrRule import org.junit.contrib.java.lang.system.SystemOutRule import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mockito +import org.mockito.invocation.InvocationOnMock +import org.mockito.stubbing.Answer import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -87,7 +91,7 @@ class JettyServerGroovyTest extends GroovyTestCase { // These protocol versions should not ever be supported static private final List LEGACY_TLS_PROTOCOLS = ["TLS", "TLSv1", "TLSv1.1", "SSL", "SSLv2", "SSLv2Hello", "SSLv3"] - NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: new Properties([ + NiFiProperties httpsProps = new NiFiProperties(new Properties([ (NiFiProperties.WEB_HTTPS_PORT) : HTTPS_PORT as String, (NiFiProperties.WEB_HTTPS_HOST) : HTTPS_HOSTNAME, (NiFiProperties.SECURITY_KEYSTORE) : KEYSTORE_PATH, @@ -134,15 +138,16 @@ class JettyServerGroovyTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com", (NiFiProperties.WEB_THREADS) : NiFiProperties.DEFAULT_WEB_THREADS ] - NiFiProperties mockProps = [ - getPort : { -> 8080 }, - getSslPort : { -> 8443 }, - getProperty: { String prop -> - String value = badProps[prop] ?: "no_value" - logger.mock("getProperty(${prop}) -> ${value}") - value - }, - ] as StandardNiFiProperties + NiFiProperties mockProps = Mockito.mock(NiFiProperties.class) + Mockito.when(mockProps.getPort()).thenReturn(8080) + Mockito.when(mockProps.getSslPort()).thenReturn(8443) + + Mockito.when(mockProps.getProperty(ArgumentMatchers.anyString())).thenAnswer(new Answer() { + @Override + Object answer(InvocationOnMock invocation) throws Throwable { + badProps[(String) invocation.getArgument(0)] ?: "no_value" + } + }) // Act boolean bothConfigsPresent = JettyServer.bothHttpAndHttpsConnectorsConfigured(mockProps) @@ -170,7 +175,7 @@ class JettyServerGroovyTest extends GroovyTestCase { logger.mock("getProperty(${prop}) -> ${value}") value }, - ] as StandardNiFiProperties + ] as NiFiProperties Map httpsMap = [ (NiFiProperties.WEB_HTTP_HOST) : null, @@ -184,7 +189,7 @@ class JettyServerGroovyTest extends GroovyTestCase { logger.mock("getProperty(${prop}) -> ${value}") value }, - ] as StandardNiFiProperties + ] as NiFiProperties // Act boolean bothConfigsPresentForHttp = JettyServer.bothHttpAndHttpsConnectorsConfigured(httpProps) @@ -221,7 +226,7 @@ class JettyServerGroovyTest extends GroovyTestCase { getWebThreads : { -> NiFiProperties.DEFAULT_WEB_THREADS }, getWebMaxHeaderSize: { -> NiFiProperties.DEFAULT_WEB_MAX_HEADER_SIZE }, isHTTPSConfigured : { -> true } - ] as StandardNiFiProperties + ] as NiFiProperties // The web server should fail to start and exit Java exit.expectSystemExitWithStatus(1) @@ -253,7 +258,7 @@ class JettyServerGroovyTest extends GroovyTestCase { // Arrange final String externalHostname = "localhost" - NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: new Properties([ + NiFiProperties httpsProps = new NiFiProperties(new Properties([ (NiFiProperties.WEB_HTTPS_PORT): HTTPS_PORT as String, (NiFiProperties.WEB_HTTPS_HOST): externalHostname, (NiFiProperties.SECURITY_KEYSTORE): "src/test/resources/multiple_cert_keystore.p12", @@ -285,7 +290,7 @@ class JettyServerGroovyTest extends GroovyTestCase { // Arrange final String externalHostname = "localhost" - NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: new Properties([ + NiFiProperties httpsProps = new NiFiProperties(new Properties([ (NiFiProperties.WEB_HTTPS_PORT): HTTPS_PORT as String, (NiFiProperties.WEB_HTTPS_HOST): externalHostname, (NiFiProperties.SECURITY_KEYSTORE): "src/test/resources/multiple_cert_keystore.jks", @@ -308,7 +313,7 @@ class JettyServerGroovyTest extends GroovyTestCase { jetty.stop() } - private static JettyServer createJettyServer(StandardNiFiProperties httpsProps) { + private static JettyServer createJettyServer(NiFiProperties httpsProps) { Server internalServer = new Server() JettyServer jetty = new JettyServer(internalServer, httpsProps) jetty.systemBundle = SystemBundle.create(httpsProps) @@ -323,7 +328,7 @@ class JettyServerGroovyTest extends GroovyTestCase { // Arrange final String externalHostname = "localhost" - NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: new Properties([ + NiFiProperties httpsProps = new NiFiProperties(new Properties([ (NiFiProperties.WEB_HTTPS_PORT): HTTPS_PORT as String, (NiFiProperties.WEB_HTTPS_HOST): externalHostname, ])) @@ -462,7 +467,7 @@ class JettyServerGroovyTest extends GroovyTestCase { (NiFiProperties.WEB_HTTP_HOST) : "localhost", (NiFiProperties.WEB_MAX_CONTENT_SIZE): "1 MB", ] - NiFiProperties mockProps = new StandardNiFiProperties(new Properties(defaultProps)) + NiFiProperties mockProps = new NiFiProperties(new Properties(defaultProps)) List filters = [] def mockWebContext = [ @@ -505,7 +510,7 @@ class JettyServerGroovyTest extends GroovyTestCase { (NiFiProperties.WEB_HTTP_PORT): "8080", (NiFiProperties.WEB_HTTP_HOST): "localhost", ] - NiFiProperties mockProps = new StandardNiFiProperties(new Properties(defaultProps)) + NiFiProperties mockProps = new NiFiProperties(new Properties(defaultProps)) List filters = [] def mockWebContext = [ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/HostHeaderHandlerTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/HostHeaderHandlerTest.groovy index a08be9d8b2..80a3c580ad 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/HostHeaderHandlerTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/HostHeaderHandlerTest.groovy @@ -17,7 +17,7 @@ package org.apache.nifi.web.server import org.apache.commons.lang3.StringUtils -import org.apache.nifi.properties.StandardNiFiProperties + import org.apache.nifi.util.NiFiProperties import org.junit.After import org.junit.Before @@ -112,7 +112,7 @@ class HostHeaderHandlerTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_HOST): DEFAULT_HOSTNAME, (NiFiProperties.WEB_HTTPS_PORT): "${DEFAULT_PORT}".toString(), ]) - NiFiProperties simpleProperties = new StandardNiFiProperties(rawProps) + NiFiProperties simpleProperties = new NiFiProperties(rawProps) // Act HostHeaderHandler handler = new HostHeaderHandler(simpleProperties) @@ -141,7 +141,7 @@ class HostHeaderHandlerTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_PORT): "${DEFAULT_PORT}".toString(), (NiFiProperties.WEB_PROXY_HOST): concatenatedHosts ]) - NiFiProperties simpleProperties = new StandardNiFiProperties(rawProps) + NiFiProperties simpleProperties = new NiFiProperties(rawProps) HostHeaderHandler handler = new HostHeaderHandler(simpleProperties) logger.info("Handler: ${handler}") @@ -190,7 +190,7 @@ class HostHeaderHandlerTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_PORT): "${DEFAULT_PORT}".toString(), (NiFiProperties.WEB_PROXY_HOST): concatenatedHosts ]) - NiFiProperties simpleProperties = new StandardNiFiProperties(rawProps) + NiFiProperties simpleProperties = new NiFiProperties(rawProps) HostHeaderHandler handler = new HostHeaderHandler(simpleProperties) logger.info("Handler: ${handler}") @@ -236,7 +236,7 @@ class HostHeaderHandlerTest extends GroovyTestCase { (NiFiProperties.WEB_HTTPS_PORT): "${DEFAULT_PORT}".toString(), (NiFiProperties.WEB_PROXY_HOST): concatenatedHosts ]) - NiFiProperties simpleProperties = new StandardNiFiProperties(rawProps) + NiFiProperties simpleProperties = new NiFiProperties(rawProps) HostHeaderHandler handler = new HostHeaderHandler(simpleProperties) logger.info("Handler: ${handler}") diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/api/ApplicationResourceTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/api/ApplicationResourceTest.groovy index 2d4b496ee6..356b067a02 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/api/ApplicationResourceTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/api/ApplicationResourceTest.groovy @@ -16,7 +16,7 @@ */ package org.apache.nifi.web.api -import org.apache.nifi.properties.StandardNiFiProperties + import org.apache.nifi.util.NiFiProperties import org.glassfish.jersey.uri.internal.JerseyUriBuilder import org.junit.After @@ -109,7 +109,7 @@ class ApplicationResourceTest extends GroovyTestCase { resource.setHttpServletRequest(mockRequest) resource.setUriInfo(mockUriInfo) - resource.properties = new StandardNiFiProperties() + resource.properties = new NiFiProperties() resource } @@ -136,7 +136,7 @@ class ApplicationResourceTest extends GroovyTestCase { // Arrange ApplicationResource resource = buildApplicationResource() logger.info("Allowed path(s): ${ALLOWED_PATH}") - NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties) + NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties) resource.properties = niFiProperties // Act @@ -153,7 +153,7 @@ class ApplicationResourceTest extends GroovyTestCase { ApplicationResource resource = buildApplicationResource() String multipleAllowedPaths = [ALLOWED_PATH, "another/path", "a/third/path"].join(",") logger.info("Allowed path(s): ${multipleAllowedPaths}") - NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties) + NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties) resource.properties = niFiProperties // Act @@ -203,7 +203,7 @@ class ApplicationResourceTest extends GroovyTestCase { // Arrange ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER]) logger.info("Allowed path(s): ${ALLOWED_PATH}") - NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties) + NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties) resource.properties = niFiProperties // Act @@ -219,7 +219,7 @@ class ApplicationResourceTest extends GroovyTestCase { // Arrange ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER]) logger.info("Allowed path(s): ${ALLOWED_PATH}") - NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties) + NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): ALLOWED_PATH] as Properties) resource.properties = niFiProperties // Act @@ -236,7 +236,7 @@ class ApplicationResourceTest extends GroovyTestCase { ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER]) String multipleAllowedPaths = [ALLOWED_PATH, "another/path", "a/third/path"].join(",") logger.info("Allowed path(s): ${multipleAllowedPaths}") - NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties) + NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties) resource.properties = niFiProperties // Act @@ -253,7 +253,7 @@ class ApplicationResourceTest extends GroovyTestCase { ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER]) String multipleAllowedPaths = [ALLOWED_PATH, "another/path", "a/third/path"].join(",") logger.info("Allowed path(s): ${multipleAllowedPaths}") - NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties) + NiFiProperties niFiProperties = new NiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleAllowedPaths] as Properties) resource.properties = niFiProperties // Act diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java index 1fdab5db24..1ef38b5a08 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java @@ -168,7 +168,8 @@ public class NiFiTestServer { } public Client getClient() throws TlsException { - return WebUtils.createClient(null, org.apache.nifi.security.util.SslContextFactory.createSslContext(StandardTlsConfiguration.fromNiFiProperties(properties))); + return WebUtils.createClient(null, org.apache.nifi.security.util.SslContextFactory.createSslContext(StandardTlsConfiguration + .fromNiFiProperties(properties))); } /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java index c6e433a0cc..3e581c2852 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java @@ -16,24 +16,6 @@ */ package org.apache.nifi.web.security.spring; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.stream.XMLStreamReader; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authentication.AuthenticationResponse; import org.apache.nifi.authentication.LoginCredentials; @@ -50,11 +32,9 @@ import org.apache.nifi.authentication.generated.Provider; import org.apache.nifi.bundle.Bundle; import org.apache.nifi.nar.ExtensionManager; import org.apache.nifi.nar.NarCloseable; -import org.apache.nifi.properties.AESSensitivePropertyProviderFactory; +import org.apache.nifi.properties.PropertyProtectionScheme; import org.apache.nifi.properties.SensitivePropertyProtectionException; -import org.apache.nifi.properties.SensitivePropertyProvider; -import org.apache.nifi.properties.SensitivePropertyProviderFactory; -import org.apache.nifi.security.kms.CryptoUtils; +import org.apache.nifi.properties.SensitivePropertyProviderFactoryAware; import org.apache.nifi.security.xml.XmlUtils; import org.apache.nifi.util.NiFiProperties; import org.slf4j.Logger; @@ -63,18 +43,36 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.xml.sax.SAXException; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * */ -public class LoginIdentityProviderFactoryBean implements FactoryBean, DisposableBean, LoginIdentityProviderLookup { +public class LoginIdentityProviderFactoryBean extends SensitivePropertyProviderFactoryAware + implements FactoryBean, DisposableBean, LoginIdentityProviderLookup { private static final Logger logger = LoggerFactory.getLogger(LoginIdentityProviderFactoryBean.class); private static final String LOGIN_IDENTITY_PROVIDERS_XSD = "/login-identity-providers.xsd"; private static final String JAXB_GENERATED_PATH = "org.apache.nifi.authentication.generated"; private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); - private static SensitivePropertyProviderFactory SENSITIVE_PROPERTY_PROVIDER_FACTORY; - private static SensitivePropertyProvider SENSITIVE_PROPERTY_PROVIDER; + private NiFiProperties properties; /** * Load the JAXBContext. @@ -87,11 +85,14 @@ public class LoginIdentityProviderFactoryBean implements FactoryBean, Disposable } } - private NiFiProperties properties; private ExtensionManager extensionManager; private LoginIdentityProvider loginIdentityProvider; private final Map loginIdentityProviders = new HashMap<>(); + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } + @Override public LoginIdentityProvider getLoginIdentityProvider(String identifier) { return loginIdentityProviders.get(identifier); @@ -218,26 +219,9 @@ public class LoginIdentityProviderFactoryBean implements FactoryBean, Disposable return new StandardLoginIdentityProviderConfigurationContext(provider.getIdentifier(), providerProperties); } - private String decryptValue(String cipherText, String encryptionScheme) throws SensitivePropertyProtectionException { - initializeSensitivePropertyProvider(encryptionScheme); - return SENSITIVE_PROPERTY_PROVIDER.unprotect(cipherText); - } - - private static void initializeSensitivePropertyProvider(String encryptionScheme) throws SensitivePropertyProtectionException { - if (SENSITIVE_PROPERTY_PROVIDER == null || !SENSITIVE_PROPERTY_PROVIDER.getIdentifierKey().equalsIgnoreCase(encryptionScheme)) { - try { - String keyHex = getRootKey(); - SENSITIVE_PROPERTY_PROVIDER_FACTORY = new AESSensitivePropertyProviderFactory(keyHex); - SENSITIVE_PROPERTY_PROVIDER = SENSITIVE_PROPERTY_PROVIDER_FACTORY.getProvider(); - } catch (IOException e) { - logger.error("Error extracting master key from bootstrap.conf for login identity provider decryption", e); - throw new SensitivePropertyProtectionException("Could not read root key from bootstrap.conf"); - } - } - } - - private static String getRootKey() throws IOException { - return CryptoUtils.extractKeyFromBootstrapFile(); + private String decryptValue(final String cipherText, final String protectionScheme) throws SensitivePropertyProtectionException { + return getSensitivePropertyProviderFactory().getProvider(PropertyProtectionScheme.fromIdentifier(protectionScheme)) + .unprotect(cipherText); } private void performMethodInjection(final LoginIdentityProvider instance, final Class loginIdentityProviderClass) @@ -356,10 +340,6 @@ public class LoginIdentityProviderFactoryBean implements FactoryBean, Disposable } } - public void setProperties(NiFiProperties properties) { - this.properties = properties; - } - public void setExtensionManager(ExtensionManager extensionManager) { this.extensionManager = extensionManager; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/OidcServiceGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/OidcServiceGroovyTest.groovy index 5480ad4837..011788f8b5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/OidcServiceGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/OidcServiceGroovyTest.groovy @@ -45,16 +45,15 @@ class OidcServiceGroovyTest extends GroovyTestCase { private static final Key SIGNING_KEY = new Key(id: 1, identity: "signingKey", key: "mock-signing-key-value") private static final Map DEFAULT_NIFI_PROPERTIES = [ - isOidcEnabled : true, - getOidcDiscoveryUrl : "https://localhost/oidc", - isLoginIdentityProviderEnabled: false, - isKnoxSsoEnabled : false, - getOidcConnectTimeout : "1000", - getOidcReadTimeout : "1000", - getOidcClientId : "expected_client_id", - getOidcClientSecret : "expected_client_secret", - getOidcClaimIdentifyingUser : "username", - getOidcPreferredJwsAlgorithm : "" + "nifi.security.user.oidc.discovery.url" : "https://localhost/oidc", + "nifi.security.user.login.identity.provider" : "provider", + "nifi.security.user.knox.url" : "url", + "nifi.security.user.oidc.connect.timeout" : "1000", + "nifi.security.user.oidc.read.timeout" : "1000", + "nifi.security.user.oidc.client.id" : "expected_client_id", + "nifi.security.user.oidc.client.secret" : "expected_client_secret", + "nifi.security.user.oidc.claim.identifying.user" : "username", + "nifi.security.user.oidc.preferred.jwsalgorithm" : "" ] // Mock collaborators @@ -89,10 +88,7 @@ class OidcServiceGroovyTest extends GroovyTestCase { private static NiFiProperties buildNiFiProperties(Map props = [:]) { def combinedProps = DEFAULT_NIFI_PROPERTIES + props - def mockNFP = combinedProps.collectEntries { String k, def v -> - [k, { -> return v }] - } - mockNFP as NiFiProperties + new NiFiProperties(combinedProps) } private static JwtService buildJwtService() { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy index c52d4cd581..089f28a147 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy @@ -68,17 +68,15 @@ class StandardOidcIdentityProviderGroovyTest extends GroovyTestCase { private static final Key SIGNING_KEY = new Key(id: 1, identity: "signingKey", key: "mock-signing-key-value") private static final Map DEFAULT_NIFI_PROPERTIES = [ -// isOidcEnabled : false, - isOidcEnabled : true, - getOidcDiscoveryUrl : "https://localhost/oidc", - isLoginIdentityProviderEnabled: false, - isKnoxSsoEnabled : false, - getOidcConnectTimeout : 1000, - getOidcReadTimeout : 1000, - getOidcClientId : "expected_client_id", - getOidcClientSecret : "expected_client_secret", - getOidcClaimIdentifyingUser : "username", - getOidcPreferredJwsAlgorithm : "" + "nifi.security.user.oidc.discovery.url" : "https://localhost/oidc", + "nifi.security.user.login.identity.provider" : "provider", + "nifi.security.user.knox.url" : "url", + "nifi.security.user.oidc.connect.timeout" : "1000", + "nifi.security.user.oidc.read.timeout" : "1000", + "nifi.security.user.oidc.client.id" : "expected_client_id", + "nifi.security.user.oidc.client.secret" : "expected_client_secret", + "nifi.security.user.oidc.claim.identifying.user" : "username", + "nifi.security.user.oidc.preferred.jwsalgorithm" : "" ] // Mock collaborators @@ -108,10 +106,7 @@ class StandardOidcIdentityProviderGroovyTest extends GroovyTestCase { private static NiFiProperties buildNiFiProperties(Map props = [:]) { def combinedProps = DEFAULT_NIFI_PROPERTIES + props - def mockNFP = combinedProps.collectEntries { String k, def v -> - [k, { -> return v }] - } - mockNFP as NiFiProperties + new NiFiProperties(combinedProps) } private static JwtService buildJwtService() { @@ -414,7 +409,9 @@ class StandardOidcIdentityProviderGroovyTest extends GroovyTestCase { @Test void testConvertOIDCTokenToLoginAuthenticationTokenShouldHandleNoEmailClaimHasFallbackClaims() { // Arrange - StandardOidcIdentityProvider soip = buildIdentityProviderWithMockTokenValidator(["getOidcClaimIdentifyingUser": "email", "getOidcFallbackClaimsIdentifyingUser": ["upn"] ]) + StandardOidcIdentityProvider soip = buildIdentityProviderWithMockTokenValidator( + ["nifi.security.user.oidc.claim.identifying.user": "email", + "nifi.security.user.oidc.fallback.claims.identifying.user": "upn" ]) String expectedUpn = "xxx@aaddomain"; OIDCTokenResponse mockResponse = mockOIDCTokenResponse(["email": null, "upn": expectedUpn]) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBeanTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBeanTest.groovy index 13eab19ed1..eca7efbf5b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBeanTest.groovy +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBeanTest.groovy @@ -18,13 +18,9 @@ package org.apache.nifi.web.security.spring import org.apache.nifi.authentication.generated.Property import org.apache.nifi.authentication.generated.Provider -import org.apache.nifi.properties.AESSensitivePropertyProvider import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.junit.After -import org.junit.AfterClass import org.junit.Before import org.junit.BeforeClass -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -54,8 +50,10 @@ class LoginIdentityProviderFactoryBeanTest extends GroovyTestCase { private static final String PASSWORD = "thisIsABadPassword" + private LoginIdentityProviderFactoryBean bean + @BeforeClass - public static void setUpOnce() throws Exception { + static void setUpOnce() throws Exception { Security.addProvider(new BouncyCastleProvider()) logger.metaClass.methodMissing = { String name, args -> @@ -63,47 +61,16 @@ class LoginIdentityProviderFactoryBeanTest extends GroovyTestCase { } } - @AfterClass - public static void tearDownOnce() throws Exception { - } - @Before - public void setUp() throws Exception { - LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER = new AESSensitivePropertyProvider(KEY_HEX) - } - - @After - public void tearDown() throws Exception { - LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER = null - LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER_FACTORY = null + void setUp() { + bean = new LoginIdentityProviderFactoryBean() + bean.configureSensitivePropertyProviderFactory(KEY_HEX, null) } private static boolean isUnlimitedStrengthCryptoAvailable() { Cipher.getMaxAllowedKeyLength("AES") > 128 } - private static int getKeyLength(String keyHex = KEY_HEX) { - keyHex?.size() * 4 - } - - @Ignore("Can't test without overloading static metaClass method") - @Test - void testShouldInitializeSensitivePropertyProvider() { - // Arrange - assert !LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER - assert !LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER_FACTORY - - logger.info("Encryption scheme: ${ENCRYPTION_SCHEME}") - - // Act - LoginIdentityProviderFactoryBean.initializeSensitivePropertyProvider(ENCRYPTION_SCHEME) - - // Assert - assert LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER - assert LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER_FACTORY - assert LoginIdentityProviderFactoryBean.SENSITIVE_PROPERTY_PROVIDER.getIdentifierKey() == ENCRYPTION_SCHEME - } - @Test void testShouldDecryptValue() { // Arrange @@ -111,7 +78,7 @@ class LoginIdentityProviderFactoryBeanTest extends GroovyTestCase { logger.info("Cipher text: ${CIPHER_TEXT}") // Act - String decrypted = new LoginIdentityProviderFactoryBean().decryptValue(CIPHER_TEXT, ENCRYPTION_SCHEME) + String decrypted = bean.decryptValue(CIPHER_TEXT, ENCRYPTION_SCHEME) logger.info("Decrypted ${CIPHER_TEXT} -> ${decrypted}") // Assert @@ -129,7 +96,6 @@ class LoginIdentityProviderFactoryBeanTest extends GroovyTestCase { encryptedProvider.property = [managerPasswordProperty] logger.info("Manager Password property: ${managerPasswordProperty.dump()}") - def bean = new LoginIdentityProviderFactoryBean() // Act def context = bean.loadLoginIdentityProviderConfiguration(encryptedProvider) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java index e1232fa581..b4a85f38fb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProviderTest.java @@ -28,7 +28,6 @@ import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserDetails; import org.apache.nifi.idp.IdpType; import org.apache.nifi.idp.IdpUserGroup; -import org.apache.nifi.properties.StandardNiFiProperties; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.InvalidAuthenticationException; import org.apache.nifi.web.security.token.LoginAuthenticationToken; @@ -87,7 +86,7 @@ public class JwtAuthenticationProviderTest { Properties props = new Properties(); props.put(properties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX, "^(.*?)@(.*?)$"); props.put(properties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX, "$1"); - properties = new StandardNiFiProperties(props); + properties = new NiFiProperties(props); jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtService, properties, authorizer, idpUserGroupService); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java index 513eb0cbd4..a89082f053 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java @@ -25,7 +25,7 @@ import org.apache.nifi.authorization.user.StandardNiFiUser; import org.apache.nifi.authorization.util.IdentityMapping; import org.apache.nifi.authorization.util.IdentityMappingUtil; import org.apache.nifi.key.Key; -import org.apache.nifi.properties.StandardNiFiProperties; +import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.token.LoginAuthenticationToken; import org.codehaus.jettison.json.JSONObject; import org.junit.After; @@ -244,7 +244,7 @@ public class JwtServiceTest { Properties props = new Properties(); props.setProperty(SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX+"kerb", "^(.*?)@(.*?)$"); props.setProperty(SECURITY_IDENTITY_MAPPING_VALUE_PREFIX+"kerb", "$1"); - identityMappings = IdentityMappingUtil.getIdentityMappings(new StandardNiFiProperties(props)); + identityMappings = IdentityMappingUtil.getIdentityMappings(new NiFiProperties(props)); } @After diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml/impl/TestStandardSAMLService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml/impl/TestStandardSAMLService.java index 745bcecacf..8ad2fc4c9c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml/impl/TestStandardSAMLService.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml/impl/TestStandardSAMLService.java @@ -28,6 +28,8 @@ import org.junit.BeforeClass; import org.junit.Test; import java.io.File; +import java.util.Arrays; +import java.util.HashSet; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -72,6 +74,15 @@ public class TestStandardSAMLService { when(properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE)).thenReturn("src/test/resources/saml/truststore.jks"); when(properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD)).thenReturn("passwordpassword"); when(properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE)).thenReturn("JKS"); + when(properties.getPropertyKeys()).thenReturn(new HashSet<>(Arrays.asList( + NiFiProperties.SECURITY_KEYSTORE, + NiFiProperties.SECURITY_KEYSTORE_PASSWD, + NiFiProperties.SECURITY_KEY_PASSWD, + NiFiProperties.SECURITY_KEYSTORE_TYPE, + NiFiProperties.SECURITY_TRUSTSTORE, + NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, + NiFiProperties.SECURITY_TRUSTSTORE_TYPE + ))); when(properties.isSamlEnabled()).thenReturn(true); when(properties.getSamlServiceProviderEntityId()).thenReturn(spEntityId); diff --git a/nifi-nar-bundles/nifi-framework-bundle/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/pom.xml index bce465562d..452b843de6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/pom.xml @@ -145,6 +145,11 @@ nifi-properties-loader 1.14.0-SNAPSHOT + + org.apache.nifi + nifi-sensitive-property-provider + 1.14.0-SNAPSHOT + org.apache.nifi nifi-framework-authorization diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java index 24ee0b5f1f..24d80d6f8c 100644 --- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java +++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/main/java/org/apache/nifi/provenance/EncryptedWriteAheadProvenanceRepository.java @@ -16,8 +16,6 @@ */ package org.apache.nifi.provenance; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.events.EventReporter; import org.apache.nifi.provenance.serialization.RecordReaders; @@ -35,7 +33,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.security.KeyManagementException; @@ -89,7 +86,7 @@ public class EncryptedWriteAheadProvenanceRepository extends WriteAheadProvenanc try { KeyProvider keyProvider; if (KeyProviderFactory.requiresRootKey(getConfig().getKeyProviderImplementation())) { - SecretKey rootKey = getRootKey(); + SecretKey rootKey = CryptoUtils.getRootKey(); keyProvider = buildKeyProvider(rootKey); } else { keyProvider = buildKeyProvider(); @@ -151,15 +148,4 @@ public class EncryptedWriteAheadProvenanceRepository extends WriteAheadProvenanc return KeyProviderFactory.buildKeyProvider(implementationClassName, config.getKeyProviderLocation(), config.getKeyId(), config.getEncryptionKeys(), rootKey); } - - private static SecretKey getRootKey() throws KeyManagementException { - try { - // Get the root encryption key from bootstrap.conf - String rootKeyHex = CryptoUtils.extractKeyFromBootstrapFile(); - return new SecretKeySpec(Hex.decodeHex(rootKeyHex.toCharArray()), "AES"); - } catch (IOException | DecoderException e) { - logger.error("Encountered an error: ", e); - throw new KeyManagementException(e); - } - } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java index 3c2a3f4b02..9a06d59806 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authentication/IdentityProviderFactory.java @@ -17,10 +17,10 @@ package org.apache.nifi.registry.security.authentication; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.properties.SensitivePropertyProtectionException; +import org.apache.nifi.properties.SensitivePropertyProvider; import org.apache.nifi.registry.extension.ExtensionManager; import org.apache.nifi.registry.properties.NiFiRegistryProperties; -import org.apache.nifi.registry.properties.SensitivePropertyProtectionException; -import org.apache.nifi.registry.properties.SensitivePropertyProvider; import org.apache.nifi.registry.security.authentication.annotation.IdentityProviderContext; import org.apache.nifi.registry.security.authentication.generated.IdentityProviders; import org.apache.nifi.registry.security.authentication.generated.Property; diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java index 1fb3d90584..3d1e790353 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java @@ -18,12 +18,12 @@ package org.apache.nifi.registry.security.authorization; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.apache.nifi.properties.SensitivePropertyProtectionException; +import org.apache.nifi.properties.SensitivePropertyProvider; import org.apache.nifi.registry.extension.ExtensionClassLoader; import org.apache.nifi.registry.extension.ExtensionCloseable; import org.apache.nifi.registry.extension.ExtensionManager; import org.apache.nifi.registry.properties.NiFiRegistryProperties; -import org.apache.nifi.registry.properties.SensitivePropertyProtectionException; -import org.apache.nifi.registry.properties.SensitivePropertyProvider; import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext; import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException; import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException; diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/crypto/SensitivePropertyProviderConfiguration.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/crypto/SensitivePropertyProviderConfiguration.java index 78594921b7..055b71e8d7 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/crypto/SensitivePropertyProviderConfiguration.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/crypto/SensitivePropertyProviderConfiguration.java @@ -16,25 +16,25 @@ */ package org.apache.nifi.registry.security.crypto; -import org.apache.nifi.registry.properties.AESSensitivePropertyProvider; -import org.apache.nifi.registry.properties.SensitivePropertyProtectionException; -import org.apache.nifi.registry.properties.SensitivePropertyProvider; -import org.apache.nifi.registry.properties.SensitivePropertyProviderFactory; +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.apache.nifi.registry.properties.util.NiFiRegistryBootstrapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import javax.crypto.NoSuchPaddingException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; +import java.io.IOException; @Configuration -public class SensitivePropertyProviderConfiguration implements SensitivePropertyProviderFactory { - +public class SensitivePropertyProviderConfiguration { private static final Logger logger = LoggerFactory.getLogger(SensitivePropertyProviderConfiguration.class); + private static final PropertyProtectionScheme DEFAULT_SCHEME = PropertyProtectionScheme.AES_GCM; + @Autowired(required = false) private CryptoKeyProvider masterKeyProvider; @@ -43,7 +43,6 @@ public class SensitivePropertyProviderConfiguration implements SensitiveProperty * or null if the master key is not present. */ @Bean - @Override public SensitivePropertyProvider getProvider() { if (masterKeyProvider == null || masterKeyProvider.isEmpty()) { // This NiFi Registry was not configured with a master key, so the assumption is @@ -56,11 +55,18 @@ public class SensitivePropertyProviderConfiguration implements SensitiveProperty // returned provider, which has a copy of the sensitive master key material // to be reaped when it goes out of scope in order to decrease the time // key material is held in memory. - String key = masterKeyProvider.getKey(); - return new AESSensitivePropertyProvider(masterKeyProvider.getKey()); - } catch (MissingCryptoKeyException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) { - logger.warn("Error creating AES Sensitive Property Provider", e); - throw new SensitivePropertyProtectionException("Error creating AES Sensitive Property Provider", e); + return StandardSensitivePropertyProviderFactory + .withKeyAndBootstrapSupplier(masterKeyProvider.getKey(), () -> { + try { + return NiFiRegistryBootstrapUtils.loadBootstrapProperties(); + } catch (IOException e) { + throw new SensitivePropertyProtectionException("Error creating Sensitive Property Provider", e); + } + }) + .getProvider(DEFAULT_SCHEME); + } catch (final MissingCryptoKeyException e) { + logger.warn("Error creating Sensitive Property Provider", e); + throw new SensitivePropertyProtectionException("Error creating Sensitive Property Provider", e); } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java index 7fb8deeca3..75e10c6669 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java @@ -26,6 +26,7 @@ import org.mockito.Mockito; import javax.sql.DataSource; import java.net.URL; +import java.util.Properties; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -36,8 +37,9 @@ public class TestStandardProviderFactory { @Test public void testGetProvidersSuccess() { - final NiFiRegistryProperties props = new NiFiRegistryProperties(); - props.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-good.xml"); + final Properties properties = new Properties(); + properties.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-good.xml"); + final NiFiRegistryProperties props = new NiFiRegistryProperties(properties); final ExtensionManager extensionManager = Mockito.mock(ExtensionManager.class); when(extensionManager.getExtensionClassLoader(any(String.class))) @@ -67,8 +69,9 @@ public class TestStandardProviderFactory { @Test(expected = ProviderFactoryException.class) public void testGetFlowProviderBeforeInitializingShouldThrowException() { - final NiFiRegistryProperties props = new NiFiRegistryProperties(); - props.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-good.xml"); + final Properties properties = new Properties(); + properties.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-good.xml"); + final NiFiRegistryProperties props = new NiFiRegistryProperties(properties); final ExtensionManager extensionManager = Mockito.mock(ExtensionManager.class); when(extensionManager.getExtensionClassLoader(any(String.class))) @@ -82,8 +85,9 @@ public class TestStandardProviderFactory { @Test(expected = ProviderFactoryException.class) public void testProvidersConfigDoesNotExist() { - final NiFiRegistryProperties props = new NiFiRegistryProperties(); - props.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-does-not-exist.xml"); + final Properties properties = new Properties(); + properties.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-does-not-exist.xml"); + final NiFiRegistryProperties props = new NiFiRegistryProperties(properties); final ExtensionManager extensionManager = Mockito.mock(ExtensionManager.class); when(extensionManager.getExtensionClassLoader(any(String.class))) @@ -97,8 +101,9 @@ public class TestStandardProviderFactory { @Test(expected = ProviderFactoryException.class) public void testFlowProviderClassNotFound() { - final NiFiRegistryProperties props = new NiFiRegistryProperties(); - props.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-class-not-found.xml"); + final Properties properties = new Properties(); + properties.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/providers-class-not-found.xml"); + final NiFiRegistryProperties props = new NiFiRegistryProperties(properties); final ExtensionManager extensionManager = Mockito.mock(ExtensionManager.class); when(extensionManager.getExtensionClassLoader(any(String.class))) diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/hook/TestScriptEventHookProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/hook/TestScriptEventHookProvider.java index 39d45ae39f..0085bba95d 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/hook/TestScriptEventHookProvider.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/hook/TestScriptEventHookProvider.java @@ -28,6 +28,7 @@ import org.mockito.Mockito; import javax.sql.DataSource; import java.net.URL; +import java.util.Properties; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -36,8 +37,9 @@ public class TestScriptEventHookProvider { @Test(expected = ProviderCreationException.class) public void testBadScriptProvider() { - final NiFiRegistryProperties props = new NiFiRegistryProperties(); - props.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/hook/bad-script-provider.xml"); + final Properties properties = new Properties(); + properties.setProperty(NiFiRegistryProperties.PROVIDERS_CONFIGURATION_FILE, "src/test/resources/provider/hook/bad-script-provider.xml"); + final NiFiRegistryProperties props = new NiFiRegistryProperties(properties); final ExtensionManager extensionManager = Mockito.mock(ExtensionManager.class); when(extensionManager.getExtensionClassLoader(any(String.class))) diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseAccessPolicyProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseAccessPolicyProvider.java index 7f522ff0f3..9c2cb60e00 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseAccessPolicyProvider.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseAccessPolicyProvider.java @@ -45,6 +45,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Properties; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -286,9 +287,11 @@ public class TestDatabaseAccessPolicyProvider extends DatabaseBaseTest { @Test public void testOnConfiguredAppliesIdentityMappings() { + final Properties props = new Properties(); // Set up an identity mapping for kerberos principals - properties.setProperty("nifi.registry.security.identity.mapping.pattern.kerb", "^(.*?)@(.*?)$"); - properties.setProperty("nifi.registry.security.identity.mapping.value.kerb", "$1"); + props.setProperty("nifi.registry.security.identity.mapping.pattern.kerb", "^(.*?)@(.*?)$"); + props.setProperty("nifi.registry.security.identity.mapping.value.kerb", "$1"); + properties = new NiFiRegistryProperties(props); identityMapper = new DefaultIdentityMapper(properties); ((DatabaseAccessPolicyProvider)policyProvider).setIdentityMapper(identityMapper); @@ -311,10 +314,12 @@ public class TestDatabaseAccessPolicyProvider extends DatabaseBaseTest { @Test public void testOnConfiguredAppliesGroupMappings() { + final Properties props = new Properties(); // Set up an identity mapping for kerberos principals - properties.setProperty("nifi.registry.security.group.mapping.pattern.anyGroup", "^(.*)$"); - properties.setProperty("nifi.registry.security.group.mapping.value.anyGroup", "$1"); - properties.setProperty("nifi.registry.security.group.mapping.transform.anyGroup", "LOWER"); + props.setProperty("nifi.registry.security.group.mapping.pattern.anyGroup", "^(.*)$"); + props.setProperty("nifi.registry.security.group.mapping.value.anyGroup", "$1"); + props.setProperty("nifi.registry.security.group.mapping.transform.anyGroup", "LOWER"); + properties = new NiFiRegistryProperties(props); identityMapper = new DefaultIdentityMapper(properties); ((DatabaseAccessPolicyProvider)policyProvider).setIdentityMapper(identityMapper); diff --git a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseUserGroupProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseUserGroupProvider.java index 0f8d4322ea..dc14e66ee0 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseUserGroupProvider.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/security/authorization/database/TestDatabaseUserGroupProvider.java @@ -35,6 +35,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.UUID; @@ -179,9 +180,11 @@ public class TestDatabaseUserGroupProvider extends DatabaseBaseTest { @Test public void testOnConfiguredAppliesIdentityMappingsToInitialUsers() { + final Properties props = new Properties(); // Set up an identity mapping for kerberos principals - properties.setProperty("nifi.registry.security.identity.mapping.pattern.kerb", "^(.*?)@(.*?)$"); - properties.setProperty("nifi.registry.security.identity.mapping.value.kerb", "$1"); + props.setProperty("nifi.registry.security.identity.mapping.pattern.kerb", "^(.*?)@(.*?)$"); + props.setProperty("nifi.registry.security.identity.mapping.value.kerb", "$1"); + properties = new NiFiRegistryProperties(props); identityMapper = new DefaultIdentityMapper(properties); ((DatabaseUserGroupProvider)userGroupProvider).setIdentityMapper(identityMapper); diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml b/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml index 6458aec4c2..77c7c14ff9 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml +++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/pom.xml @@ -72,5 +72,11 @@ org.mockito mockito-core + + org.apache.nifi + nifi-sensitive-property-provider + 1.14.0-SNAPSHOT + compile + 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. - *

- * Example: - *

- * nifi.registry.sensitive.key=ABCXYZ - * nifi.registry.sensitive.key.protected=aes/gcm/256 - *

- * 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-properties 1.14.0-SNAPSHOT + + org.apache.nifi.registry + nifi-registry-properties + 1.14.0-SNAPSHOT + org.apache.nifi nifi-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-docs nifi-maven-archetypes nifi-external - nifi-toolkit nifi-docker nifi-system-tests minifi nifi-stateless nifi-registry + nifi-toolkit https://nifi.apache.org