mirror of https://github.com/apache/nifi.git
NIFI-8651: Refactor Sensitive Properties Providers for extension
This closes #5131 Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
parent
64f600d0ce
commit
1ccc4fbb0f
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
|
@ -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) {
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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.
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue