NIFI-8651: Refactor Sensitive Properties Providers for extension

This closes #5131

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Joe Gresock 2021-06-06 21:53:39 -04:00 committed by exceptionfactory
parent 64f600d0ce
commit 1ccc4fbb0f
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
108 changed files with 2873 additions and 3377 deletions

View File

@ -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);

View File

@ -15,6 +15,14 @@
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-property-utils</artifactId>
<version>1.14.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-commons</artifactId>

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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<String> getPropertyKeys();
public NiFiProperties(final Map<String, String> 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<String> 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";
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-commons</artifactId>
<version>1.14.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-property-utils</artifactId>
</project>

View File

@ -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;
}
}

View File

@ -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<String, String> properties) {
super(properties);
}
}

View File

@ -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<Object> 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<String> getBootstrapSensitiveKey() {
return Optional.ofNullable(getProperty(getPropertyKey(BOOTSTRAP_SENSITIVE_KEY)));
}
@Override
public String toString() {
return String.format("Bootstrap properties [%s] with prefix [%s]", configFilePath, propertyPrefix);
}
}

View File

@ -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 <T> The ApplicationProperties type
*/
public interface ProtectedProperties<T extends ApplicationProperties> {
/**
* 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<String> 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);
}

View File

@ -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<String> getPropertyKeys();
}

View File

@ -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<String, String> 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<String> getPropertyKeys() {
Set<String> 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();
}
}

View File

@ -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<String> stream = Files.lines(Paths.get(expectedBootstrapFile.getAbsolutePath()))) {
Optional<String> 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

View File

@ -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);

View File

@ -0,0 +1,70 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-commons</artifactId>
<version>1.14.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-sensitive-property-provider</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties</artifactId>
<version>1.14.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
<version>1.14.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<!-- Required to run Groovy tests without any Java tests -->
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/test/groovy</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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 <T> The type of protected application properties
* @param <U> The type of standard application properties that backs the protected application properties
*/
public class ApplicationPropertiesProtector<T extends ProtectedProperties<U>, U extends ApplicationProperties>
implements SensitivePropertyProtector<T, U> {
public static final String PROTECTED_KEY_SUFFIX = ".protected";
private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertiesProtector.class);
private T protectedProperties;
private Map<String, SensitivePropertyProvider> 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.
* <p>
* Example:
* <p>
* nifi.sensitive.key=ABCXYZ
* nifi.sensitive.key.protected=aes/gcm/256
* <p>
* 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<String> getPropertyKeys() {
Set<String> filteredKeys = getPropertyKeysIncludingProtectionSchemes();
filteredKeys.removeIf(p -> p.endsWith(PROTECTED_KEY_SUFFIX));
return filteredKeys;
}
@Override
public int size() {
return getPropertyKeys().size();
}
@Override
public Set<String> 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<String> splitMultipleProperties(final String multipleProperties) {
if (multipleProperties == null || multipleProperties.trim().isEmpty()) {
return new ArrayList<>(0);
} else {
List<String> 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<String> getSensitivePropertyKeys() {
final String additionalPropertiesString = getAdditionalSensitivePropertiesKeys();
final String additionalPropertiesKeyName = protectedProperties.getAdditionalSensitivePropertiesKeysName();
if (additionalPropertiesString == null || additionalPropertiesString.trim().isEmpty()) {
return protectedProperties.getDefaultSensitiveProperties();
} else {
List<String> 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<String> getPopulatedSensitivePropertyKeys() {
List<String> allSensitiveKeys = getSensitivePropertyKeys();
return allSensitiveKeys.stream().filter(k -> StringUtils.isNotBlank(getProperty(k))).collect(Collectors.toList());
}
@Override
public boolean hasProtectedKeys() {
final List<String> sensitiveKeys = getSensitivePropertyKeys();
for (String k : sensitiveKeys) {
if (isPropertyProtected(k)) {
return true;
}
}
return false;
}
@Override
public Map<String, String> getProtectedPropertyKeys() {
final List<String> sensitiveKeys = getSensitivePropertyKeys();
final Map<String, String> 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<String> getProtectionSchemes() {
return new HashSet<>(getProtectedPropertyKeys().values());
}
@Override
public boolean isPropertySensitive(final String key) {
// If the explicit check for ADDITIONAL_SENSITIVE_PROPERTIES_KEY is not here, this will loop infinitely
return key != null && !key.equals(getAdditionalSensitivePropertiesKeysName()) && getSensitivePropertyKeys().contains(key.trim());
}
/**
* 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<String> 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<String> providers = getSensitivePropertyProviders().keySet();
return new StringBuilder("ApplicationPropertiesProtector instance with ")
.append(size()).append(" properties (")
.append(getProtectedPropertyKeys().size())
.append(" protected) and ")
.append(providers.size())
.append(" sensitive property providers: ")
.append(StringUtils.join(providers, ", "))
.toString();
}
@Override
public Map<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
if (localProviderCache == null) {
localProviderCache = new HashMap<>();
}
return localProviderCache;
}
private SensitivePropertyProvider getSensitivePropertyProvider(final String protectionScheme) {
if (isProviderAvailable(protectionScheme)) {
return getSensitivePropertyProviders().get(protectionScheme);
} else {
throw new SensitivePropertyProtectionException("No provider available for " + protectionScheme);
}
}
private boolean isProviderAvailable(final String protectionScheme) {
return getSensitivePropertyProviders().containsKey(protectionScheme);
}
/**
* 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;
}
}

View File

@ -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));
}
}

View File

@ -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 <T> The ProtectedProperties type
* @param <U> The ApplicationProperties type
*/
public interface SensitivePropertyProtector<T extends ProtectedProperties<U>, U extends ApplicationProperties> {
/**
* Returns the number of properties, excluding protection scheme properties.
* <p>
* Example:
* <p>
* key: E(value, key)
* key.protected: aes/gcm/256
* key2: value2
* <p>
* would return size 2
*
* @return the count of real properties
*/
int size();
/**
* Retrieves all known property keys.
*
* @return all known property keys
*/
Set<String> 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<String> 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<String> 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<String> 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<String> getProtectionSchemes();
/**
* Returns a Map of the keys identifying "sensitive" properties that are currently protected and the "protection" key for each.
* This may or may not include all properties marked as sensitive.
*
* @return the Map of protected property keys and the protection identifier for each
*/
Map<String, String> getProtectedPropertyKeys();
/**
* Returns the local provider cache (null-safe) as a Map of protection schemes -> implementations.
*
* @return the map
*/
Map<String, SensitivePropertyProvider> getSensitivePropertyProviders();
/**
* Returns true if the property identified by this key is considered sensitive in this instance of {@code ApplicationProperties}.
* Some properties are sensitive by default, while others can be specified by
* {@link ProtectedProperties#getAdditionalSensitivePropertiesKeys()}.
*
* @param key the key
* @return true if it is sensitive
* @see ApplicationPropertiesProtector#getSensitivePropertyKeys()
*/
boolean isPropertySensitive(String key);
/**
* 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.
* <p>
* 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);
}

View File

@ -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.

View File

@ -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<SensitivePropertyProvider> getSupportedSensitivePropertyProviders();
}

View File

@ -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<BootstrapProperties> bootstrapPropertiesSupplier) {
sensitivePropertyProviderFactory = StandardSensitivePropertyProviderFactory.withKeyAndBootstrapSupplier(keyHex, bootstrapPropertiesSupplier);
return sensitivePropertyProviderFactory;
}
}

View File

@ -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<String> keyHex;
private final Supplier<BootstrapProperties> bootstrapPropertiesSupplier;
private final Map<PropertyProtectionScheme, SensitivePropertyProvider> 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<BootstrapProperties> bootstrapPropertiesSupplier) {
return new StandardSensitivePropertyProviderFactory(keyHex, bootstrapPropertiesSupplier);
}
private StandardSensitivePropertyProviderFactory(final String keyHex, final Supplier<BootstrapProperties> 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<SensitivePropertyProvider> getSupportedSensitivePropertyProviders() {
return Arrays.stream(PropertyProtectionScheme.values())
.map(this::getProvider)
.filter(SensitivePropertyProvider::isSupported)
.collect(Collectors.toList());
}
}

View File

@ -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<BootstrapProperties> 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)));
}
}

View File

@ -35,7 +35,9 @@
<module>nifi-metrics</module>
<module>nifi-parameter</module>
<module>nifi-property-encryptor</module>
<module>nifi-property-utils</module>
<module>nifi-properties</module>
<module>nifi-sensitive-property-provider</module>
<module>nifi-record</module>
<module>nifi-record-path</module>
<module>nifi-rocksdb-utils</module>

View File

@ -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<String, UserGroupProvider> userGroupProviders = new HashMap<>();
private final Map<String, AccessPolicyProvider> accessPolicyProviders = new HashMap<>();
private final Map<String, Authorizer> 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;
}

View File

@ -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<Property> properties = [managerPasswordProperty]
logger.info("Manager Password property: ${managerPasswordProperty.dump()}")
def bean = new AuthorizerFactoryBean()
// Act
def context = bean.loadAuthorizerConfiguration(identifier, properties)

View File

@ -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) {

View File

@ -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<String, String> 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))

View File

@ -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)

View File

@ -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());

View File

@ -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();
}

View File

@ -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,

View File

@ -46,6 +46,10 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-sensitive-property-provider</artifactId>
</dependency>
</dependencies>
<build>
<!-- Required to run Groovy tests without any Java tests -->

View File

@ -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";
}
}

View File

@ -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();

View File

@ -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<NiFiProperties>,
SensitivePropertyProtector<ProtectedNiFiProperties, NiFiProperties> {
private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiProperties.class);
private NiFiProperties niFiProperties;
private SensitivePropertyProtector<ProtectedNiFiProperties, NiFiProperties> propertyProtectionDelegate;
private Map<String, SensitivePropertyProvider> 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<String> 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<String> getPropertyKeys() {
Set<String> 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<String> getPropertyKeysIncludingProtectionSchemes() {
return getInternalNiFiProperties().getPropertyKeys();
@Override
public Set<String> 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<String> splitMultipleProperties(String multipleProperties) {
if (multipleProperties == null || multipleProperties.trim().isEmpty()) {
return new ArrayList<>(0);
} else {
List<String> 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<String> getSensitivePropertyKeys() {
String additionalPropertiesString = getProperty(ADDITIONAL_SENSITIVE_PROPERTIES_KEY);
if (additionalPropertiesString == null || additionalPropertiesString.trim().isEmpty()) {
return DEFAULT_SENSITIVE_PROPERTIES;
} else {
List<String> 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<String> getPopulatedSensitivePropertyKeys() {
List<String> 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<String> 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<String, String> getProtectedPropertyKeys() {
List<String> sensitiveKeys = getSensitivePropertyKeys();
// This is the Java 8 way, but can likely be optimized (and not sure of correctness)
// Map<String, String> protectedProperties = sensitiveKeys.stream().filter(key ->
// getProperty(getProtectionKey(key)) != null).collect(Collectors.toMap(Function.identity(), key ->
// getProperty(getProtectionKey(key))));
// Groovy
// Map<String, String> groovyProtectedProperties = sensitiveKeys.collectEntries { key ->
// [(key): getProperty(getProtectionKey(key))] }.findAll { k, v -> v }
// Traditional way
Map<String, String> 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<String> 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.
* <p>
* Example:
* <p>
* nifi.sensitive.key=ABCXYZ
* nifi.sensitive.key.protected=aes/gcm/256
* <p>
* 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.
* <p>
* 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<String> 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<String> 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<String> 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<String, SensitivePropertyProvider> 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<String, SensitivePropertyProvider> 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;
}
}

View File

@ -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"
}
}

View File

@ -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<String> 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<String> 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<String> 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<String> 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

View File

@ -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)

View File

@ -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<String> 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

View File

@ -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<String, String> getProtectedPropertyKeys(NiFiProperties properties) {
getProtectedKeys(properties).collectEntries { String key ->
[(key): properties.getProperty(key + ".protected")]
[(key): properties.getProperty(key + ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)]
}
}
private static Set<String> 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

View File

@ -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)

View File

@ -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));

View File

@ -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,

View File

@ -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);

View File

@ -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<String> 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<Object>() {
@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<FilterHolder> 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<FilterHolder> filters = []
def mockWebContext = [

View File

@ -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}")

View File

@ -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

View File

@ -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)));
}
/**

View File

@ -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<String, LoginIdentityProvider> 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;
}

View File

@ -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<String, Object> 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<String, Object> 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() {

View File

@ -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<String, Object> 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<String, Object> 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])

View File

@ -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)

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -145,6 +145,11 @@
<artifactId>nifi-properties-loader</artifactId>
<version>1.14.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-sensitive-property-provider</artifactId>
<version>1.14.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-authorization</artifactId>

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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)))

View File

@ -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)))

View File

@ -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);

View File

@ -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);

View File

@ -72,5 +72,11 @@
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-sensitive-property-provider</artifactId>
<version>1.14.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -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<Integer> validKeyLengths = getValidKeyLengths();
if (!validKeyLengths.contains(key.length * 8)) {
List<String> validKeyLengthsAsStrings = validKeyLengths.stream().map(i -> Integer.toString(i)).collect(Collectors.toList());
throw new SensitivePropertyProtectionException("The key (" + key.length * 8 + " bits) must be a valid length: " + StringUtils.join(validKeyLengthsAsStrings, ", "));
}
return key;
}
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<Integer> getValidKeyLengths() {
List<Integer> validLengths = new ArrayList<>();
validLengths.add(128);
try {
if (Cipher.getMaxAllowedKeyLength("AES") > 128) {
validLengths.add(192);
validLengths.add(256);
} else {
logger.warn("JCE Unlimited Strength Cryptography Jurisdiction policies are not available, so the max key length is 128 bits");
}
} catch (NoSuchAlgorithmException e) {
logger.warn("Encountered an error determining the max key length", e);
}
return validLengths;
}
/**
* Returns the 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;
}
}

View File

@ -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";
}
}

View File

@ -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<String> failedKeys;
/**
* Constructs a new throwable with {@code null} as its detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*/
public MultipleSensitivePropertyProtectionException() {
}
/**
* Constructs a new throwable with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by
* a call to {@link #initCause}.
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public MultipleSensitivePropertyProtectionException(String message) {
super(message);
}
/**
* Constructs a new throwable with the specified detail message and
* cause. <p>Note that the detail message associated with
* {@code cause} is <i>not</i> automatically incorporated in
* this throwable's detail message.
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public MultipleSensitivePropertyProtectionException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new throwable with the specified cause and a detail
* message of {@code (cause==null ? null : cause.toString())} (which
* typically contains the class and detail message of {@code cause}).
* This constructor is useful for throwables that are little more than
* wrappers for other throwables (for example, PrivilegedActionException).
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public MultipleSensitivePropertyProtectionException(Throwable cause) {
super(cause);
}
/**
* Constructs a new exception with the provided message and a unique set of the keys that caused the error.
*
* @param message the message
* @param failedKeys any failed keys
*/
public MultipleSensitivePropertyProtectionException(String message, Collection<String> failedKeys) {
this(message, failedKeys, null);
}
/**
* Constructs a new exception with the provided message and a unique set of the keys that caused the error.
*
* @param message the message
* @param failedKeys any failed keys
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public MultipleSensitivePropertyProtectionException(String message, Collection<String> failedKeys, Throwable cause) {
super(message, cause);
this.failedKeys = new HashSet<>(failedKeys);
}
public Set<String> getFailedKeys() {
return this.failedKeys;
}
@Override
public String toString() {
return "SensitivePropertyProtectionException for [" + StringUtils.join(this.failedKeys, ", ") + "]: " + getLocalizedMessage();
}
}

View File

@ -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<String, String> props) {
this.putAll(props);
public NiFiRegistryProperties(final Map<String, String> props) {
super(props);
}
public NiFiRegistryProperties(final Properties props) {
super(props);
}
public int getWebThreads() {
@ -297,7 +311,7 @@ public class NiFiRegistryProperties extends Properties {
public Set<String> getExtensionsDirs() {
final Set<String> 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<String> getPropertyKeys() {
Set<String> 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) {

View File

@ -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();
}
/**

View File

@ -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<NiFiRegistryProperties>,
SensitivePropertyProtector<ProtectedNiFiRegistryProperties, NiFiRegistryProperties> {
private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiRegistryProperties.class);
private NiFiRegistryProperties properties;
private SensitivePropertyProtector<ProtectedNiFiRegistryProperties, NiFiRegistryProperties> propertyProtectionDelegate;
private Map<String, SensitivePropertyProvider> 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<String> 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<String> getPropertyKeys() {
return propertyProtectionDelegate.getPropertyKeys();
}
/**
* Returns the number of properties, excluding protection scheme properties.
* <p>
* Example:
* <p>
@ -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<String> getPropertyKeysIncludingProtectionSchemes() {
return getInternalNiFiProperties().getPropertyKeys();
@Override
public Set<String> 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<String> getPropertyKeysExcludingProtectionSchemes() {
Set<String> 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<String> splitMultipleProperties(String multipleProperties) {
if (multipleProperties == null || multipleProperties.trim().isEmpty()) {
return new ArrayList<>(0);
} else {
List<String> 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<String> getSensitivePropertyKeys() {
String additionalPropertiesString = getProperty(ADDITIONAL_SENSITIVE_PROPERTIES_KEY);
if (additionalPropertiesString == null || additionalPropertiesString.trim().isEmpty()) {
return DEFAULT_SENSITIVE_PROPERTIES;
} else {
List<String> 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<String> getPopulatedSensitivePropertyKeys() {
List<String> 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<String> 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<String, String> getProtectedPropertyKeys() {
List<String> sensitiveKeys = getSensitivePropertyKeys();
Map<String, String> 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<String> 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.
* <p>
* Example:
* <p>
* nifi.registry.sensitive.key=ABCXYZ
* nifi.registry.sensitive.key.protected=aes/gcm/256
* <p>
* 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.
* <p>
* 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<String> 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<String> 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<String> 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<String, SensitivePropertyProvider> getSensitivePropertyProviders() {
return propertyProtectionDelegate.getSensitivePropertyProviders();
}
@Override
public String toString() {
final Set<String> 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<String, SensitivePropertyProvider> 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;
}
}

View File

@ -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}.
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*/
public SensitivePropertyProtectionException() {
}
/**
* Constructs a new throwable with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by
* a call to {@link #initCause}.
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public SensitivePropertyProtectionException(String message) {
super(message);
}
/**
* Constructs a new throwable with the specified detail message and
* cause. <p>Note that the detail message associated with
* {@code cause} is <i>not</i> automatically incorporated in
* this throwable's detail message.
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public SensitivePropertyProtectionException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new throwable with the specified cause and a detail
* message of {@code (cause==null ? null : cause.toString())} (which
* typically contains the class and detail message of {@code cause}).
* This constructor is useful for throwables that are little more than
* wrappers for other throwables (for example, PrivilegedActionException).
* <p>
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public SensitivePropertyProtectionException(Throwable cause) {
super(cause);
}
@Override
public String toString() {
return "SensitivePropertyProtectionException: " + getLocalizedMessage();
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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<String> stream = Files.lines(Paths.get(bootstrapFile.getAbsolutePath()))) {
Optional<String> 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;
}
}

View File

@ -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
}
}

View File

@ -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<Integer> 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..<keySize.intdiv(4)]
default:
throw new IllegalArgumentException("Key size ${keySize} bits is not valid")
}
}
private static List<Integer> getAvailableKeySizes() {
if (Cipher.getMaxAllowedKeyLength("AES") > 128) {
[128, 192, 256]
} else {
[128]
}
}
private static String manipulateString(String input, int start = 0, int end = input?.length()) {
if ((input[start..end] as List).unique().size() == 1) {
throw new IllegalArgumentException("Can't manipulate a String where the entire range is identical [${input[start..end]}]")
}
List shuffled = input[start..end] as List
Collections.shuffle(shuffled)
String reconstituted = input[0..<start] + shuffled.join() + input[end + 1..-1]
return reconstituted != input ? reconstituted : manipulateString(input, start, end)
}
@Test
void testShouldProtectValue() throws Exception {
final String PLAINTEXT = "This is a plaintext value"
// Act
Map<Integer, String> CIPHER_TEXTS = KEY_SIZES.collectEntries { int keySize ->
SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
logger.info("Initialized ${spp.name} with key size ${keySize}")
[(keySize): spp.protect(PLAINTEXT)]
}
CIPHER_TEXTS.each { ks, ct -> logger.info("Encrypted for ${ks} length key: ${ct}") }
// Assert
// The IV generation is part of #protect, so the expected cipher text values must be generated after #protect has run
Map<Integer, Cipher> decryptionCiphers = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
// The 12 byte IV is the first 16 Base64-encoded characters of the "complete" cipher text
byte[] iv = decoder.decode(cipherText[0..<16])
[(keySize): getCipher(false, keySize, iv)]
}
Map<Integer, String> plaintexts = decryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
String cipherTextWithoutIVAndDelimiter = CIPHER_TEXTS[e.key][18..-1]
String plaintext = new String(e.value.doFinal(decoder.decode(cipherTextWithoutIVAndDelimiter)), StandardCharsets.UTF_8)
[(e.key): plaintext]
}
CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
}
@Test
void testShouldHandleProtectEmptyValue() throws Exception {
final List<String> EMPTY_PLAINTEXTS = ["", " ", null]
// Act
KEY_SIZES.collectEntries { int keySize ->
SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
logger.info("Initialized ${spp.name} with key size ${keySize}")
EMPTY_PLAINTEXTS.each { String emptyPlaintext ->
def msg = shouldFail(IllegalArgumentException) {
spp.protect(emptyPlaintext)
}
logger.expected("${msg} for keySize ${keySize} and plaintext [${emptyPlaintext}]")
// Assert
assert msg == "Cannot encrypt an empty value"
}
}
}
@Test
void testShouldUnprotectValue() throws Exception {
// Arrange
final String PLAINTEXT = "This is a plaintext value"
Map<Integer, Cipher> encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
byte[] iv = new byte[IV_LENGTH]
secureRandom.nextBytes(iv)
[(keySize): getCipher(true, keySize, iv)]
}
Map<Integer, String> CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
String iv = encoder.encodeToString(e.value.getIV())
String cipherText = encoder.encodeToString(e.value.doFinal(PLAINTEXT.getBytes(StandardCharsets.UTF_8)))
[(e.key): "${iv}||${cipherText}"]
}
CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
// Act
Map<Integer, String> plaintexts = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
logger.info("Initialized ${spp.name} with key size ${keySize}")
[(keySize): spp.unprotect(cipherText)]
}
plaintexts.each { ks, pt -> logger.info("Decrypted for ${ks} length key: ${pt}") }
// Assert
assert plaintexts.every { int ks, String pt -> pt == PLAINTEXT }
}
/**
* Tests inputs where the entire String is empty/blank space/{@code null}.
*
* @throws Exception
*/
@Test
void testShouldHandleUnprotectEmptyValue() throws Exception {
// Arrange
final List<String> EMPTY_CIPHER_TEXTS = ["", " ", null]
// Act
KEY_SIZES.each { int keySize ->
SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
logger.info("Initialized ${spp.name} with key size ${keySize}")
EMPTY_CIPHER_TEXTS.each { String emptyCipherText ->
def msg = shouldFail(IllegalArgumentException) {
spp.unprotect(emptyCipherText)
}
logger.expected("${msg} for keySize ${keySize} and cipher text [${emptyCipherText}]")
// Assert
assert msg == "Cannot decrypt a cipher text shorter than ${AESSensitivePropertyProvider.minCipherTextLength} chars".toString()
}
}
}
@Test
void testShouldUnprotectValueWithWhitespace() throws Exception {
// Arrange
final String PLAINTEXT = "This is a plaintext value"
Map<Integer, Cipher> encryptionCiphers = KEY_SIZES.collectEntries { int keySize ->
byte[] iv = new byte[IV_LENGTH]
secureRandom.nextBytes(iv)
[(keySize): getCipher(true, keySize, iv)]
}
Map<Integer, String> CIPHER_TEXTS = encryptionCiphers.collectEntries { Map.Entry<Integer, Cipher> e ->
String iv = encoder.encodeToString(e.value.getIV())
String cipherText = encoder.encodeToString(e.value.doFinal(PLAINTEXT.getBytes(StandardCharsets.UTF_8)))
[(e.key): "${iv}||${cipherText}"]
}
CIPHER_TEXTS.each { key, ct -> logger.expected("Cipher text for ${key} length key: ${ct}") }
// Act
Map<Integer, String> plaintexts = CIPHER_TEXTS.collectEntries { int keySize, String cipherText ->
SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
logger.info("Initialized ${spp.name} with key size ${keySize}")
[(keySize): spp.unprotect("\t" + cipherText + "\n")]
}
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<String> EMPTY_CIPHER_TEXTS = ["", " ", "\n"].collect { "${IV_AND_DELIMITER}${it}" }
// Act
KEY_SIZES.each { int keySize ->
SensitivePropertyProvider spp = new AESSensitivePropertyProvider(Hex.decode(getKeyOfSize(keySize)))
logger.info("Initialized ${spp.name} with key size ${keySize}")
EMPTY_CIPHER_TEXTS.each { String emptyCipherText ->
def msg = shouldFail(IllegalArgumentException) {
spp.unprotect(emptyCipherText)
}
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
}
}

View File

@ -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()}")

View File

@ -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

View File

@ -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<String> 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()
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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());

View File

@ -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() {

View File

@ -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;
}

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -34,6 +34,11 @@
<artifactId>nifi-properties</artifactId>
<version>1.14.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi.registry</groupId>
<artifactId>nifi-registry-properties</artifactId>
<version>1.14.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties-loader</artifactId>
@ -168,4 +173,4 @@
</plugin>
</plugins>
</build>
</project>
</project>

View File

@ -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<Integer> validHexCharLengths = validKeyLengths.collect {it / 4 }
if (!validKeyLengths.contains(hexKey.size() * 4)) {
throw new KeyException("The key (${hexKey.size()} hex chars) must be of length ${validKeyLengths} bits (${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<String> 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<String, String> 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<BootstrapProperties> getBootstrapSupplier(final String bootstrapConfPath) {
new Supplier<BootstrapProperties>() {
@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)

View File

@ -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

View File

@ -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)

Some files were not shown because too many files have changed in this diff Show More