mirror of https://github.com/apache/nifi.git
NIFI-13414 Removed Property Protection Modules and Encrypt Config
This closes #8978 - Removed nifi-property-protection-api and implementation modules - Removed nifi-toolkit-encrypt-config and minifi-toolkit-encrypt-config modules - Removed extra bootstrap.conf configuration files for property protection implementations Signed-off-by: Joseph Witt <joewitt@apache.org>
This commit is contained in:
parent
b7a48f766a
commit
49a0401058
|
@ -27,7 +27,6 @@ on:
|
|||
- 'nifi-registry/nifi-registry-docker-maven/**'
|
||||
- 'nifi-toolkit/nifi-toolkit-assembly/**'
|
||||
- 'nifi-toolkit/nifi-toolkit-cli/**'
|
||||
- 'nifi-toolkit/nifi-toolkit-encrypt-config/**'
|
||||
- 'minifi/minifi-assembly/**'
|
||||
- 'minifi/minifi-docker/**'
|
||||
pull_request:
|
||||
|
@ -39,7 +38,6 @@ on:
|
|||
- 'nifi-registry/nifi-registry-docker-maven/**'
|
||||
- 'nifi-toolkit/nifi-toolkit-assembly/**'
|
||||
- 'nifi-toolkit/nifi-toolkit-cli/**'
|
||||
- 'nifi-toolkit/nifi-toolkit-encrypt-config/**'
|
||||
- 'minifi/minifi-assembly/**'
|
||||
- 'minifi/minifi-docker/**'
|
||||
|
||||
|
@ -77,7 +75,6 @@ env:
|
|||
-pl -nifi-registry/nifi-registry-assembly
|
||||
-pl -nifi-toolkit/nifi-toolkit-assembly
|
||||
-pl -nifi-toolkit/nifi-toolkit-cli
|
||||
-pl -nifi-toolkit/nifi-toolkit-encrypt-config
|
||||
-pl -minifi/minifi-assembly
|
||||
|
||||
MAVEN_DOCKER_ARGUMENTS: >-
|
||||
|
|
|
@ -120,7 +120,7 @@ public class BootstrapFileProvider {
|
|||
}
|
||||
|
||||
public BootstrapProperties getProtectedBootstrapProperties() {
|
||||
return BootstrapPropertiesLoader.loadProtectedProperties(bootstrapConfigFile).getApplicationProperties();
|
||||
return BootstrapPropertiesLoader.loadProtectedProperties(bootstrapConfigFile);
|
||||
}
|
||||
|
||||
public Properties getStatusProperties() {
|
||||
|
|
|
@ -1,83 +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.minifi.commons.utils;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
public class SensitivePropertyUtils {
|
||||
|
||||
public static final String MINIFI_BOOTSTRAP_SENSITIVE_KEY = "minifi.bootstrap.sensitive.key";
|
||||
private static final String EMPTY = "";
|
||||
|
||||
private SensitivePropertyUtils() {
|
||||
}
|
||||
|
||||
public static String getFormattedKey() {
|
||||
String key = getKey(System.getProperty("minifi.bootstrap.conf.file.path"));
|
||||
// Format the key (check hex validity and remove spaces)
|
||||
return getFormattedKey(key);
|
||||
}
|
||||
|
||||
public static String getFormattedKey(String unformattedKey) {
|
||||
String key = formatHexKey(unformattedKey);
|
||||
|
||||
if (isNotEmpty(key) && !isHexKeyValid(key)) {
|
||||
throw new IllegalArgumentException("The key was not provided in valid hex format and of the correct length");
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatHexKey(String input) {
|
||||
return Optional.ofNullable(input)
|
||||
.map(String::trim)
|
||||
.filter(SensitivePropertyUtils::isNotEmpty)
|
||||
.map(str -> str.replaceAll("[^0-9a-fA-F]", EMPTY).toLowerCase())
|
||||
.orElse(EMPTY);
|
||||
}
|
||||
|
||||
private static boolean isHexKeyValid(String key) {
|
||||
return Optional.ofNullable(key)
|
||||
.map(String::trim)
|
||||
.filter(SensitivePropertyUtils::isNotEmpty)
|
||||
.filter(k -> k.matches("^[0-9a-fA-F]{64}$"))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private static String getKey(String bootstrapConfigFilePath) {
|
||||
Properties properties = new Properties();
|
||||
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(bootstrapConfigFilePath))) {
|
||||
properties.load(inputStream);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(format("Loading Bootstrap Properties [%s] failed", bootstrapConfigFilePath), e);
|
||||
}
|
||||
|
||||
return properties.getProperty(MINIFI_BOOTSTRAP_SENSITIVE_KEY);
|
||||
}
|
||||
|
||||
private static boolean isNotEmpty(String keyFilePath) {
|
||||
return keyFilePath != null && !keyFilePath.isBlank();
|
||||
}
|
||||
}
|
|
@ -31,19 +31,6 @@
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-loader</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-cipher</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-properties</artifactId>
|
||||
|
|
|
@ -17,33 +17,15 @@
|
|||
|
||||
package org.apache.nifi.minifi.properties;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.nifi.minifi.commons.utils.SensitivePropertyUtils.MINIFI_BOOTSTRAP_SENSITIVE_KEY;
|
||||
import static org.apache.nifi.minifi.commons.utils.SensitivePropertyUtils.getFormattedKey;
|
||||
|
||||
import java.io.File;
|
||||
import org.apache.nifi.properties.AesGcmSensitivePropertyProvider;
|
||||
|
||||
public class BootstrapPropertiesLoader {
|
||||
|
||||
public static BootstrapProperties load(File file) {
|
||||
ProtectedBootstrapProperties protectedProperties = loadProtectedProperties(file);
|
||||
if (protectedProperties.hasProtectedKeys()) {
|
||||
String sensitiveKey = protectedProperties.getApplicationProperties().getProperty(MINIFI_BOOTSTRAP_SENSITIVE_KEY);
|
||||
validateSensitiveKeyProperty(sensitiveKey);
|
||||
String keyHex = getFormattedKey(sensitiveKey);
|
||||
protectedProperties.addSensitivePropertyProvider(new AesGcmSensitivePropertyProvider(keyHex));
|
||||
}
|
||||
return protectedProperties.getUnprotectedProperties();
|
||||
return loadProtectedProperties(file);
|
||||
}
|
||||
|
||||
public static ProtectedBootstrapProperties loadProtectedProperties(File file) {
|
||||
return new ProtectedBootstrapProperties(PropertiesLoader.load(file, "Bootstrap"));
|
||||
}
|
||||
|
||||
private static void validateSensitiveKeyProperty(String sensitiveKey) {
|
||||
if (sensitiveKey == null || sensitiveKey.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException(format("bootstrap.conf contains protected properties but %s is not found", MINIFI_BOOTSTRAP_SENSITIVE_KEY));
|
||||
}
|
||||
public static BootstrapProperties loadProtectedProperties(File file) {
|
||||
return new BootstrapProperties(PropertiesLoader.load(file, "Bootstrap"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
package org.apache.nifi.minifi.properties;
|
||||
|
||||
import java.io.File;
|
||||
import org.apache.nifi.properties.AesGcmSensitivePropertyProvider;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.nifi.util.NiFiBootstrapUtils;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
|
||||
|
@ -26,40 +27,17 @@ public class MiNiFiPropertiesLoader {
|
|||
private static final String DEFAULT_APPLICATION_PROPERTIES_FILE_PATH = NiFiBootstrapUtils.getDefaultApplicationPropertiesFilePath();
|
||||
|
||||
private NiFiProperties instance;
|
||||
private String keyHex;
|
||||
|
||||
public MiNiFiPropertiesLoader(String keyHex) {
|
||||
this.keyHex = keyHex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ProtectedMiNiFiProperties} instance loaded from the
|
||||
* serialized form in the file. Responsible for actually reading from disk
|
||||
* and deserializing the properties. Returns a protected instance to allow
|
||||
* for decryption operations.
|
||||
*
|
||||
* @param file the file containing serialized properties
|
||||
* @return the ProtectedMiNiFiProperties instance
|
||||
*/
|
||||
ProtectedMiNiFiProperties loadProtectedProperties(File file) {
|
||||
return new ProtectedMiNiFiProperties(PropertiesLoader.load(file, "Application"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link NiFiProperties} loaded from the provided
|
||||
* {@link File}. If any properties are protected, will attempt to use the
|
||||
* {@link AesGcmSensitivePropertyProvider} to unprotect them
|
||||
* transparently.
|
||||
* {@link File}.
|
||||
*
|
||||
* @param file the File containing the serialized properties
|
||||
* @return the NiFiProperties instance
|
||||
*/
|
||||
public NiFiProperties load(File file) {
|
||||
ProtectedMiNiFiProperties protectedProperties = loadProtectedProperties(file);
|
||||
if (protectedProperties.hasProtectedKeys()) {
|
||||
protectedProperties.addSensitivePropertyProvider(new AesGcmSensitivePropertyProvider(keyHex));
|
||||
}
|
||||
return new MultiSourceMinifiProperties(protectedProperties.getUnprotectedPropertiesAsMap());
|
||||
final Properties properties = PropertiesLoader.load(file, "Application");
|
||||
return new MultiSourceMinifiProperties(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.apache.nifi.minifi.properties;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -29,7 +29,7 @@ import org.apache.nifi.util.NiFiProperties;
|
|||
*/
|
||||
public class MultiSourceMinifiProperties extends NiFiProperties {
|
||||
|
||||
public MultiSourceMinifiProperties(Map<String, String> props) {
|
||||
public MultiSourceMinifiProperties(Properties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
public interface PropertiesLoader {
|
||||
|
||||
static final Logger logger = LoggerFactory.getLogger(PropertiesLoader.class);
|
||||
Logger logger = LoggerFactory.getLogger(PropertiesLoader.class);
|
||||
|
||||
static Properties load(File file, String propertiesType) {
|
||||
if (file == null || !file.exists() || !file.canRead()) {
|
||||
|
|
|
@ -1,134 +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.minifi.properties;
|
||||
|
||||
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ADDITIONAL_SENSITIVE_PROPERTIES_KEY;
|
||||
import static org.apache.nifi.minifi.properties.ProtectedMiNiFiProperties.DEFAULT_SENSITIVE_PROPERTIES;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
|
||||
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;
|
||||
|
||||
public class ProtectedBootstrapProperties extends BootstrapProperties implements ProtectedProperties<BootstrapProperties>,
|
||||
SensitivePropertyProtector<ProtectedBootstrapProperties, BootstrapProperties> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProtectedBootstrapProperties.class);
|
||||
|
||||
private BootstrapProperties bootstrapProperties;
|
||||
|
||||
private final SensitivePropertyProtector<ProtectedBootstrapProperties, BootstrapProperties> propertyProtectionDelegate;
|
||||
|
||||
public ProtectedBootstrapProperties(BootstrapProperties props) {
|
||||
super();
|
||||
this.bootstrapProperties = props;
|
||||
this.propertyProtectionDelegate = new ApplicationPropertiesProtector<>(this);
|
||||
logger.debug("Loaded {} properties (including {} protection schemes) into ProtectedBootstrapProperties", getApplicationProperties().getPropertyKeys().size(),
|
||||
getProtectedPropertyKeys().size());
|
||||
}
|
||||
|
||||
public ProtectedBootstrapProperties(Properties rawProps) {
|
||||
this(new BootstrapProperties(rawProps));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPropertyKeysIncludingProtectionSchemes() {
|
||||
return propertyProtectionDelegate.getPropertyKeysIncludingProtectionSchemes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSensitivePropertyKeys() {
|
||||
return propertyProtectionDelegate.getSensitivePropertyKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPopulatedSensitivePropertyKeys() {
|
||||
return propertyProtectionDelegate.getPopulatedSensitivePropertyKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasProtectedKeys() {
|
||||
return propertyProtectionDelegate.hasProtectedKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProtectedPropertyKeys() {
|
||||
return propertyProtectionDelegate.getProtectedPropertyKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPropertySensitive(String key) {
|
||||
return propertyProtectionDelegate.isPropertySensitive(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPropertyProtected(String key) {
|
||||
return propertyProtectionDelegate.isPropertyProtected(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BootstrapProperties getUnprotectedProperties() throws SensitivePropertyProtectionException {
|
||||
return propertyProtectionDelegate.getUnprotectedProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSensitivePropertyProvider(SensitivePropertyProvider sensitivePropertyProvider) {
|
||||
propertyProtectionDelegate.addSensitivePropertyProvider(sensitivePropertyProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAdditionalSensitivePropertiesKeys() {
|
||||
return getProperty(getAdditionalSensitivePropertiesKeysName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAdditionalSensitivePropertiesKeysName() {
|
||||
return ADDITIONAL_SENSITIVE_PROPERTIES_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDefaultSensitiveProperties() {
|
||||
return Stream.of(DEFAULT_SENSITIVE_PROPERTIES, Arrays.stream(MiNiFiProperties.values()).filter(MiNiFiProperties::isSensitive).map(MiNiFiProperties::getKey).collect(Collectors.toList()))
|
||||
.flatMap(List::stream).distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BootstrapProperties getApplicationProperties() {
|
||||
if (this.bootstrapProperties == null) {
|
||||
this.bootstrapProperties = new BootstrapProperties();
|
||||
}
|
||||
|
||||
return this.bootstrapProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BootstrapProperties createApplicationProperties(Properties rawProperties) {
|
||||
return new BootstrapProperties(rawProperties);
|
||||
}
|
||||
}
|
|
@ -1,239 +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.minifi.properties;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ADDITIONAL_SENSITIVE_PROPERTIES_KEY;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
|
||||
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.apache.nifi.util.NiFiProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Decorator class for intermediate phase when {@link MiNiFiPropertiesLoader} loads the
|
||||
* raw properties file and performs unprotection activities before returning a clean
|
||||
* implementation of {@link NiFiProperties}.
|
||||
* This encapsulates the sensitive property access logic from external consumers
|
||||
* of {@code NiFiProperties}.
|
||||
*/
|
||||
public class ProtectedMiNiFiProperties extends NiFiProperties implements ProtectedProperties<NiFiProperties>,
|
||||
SensitivePropertyProtector<ProtectedMiNiFiProperties, NiFiProperties> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProtectedMiNiFiProperties.class);
|
||||
public static final List<String> DEFAULT_SENSITIVE_PROPERTIES = new ArrayList<>(asList(
|
||||
SECURITY_KEY_PASSWD,
|
||||
SECURITY_KEYSTORE_PASSWD,
|
||||
SECURITY_TRUSTSTORE_PASSWD,
|
||||
SENSITIVE_PROPS_KEY
|
||||
));
|
||||
|
||||
private final SensitivePropertyProtector<ProtectedMiNiFiProperties, NiFiProperties> propertyProtectionDelegate;
|
||||
|
||||
private NiFiProperties applicationProperties;
|
||||
|
||||
public ProtectedMiNiFiProperties() {
|
||||
this(new NiFiProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance containing the provided {@link NiFiProperties}.
|
||||
*
|
||||
* @param props the NiFiProperties to contain
|
||||
*/
|
||||
public ProtectedMiNiFiProperties(NiFiProperties props) {
|
||||
this.applicationProperties = props;
|
||||
this.propertyProtectionDelegate = new ApplicationPropertiesProtector<>(this);
|
||||
logger.debug("Loaded {} properties (including {} protection schemes) into ProtectedNiFiProperties", getApplicationProperties()
|
||||
.getPropertyKeys().size(), getProtectedPropertyKeys().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance containing the provided raw {@link Properties}.
|
||||
*
|
||||
* @param rawProps the Properties to contain
|
||||
*/
|
||||
public ProtectedMiNiFiProperties(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 Stream.of(DEFAULT_SENSITIVE_PROPERTIES,
|
||||
Arrays.stream(MiNiFiProperties.values()).filter(MiNiFiProperties::isSensitive).map(MiNiFiProperties::getKey).toList()).flatMap(List::stream).distinct().toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* key: E(value, key)
|
||||
* key.protected: aes/gcm/256
|
||||
* key2: value2
|
||||
* <p>
|
||||
* would return size 2
|
||||
*
|
||||
* @return the count of real properties
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return propertyProtectionDelegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPropertyKeysIncludingProtectionSchemes() {
|
||||
return propertyProtectionDelegate.getPropertyKeysIncludingProtectionSchemes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSensitivePropertyKeys() {
|
||||
return propertyProtectionDelegate.getSensitivePropertyKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPopulatedSensitivePropertyKeys() {
|
||||
return propertyProtectionDelegate.getPopulatedSensitivePropertyKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasProtectedKeys() {
|
||||
return propertyProtectionDelegate.hasProtectedKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProtectedPropertyKeys() {
|
||||
return propertyProtectionDelegate.getProtectedPropertyKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPropertySensitive(final String key) {
|
||||
return propertyProtectionDelegate.isPropertySensitive(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPropertyProtected(final String key) {
|
||||
return propertyProtectionDelegate.isPropertyProtected(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NiFiProperties getUnprotectedProperties() throws SensitivePropertyProtectionException {
|
||||
return propertyProtectionDelegate.getUnprotectedProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSensitivePropertyProvider(final SensitivePropertyProvider sensitivePropertyProvider) {
|
||||
propertyProtectionDelegate.addSensitivePropertyProvider(sensitivePropertyProvider);
|
||||
}
|
||||
|
||||
public Map<String, String> getUnprotectedPropertiesAsMap() {
|
||||
NiFiProperties niFiProperties = propertyProtectionDelegate.getUnprotectedProperties();
|
||||
return niFiProperties.getPropertyKeys().stream().collect(Collectors.toMap(Function.identity(), niFiProperties::getProperty));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of properties that are marked as protected in the provided {@link NiFiProperties} instance without requiring external creation of a
|
||||
* {@link ProtectedMiNiFiProperties} instance.
|
||||
*
|
||||
* @param plainProperties the instance to count protected properties
|
||||
* @return the number of protected properties
|
||||
*/
|
||||
public static int countProtectedProperties(final NiFiProperties plainProperties) {
|
||||
return new ProtectedMiNiFiProperties(plainProperties).getProtectedPropertyKeys().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of properties that are marked as sensitive in the provided {@link NiFiProperties} instance without requiring external creation of a
|
||||
* {@link ProtectedMiNiFiProperties} instance.
|
||||
*
|
||||
* @param plainProperties the instance to count sensitive properties
|
||||
* @return the number of sensitive properties
|
||||
*/
|
||||
public static int countSensitiveProperties(final NiFiProperties plainProperties) {
|
||||
return new ProtectedMiNiFiProperties(plainProperties).getSensitivePropertyKeys().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s Size [%d]", getClass().getSimpleName(), size());
|
||||
}
|
||||
}
|
|
@ -1,82 +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.minifi.properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class BootstrapPropertiesLoaderTest {
|
||||
|
||||
private static final String NON_EXISTING_FILE_PATH = "/conf/nonexisting.conf";
|
||||
private static final String DUPLICATED_ITEMS_FILE_PATH = "/conf/bootstrap_duplicated_items.conf";
|
||||
private static final String UNPROTECTED_ITEMS_FILE_PATH = "/conf/bootstrap_unprotected.conf";
|
||||
private static final String PROTECTED_ITEMS_FILE_PATH = "/conf/bootstrap_protected.conf";
|
||||
private static final String PROTECTED_ITEMS_WITHOUT_SENSITIVE_KEY_FILE_PATH = "/conf/bootstrap_protected_without_sensitive_key.conf";
|
||||
|
||||
private static final Map<String, String> UNPROTECTED_PROPERTIES = Map.of("nifi.minifi.security.keystorePasswd", "testPassword", "nifi.minifi.sensitive.props.key", "testSensitivePropsKey");
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentExceptionIfFileIsNotProvided() {
|
||||
assertThrows(IllegalArgumentException.class, () -> BootstrapPropertiesLoader.load(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentExceptionIfFileDoesNotExists() {
|
||||
assertThrows(IllegalArgumentException.class, () -> BootstrapPropertiesLoader.load(new File(NON_EXISTING_FILE_PATH)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentExceptionIfTheConfigFileContainsDuplicatedKeysWithDifferentValues() {
|
||||
assertThrows(IllegalArgumentException.class, () -> BootstrapPropertiesLoader.load(getFile(DUPLICATED_ITEMS_FILE_PATH)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnPropertiesIfConfigFileDoesNotContainProtectedProperties() {
|
||||
BootstrapProperties bootstrapProperties = BootstrapPropertiesLoader.load(getFile(UNPROTECTED_ITEMS_FILE_PATH));
|
||||
|
||||
assertEquals(UNPROTECTED_PROPERTIES,
|
||||
bootstrapProperties.getPropertyKeys().stream().filter(UNPROTECTED_PROPERTIES::containsKey).collect(Collectors.toMap(Function.identity(), bootstrapProperties::getProperty)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUnProtectedProperties() {
|
||||
BootstrapProperties bootstrapProperties = BootstrapPropertiesLoader.load(getFile(PROTECTED_ITEMS_FILE_PATH));
|
||||
|
||||
assertEquals(UNPROTECTED_PROPERTIES,
|
||||
bootstrapProperties.getPropertyKeys().stream().filter(UNPROTECTED_PROPERTIES::containsKey).collect(Collectors.toMap(Function.identity(), bootstrapProperties::getProperty)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentExceptionIfFileContainsProtectedPropertiesButSensitiveKeyIsMissing() {
|
||||
assertThrows(IllegalArgumentException.class, () -> BootstrapPropertiesLoader.load(getFile(PROTECTED_ITEMS_WITHOUT_SENSITIVE_KEY_FILE_PATH)));
|
||||
}
|
||||
|
||||
private File getFile(String duplicatedItemsFilePath) {
|
||||
URL resource = BootstrapPropertiesLoaderTest.class.getResource(duplicatedItemsFilePath);
|
||||
assertNotNull(resource);
|
||||
return new File(resource.getPath());
|
||||
}
|
||||
}
|
|
@ -1,90 +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.minifi.properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class MiNiFiPropertiesLoaderTest {
|
||||
private static final String NON_EXISTING_FILE_PATH = "/conf/nonexisting.properties";
|
||||
private static final String DUPLICATED_ITEMS_FILE_PATH = "/conf/bootstrap_duplicated_items.conf";
|
||||
private static final String UNPROTECTED_ITEMS_FILE_PATH = "/conf/minifi_unprotected.properties";
|
||||
private static final String PROTECTED_ITEMS_FILE_PATH = "/conf/minifi_protected.properties";
|
||||
private static final Map<String, String> UNPROTECTED_PROPERTIES = Map.of("nifi.security.keystorePasswd", "testPassword", "nifi.security.keyPasswd",
|
||||
"testSensitivePropsKey");
|
||||
private static final String PROTECTION_KEY = "00714ae7a77b24cde1d36bd19472777e0d4ab02c38913b7f9bf41f3963147b4f";
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentExceptionIfFileIsNotProvided() {
|
||||
MiNiFiPropertiesLoader miNiFiPropertiesLoader = new MiNiFiPropertiesLoader("");
|
||||
assertThrows(IllegalArgumentException.class, () -> miNiFiPropertiesLoader.load((String) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentExceptionIfFileDoesNotExists() {
|
||||
MiNiFiPropertiesLoader miNiFiPropertiesLoader = new MiNiFiPropertiesLoader("");
|
||||
assertThrows(IllegalArgumentException.class, () -> miNiFiPropertiesLoader.load(new File(NON_EXISTING_FILE_PATH)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalArgumentExceptionIfTheConfigFileContainsDuplicatedKeysWithDifferentValues() {
|
||||
MiNiFiPropertiesLoader miNiFiPropertiesLoader = new MiNiFiPropertiesLoader("");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> miNiFiPropertiesLoader.load(getFile(DUPLICATED_ITEMS_FILE_PATH)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnPropertiesIfConfigFileDoesNotContainProtectedProperties() {
|
||||
MiNiFiPropertiesLoader miNiFiPropertiesLoader = new MiNiFiPropertiesLoader("");
|
||||
|
||||
NiFiProperties niFiProperties = miNiFiPropertiesLoader.load(getFile(UNPROTECTED_ITEMS_FILE_PATH));
|
||||
|
||||
assertEquals(UNPROTECTED_PROPERTIES,
|
||||
niFiProperties.getPropertyKeys().stream().filter(UNPROTECTED_PROPERTIES::containsKey).collect(Collectors.toMap(Function.identity(), niFiProperties::getProperty)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUnProtectedProperties() {
|
||||
MiNiFiPropertiesLoader miNiFiPropertiesLoader = new MiNiFiPropertiesLoader(PROTECTION_KEY);
|
||||
|
||||
NiFiProperties niFiProperties = miNiFiPropertiesLoader.load(getUrl(PROTECTED_ITEMS_FILE_PATH).getPath());
|
||||
|
||||
assertEquals(UNPROTECTED_PROPERTIES,
|
||||
niFiProperties.getPropertyKeys().stream().filter(UNPROTECTED_PROPERTIES::containsKey).collect(Collectors.toMap(Function.identity(), niFiProperties::getProperty)));
|
||||
}
|
||||
|
||||
private File getFile(String propertiesFilePath) {
|
||||
URL resource = getUrl(propertiesFilePath);
|
||||
return new File(resource.getPath());
|
||||
}
|
||||
|
||||
private static URL getUrl(String propertiesFilePath) {
|
||||
URL resource = BootstrapPropertiesLoaderTest.class.getResource(propertiesFilePath);
|
||||
assertNotNull(resource);
|
||||
return resource;
|
||||
}
|
||||
}
|
|
@ -1,18 +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.
|
||||
|
||||
# property file that contains the same key with different values
|
||||
property1=test
|
||||
property1=test2
|
|
@ -1,22 +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.
|
||||
|
||||
# property file that contains the protected properties and a sensitive key to decrypt it
|
||||
nifi.minifi.security.keystorePasswd=noBD32A0ANcz/BHv||nhtyNhlFgNNZcVhgxEOg2xUZ5UAihgyBit1drQ==
|
||||
nifi.minifi.security.keystorePasswd.protected=aes/gcm/256
|
||||
nifi.minifi.sensitive.props.key=JUMeAtpD1Q7CiXHt||BppiPMoWXxkKBl5mYsxP5vkVabsXZyLu2lxjyv9LMHc6RJEE9g==
|
||||
nifi.minifi.sensitive.props.key.protected=aes/gcm/256
|
||||
|
||||
minifi.bootstrap.sensitive.key=00714ae7a77b24cde1d36bd19472777e0d4ab02c38913b7f9bf41f3963147b4f
|
|
@ -1,20 +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.
|
||||
|
||||
# property file that contains the protected properties but without minifi.bootstrap.sensitive.key
|
||||
nifi.minifi.security.keystorePasswd=noBD32A0ANcz/BHv||nhtyNhlFgNNZcVhgxEOg2xUZ5UAihgyBit1drQ==
|
||||
nifi.minifi.security.keystorePasswd.protected=aes/gcm/256
|
||||
nifi.minifi.sensitive.props.key=JUMeAtpD1Q7CiXHt||BppiPMoWXxkKBl5mYsxP5vkVabsXZyLu2lxjyv9LMHc6RJEE9g==
|
||||
nifi.minifi.sensitive.props.key.protected=aes/gcm/256
|
|
@ -1,18 +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.
|
||||
|
||||
# property file that contains the unprotected properties
|
||||
nifi.minifi.security.keystorePasswd=testPassword
|
||||
nifi.minifi.sensitive.props.key=testSensitivePropsKey
|
|
@ -1,20 +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.
|
||||
|
||||
nifi.security.keystorePasswd=s+jNt0t608+F0uq9||PWPtOCr5fmItmL8ZsgpheQxkkJJzXWqrNvqdCL/gcGE2cy1NTgGDhI1apuacNHjj
|
||||
nifi.security.keystorePasswd=123
|
||||
nifi.security.keystorePasswd.protected=aes/gcm/256
|
||||
nifi.security.keyPasswd=JUMeAtpD1Q7CiXHt||BppiPMoWXxkKBl5mYsxP5vkVabsXZyLu2lxjyv9LMHc6RJEE9g==
|
||||
nifi.security.keyPasswd.protected=aes/gcm/256
|
|
@ -1,19 +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.
|
||||
|
||||
nifi.security.keystorePasswd=noBD32A0ANcz/BHv||nhtyNhlFgNNZcVhgxEOg2xUZ5UAihgyBit1drQ==
|
||||
nifi.security.keystorePasswd.protected=aes/gcm/256
|
||||
nifi.security.keyPasswd=JUMeAtpD1Q7CiXHt||BppiPMoWXxkKBl5mYsxP5vkVabsXZyLu2lxjyv9LMHc6RJEE9g==
|
||||
nifi.security.keyPasswd.protected=aes/gcm/256
|
|
@ -1,17 +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.
|
||||
|
||||
nifi.security.keystorePasswd=testPassword
|
||||
nifi.security.keyPasswd=testSensitivePropsKey
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.apache.nifi.minifi;
|
||||
|
||||
import static org.apache.nifi.minifi.commons.utils.SensitivePropertyUtils.getFormattedKey;
|
||||
import static org.apache.nifi.minifi.util.BootstrapClassLoaderUtils.createBootstrapClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -239,14 +238,13 @@ public class MiNiFi {
|
|||
|
||||
private static NiFiProperties initializeProperties(ClassLoader boostrapLoader) {
|
||||
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
String key = getFormattedKey();
|
||||
|
||||
Thread.currentThread().setContextClassLoader(boostrapLoader);
|
||||
|
||||
try {
|
||||
Class<?> propsLoaderClass = Class.forName("org.apache.nifi.minifi.properties.MiNiFiPropertiesLoader", true, boostrapLoader);
|
||||
Constructor<?> constructor = propsLoaderClass.getDeclaredConstructor(String.class);
|
||||
Object loaderInstance = constructor.newInstance(key);
|
||||
Constructor<?> constructor = propsLoaderClass.getConstructor();
|
||||
Object loaderInstance = constructor.newInstance();
|
||||
Method getMethod = propsLoaderClass.getMethod("get");
|
||||
NiFiProperties properties = (NiFiProperties) getMethod.invoke(loaderInstance);
|
||||
logger.info("Application Properties loaded [{}]", properties.size());
|
||||
|
|
|
@ -79,16 +79,6 @@ limitations under the License.
|
|||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-loader</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-cipher</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
|
@ -61,102 +61,6 @@ After downloading the binary and extracting it, to run the MiNiFi Toolkit Conver
|
|||
## Note
|
||||
It's not guaranteed in all circumstances that the migration will result in a correct flow. For example if a processor's configuration has changed between version, the conversion tool won't be aware of this, and will use the deprecated property names. You will need to fix such issues manually.
|
||||
|
||||
# <a id="encrypt-sensitive-properties-in-bootstrapconf" href="#encrypt-sensitive-properties-in-bootstrapconf">Encrypting Sensitive Properties in bootstrap.conf</a>
|
||||
|
||||
## MiNiFi Encrypt-Config Tool
|
||||
The encrypt-config command line tool (invoked in minifi-toolkit as ./bin/encrypt-config.sh or bin\encrypt-config.bat) reads from a bootstrap.conf file with plaintext sensitive configuration values and encrypts each value using a random encryption key. It replaces the plain values with the protected value in the same file, or writes to a new bootstrap.conf file if specified. Additionally it can be used to encrypt the unencrypted sensitive properties (if any) in the flow.json.raw. For using this functionality `nifi.minifi.sensitive.props.key` and `nifi.minifi.sensitive.props.algorithm` has to be provided in bootstrap.conf.
|
||||
|
||||
The supported encryption algorithm utilized is AES/GCM 256-bit.
|
||||
|
||||
### Usage
|
||||
To show help:
|
||||
|
||||
```
|
||||
./bin/encrypt-config.sh -h
|
||||
```
|
||||
|
||||
The following are the available options:
|
||||
* -b, --bootstrapConf <bootstrapConfPath> Path to file containing Bootstrap Configuration [bootstrap.conf]
|
||||
* -B, --outputBootstrapConf <outputBootstrapConf> Path to output file for Bootstrap Configuration [bootstrap.conf] with root key configured
|
||||
* -x, --encryptRawFlowJsonOnly Process Raw Flow Configuration [flow.json.raw] sensitive property values without modifying other configuration files
|
||||
* -f, --rawFlowJson <flowConfigurationPath> Path to file containing Raw Flow Configuration [flow.json.raw] that will be updated unless the output argument is provided
|
||||
* -g, --outputRawFlowJson <outputFlowConfigurationPath> ath to output file for Raw Flow Configuration [flow.json.raw] with property protection applied
|
||||
* -h, --help Show help message and exit.
|
||||
|
||||
### Example 1
|
||||
As an example of how the tool works with the following existing values in the bootstrap.conf file:
|
||||
```
|
||||
nifi.sensitive.props.key=thisIsABadSensitiveKeyPassword
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
nifi.security.keystoreType=JKS
|
||||
nifi.security.keystorePasswd=thisIsABadKeystorePassword
|
||||
nifi.security.keyPasswd=thisIsABadKeyPassword
|
||||
nifi.security.truststore=
|
||||
nifi.security.truststoreType=
|
||||
nifi.security.truststorePasswd=
|
||||
c2.security.truststore.location=
|
||||
c2.security.truststore.password=thisIsABadTruststorePassword
|
||||
c2.security.truststore.type=JKS
|
||||
c2.security.keystore.location=
|
||||
c2.security.keystore.password=thisIsABadKeystorePassword
|
||||
c2.security.keystore.type=JKS
|
||||
```
|
||||
Enter the following arguments when using the tool:
|
||||
```
|
||||
encrypt-config.sh \
|
||||
-b bootstrap.conf \
|
||||
```
|
||||
As a result, the bootstrap.conf file is overwritten with protected properties and sibling encryption identifiers (aes/gcm/256, the currently supported algorithm):
|
||||
```
|
||||
nifi.sensitive.props.key=4OjkrFywZb7BlGz4||Tm9pg0jV4TltvVKeiMlm9zBsqmtmYUA2QkzcLKQpspyggtQuhNAkAla5s2695A==
|
||||
nifi.sensitive.props.key.protected=aes/gcm/256
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
nifi.security.keystoreType=JKS
|
||||
nifi.security.keystorePasswd=iXDmDCadoNJ3VotZ||WvOGbrii4Gk0vr3b6mDstZg+NE0BPZUPk6LVqQlf2Sx3G5XFbUbUYAUz
|
||||
nifi.security.keystorePasswd.protected=aes/gcm/256
|
||||
nifi.security.keyPasswd=199uUUgpPqB4Fuoo||KckbW7iu+HZf1r4KSMQAFn8NLJK+CnUuayqPsTsdM0Wxou1BHg==
|
||||
nifi.security.keyPasswd.protected=aes/gcm/256
|
||||
nifi.security.truststore=
|
||||
nifi.security.truststoreType=
|
||||
nifi.security.truststorePasswd=
|
||||
c2.security.truststore.location=
|
||||
c2.security.truststore.password=0pHpp+l/WHsDM/sm||fXBvDAQ1BXvNQ8b4EHKa1GspsLx+UD+2EDhph0HbsdmgpVhEv4qj0q5TDo0=
|
||||
c2.security.truststore.password.protected=aes/gcm/256
|
||||
c2.security.truststore.type=JKS
|
||||
c2.security.keystore.location=
|
||||
c2.security.keystore.password=j+80L7++RNDf9INQ||RX/QkdVFwRos6Y4XJ8YSUWoI3W5Wx50dyw7HrAA84719SvfxA9eUSDEA
|
||||
c2.security.keystore.password.protected=aes/gcm/256
|
||||
c2.security.keystore.type=JKS
|
||||
```
|
||||
|
||||
Additionally, the bootstrap.conf file is updated with the encryption key as follows:
|
||||
```
|
||||
minifi.bootstrap.sensitive.key=c92623e798be949379d0d18f432a57f1b74732141be321cb4af9ed94aa0ae8ac
|
||||
```
|
||||
|
||||
Sensitive configuration values are encrypted by the tool by default, however you can encrypt any additional properties, if desired. To encrypt additional properties, specify them as comma-separated values in the minifi.sensitive.props.additional.keys property.
|
||||
|
||||
If the bootstrap.conf file already has valid protected values, those property values are not modified by the tool.
|
||||
|
||||
### Example 2
|
||||
An example to encrypt non encrypted sensitive properties in flow.json.raw
|
||||
```
|
||||
nifi.sensitive.props.key=sensitivePropsKey
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
```
|
||||
Enter the following arguments when using the tool:
|
||||
```
|
||||
encrypt-config.sh -x -f flow.json.raw
|
||||
```
|
||||
As a result, the flow.json.raw file is overwritten with encrypted sensitive properties
|
||||
The algorithm uses the property descriptors in the flow.json.raw to determine if a property is sensitive or not. If that information is missing, no properties will be encrypted even if it is defined sensitive in the agent manifest.
|
||||
|
||||
## Getting Help
|
||||
If you have questions, you can reach out to our mailing list: dev@nifi.apache.org
|
||||
([archive](https://mail-archives.apache.org/mod_mbox/nifi-dev)).
|
||||
|
|
|
@ -62,11 +62,6 @@ limitations under the License.
|
|||
<artifactId>minifi-toolkit-configuration</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi.minifi</groupId>
|
||||
<artifactId>minifi-toolkit-encrypt-config</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
@echo off
|
||||
rem
|
||||
rem Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
rem contributor license agreements. See the NOTICE file distributed with
|
||||
rem this work for additional information regarding copyright ownership.
|
||||
rem The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
rem (the "License"); you may not use this file except in compliance with
|
||||
rem the License. You may obtain a copy of the License at
|
||||
rem
|
||||
rem http://www.apache.org/licenses/LICENSE-2.0
|
||||
rem
|
||||
rem Unless required by applicable law or agreed to in writing, software
|
||||
rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
rem See the License for the specific language governing permissions and
|
||||
rem limitations under the License.
|
||||
rem
|
||||
|
||||
rem Use JAVA_HOME if it's set; otherwise, just use java
|
||||
|
||||
if "%JAVA_HOME%" == "" goto noJavaHome
|
||||
if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
|
||||
set JAVA_EXE=%JAVA_HOME%\bin\java.exe
|
||||
goto startConfig
|
||||
|
||||
:noJavaHome
|
||||
echo The JAVA_HOME environment variable is not defined correctly.
|
||||
echo Instead the PATH will be used to find the java executable.
|
||||
echo.
|
||||
set JAVA_EXE=java
|
||||
goto startConfig
|
||||
|
||||
:startConfig
|
||||
set LIB_DIR=%~dp0..\classpath;%~dp0..\lib
|
||||
|
||||
if "%JAVA_OPTS%" == "" set JAVA_OPTS=-Xms128m -Xmx256m
|
||||
|
||||
SET JAVA_PARAMS=-cp %LIB_DIR%\* %JAVA_OPTS% org.apache.nifi.minifi.toolkit.config.EncryptConfigCommand
|
||||
|
||||
cmd.exe /C ""%JAVA_EXE%" %JAVA_PARAMS% %* ""
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
# Script structure inspired from Apache Karaf and other Apache projects with similar startup approaches
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
MINIFI_TOOLKIT_HOME=$(cd "${SCRIPT_DIR}" && cd .. && pwd)
|
||||
PROGNAME=$(basename "$0")
|
||||
|
||||
|
||||
warn() {
|
||||
echo "${PROGNAME}: $*"
|
||||
}
|
||||
|
||||
die() {
|
||||
warn "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
detectOS() {
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false;
|
||||
aix=false;
|
||||
os400=false;
|
||||
darwin=false;
|
||||
case "$(uname)" in
|
||||
CYGWIN*)
|
||||
cygwin=true
|
||||
;;
|
||||
AIX*)
|
||||
aix=true
|
||||
;;
|
||||
OS400*)
|
||||
os400=true
|
||||
;;
|
||||
Darwin)
|
||||
darwin=true
|
||||
;;
|
||||
esac
|
||||
# For AIX, set an environment variable
|
||||
if ${aix}; then
|
||||
export LDR_CNTRL=MAXDATA=0xB0000000@DSA
|
||||
echo ${LDR_CNTRL}
|
||||
fi
|
||||
}
|
||||
|
||||
locateJava() {
|
||||
# Setup the Java Virtual Machine
|
||||
if $cygwin ; then
|
||||
[ -n "${JAVA}" ] && JAVA=$(cygpath --unix "${JAVA}")
|
||||
[ -n "${JAVA_HOME}" ] && JAVA_HOME=$(cygpath --unix "${JAVA_HOME}")
|
||||
fi
|
||||
|
||||
if [ "x${JAVA}" = "x" ] && [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=$(java-config --jre-home)
|
||||
fi
|
||||
if [ "x${JAVA}" = "x" ]; then
|
||||
if [ "x${JAVA_HOME}" != "x" ]; then
|
||||
if [ ! -d "${JAVA_HOME}" ]; then
|
||||
die "JAVA_HOME is not valid: ${JAVA_HOME}"
|
||||
fi
|
||||
JAVA="${JAVA_HOME}/bin/java"
|
||||
else
|
||||
warn "JAVA_HOME not set; results may vary"
|
||||
JAVA=$(type java)
|
||||
JAVA=$(expr "${JAVA}" : '.* \(/.*\)$')
|
||||
if [ "x${JAVA}" = "x" ]; then
|
||||
die "java command not found"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
init() {
|
||||
# Determine if there is special OS handling we must perform
|
||||
detectOS
|
||||
|
||||
# Locate the Java VM to execute
|
||||
locateJava
|
||||
}
|
||||
|
||||
run() {
|
||||
LIBS="${MINIFI_TOOLKIT_HOME}/lib/*"
|
||||
|
||||
sudo_cmd_prefix=""
|
||||
if $cygwin; then
|
||||
MINIFI_TOOLKIT_HOME=$(cygpath --path --windows "${MINIFI_TOOLKIT_HOME}")
|
||||
CLASSPATH="$MINIFI_TOOLKIT_HOME/classpath;$(cygpath --path --windows "${LIBS}")"
|
||||
else
|
||||
CLASSPATH="$MINIFI_TOOLKIT_HOME/classpath:${LIBS}"
|
||||
fi
|
||||
|
||||
export JAVA_HOME="$JAVA_HOME"
|
||||
export MINIFI_TOOLKIT_HOME="$MINIFI_TOOLKIT_HOME"
|
||||
|
||||
umask 0077
|
||||
exec "${JAVA}" -cp "${CLASSPATH}" ${JAVA_OPTS:--Xms128m -Xmx256m} org.apache.nifi.minifi.toolkit.config.EncryptConfigCommand "$@"
|
||||
}
|
||||
|
||||
|
||||
init
|
||||
run "$@"
|
|
@ -1,97 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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">
|
||||
<parent>
|
||||
<groupId>org.apache.nifi.minifi</groupId>
|
||||
<artifactId>minifi-toolkit</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>minifi-toolkit-encrypt-config</artifactId>
|
||||
<description>Tool to encrypt sensitive configuration values</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>4.7.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-framework-core-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-encryptor</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-loader</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>c2-protocol-component-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-cipher</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi.minifi</groupId>
|
||||
<artifactId>minifi-properties-loader</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi.minifi</groupId>
|
||||
<artifactId>minifi-commons-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi.minifi</groupId>
|
||||
<artifactId>minifi-commons-framework</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-framework-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,42 +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.minifi.toolkit.config;
|
||||
|
||||
import org.apache.nifi.minifi.toolkit.config.command.MiNiFiEncryptConfig;
|
||||
import picocli.CommandLine;
|
||||
|
||||
/**
|
||||
* Encrypt Config Command launcher for Command Line implementation
|
||||
*/
|
||||
public class EncryptConfigCommand {
|
||||
|
||||
/**
|
||||
* Main command method launches Picocli Command Line implementation of Encrypt Config
|
||||
*
|
||||
* @param arguments Command line arguments
|
||||
*/
|
||||
public static void main(String[] arguments) {
|
||||
CommandLine commandLine = new CommandLine(new MiNiFiEncryptConfig());
|
||||
if (arguments.length == 0) {
|
||||
commandLine.usage(System.out);
|
||||
} else {
|
||||
int status = commandLine.execute(arguments);
|
||||
System.exit(status);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,222 +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.minifi.toolkit.config.command;
|
||||
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
import static java.nio.file.Files.write;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static org.apache.commons.lang3.StringUtils.isAnyBlank;
|
||||
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_SENSITIVE_PROPS_ALGORITHM;
|
||||
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_SENSITIVE_PROPS_KEY;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashSet;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Set;
|
||||
import org.apache.nifi.controller.flow.VersionedDataflow;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
|
||||
import org.apache.nifi.minifi.commons.service.FlowPropertyEncryptor;
|
||||
import org.apache.nifi.minifi.commons.service.FlowSerDeService;
|
||||
import org.apache.nifi.minifi.commons.service.StandardFlowPropertyEncryptor;
|
||||
import org.apache.nifi.minifi.commons.service.StandardFlowSerDeService;
|
||||
import org.apache.nifi.minifi.properties.BootstrapProperties;
|
||||
import org.apache.nifi.minifi.properties.BootstrapPropertiesLoader;
|
||||
import org.apache.nifi.minifi.properties.ProtectedBootstrapProperties;
|
||||
import org.apache.nifi.minifi.toolkit.config.transformer.ApplicationPropertiesFileTransformer;
|
||||
import org.apache.nifi.minifi.toolkit.config.transformer.BootstrapConfigurationFileTransformer;
|
||||
import org.apache.nifi.minifi.toolkit.config.transformer.FileTransformer;
|
||||
import org.apache.nifi.properties.AesGcmSensitivePropertyProvider;
|
||||
import org.apache.nifi.properties.SensitivePropertyProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
/**
|
||||
* Encrypt Configuration for MiNiFi
|
||||
*/
|
||||
@Command(
|
||||
name = "encrypt-config",
|
||||
sortOptions = false,
|
||||
mixinStandardHelpOptions = true,
|
||||
usageHelpWidth = 160,
|
||||
separator = " ",
|
||||
version = {
|
||||
"Java ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})"
|
||||
},
|
||||
descriptionHeading = "Description: ",
|
||||
description = {
|
||||
"encrypt-config supports protection of sensitive values in Apache MiNiFi"
|
||||
}
|
||||
)
|
||||
public class MiNiFiEncryptConfig implements Runnable {
|
||||
|
||||
static final String BOOTSTRAP_ROOT_KEY_PROPERTY = "minifi.bootstrap.sensitive.key";
|
||||
|
||||
private static final String WORKING_FILE_NAME_FORMAT = "%s.%d.working";
|
||||
private static final int KEY_LENGTH = 32;
|
||||
|
||||
@Option(
|
||||
names = {"-b", "--bootstrapConf"},
|
||||
description = "Path to file containing Bootstrap Configuration [bootstrap.conf] for optional root key and property protection scheme settings"
|
||||
)
|
||||
Path bootstrapConfPath;
|
||||
|
||||
@Option(
|
||||
names = {"-B", "--outputBootstrapConf"},
|
||||
description = "Path to output file for Bootstrap Configuration [bootstrap.conf] with root key configured"
|
||||
)
|
||||
Path outputBootstrapConf;
|
||||
|
||||
@Option(
|
||||
names = {"-x", "--encryptRawFlowJsonOnly"},
|
||||
description = "Process Raw Flow Configuration [flow.json.raw] sensitive property values without modifying other configuration files"
|
||||
)
|
||||
boolean flowConfigurationRequested;
|
||||
|
||||
@Option(
|
||||
names = {"-f", "--rawFlowJson"},
|
||||
description = "Path to file containing Raw Flow Configuration [flow.json.raw] that will be updated unless the output argument is provided"
|
||||
)
|
||||
Path flowConfigurationPath;
|
||||
|
||||
@Option(
|
||||
names = {"-g", "--outputRawFlowJson"},
|
||||
description = "Path to output file for Raw Flow Configuration [flow.json.raw] with property protection applied"
|
||||
)
|
||||
Path outputFlowConfigurationPath;
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
BootstrapProperties unprotectedProperties = BootstrapPropertiesLoader.load(bootstrapConfPath.toFile());
|
||||
processBootstrapConf(unprotectedProperties);
|
||||
processFlowConfiguration(unprotectedProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process bootstrap.conf writing new Root Key to specified Root Key Property when bootstrap.conf is specified
|
||||
*/
|
||||
protected void processBootstrapConf(BootstrapProperties unprotectedProperties) {
|
||||
if (flowConfigurationRequested) {
|
||||
logger.info("Bootstrap Configuration [bootstrap.conf] not modified based on provided arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Started processing Bootstrap Configuration [{}]", bootstrapConfPath);
|
||||
|
||||
String newRootKey = getRootKey();
|
||||
|
||||
Set<String> sensitivePropertyNames = new HashSet<>((new ProtectedBootstrapProperties(unprotectedProperties)).getSensitivePropertyKeys());
|
||||
FileTransformer fileTransformer2 = new ApplicationPropertiesFileTransformer(unprotectedProperties, getInputSensitivePropertyProvider(newRootKey), sensitivePropertyNames);
|
||||
runFileTransformer(fileTransformer2, bootstrapConfPath, outputBootstrapConf);
|
||||
|
||||
FileTransformer fileTransformer = new BootstrapConfigurationFileTransformer(BOOTSTRAP_ROOT_KEY_PROPERTY, newRootKey);
|
||||
runFileTransformer(fileTransformer, ofNullable(outputBootstrapConf).orElse(bootstrapConfPath), outputBootstrapConf);
|
||||
logger.info("Completed processing Bootstrap Configuration [{}]", bootstrapConfPath);
|
||||
}
|
||||
|
||||
private String getRootKey() {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] sensitivePropertiesKeyBinary = new byte[KEY_LENGTH];
|
||||
secureRandom.nextBytes(sensitivePropertiesKeyBinary);
|
||||
return HexFormat.of().formatHex(sensitivePropertiesKeyBinary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run File Transformer using working path based on output path
|
||||
*
|
||||
* @param fileTransformer File Transformer to be invoked
|
||||
* @param inputPath Input path of file to be transformed
|
||||
* @param outputPath Output path for transformed file that defaults to the input path when not specified
|
||||
*/
|
||||
protected void runFileTransformer(FileTransformer fileTransformer, Path inputPath, Path outputPath) {
|
||||
Path configuredOutputPath = outputPath == null ? inputPath : outputPath;
|
||||
Path workingPath = getWorkingPath(configuredOutputPath);
|
||||
try {
|
||||
fileTransformer.transform(inputPath, workingPath);
|
||||
Files.move(workingPath, configuredOutputPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
String message = String.format("Processing Configuration [%s] failed", inputPath);
|
||||
throw new UncheckedIOException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Input Sensitive Property Provider for decrypting previous values
|
||||
*
|
||||
* @return Input Sensitive Property Provider
|
||||
*/
|
||||
protected SensitivePropertyProvider getInputSensitivePropertyProvider(String keyHex) {
|
||||
return new AesGcmSensitivePropertyProvider(keyHex);
|
||||
}
|
||||
|
||||
private Path getWorkingPath(Path resourcePath) {
|
||||
Path fileName = resourcePath.getFileName();
|
||||
String workingFileName = String.format(WORKING_FILE_NAME_FORMAT, fileName, System.currentTimeMillis());
|
||||
return resourcePath.resolveSibling(workingFileName);
|
||||
}
|
||||
|
||||
private void processFlowConfiguration(BootstrapProperties unprotectedProperties) {
|
||||
if (flowConfigurationPath == null) {
|
||||
logger.info("Flow Configuration not specified");
|
||||
return;
|
||||
}
|
||||
String sensitivePropertiesKey = unprotectedProperties.getProperty(NIFI_MINIFI_SENSITIVE_PROPS_KEY.getKey());
|
||||
String sensitivePropertiesAlgorithm = unprotectedProperties.getProperty(NIFI_MINIFI_SENSITIVE_PROPS_ALGORITHM.getKey());
|
||||
if (isAnyBlank(sensitivePropertiesKey, sensitivePropertiesAlgorithm)) {
|
||||
logger.info("Sensitive Properties Key or Sensitive Properties Algorithm is not provided");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Started processing Flow Configuration [{}]", flowConfigurationPath);
|
||||
|
||||
byte[] flowAsBytes;
|
||||
try {
|
||||
flowAsBytes = readAllBytes(flowConfigurationPath);
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to load Flow Configuration [{}]", flowConfigurationPath);
|
||||
return;
|
||||
}
|
||||
|
||||
FlowSerDeService flowSerDeService = StandardFlowSerDeService.defaultInstance();
|
||||
FlowPropertyEncryptor flowPropertyEncryptor = new StandardFlowPropertyEncryptor(
|
||||
new PropertyEncryptorBuilder(sensitivePropertiesKey).setAlgorithm(sensitivePropertiesAlgorithm).build(), null);
|
||||
|
||||
VersionedDataflow flow = flowSerDeService.deserialize(flowAsBytes);
|
||||
VersionedDataflow encryptedFlow = flowPropertyEncryptor.encryptSensitiveProperties(flow);
|
||||
byte[] encryptedFlowAsBytes = flowSerDeService.serialize(encryptedFlow);
|
||||
|
||||
Path targetPath = ofNullable(outputFlowConfigurationPath).orElse(flowConfigurationPath);
|
||||
try {
|
||||
write(targetPath, encryptedFlowAsBytes);
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to write Flow Configuration [{}]", targetPath);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Completed processing Flow Configuration [{}]", flowConfigurationPath);
|
||||
}
|
||||
}
|
|
@ -1,152 +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.minifi.toolkit.config.transformer;
|
||||
|
||||
import static org.apache.nifi.properties.ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.nifi.properties.ApplicationProperties;
|
||||
import org.apache.nifi.properties.ApplicationPropertiesProtector;
|
||||
import org.apache.nifi.properties.ProtectedPropertyContext;
|
||||
import org.apache.nifi.properties.SensitivePropertyProvider;
|
||||
|
||||
/**
|
||||
* File Transformer supporting transformation of Application Properties with sensitive property values
|
||||
*/
|
||||
public class ApplicationPropertiesFileTransformer implements FileTransformer {
|
||||
|
||||
private static final Pattern PROPERTY_VALUE_PATTERN = Pattern.compile("^([^#!][^=]+?)\\s*=\\s?(.+)");
|
||||
|
||||
private static final int NAME_GROUP = 1;
|
||||
|
||||
private static final int VALUE_GROUP = 2;
|
||||
|
||||
private static final char PROPERTY_VALUE_SEPARATOR = '=';
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
private final SensitivePropertyProvider outputSensitivePropertyProvider;
|
||||
|
||||
private final Set<String> sensitivePropertyNames;
|
||||
|
||||
/**
|
||||
* Application Properties File Transformer uses provided Application Properties as the source of protected values
|
||||
*
|
||||
* @param applicationProperties Application Properties containing decrypted source property values
|
||||
* @param outputSensitivePropertyProvider Sensitive Property Provider encrypts specified sensitive property values
|
||||
* @param sensitivePropertyNames Sensitive Property Names marked for encryption
|
||||
*/
|
||||
public ApplicationPropertiesFileTransformer(
|
||||
ApplicationProperties applicationProperties,
|
||||
SensitivePropertyProvider outputSensitivePropertyProvider,
|
||||
Set<String> sensitivePropertyNames
|
||||
) {
|
||||
this.applicationProperties = Objects.requireNonNull(applicationProperties, "Application Properties required");
|
||||
this.outputSensitivePropertyProvider = Objects.requireNonNull(outputSensitivePropertyProvider, "Output Property Provider required");
|
||||
this.sensitivePropertyNames = Objects.requireNonNull(sensitivePropertyNames, "Sensitive Property Names required");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input application properties using configured Sensitive Property Provider and write output properties
|
||||
*
|
||||
* @param inputPath Input file path to be transformed containing source application properties
|
||||
* @param outputPath Output file path for protected application properties
|
||||
* @throws IOException Thrown on transformation failures
|
||||
*/
|
||||
@Override
|
||||
public void transform(Path inputPath, Path outputPath) throws IOException {
|
||||
Objects.requireNonNull(inputPath, "Input path required");
|
||||
Objects.requireNonNull(outputPath, "Output path required");
|
||||
|
||||
try (BufferedReader reader = Files.newBufferedReader(inputPath);
|
||||
BufferedWriter writer = Files.newBufferedWriter(outputPath)) {
|
||||
transform(reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void transform(BufferedReader reader, BufferedWriter writer) throws IOException {
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
Matcher matcher = PROPERTY_VALUE_PATTERN.matcher(line);
|
||||
String nextLine = null;
|
||||
if (matcher.matches()) {
|
||||
nextLine = processPropertyLine(reader, writer, matcher, line);
|
||||
} else {
|
||||
writeNonSensitiveLine(writer, line);
|
||||
}
|
||||
|
||||
line = nextLine == null ? reader.readLine() : nextLine;
|
||||
}
|
||||
}
|
||||
|
||||
private String processPropertyLine(BufferedReader reader, BufferedWriter writer, Matcher matcher, String line) throws IOException {
|
||||
String actualPropertyName = matcher.group(NAME_GROUP);
|
||||
String actualPropertyValue = matcher.group(VALUE_GROUP);
|
||||
String nextLine = null;
|
||||
|
||||
if (!actualPropertyName.endsWith(PROTECTED_KEY_SUFFIX)) {
|
||||
nextLine = reader.readLine();
|
||||
if (sensitivePropertyNames.contains(actualPropertyName) || isNextPropertyProtected(actualPropertyName, nextLine)) {
|
||||
String applicationProperty = applicationProperties.getProperty(actualPropertyName, actualPropertyValue);
|
||||
writeProtectedProperty(writer, actualPropertyName, applicationProperty);
|
||||
} else {
|
||||
writeNonSensitiveLine(writer, line);
|
||||
}
|
||||
}
|
||||
return nextLine;
|
||||
}
|
||||
|
||||
private void writeNonSensitiveLine(BufferedWriter writer, String line) throws IOException {
|
||||
writer.write(line);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
private boolean isNextPropertyProtected(String actualPropertyName, String nextLine) {
|
||||
if (nextLine != null) {
|
||||
Matcher nextLineMatcher = PROPERTY_VALUE_PATTERN.matcher(nextLine);
|
||||
if (nextLineMatcher.matches()) {
|
||||
String protectedActualPropertyName = ApplicationPropertiesProtector.getProtectionKey(actualPropertyName);
|
||||
String nextName = nextLineMatcher.group(NAME_GROUP);
|
||||
return protectedActualPropertyName.equals(nextName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void writeProtectedProperty(BufferedWriter writer, String name, String value) throws IOException {
|
||||
ProtectedPropertyContext propertyContext = ProtectedPropertyContext.defaultContext(name);
|
||||
String protectedValue = outputSensitivePropertyProvider.protect(value, propertyContext);
|
||||
|
||||
writer.write(name);
|
||||
writer.write(PROPERTY_VALUE_SEPARATOR);
|
||||
writeNonSensitiveLine(writer, protectedValue);
|
||||
|
||||
String protectedName = ApplicationPropertiesProtector.getProtectionKey(name);
|
||||
writer.write(protectedName);
|
||||
writer.write(PROPERTY_VALUE_SEPARATOR);
|
||||
String protectionIdentifierKey = outputSensitivePropertyProvider.getIdentifierKey();
|
||||
writeNonSensitiveLine(writer, protectionIdentifierKey);
|
||||
}
|
||||
}
|
|
@ -1,108 +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.minifi.toolkit.config.transformer;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* File Transformer supporting Bootstrap Configuration with updated Root Key
|
||||
*/
|
||||
public class BootstrapConfigurationFileTransformer implements FileTransformer {
|
||||
|
||||
private static final Pattern PROPERTY_VALUE_PATTERN = Pattern.compile("^([^#!][^=]+?)\\s*=.*");
|
||||
|
||||
private static final int NAME_GROUP = 1;
|
||||
|
||||
private static final char PROPERTY_VALUE_SEPARATOR = '=';
|
||||
|
||||
private final String rootKeyPropertyName;
|
||||
|
||||
private final String rootKey;
|
||||
|
||||
/**
|
||||
* Bootstrap Configuration File Transformer writes provided Root Key to output files
|
||||
*
|
||||
* @param rootKeyPropertyName Root Key property name to be written
|
||||
* @param rootKey Root Key to be written
|
||||
*/
|
||||
public BootstrapConfigurationFileTransformer(String rootKeyPropertyName, String rootKey) {
|
||||
this.rootKeyPropertyName = Objects.requireNonNull(rootKeyPropertyName, "Root Key Property Name required");
|
||||
this.rootKey = Objects.requireNonNull(rootKey, "Root Key required");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input configuration and write Root Key to output location
|
||||
*
|
||||
* @param inputPath Input file path to be transformed containing Bootstrap Configuration
|
||||
* @param outputPath Output file path for updated configuration
|
||||
* @throws IOException Thrown on transformation failures
|
||||
*/
|
||||
@Override
|
||||
public void transform(Path inputPath, Path outputPath) throws IOException {
|
||||
Objects.requireNonNull(inputPath, "Input path required");
|
||||
Objects.requireNonNull(outputPath, "Output path required");
|
||||
|
||||
try (BufferedReader reader = Files.newBufferedReader(inputPath);
|
||||
BufferedWriter writer = Files.newBufferedWriter(outputPath)) {
|
||||
transform(reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void transform(BufferedReader reader, BufferedWriter writer) throws IOException {
|
||||
boolean rootKeyPropertyNotFound = true;
|
||||
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
Matcher matcher = PROPERTY_VALUE_PATTERN.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
String name = matcher.group(NAME_GROUP);
|
||||
|
||||
if (rootKeyPropertyName.equals(name)) {
|
||||
writeRootKey(writer);
|
||||
rootKeyPropertyNotFound = false;
|
||||
} else {
|
||||
writer.write(line);
|
||||
writer.newLine();
|
||||
}
|
||||
} else {
|
||||
writer.write(line);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
line = reader.readLine();
|
||||
}
|
||||
|
||||
if (rootKeyPropertyNotFound) {
|
||||
writer.newLine();
|
||||
writeRootKey(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRootKey(BufferedWriter writer) throws IOException {
|
||||
writer.write(rootKeyPropertyName);
|
||||
writer.write(PROPERTY_VALUE_SEPARATOR);
|
||||
writer.write(rootKey);
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
|
@ -1,34 +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.minifi.toolkit.config.transformer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Abstraction for transforming Files
|
||||
*/
|
||||
public interface FileTransformer {
|
||||
/**
|
||||
* Transform input file and write contents to output file path
|
||||
*
|
||||
* @param inputPath Input file path to be transformed
|
||||
* @param outputPath Output file path
|
||||
* @throws IOException Thrown on input or output processing failures
|
||||
*/
|
||||
void transform(Path inputPath, Path outputPath) throws IOException;
|
||||
}
|
|
@ -1,99 +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.minifi.toolkit.config.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import org.apache.nifi.properties.ApplicationProperties;
|
||||
import org.apache.nifi.properties.SensitivePropertyProvider;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ApplicationPropertiesFileTransformerTest {
|
||||
|
||||
private static final String UNPROTECTED_BOOTSTRAP_CONF = "/transformer/unprotected.conf";
|
||||
private static final String PROTECTED_BOOTSTRAP_PROPERTIES = "/transformer/protected.conf";
|
||||
private static final String PROPERTIES_TRANSFORMED = "transformed.conf";
|
||||
private static final String SENSITIVE_PROPERTY_NAME_1 = "property1";
|
||||
private static final String SENSITIVE_PROPERTY_NAME_2 = "property2";
|
||||
private static final String SENSITIVE_PROPERTY_NAME_3 = "property3";
|
||||
private static final String PROVIDER_IDENTIFIER_KEY = "mocked-provider";
|
||||
private static final String UNPROTECTED = "UNPROTECTED";
|
||||
private static final String ENCRYPTED = "ENCRYPTED";
|
||||
|
||||
private static final Set<String> SENSITIVE_PROPERTY_NAMES = Set.of(SENSITIVE_PROPERTY_NAME_1, SENSITIVE_PROPERTY_NAME_2, SENSITIVE_PROPERTY_NAME_3);
|
||||
|
||||
@TempDir
|
||||
private Path tempDir;
|
||||
|
||||
@Mock
|
||||
private SensitivePropertyProvider sensitivePropertyProvider;
|
||||
|
||||
@Test
|
||||
void shouldTransformProperties() throws URISyntaxException, IOException {
|
||||
Path propertiesPath = getResourcePath(UNPROTECTED_BOOTSTRAP_CONF);
|
||||
|
||||
Path tempPropertiesPath = tempDir.resolve(propertiesPath.getFileName());
|
||||
Files.copy(propertiesPath, tempPropertiesPath);
|
||||
|
||||
Path outputPropertiesPath = tempDir.resolve(PROPERTIES_TRANSFORMED);
|
||||
ApplicationProperties applicationProperties = new ApplicationProperties(Map.of(SENSITIVE_PROPERTY_NAME_3, UNPROTECTED));
|
||||
FileTransformer transformer = new ApplicationPropertiesFileTransformer(applicationProperties, sensitivePropertyProvider, SENSITIVE_PROPERTY_NAMES);
|
||||
|
||||
when(sensitivePropertyProvider.getIdentifierKey()).thenReturn(PROVIDER_IDENTIFIER_KEY);
|
||||
when(sensitivePropertyProvider.protect(eq(UNPROTECTED), any())).thenReturn(ENCRYPTED);
|
||||
|
||||
transformer.transform(tempPropertiesPath, outputPropertiesPath);
|
||||
|
||||
Properties expectedProperties = loadProperties(getResourcePath(PROTECTED_BOOTSTRAP_PROPERTIES));
|
||||
Properties resultProperties = loadProperties(outputPropertiesPath);
|
||||
|
||||
assertEquals(expectedProperties, resultProperties);
|
||||
}
|
||||
|
||||
private Properties loadProperties(Path resourcePath) throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream propertiesStream = Files.newInputStream(resourcePath)) {
|
||||
properties.load(propertiesStream);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private Path getResourcePath(String resource) throws URISyntaxException {
|
||||
final URL resourceUrl = Objects.requireNonNull(getClass().getResource(resource), String.format("Resource [%s] not found", resource));
|
||||
return Paths.get(resourceUrl.toURI());
|
||||
}
|
||||
}
|
|
@ -1,74 +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.minifi.toolkit.config.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BootstrapConfigurationFileTransformerTest {
|
||||
private static final String BOOTSTRAP_ROOT_KEY_PROPERTY = "minifi.bootstrap.sensitive.key";
|
||||
private static final String MOCK_KEY = "mockKey";
|
||||
private static final String BOOTSTRAP_CONF_FILE_WITHOUT_KEY = "/transformer/bootstrap_without_key.conf";
|
||||
private static final String BOOTSTRAP_CONF_TRANSFORMED = "transformed.conf";
|
||||
|
||||
@TempDir
|
||||
private Path tempDir;
|
||||
|
||||
@Test
|
||||
void shouldWriteRootPropertyKeyIfItIsNotPresent() throws URISyntaxException, IOException {
|
||||
Path propertiesPath = getResourcePath(BOOTSTRAP_CONF_FILE_WITHOUT_KEY);
|
||||
|
||||
Path tempPropertiesPath = tempDir.resolve(propertiesPath.getFileName());
|
||||
Files.copy(propertiesPath, tempPropertiesPath);
|
||||
|
||||
Path outputPropertiesPath = tempDir.resolve(BOOTSTRAP_CONF_TRANSFORMED);
|
||||
|
||||
BootstrapConfigurationFileTransformer transformer = new BootstrapConfigurationFileTransformer(BOOTSTRAP_ROOT_KEY_PROPERTY, MOCK_KEY);
|
||||
transformer.transform(tempPropertiesPath, outputPropertiesPath);
|
||||
|
||||
Properties properties = loadProperties(outputPropertiesPath);
|
||||
assertEquals(MOCK_KEY, properties.get(BOOTSTRAP_ROOT_KEY_PROPERTY));
|
||||
}
|
||||
|
||||
private Properties loadProperties(Path resourcePath) throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream propertiesStream = Files.newInputStream(resourcePath)) {
|
||||
properties.load(propertiesStream);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private Path getResourcePath(String resource) throws URISyntaxException {
|
||||
final URL resourceUrl = Objects.requireNonNull(getClass().getResource(resource), String.format("Resource [%s] not found", resource));
|
||||
return Paths.get(resourceUrl.toURI());
|
||||
}
|
||||
}
|
|
@ -1,14 +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.
|
|
@ -1,22 +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.
|
||||
|
||||
property1=ENCRYPTED
|
||||
property1.protected=mocked-provider
|
||||
property2=ENCRYPTED
|
||||
property2.protected=mocked-provider
|
||||
nonsensitive.property=value
|
||||
property3=ENCRYPTED
|
||||
property3.protected=mocked-provider
|
|
@ -1,20 +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.
|
||||
|
||||
property1=UNPROTECTED
|
||||
property2=UNPROTECTED
|
||||
nonsensitive.property=value
|
||||
property3=ENCRYPTED
|
||||
property3.protected=mocked-provider
|
|
@ -28,7 +28,6 @@ limitations under the License.
|
|||
<module>minifi-toolkit-schema</module>
|
||||
<module>minifi-toolkit-configuration</module>
|
||||
<module>minifi-toolkit-assembly</module>
|
||||
<module>minifi-toolkit-encrypt-config</module>
|
||||
</modules>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
@ -186,16 +186,6 @@ language governing permissions and limitations under the License. -->
|
|||
<artifactId>nifi-bootstrap</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-factory</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-resources</artifactId>
|
||||
|
|
|
@ -51,19 +51,6 @@
|
|||
</includes>
|
||||
</dependencySet>
|
||||
|
||||
<!-- Write Property Protection implementation libraries to isolated directory-->
|
||||
<dependencySet>
|
||||
<scope>runtime</scope>
|
||||
<useProjectArtifact>false</useProjectArtifact>
|
||||
<outputDirectory>lib/properties</outputDirectory>
|
||||
<directoryMode>0770</directoryMode>
|
||||
<fileMode>0664</fileMode>
|
||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
||||
<includes>
|
||||
<include>*:nifi-property-protection-factory</include>
|
||||
</includes>
|
||||
</dependencySet>
|
||||
|
||||
<!-- Write out the conf directory contents -->
|
||||
<dependencySet>
|
||||
<scope>runtime</scope>
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -42,15 +41,11 @@ import java.lang.reflect.Method;
|
|||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -102,7 +97,6 @@ public class RunNiFi {
|
|||
public static final String NIFI_PID_FILE_NAME = "nifi.pid";
|
||||
public static final String NIFI_STATUS_FILE_NAME = "nifi.status";
|
||||
public static final String NIFI_LOCK_FILE_NAME = "nifi.lock";
|
||||
public static final String NIFI_BOOTSTRAP_SENSITIVE_KEY = "nifi.bootstrap.sensitive.key";
|
||||
|
||||
public static final String NIFI_BOOTSTRAP_LISTEN_PORT_PROP = "nifi.bootstrap.listen.port";
|
||||
|
||||
|
@ -1179,11 +1173,6 @@ public class RunNiFi {
|
|||
cmd.add("-Dapp=NiFi");
|
||||
cmd.add("-Dorg.apache.nifi.bootstrap.config.log.dir=" + nifiLogDir);
|
||||
cmd.add("org.apache.nifi.NiFi");
|
||||
if (isSensitiveKeyPresent(props)) {
|
||||
Path sensitiveKeyFile = createSensitiveKeyFile(confDir);
|
||||
writeSensitiveKeyFile(props, sensitiveKeyFile);
|
||||
cmd.add("-K " + sensitiveKeyFile.toFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
builder.command(cmd);
|
||||
|
||||
|
@ -1264,11 +1253,6 @@ public class RunNiFi {
|
|||
setNiFiStarted(false);
|
||||
}
|
||||
|
||||
if (isSensitiveKeyPresent(props)) {
|
||||
Path sensitiveKeyFile = createSensitiveKeyFile(confDir);
|
||||
writeSensitiveKeyFile(props, sensitiveKeyFile);
|
||||
}
|
||||
|
||||
defaultLogger.warn("Apache NiFi appears to have died. Restarting...");
|
||||
secretKey = null;
|
||||
process = builder.start();
|
||||
|
@ -1328,50 +1312,6 @@ public class RunNiFi {
|
|||
return details.toString();
|
||||
}
|
||||
|
||||
private void writeSensitiveKeyFile(Map<String, String> props, Path sensitiveKeyFile) throws IOException {
|
||||
BufferedWriter sensitiveKeyWriter = Files.newBufferedWriter(sensitiveKeyFile, StandardCharsets.UTF_8);
|
||||
sensitiveKeyWriter.write(props.get(NIFI_BOOTSTRAP_SENSITIVE_KEY));
|
||||
sensitiveKeyWriter.close();
|
||||
}
|
||||
|
||||
private Path createSensitiveKeyFile(File confDir) {
|
||||
Path sensitiveKeyFile = Paths.get(confDir + "/sensitive.key");
|
||||
|
||||
final boolean isPosixSupported = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
|
||||
try {
|
||||
if (isPosixSupported) {
|
||||
// Initially create file with the empty permission set (so nobody can get a file descriptor on it):
|
||||
Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
|
||||
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
|
||||
sensitiveKeyFile = Files.createFile(sensitiveKeyFile, attr);
|
||||
|
||||
// Then, once created, add owner-only rights:
|
||||
perms.add(PosixFilePermission.OWNER_WRITE);
|
||||
perms.add(PosixFilePermission.OWNER_READ);
|
||||
attr = PosixFilePermissions.asFileAttribute(perms);
|
||||
Files.setPosixFilePermissions(sensitiveKeyFile, perms);
|
||||
} else {
|
||||
// If Posix is not supported (e.g. Windows) then create the key file without permission settings.
|
||||
cmdLogger.info("Current file system does not support Posix, using default permission settings.");
|
||||
sensitiveKeyFile = Files.createFile(sensitiveKeyFile);
|
||||
}
|
||||
|
||||
} catch (final FileAlreadyExistsException faee) {
|
||||
cmdLogger.error("The sensitive.key file {} already exists. That shouldn't have been. Aborting.", sensitiveKeyFile);
|
||||
System.exit(1);
|
||||
} catch (final Exception e) {
|
||||
cmdLogger.error("Other failure relating to setting permissions on {}. "
|
||||
+ "(so that only the owner can read it). "
|
||||
+ "This is fatal to the bootstrap process for security reasons. Exception was: {}", sensitiveKeyFile, e);
|
||||
System.exit(1);
|
||||
}
|
||||
return sensitiveKeyFile;
|
||||
}
|
||||
|
||||
private boolean isSensitiveKeyPresent(Map<String, String> props) {
|
||||
return props.containsKey(NIFI_BOOTSTRAP_SENSITIVE_KEY) && !props.get(NIFI_BOOTSTRAP_SENSITIVE_KEY).isBlank();
|
||||
}
|
||||
|
||||
private void handleLogging(final Process process) {
|
||||
final Set<Future<?>> existingFutures = loggingFutures;
|
||||
if (existingFutures != null) {
|
||||
|
|
|
@ -302,51 +302,6 @@
|
|||
<artifactId>nifi-property-encryptor</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-aws</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-azure</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-cipher</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-factory</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-gcp</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-hashicorp</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-loader</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-shared</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
|
@ -1784,11 +1739,6 @@
|
|||
<artifactId>nifi-toolkit-cli</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-toolkit-encrypt-config</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -27,17 +27,6 @@ import java.io.IOException;
|
|||
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
|
||||
|
@ -57,19 +46,6 @@ public class NiFiBootstrapUtils {
|
|||
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
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,41 +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;
|
||||
|
||||
/**
|
||||
* Sensitive Property Protection Exception indicating runtime failures
|
||||
*/
|
||||
public class SensitivePropertyProtectionException extends RuntimeException {
|
||||
/**
|
||||
* Sensitive Property Protection Exception constructor with message and without associated cause
|
||||
*
|
||||
* @param message Message describing failure condition
|
||||
*/
|
||||
public SensitivePropertyProtectionException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sensitive Property Protection Exception constructor with message and associated cause
|
||||
*
|
||||
* @param message Message describing failure condition
|
||||
* @param cause Throwable containing associated cause of failure condition
|
||||
*/
|
||||
public SensitivePropertyProtectionException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.properties;
|
||||
|
||||
import java.util.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 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 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
|
||||
*/
|
||||
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);
|
||||
}
|
|
@ -1,61 +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;
|
||||
|
||||
/**
|
||||
* Sensitive Property Provider abstracts persistence and retrieval of properties that require additional protection
|
||||
*/
|
||||
public interface SensitivePropertyProvider {
|
||||
/**
|
||||
* 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 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.
|
||||
*
|
||||
* @param unprotectedValue the sensitive value
|
||||
* @param context The context of the value
|
||||
* @return the value to persist in the {@code nifi.properties} file
|
||||
*/
|
||||
String protect(String unprotectedValue, ProtectedPropertyContext context) 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
|
||||
* @param context The context of the value
|
||||
* @return the raw value to be used by the application
|
||||
*/
|
||||
String unprotect(String protectedValue, ProtectedPropertyContext context) throws SensitivePropertyProtectionException;
|
||||
|
||||
/**
|
||||
* Cleans up resources that may have been allocated/used by an SPP implementation
|
||||
*/
|
||||
void cleanUp();
|
||||
}
|
|
@ -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.properties;
|
||||
|
||||
import org.apache.nifi.properties.scheme.ProtectionScheme;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Sensitive Property Provider Factory abstracts instantiation of supported providers
|
||||
*/
|
||||
public interface SensitivePropertyProviderFactory {
|
||||
/**
|
||||
* Get Provider for specified Protection Strategy
|
||||
*
|
||||
* @param protectionScheme Protection Strategy requested
|
||||
* @return Property Provider implementation
|
||||
*/
|
||||
SensitivePropertyProvider getProvider(ProtectionScheme protectionScheme);
|
||||
|
||||
/**
|
||||
* Get Supported Property Providers
|
||||
*
|
||||
* @return Collection of supported provider implementations
|
||||
*/
|
||||
Collection<SensitivePropertyProvider> getSupportedProviders();
|
||||
|
||||
/**
|
||||
* Returns a ProtectedPropertyContext with the given property name. The ProtectedPropertyContext's
|
||||
* contextName will be the name found in a matching context mapping from bootstrap.conf, or 'default' if
|
||||
* no matching mapping was found.
|
||||
*
|
||||
* @param groupIdentifier The identifier of a group that contains the configuration property. The definition
|
||||
* of a group depends on the type of configuration file.
|
||||
* @param propertyName A property name
|
||||
* @return The property context, using any mappings configured in bootstrap.conf to match against the
|
||||
* provided group identifier (or the default context if none match).
|
||||
*/
|
||||
ProtectedPropertyContext getPropertyContext(String groupIdentifier, String propertyName);
|
||||
}
|
|
@ -1,29 +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.scheme;
|
||||
|
||||
/**
|
||||
* Definition of Protection Scheme
|
||||
*/
|
||||
public interface ProtectionScheme {
|
||||
/**
|
||||
* Get path of the protection scheme definition
|
||||
*
|
||||
* @return Protection scheme path
|
||||
*/
|
||||
String getPath();
|
||||
}
|
|
@ -1,30 +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.scheme;
|
||||
|
||||
/**
|
||||
* Protection Scheme Resolver abstracts resolution of Protection Scheme references from string arguments
|
||||
*/
|
||||
public interface ProtectionSchemeResolver {
|
||||
/**
|
||||
* Get Protection Scheme
|
||||
*
|
||||
* @param scheme Scheme name required
|
||||
* @return Protection Scheme
|
||||
*/
|
||||
ProtectionScheme getProtectionScheme(String scheme);
|
||||
}
|
|
@ -1,35 +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.scheme;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Standard implementation of Protection Scheme with configurable parameters
|
||||
*/
|
||||
public class StandardProtectionScheme implements ProtectionScheme {
|
||||
private String path;
|
||||
|
||||
public StandardProtectionScheme(final String path) {
|
||||
this.path = Objects.requireNonNull(path, "Path required");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-aws</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-shared</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>regions</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>kms</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>aws-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>sdk-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>apache-client</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>secretsmanager</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>apache-client</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.properties;
|
||||
|
||||
import software.amazon.awssdk.core.SdkBytes;
|
||||
import software.amazon.awssdk.services.kms.KmsClient;
|
||||
import software.amazon.awssdk.services.kms.model.DecryptRequest;
|
||||
import software.amazon.awssdk.services.kms.model.DecryptResponse;
|
||||
import software.amazon.awssdk.services.kms.model.DescribeKeyRequest;
|
||||
import software.amazon.awssdk.services.kms.model.DescribeKeyResponse;
|
||||
import software.amazon.awssdk.services.kms.model.EncryptRequest;
|
||||
import software.amazon.awssdk.services.kms.model.EncryptResponse;
|
||||
import software.amazon.awssdk.services.kms.model.KeyMetadata;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Amazon Web Services Key Management Service Sensitive Property Provider
|
||||
*/
|
||||
public class AwsKmsSensitivePropertyProvider extends ClientBasedEncodedSensitivePropertyProvider<KmsClient> {
|
||||
protected static final String KEY_ID_PROPERTY = "aws.kms.key.id";
|
||||
|
||||
private static final String IDENTIFIER_KEY = "aws/kms";
|
||||
|
||||
AwsKmsSensitivePropertyProvider(final KmsClient kmsClient, final Properties properties) throws SensitivePropertyProtectionException {
|
||||
super(kmsClient, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return IDENTIFIER_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close KMS Client when configured
|
||||
*/
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
final KmsClient kmsClient = getClient();
|
||||
if (kmsClient == null) {
|
||||
logger.debug("AWS KMS Client not configured");
|
||||
} else {
|
||||
kmsClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Client and Key Identifier status when client is configured
|
||||
*
|
||||
* @param kmsClient KMS Client
|
||||
*/
|
||||
@Override
|
||||
protected void validate(final KmsClient kmsClient) {
|
||||
if (kmsClient == null) {
|
||||
logger.debug("AWS KMS Client not configured");
|
||||
} else {
|
||||
final String keyId = getKeyId();
|
||||
try {
|
||||
final DescribeKeyRequest describeKeyRequest = DescribeKeyRequest.builder()
|
||||
.keyId(keyId)
|
||||
.build();
|
||||
final DescribeKeyResponse describeKeyResponse = kmsClient.describeKey(describeKeyRequest);
|
||||
final KeyMetadata keyMetadata = describeKeyResponse.keyMetadata();
|
||||
if (keyMetadata.enabled()) {
|
||||
logger.info("AWS KMS Key [{}] Enabled", keyId);
|
||||
} else {
|
||||
throw new SensitivePropertyProtectionException(String.format("AWS KMS Key [%s] Disabled", keyId));
|
||||
}
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("AWS KMS Key [%s] Validation Failed", keyId), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encrypted bytes
|
||||
*
|
||||
* @param bytes Unprotected bytes
|
||||
* @return Encrypted bytes
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getEncrypted(final byte[] bytes) {
|
||||
final SdkBytes plainBytes = SdkBytes.fromByteArray(bytes);
|
||||
final EncryptRequest encryptRequest = EncryptRequest.builder()
|
||||
.keyId(getKeyId())
|
||||
.plaintext(plainBytes)
|
||||
.build();
|
||||
final EncryptResponse response = getClient().encrypt(encryptRequest);
|
||||
final SdkBytes encryptedData = response.ciphertextBlob();
|
||||
return encryptedData.asByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get decrypted bytes
|
||||
*
|
||||
* @param bytes Encrypted bytes
|
||||
* @return Decrypted bytes
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getDecrypted(final byte[] bytes) {
|
||||
final SdkBytes cipherBytes = SdkBytes.fromByteArray(bytes);
|
||||
final DecryptRequest decryptRequest = DecryptRequest.builder()
|
||||
.ciphertextBlob(cipherBytes)
|
||||
.keyId(getKeyId())
|
||||
.build();
|
||||
final DecryptResponse response = getClient().decrypt(decryptRequest);
|
||||
final SdkBytes decryptedData = response.plaintext();
|
||||
return decryptedData.asByteArray();
|
||||
}
|
||||
|
||||
private String getKeyId() {
|
||||
return getProperties().getProperty(KEY_ID_PROPERTY);
|
||||
}
|
||||
}
|
|
@ -1,161 +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 com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
|
||||
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
|
||||
import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
|
||||
import software.amazon.awssdk.services.secretsmanager.model.SecretsManagerException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Amazon Web Services Secrets Manager implementation of Sensitive Property Provider
|
||||
*/
|
||||
public class AwsSecretsManagerSensitivePropertyProvider implements SensitivePropertyProvider {
|
||||
private static final String IDENTIFIER_KEY = "aws/secretsmanager";
|
||||
|
||||
private final SecretsManagerClient client;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
|
||||
private final Lock readLock = rwLock.readLock();
|
||||
private final Lock writeLock = rwLock.writeLock();
|
||||
|
||||
AwsSecretsManagerSensitivePropertyProvider(final SecretsManagerClient client) {
|
||||
this.client = client;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return IDENTIFIER_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return client != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String protect(final String unprotectedValue, final ProtectedPropertyContext context)
|
||||
throws SensitivePropertyProtectionException {
|
||||
Objects.requireNonNull(context, "Property context must be provided");
|
||||
Objects.requireNonNull(unprotectedValue, "Property value must be provided");
|
||||
|
||||
if (client == null) {
|
||||
throw new SensitivePropertyProtectionException("AWS Secrets Manager Provider Not Configured");
|
||||
}
|
||||
|
||||
try {
|
||||
writeLock.lock();
|
||||
final String secretName = context.getContextName();
|
||||
final Optional<ObjectNode> secretKeyValuesOptional = getSecretKeyValues(context);
|
||||
final ObjectNode secretObject = secretKeyValuesOptional.orElse(objectMapper.createObjectNode());
|
||||
|
||||
secretObject.put(context.getPropertyName(), unprotectedValue);
|
||||
final String secretString = objectMapper.writeValueAsString(secretObject);
|
||||
|
||||
if (secretKeyValuesOptional.isPresent()) {
|
||||
client.putSecretValue(builder -> builder.secretId(secretName).secretString(secretString));
|
||||
} else {
|
||||
client.createSecret(builder -> builder.name(secretName).secretString(secretString));
|
||||
}
|
||||
return context.getContextKey();
|
||||
} catch (final SecretsManagerException | JsonProcessingException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("AWS Secrets Manager Secret Could Not Be Stored for [%s]", context), e);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String unprotect(final String protectedValue, final ProtectedPropertyContext context)
|
||||
throws SensitivePropertyProtectionException {
|
||||
Objects.requireNonNull(context, "Property context must be provided");
|
||||
|
||||
if (client == null) {
|
||||
throw new SensitivePropertyProtectionException("AWS Secrets Manager Provider Not Configured");
|
||||
}
|
||||
try {
|
||||
readLock.lock();
|
||||
|
||||
String propertyValue = null;
|
||||
final Optional<ObjectNode> secretKeyValuesOptional = getSecretKeyValues(context);
|
||||
if (secretKeyValuesOptional.isPresent()) {
|
||||
final ObjectNode secretKeyValues = secretKeyValuesOptional.get();
|
||||
final String propertyName = context.getPropertyName();
|
||||
if (secretKeyValues.has(propertyName)) {
|
||||
propertyValue = secretKeyValues.get(propertyName).textValue();
|
||||
}
|
||||
}
|
||||
if (propertyValue == null) {
|
||||
throw new SensitivePropertyProtectionException(
|
||||
String.format("AWS Secret Name [%s] Property Name [%s] not found", context.getContextName(), context.getPropertyName()));
|
||||
}
|
||||
|
||||
return propertyValue;
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the optional parsed JSON from the matching secret, or empty if the secret does not exist.
|
||||
* @param context The property context
|
||||
* @return The optional parsed JSON, or empty if the secret does not exist
|
||||
*/
|
||||
private Optional<ObjectNode> getSecretKeyValues(final ProtectedPropertyContext context) {
|
||||
try {
|
||||
final GetSecretValueResponse response = client.getSecretValue(builder -> builder.secretId(context.getContextName()));
|
||||
|
||||
if (response.secretString() == null) {
|
||||
throw new SensitivePropertyProtectionException(String.format("AWS Secret Name [%s] string value not found",
|
||||
context.getContextKey()));
|
||||
}
|
||||
final JsonNode responseNode = objectMapper.readTree(response.secretString());
|
||||
if (!(responseNode instanceof ObjectNode)) {
|
||||
throw new SensitivePropertyProtectionException(String.format("AWS Secrets Manager Secret [%s] JSON parsing failed",
|
||||
context.getContextKey()));
|
||||
}
|
||||
return Optional.of((ObjectNode) responseNode);
|
||||
} catch (final ResourceNotFoundException e) {
|
||||
return Optional.empty();
|
||||
} catch (final SecretsManagerException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("AWS Secrets Manager Secret [%s] retrieval failed",
|
||||
context.getContextKey()), e);
|
||||
} catch (final JsonProcessingException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("AWS Secrets Manager Secret [%s] JSON parsing failed",
|
||||
context.getContextKey()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.properties.configuration;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.properties.BootstrapProperties;
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
|
||||
import software.amazon.awssdk.core.SdkClient;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Amazon Web Services Service Client Provider base class
|
||||
*/
|
||||
public abstract class AbstractAwsClientProvider<T extends SdkClient> extends BootstrapPropertiesClientProvider<T> {
|
||||
private static final String ACCESS_KEY_PROPS_NAME = "aws.access.key.id";
|
||||
|
||||
private static final String SECRET_KEY_PROPS_NAME = "aws.secret.access.key";
|
||||
|
||||
private static final String REGION_KEY_PROPS_NAME = "aws.region";
|
||||
|
||||
public AbstractAwsClientProvider() {
|
||||
super(BootstrapProperties.BootstrapPropertyKey.AWS_SENSITIVE_PROPERTY_PROVIDER_CONF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Configured Client using either Client Properties or AWS Default Credentials Provider
|
||||
*
|
||||
* @param clientProperties Client Properties
|
||||
* @return KMS Client
|
||||
*/
|
||||
@Override
|
||||
protected T getConfiguredClient(final Properties clientProperties) {
|
||||
final String accessKey = clientProperties.getProperty(ACCESS_KEY_PROPS_NAME);
|
||||
final String secretKey = clientProperties.getProperty(SECRET_KEY_PROPS_NAME);
|
||||
final String region = clientProperties.getProperty(REGION_KEY_PROPS_NAME);
|
||||
|
||||
if (StringUtils.isNoneBlank(accessKey, secretKey, region)) {
|
||||
logger.debug("AWS Credentials Location: Client Properties");
|
||||
try {
|
||||
final AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
|
||||
return createClient(credentials, region);
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException("AWS Client Builder Failed using Client Properties", e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("AWS Credentials Location: Default Credentials Provider");
|
||||
try {
|
||||
final DefaultCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build();
|
||||
return createDefaultClient(credentialsProvider);
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException("AWS Client Builder Failed using Default Credentials Provider", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a client with the given credentials and region.
|
||||
* @param credentials AWS credentials
|
||||
* @param region AWS region
|
||||
* @return The created client
|
||||
*/
|
||||
protected abstract T createClient(AwsCredentials credentials, String region);
|
||||
|
||||
/**
|
||||
* Create a default client with the given credentials provider.
|
||||
* @param credentialsProvider AWS credentials provider
|
||||
* @return The created client
|
||||
*/
|
||||
protected abstract T createDefaultClient(AwsCredentialsProvider credentialsProvider);
|
||||
}
|
|
@ -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.properties.configuration;
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.kms.KmsClient;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Amazon Web Services Key Management Service Client Provider
|
||||
*/
|
||||
public class AwsKmsClientProvider extends AbstractAwsClientProvider<KmsClient> {
|
||||
|
||||
protected static final String KEY_ID_PROPERTY = "aws.kms.key.id";
|
||||
|
||||
@Override
|
||||
protected KmsClient createClient(final AwsCredentials credentials, final String region) {
|
||||
return KmsClient.builder()
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.region(Region.of(region))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KmsClient createDefaultClient(final AwsCredentialsProvider credentialsProvider) {
|
||||
return KmsClient.builder().credentialsProvider(credentialsProvider).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getRequiredPropertyNames() {
|
||||
return Collections.singleton(KEY_ID_PROPERTY);
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.properties.configuration;
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Amazon Web Services Secrets Manager Client Provider
|
||||
*/
|
||||
public class AwsSecretsManagerClientProvider extends AbstractAwsClientProvider<SecretsManagerClient> {
|
||||
|
||||
@Override
|
||||
protected SecretsManagerClient createClient(final AwsCredentials credentials, final String region) {
|
||||
return SecretsManagerClient.builder()
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.region(Region.of(region))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecretsManagerClient createDefaultClient(final AwsCredentialsProvider credentialsProvider) {
|
||||
return SecretsManagerClient.builder().credentialsProvider(credentialsProvider).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getRequiredPropertyNames() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
|
@ -1,127 +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.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import software.amazon.awssdk.core.SdkBytes;
|
||||
import software.amazon.awssdk.services.kms.KmsClient;
|
||||
import software.amazon.awssdk.services.kms.model.DecryptRequest;
|
||||
import software.amazon.awssdk.services.kms.model.DecryptResponse;
|
||||
import software.amazon.awssdk.services.kms.model.DescribeKeyRequest;
|
||||
import software.amazon.awssdk.services.kms.model.DescribeKeyResponse;
|
||||
import software.amazon.awssdk.services.kms.model.EncryptRequest;
|
||||
import software.amazon.awssdk.services.kms.model.EncryptResponse;
|
||||
import software.amazon.awssdk.services.kms.model.KeyMetadata;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AwsKmsSensitivePropertyProviderTest {
|
||||
private static final String PROPERTY_NAME = String.class.getSimpleName();
|
||||
|
||||
private static final String PROPERTY = String.class.getName();
|
||||
|
||||
private static final String ENCRYPTED_PROPERTY = Integer.class.getName();
|
||||
|
||||
private static final byte[] ENCRYPTED_BYTES = ENCRYPTED_PROPERTY.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private static final String PROTECTED_PROPERTY = Base64.getEncoder().withoutPadding().encodeToString(ENCRYPTED_BYTES);
|
||||
|
||||
private static final String KEY_ID = AwsKmsSensitivePropertyProvider.class.getSimpleName();
|
||||
|
||||
private static final Properties PROPERTIES = new Properties();
|
||||
|
||||
private static final String IDENTIFIER_KEY = "aws/kms";
|
||||
|
||||
static {
|
||||
PROPERTIES.setProperty(AwsKmsSensitivePropertyProvider.KEY_ID_PROPERTY, KEY_ID);
|
||||
}
|
||||
|
||||
@Mock
|
||||
private KmsClient kmsClient;
|
||||
|
||||
private AwsKmsSensitivePropertyProvider provider;
|
||||
|
||||
@BeforeEach
|
||||
public void setProvider() {
|
||||
final KeyMetadata keyMetadata = KeyMetadata.builder().enabled(true).build();
|
||||
final DescribeKeyResponse describeKeyResponse = DescribeKeyResponse.builder().keyMetadata(keyMetadata).build();
|
||||
when(kmsClient.describeKey(any(DescribeKeyRequest.class))).thenReturn(describeKeyResponse);
|
||||
|
||||
provider = new AwsKmsSensitivePropertyProvider(kmsClient, PROPERTIES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateClientNull() {
|
||||
final AwsKmsSensitivePropertyProvider provider = new AwsKmsSensitivePropertyProvider(null, PROPERTIES);
|
||||
assertNotNull(provider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateKeyDisabled() {
|
||||
final KeyMetadata keyMetadata = KeyMetadata.builder().enabled(false).build();
|
||||
final DescribeKeyResponse describeKeyResponse = DescribeKeyResponse.builder().keyMetadata(keyMetadata).build();
|
||||
when(kmsClient.describeKey(any(DescribeKeyRequest.class))).thenReturn(describeKeyResponse);
|
||||
|
||||
assertThrows(SensitivePropertyProtectionException.class, () -> new AwsKmsSensitivePropertyProvider(kmsClient, PROPERTIES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanUp() {
|
||||
provider.cleanUp();
|
||||
verify(kmsClient).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtect() {
|
||||
final SdkBytes blob = SdkBytes.fromUtf8String(ENCRYPTED_PROPERTY);
|
||||
final EncryptResponse encryptResponse = EncryptResponse.builder().ciphertextBlob(blob).build();
|
||||
when(kmsClient.encrypt(any(EncryptRequest.class))).thenReturn(encryptResponse);
|
||||
|
||||
final String protectedProperty = provider.protect(PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
|
||||
assertEquals(PROTECTED_PROPERTY, protectedProperty);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprotect() {
|
||||
final SdkBytes blob = SdkBytes.fromUtf8String(PROPERTY);
|
||||
final DecryptResponse decryptResponse = DecryptResponse.builder().plaintext(blob).build();
|
||||
when(kmsClient.decrypt(any(DecryptRequest.class))).thenReturn(decryptResponse);
|
||||
|
||||
final String property = provider.unprotect(PROTECTED_PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
|
||||
assertEquals(PROPERTY, property);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKey() {
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
assertEquals(IDENTIFIER_KEY, identifierKey);
|
||||
}
|
||||
}
|
|
@ -1,125 +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.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import software.amazon.awssdk.core.SdkBytes;
|
||||
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
|
||||
import software.amazon.awssdk.services.secretsmanager.model.CreateSecretResponse;
|
||||
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
|
||||
import software.amazon.awssdk.services.secretsmanager.model.PutSecretValueResponse;
|
||||
import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AwsSecretsManagerSensitivePropertyProviderTest {
|
||||
private static final String PROPERTY_NAME = "propertyName";
|
||||
private static final String PROPERTY_VALUE = "propertyValue";
|
||||
|
||||
private static final String SECRET_STRING = String.format("{ \"%s\": \"%s\" }", PROPERTY_NAME, PROPERTY_VALUE);
|
||||
|
||||
private static final String IDENTIFIER_KEY = "aws/secretsmanager";
|
||||
|
||||
@Mock
|
||||
private SecretsManagerClient secretsManagerClient;
|
||||
|
||||
private AwsSecretsManagerSensitivePropertyProvider provider;
|
||||
|
||||
@BeforeEach
|
||||
public void setProvider() {
|
||||
provider = new AwsSecretsManagerSensitivePropertyProvider(secretsManagerClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateClientNull() {
|
||||
final AwsSecretsManagerSensitivePropertyProvider provider = new AwsSecretsManagerSensitivePropertyProvider(null);
|
||||
assertNotNull(provider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateKeyNoSecretString() {
|
||||
final GetSecretValueResponse getSecretValueResponse = GetSecretValueResponse.builder()
|
||||
.secretBinary(SdkBytes.fromString("binary", Charset.defaultCharset())).build();
|
||||
when(secretsManagerClient.getSecretValue(any(Consumer.class))).thenReturn(getSecretValueResponse);
|
||||
|
||||
assertThrows(SensitivePropertyProtectionException.class, () ->
|
||||
provider.unprotect("anyValue", ProtectedPropertyContext.defaultContext(PROPERTY_NAME)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanUp() {
|
||||
provider.cleanUp();
|
||||
verify(secretsManagerClient).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtectCreateSecret() {
|
||||
final String secretName = ProtectedPropertyContext.defaultContext(PROPERTY_NAME).getContextKey();
|
||||
|
||||
when(secretsManagerClient.getSecretValue(any(Consumer.class))).thenThrow(ResourceNotFoundException.builder().message("Not found").build());
|
||||
|
||||
final CreateSecretResponse createSecretResponse = CreateSecretResponse.builder()
|
||||
.name(secretName).build();
|
||||
when(secretsManagerClient.createSecret(any(Consumer.class))).thenReturn(createSecretResponse);
|
||||
|
||||
final String protectedProperty = provider.protect(PROPERTY_VALUE, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
|
||||
assertEquals(secretName, protectedProperty);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtectExistingSecret() {
|
||||
final String secretName = ProtectedPropertyContext.defaultContext(PROPERTY_NAME).getContextKey();
|
||||
final GetSecretValueResponse getSecretValueResponse = GetSecretValueResponse.builder().secretString(SECRET_STRING).build();
|
||||
when(secretsManagerClient.getSecretValue(any(Consumer.class))).thenReturn(getSecretValueResponse);
|
||||
|
||||
final PutSecretValueResponse putSecretValueResponse = PutSecretValueResponse.builder()
|
||||
.name(secretName).build();
|
||||
when(secretsManagerClient.putSecretValue(any(Consumer.class))).thenReturn(putSecretValueResponse);
|
||||
|
||||
final String protectedProperty = provider.protect(PROPERTY_VALUE, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
|
||||
assertEquals(secretName, protectedProperty);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprotect() {
|
||||
final GetSecretValueResponse getSecretValueResponse = GetSecretValueResponse.builder().secretString(SECRET_STRING).build();
|
||||
when(secretsManagerClient.getSecretValue(any(Consumer.class))).thenReturn(getSecretValueResponse);
|
||||
|
||||
final String property = provider.unprotect("anyValue", ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
|
||||
assertEquals(PROPERTY_VALUE, property);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKey() {
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
assertEquals(IDENTIFIER_KEY, identifierKey);
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-azure</artifactId>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-sdk-bom</artifactId>
|
||||
<version>1.2.24</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-shared</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-security-keyvault-secrets</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core-http-netty</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-security-keyvault-keys</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core-http-netty</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-identity</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core-http-netty</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Override MSAL4J 1.14.0 from azure-identity -->
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>msal4j</artifactId>
|
||||
<version>1.15.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,110 +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 com.azure.security.keyvault.keys.models.KeyVaultKey;
|
||||
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
|
||||
import com.azure.security.keyvault.keys.cryptography.models.DecryptResult;
|
||||
import com.azure.security.keyvault.keys.cryptography.models.EncryptResult;
|
||||
import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm;
|
||||
import com.azure.security.keyvault.keys.models.KeyOperation;
|
||||
import com.azure.security.keyvault.keys.models.KeyProperties;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Microsoft Azure Key Vault Key Sensitive Property Provider using Cryptography Client for encryption operations
|
||||
*/
|
||||
public class AzureKeyVaultKeySensitivePropertyProvider extends ClientBasedEncodedSensitivePropertyProvider<CryptographyClient> {
|
||||
protected static final String ENCRYPTION_ALGORITHM_PROPERTY = "azure.keyvault.encryption.algorithm";
|
||||
|
||||
protected static final List<KeyOperation> REQUIRED_OPERATIONS = Arrays.asList(KeyOperation.DECRYPT, KeyOperation.ENCRYPT);
|
||||
|
||||
private static final String IDENTIFIER_KEY = "azure/keyvault/key";
|
||||
|
||||
private EncryptionAlgorithm encryptionAlgorithm;
|
||||
|
||||
AzureKeyVaultKeySensitivePropertyProvider(final CryptographyClient cryptographyClient, final Properties properties) {
|
||||
super(cryptographyClient, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return IDENTIFIER_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Client and Key Operations with Encryption Algorithm when configured
|
||||
*
|
||||
* @param cryptographyClient Cryptography Client
|
||||
*/
|
||||
@Override
|
||||
protected void validate(final CryptographyClient cryptographyClient) {
|
||||
if (cryptographyClient == null) {
|
||||
logger.debug("Azure Cryptography Client not configured");
|
||||
} else {
|
||||
try {
|
||||
final KeyVaultKey keyVaultKey = cryptographyClient.getKey();
|
||||
final String id = keyVaultKey.getId();
|
||||
final KeyProperties keyProperties = keyVaultKey.getProperties();
|
||||
if (keyProperties.isEnabled()) {
|
||||
final List<KeyOperation> keyOperations = keyVaultKey.getKeyOperations();
|
||||
if (keyOperations.containsAll(REQUIRED_OPERATIONS)) {
|
||||
logger.info("Azure Key Vault Key [{}] Validated", id);
|
||||
} else {
|
||||
throw new SensitivePropertyProtectionException(String.format("Azure Key Vault Key [%s] Missing Operations %s", id, REQUIRED_OPERATIONS));
|
||||
}
|
||||
} else {
|
||||
throw new SensitivePropertyProtectionException(String.format("Azure Key Vault Key [%s] Disabled", id));
|
||||
}
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException("Azure Key Vault Key Validation Failed", e);
|
||||
}
|
||||
final String algorithm = getProperties().getProperty(ENCRYPTION_ALGORITHM_PROPERTY);
|
||||
if (algorithm == null || algorithm.isEmpty()) {
|
||||
throw new SensitivePropertyProtectionException("Azure Key Vault Key Algorithm not configured");
|
||||
}
|
||||
encryptionAlgorithm = EncryptionAlgorithm.fromString(algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encrypted bytes
|
||||
*
|
||||
* @param bytes Unprotected bytes
|
||||
* @return Encrypted bytes
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getEncrypted(final byte[] bytes) {
|
||||
final EncryptResult encryptResult = getClient().encrypt(encryptionAlgorithm, bytes);
|
||||
return encryptResult.getCipherText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get decrypted bytes
|
||||
*
|
||||
* @param bytes Encrypted bytes
|
||||
* @return Decrypted bytes
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getDecrypted(final byte[] bytes) {
|
||||
final DecryptResult decryptResult = getClient().decrypt(encryptionAlgorithm, bytes);
|
||||
return decryptResult.getPlainText();
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.properties;
|
||||
|
||||
import com.azure.core.exception.ResourceNotFoundException;
|
||||
import com.azure.security.keyvault.secrets.SecretClient;
|
||||
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Microsoft Azure Key Vault Secret implementation of Sensitive Property Provider for externalized storage of properties
|
||||
*/
|
||||
public class AzureKeyVaultSecretSensitivePropertyProvider implements SensitivePropertyProvider {
|
||||
private static final String FORWARD_SLASH = "/";
|
||||
|
||||
private static final String PERIOD = "\\.";
|
||||
|
||||
private static final String DASH = "-";
|
||||
|
||||
private static final String IDENTIFIER_KEY = "azure/keyvault/secret";
|
||||
|
||||
private SecretClient secretClient;
|
||||
|
||||
AzureKeyVaultSecretSensitivePropertyProvider(final SecretClient secretClient) {
|
||||
this.secretClient = secretClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Identifier key using Protection Scheme
|
||||
*
|
||||
* @return Identifier key
|
||||
*/
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return IDENTIFIER_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Provider supported returns status based on configuration of Secret Client
|
||||
*
|
||||
* @return Supported status
|
||||
*/
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return secretClient != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protect value stores a Secret in Azure Key Vault using the Property Context Key as the Secret Name
|
||||
*
|
||||
* @param unprotectedValue Value to be stored
|
||||
* @param context Property Context containing Context Key used to store Secret
|
||||
* @return Key Vault Secret Identifier
|
||||
* @throws SensitivePropertyProtectionException Thrown when storing Secret failed
|
||||
*/
|
||||
@Override
|
||||
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
Objects.requireNonNull(unprotectedValue, "Value required");
|
||||
final String secretName = getSecretName(context);
|
||||
try {
|
||||
final KeyVaultSecret keyVaultSecret = secretClient.setSecret(secretName, unprotectedValue);
|
||||
return keyVaultSecret.getId();
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("Set Secret [%s] failed", secretName), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unprotect value retrieves a Secret from Azure Key Vault using Property Context Key
|
||||
*
|
||||
* @param protectedValue Key Vault Secret Identifier is not used
|
||||
* @param context Property Context containing Context Key used to retrieve Secret
|
||||
* @return Secret Value
|
||||
* @throws SensitivePropertyProtectionException Thrown when Secret not found or retrieval failed
|
||||
*/
|
||||
@Override
|
||||
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
final String secretName = getSecretName(context);
|
||||
try {
|
||||
final KeyVaultSecret keyVaultSecret = secretClient.getSecret(secretName);
|
||||
return keyVaultSecret.getValue();
|
||||
} catch (final ResourceNotFoundException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("Secret [%s] not found", secretName), e);
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("Secret [%s] request failed", secretName), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up not implemented
|
||||
*/
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
|
||||
}
|
||||
|
||||
private String getSecretName(final ProtectedPropertyContext context) {
|
||||
final String contextKey = Objects.requireNonNull(context, "Context required").getContextKey();
|
||||
// Replace forward slash and period with dash since Azure Key Vault Secret Names do not support certain characters
|
||||
return contextKey.replaceAll(FORWARD_SLASH, DASH).replaceAll(PERIOD, DASH);
|
||||
}
|
||||
}
|
|
@ -1,39 +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.configuration;
|
||||
|
||||
import com.azure.core.credential.TokenCredential;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
import org.apache.nifi.properties.BootstrapProperties;
|
||||
|
||||
/**
|
||||
* Abstract Microsoft Azure Client Provider
|
||||
*/
|
||||
public abstract class AzureClientProvider<T> extends BootstrapPropertiesClientProvider<T> {
|
||||
public AzureClientProvider() {
|
||||
super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Default Azure Token Credential using Default Credentials Builder for environment variables and system properties
|
||||
*
|
||||
* @return Token Credential
|
||||
*/
|
||||
protected TokenCredential getDefaultTokenCredential() {
|
||||
return new DefaultAzureCredentialBuilder().build();
|
||||
}
|
||||
}
|
|
@ -1,66 +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.configuration;
|
||||
|
||||
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
|
||||
import com.azure.security.keyvault.keys.cryptography.CryptographyClientBuilder;
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Microsoft Azure Cryptography Client Provider
|
||||
*/
|
||||
public class AzureCryptographyClientProvider extends AzureClientProvider<CryptographyClient> {
|
||||
protected static final String KEY_ID_PROPERTY = "azure.keyvault.key.id";
|
||||
|
||||
private static final Set<String> REQUIRED_PROPERTY_NAMES = new HashSet<>(Collections.singletonList(KEY_ID_PROPERTY));
|
||||
|
||||
/**
|
||||
* Get Configured Client using Default Azure Credentials Builder and configured Key Identifier
|
||||
*
|
||||
* @param clientProperties Client Properties
|
||||
* @return Cryptography Client
|
||||
*/
|
||||
@Override
|
||||
protected CryptographyClient getConfiguredClient(final Properties clientProperties) {
|
||||
final String keyIdentifier = clientProperties.getProperty(KEY_ID_PROPERTY);
|
||||
logger.debug("Azure Cryptography Client with Key Identifier [{}]", keyIdentifier);
|
||||
|
||||
try {
|
||||
return new CryptographyClientBuilder()
|
||||
.credential(getDefaultTokenCredential())
|
||||
.keyIdentifier(keyIdentifier)
|
||||
.buildClient();
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException("Azure Cryptography Builder Client Failed using Default Credentials", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required property names for Azure Cryptography Client
|
||||
*
|
||||
* @return Required client property names
|
||||
*/
|
||||
@Override
|
||||
protected Set<String> getRequiredPropertyNames() {
|
||||
return REQUIRED_PROPERTY_NAMES;
|
||||
}
|
||||
}
|
|
@ -1,69 +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.configuration;
|
||||
|
||||
import com.azure.core.credential.TokenCredential;
|
||||
import com.azure.identity.DefaultAzureCredentialBuilder;
|
||||
import com.azure.security.keyvault.secrets.SecretClient;
|
||||
import com.azure.security.keyvault.secrets.SecretClientBuilder;
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Microsoft Azure Secret Client Provider
|
||||
*/
|
||||
public class AzureSecretClientProvider extends AzureClientProvider<SecretClient> {
|
||||
protected static final String URI_PROPERTY = "azure.keyvault.uri";
|
||||
|
||||
private static final Set<String> REQUIRED_PROPERTY_NAMES = new HashSet<>(Collections.singletonList(URI_PROPERTY));
|
||||
|
||||
/**
|
||||
* Get Secret Client using Default Azure Credentials Builder and default configuration from environment variables
|
||||
*
|
||||
* @param clientProperties Client Properties
|
||||
* @return Secret Client
|
||||
*/
|
||||
@Override
|
||||
protected SecretClient getConfiguredClient(final Properties clientProperties) {
|
||||
final String uri = clientProperties.getProperty(URI_PROPERTY);
|
||||
logger.debug("Azure Secret Client with URI [{}]", uri);
|
||||
|
||||
try {
|
||||
final TokenCredential credential = new DefaultAzureCredentialBuilder().build();
|
||||
return new SecretClientBuilder()
|
||||
.credential(credential)
|
||||
.vaultUrl(uri)
|
||||
.buildClient();
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException("Azure Secret Builder Client Failed using Default Credentials", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required property names for Azure Secret Client
|
||||
*
|
||||
* @return Required client property names
|
||||
*/
|
||||
@Override
|
||||
protected Set<String> getRequiredPropertyNames() {
|
||||
return REQUIRED_PROPERTY_NAMES;
|
||||
}
|
||||
}
|
|
@ -1,118 +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 com.azure.security.keyvault.keys.cryptography.CryptographyClient;
|
||||
import com.azure.security.keyvault.keys.cryptography.models.DecryptResult;
|
||||
import com.azure.security.keyvault.keys.cryptography.models.EncryptResult;
|
||||
import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm;
|
||||
import com.azure.security.keyvault.keys.models.KeyProperties;
|
||||
import com.azure.security.keyvault.keys.models.KeyVaultKey;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AzureKeyVaultKeySensitivePropertyProviderTest {
|
||||
private static final String PROPERTY_NAME = String.class.getSimpleName();
|
||||
|
||||
private static final String PROPERTY = String.class.getName();
|
||||
|
||||
private static final byte[] PROPERTY_BYTES = PROPERTY.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private static final String ENCRYPTED_PROPERTY = Integer.class.getName();
|
||||
|
||||
private static final byte[] ENCRYPTED_BYTES = ENCRYPTED_PROPERTY.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private static final String PROTECTED_PROPERTY = Base64.getEncoder().withoutPadding().encodeToString(ENCRYPTED_BYTES);
|
||||
|
||||
private static final String ID = KeyVaultKey.class.getSimpleName();
|
||||
|
||||
private static final Properties PROPERTIES = new Properties();
|
||||
|
||||
private static final EncryptionAlgorithm ALGORITHM = EncryptionAlgorithm.A256GCM;
|
||||
|
||||
private static final String IDENTIFIER_KEY = "azure/keyvault/key";
|
||||
|
||||
static {
|
||||
PROPERTIES.setProperty(AzureKeyVaultKeySensitivePropertyProvider.ENCRYPTION_ALGORITHM_PROPERTY, ALGORITHM.toString());
|
||||
}
|
||||
|
||||
@Mock
|
||||
private CryptographyClient cryptographyClient;
|
||||
|
||||
@Mock
|
||||
private KeyVaultKey keyVaultKey;
|
||||
|
||||
@Mock
|
||||
private KeyProperties keyProperties;
|
||||
|
||||
private AzureKeyVaultKeySensitivePropertyProvider provider;
|
||||
|
||||
@BeforeEach
|
||||
public void setProvider() {
|
||||
when(keyProperties.isEnabled()).thenReturn(true);
|
||||
when(keyVaultKey.getId()).thenReturn(ID);
|
||||
when(keyVaultKey.getProperties()).thenReturn(keyProperties);
|
||||
when(keyVaultKey.getKeyOperations()).thenReturn(AzureKeyVaultKeySensitivePropertyProvider.REQUIRED_OPERATIONS);
|
||||
when(cryptographyClient.getKey()).thenReturn(keyVaultKey);
|
||||
|
||||
provider = new AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient, PROPERTIES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateClientNull() {
|
||||
final AzureKeyVaultKeySensitivePropertyProvider provider = new AzureKeyVaultKeySensitivePropertyProvider(null, PROPERTIES);
|
||||
assertNotNull(provider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtect() {
|
||||
final EncryptResult encryptResult = new EncryptResult(ENCRYPTED_BYTES, ALGORITHM, ID);
|
||||
when(cryptographyClient.encrypt(eq(ALGORITHM), any(byte[].class))).thenReturn(encryptResult);
|
||||
|
||||
final String protectedProperty = provider.protect(PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
|
||||
assertEquals(PROTECTED_PROPERTY, protectedProperty);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprotect() {
|
||||
final DecryptResult decryptResult = new DecryptResult(PROPERTY_BYTES, ALGORITHM, ID);
|
||||
when(cryptographyClient.decrypt(eq(ALGORITHM), any(byte[].class))).thenReturn(decryptResult);
|
||||
|
||||
final String property = provider.unprotect(PROTECTED_PROPERTY, ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
|
||||
assertEquals(PROPERTY, property);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKey() {
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
assertEquals(IDENTIFIER_KEY, identifierKey);
|
||||
}
|
||||
}
|
|
@ -1,116 +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 com.azure.core.exception.AzureException;
|
||||
import com.azure.core.exception.ResourceNotFoundException;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.azure.security.keyvault.secrets.SecretClient;
|
||||
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AzureKeyVaultSecretSensitivePropertyProviderTest {
|
||||
private static final String PROPERTY_NAME = "property.name";
|
||||
|
||||
private static final String SECRET_NAME = "default-property-name";
|
||||
|
||||
private static final String PROPERTY = String.class.getName();
|
||||
|
||||
private static final String PROTECTED_PROPERTY = KeyVaultSecret.class.getSimpleName();
|
||||
|
||||
private static final String ID = KeyVaultSecret.class.getName();
|
||||
|
||||
private static final String IDENTIFIER_KEY = "azure/keyvault/secret";
|
||||
|
||||
@Mock
|
||||
private SecretClient secretClient;
|
||||
|
||||
@Mock
|
||||
private KeyVaultSecret keyVaultSecret;
|
||||
|
||||
@Mock
|
||||
private HttpResponse httpResponse;
|
||||
|
||||
private AzureKeyVaultSecretSensitivePropertyProvider provider;
|
||||
|
||||
@BeforeEach
|
||||
public void setProvider() {
|
||||
provider = new AzureKeyVaultSecretSensitivePropertyProvider(secretClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientNull() {
|
||||
final AzureKeyVaultSecretSensitivePropertyProvider provider = new AzureKeyVaultSecretSensitivePropertyProvider(null);
|
||||
assertNotNull(provider);
|
||||
assertFalse(provider.isSupported());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtect() {
|
||||
when(secretClient.setSecret(eq(SECRET_NAME), eq(PROPERTY))).thenReturn(keyVaultSecret);
|
||||
when(keyVaultSecret.getId()).thenReturn(ID);
|
||||
|
||||
final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
|
||||
final String protectedProperty = provider.protect(PROPERTY, context);
|
||||
assertEquals(ID, protectedProperty);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtectException() {
|
||||
final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
|
||||
final String secretName = context.getContextKey();
|
||||
when(secretClient.setSecret(eq(secretName), eq(PROPERTY))).thenThrow(new AzureException());
|
||||
|
||||
assertThrows(SensitivePropertyProtectionException.class, () -> provider.protect(PROPERTY, context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprotect() {
|
||||
when(secretClient.getSecret(eq(SECRET_NAME))).thenReturn(keyVaultSecret);
|
||||
when(keyVaultSecret.getValue()).thenReturn(PROPERTY);
|
||||
|
||||
final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
|
||||
final String property = provider.unprotect(PROTECTED_PROPERTY, context);
|
||||
assertEquals(PROPERTY, property);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprotectResourceNotFoundException() {
|
||||
when(secretClient.getSecret(eq(SECRET_NAME))).thenThrow(new ResourceNotFoundException(SECRET_NAME, httpResponse));
|
||||
|
||||
final ProtectedPropertyContext context = ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
|
||||
assertThrows(SensitivePropertyProtectionException.class, () -> provider.unprotect(PROTECTED_PROPERTY, context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKey() {
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
assertEquals(IDENTIFIER_KEY, identifierKey);
|
||||
}
|
||||
}
|
|
@ -1,44 +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.configuration;
|
||||
|
||||
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
public class AzureCryptographyClientProviderTest {
|
||||
|
||||
@Test
|
||||
public void testClientPropertiesNull() {
|
||||
final AzureCryptographyClientProvider provider = new AzureCryptographyClientProvider();
|
||||
final Optional<CryptographyClient> optionalClient = provider.getClient(null);
|
||||
assertFalse(optionalClient.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientPropertiesKeyIdBlank() {
|
||||
final AzureCryptographyClientProvider provider = new AzureCryptographyClientProvider();
|
||||
final Properties clientProperties = new Properties();
|
||||
clientProperties.setProperty(AzureCryptographyClientProvider.KEY_ID_PROPERTY, "");
|
||||
final Optional<CryptographyClient> optionalClient = provider.getClient(clientProperties);
|
||||
assertFalse(optionalClient.isPresent());
|
||||
}
|
||||
}
|
|
@ -1,44 +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.configuration;
|
||||
|
||||
import com.azure.security.keyvault.secrets.SecretClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
public class AzureSecretClientProviderTest {
|
||||
|
||||
@Test
|
||||
public void testClientPropertiesNull() {
|
||||
final AzureSecretClientProvider provider = new AzureSecretClientProvider();
|
||||
final Optional<SecretClient> optionalClient = provider.getClient(null);
|
||||
assertFalse(optionalClient.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientPropertiesUriBlank() {
|
||||
final AzureSecretClientProvider provider = new AzureSecretClientProvider();
|
||||
final Properties clientProperties = new Properties();
|
||||
clientProperties.setProperty(AzureSecretClientProvider.URI_PROPERTY, "");
|
||||
final Optional<SecretClient> optionalClient = provider.getClient(clientProperties);
|
||||
assertFalse(optionalClient.isPresent());
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-cipher</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,200 +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.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Sensitive Property Provider implementation using AES-GCM with configurable key sizes
|
||||
*/
|
||||
public class AesGcmSensitivePropertyProvider implements SensitivePropertyProvider {
|
||||
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||
private static final String SECRET_KEY_ALGORITHM = "AES";
|
||||
private static final String DELIMITER = "||"; // "|" is not a valid Base64 character, so ensured not to be present in cipher text
|
||||
private static final int IV_LENGTH = 12;
|
||||
private static final int TAG_LENGTH = 128;
|
||||
private static final int MIN_CIPHER_TEXT_LENGTH = IV_LENGTH * 4 / 3 + DELIMITER.length() + 1;
|
||||
private static final List<Integer> VALID_KEY_LENGTHS = Arrays.asList(128, 192, 256);
|
||||
|
||||
private static final String IDENTIFIER_KEY_FORMAT = "aes/gcm/%d";
|
||||
private static final int BITS_PER_BYTE = 8;
|
||||
|
||||
private static final Base64.Encoder BASE_64_ENCODER = Base64.getEncoder();
|
||||
private static final Base64.Decoder BASE_64_DECODER = Base64.getDecoder();
|
||||
|
||||
private final SecureRandom secureRandom;
|
||||
private final Cipher cipher;
|
||||
private final SecretKey key;
|
||||
private final String identifierKey;
|
||||
|
||||
public AesGcmSensitivePropertyProvider(final String keyHex) {
|
||||
byte[] keyBytes = validateKey(keyHex);
|
||||
|
||||
secureRandom = new SecureRandom();
|
||||
try {
|
||||
cipher = Cipher.getInstance(ALGORITHM);
|
||||
key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM);
|
||||
|
||||
final int keySize = keyBytes.length * BITS_PER_BYTE;
|
||||
identifierKey = String.format(IDENTIFIER_KEY_FORMAT, keySize);
|
||||
} catch (final NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("Cipher [%s] initialization failed", ALGORITHM), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key used to identify the provider implementation in {@code nifi.properties}.
|
||||
*
|
||||
* @return the key to persist in the sibling property
|
||||
*/
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return identifierKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encrypted cipher text.
|
||||
*
|
||||
* @param unprotectedValue the sensitive value
|
||||
* @param context The property context, unused in this provider
|
||||
* @return the value to persist in the {@code nifi.properties} file
|
||||
* @throws SensitivePropertyProtectionException if there is an exception encrypting the value
|
||||
*/
|
||||
@Override
|
||||
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
Objects.requireNonNull(unprotectedValue, "Value required");
|
||||
|
||||
final byte[] iv = generateIV();
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, this.key, new GCMParameterSpec(TAG_LENGTH, iv));
|
||||
|
||||
byte[] plainBytes = unprotectedValue.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] cipherBytes = cipher.doFinal(plainBytes);
|
||||
return BASE_64_ENCODER.encodeToString(iv) + DELIMITER + BASE_64_ENCODER.encodeToString(cipherBytes);
|
||||
} catch (final BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
|
||||
throw new SensitivePropertyProtectionException("AES-GCM encryption failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decrypted plaintext.
|
||||
*
|
||||
* @param protectedValue the cipher text read from the {@code nifi.properties} file
|
||||
* @param context The property context, unused in this provider
|
||||
* @return the raw value to be used by the application
|
||||
* @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
|
||||
*/
|
||||
@Override
|
||||
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
if (protectedValue == null || protectedValue.trim().length() < MIN_CIPHER_TEXT_LENGTH) {
|
||||
throw new IllegalArgumentException("Cannot decrypt a cipher text shorter than " + MIN_CIPHER_TEXT_LENGTH + " chars");
|
||||
}
|
||||
|
||||
if (!protectedValue.contains(DELIMITER)) {
|
||||
throw new IllegalArgumentException("The cipher text does not contain the delimiter " + DELIMITER + " -- it should be of the form Base64(IV) || Base64(cipherText)");
|
||||
}
|
||||
final String trimmedProtectedValue = protectedValue.trim();
|
||||
|
||||
final String armoredIV = trimmedProtectedValue.substring(0, trimmedProtectedValue.indexOf(DELIMITER));
|
||||
final byte[] iv = BASE_64_DECODER.decode(armoredIV);
|
||||
if (iv.length < IV_LENGTH) {
|
||||
throw new IllegalArgumentException(String.format("The IV (%s bytes) must be at least %s bytes", iv.length, IV_LENGTH));
|
||||
}
|
||||
|
||||
final String encodedCipherBytes = trimmedProtectedValue.substring(trimmedProtectedValue.indexOf(DELIMITER) + 2);
|
||||
|
||||
try {
|
||||
final byte[] cipherBytes = BASE_64_DECODER.decode(encodedCipherBytes);
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, this.key, new GCMParameterSpec(TAG_LENGTH, iv));
|
||||
final byte[] plainBytes = cipher.doFinal(cipherBytes);
|
||||
return new String(plainBytes, StandardCharsets.UTF_8);
|
||||
} catch (final BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
|
||||
throw new SensitivePropertyProtectionException("AES-GCM decryption failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No cleanup necessary
|
||||
*/
|
||||
@Override
|
||||
public void cleanUp() { }
|
||||
|
||||
private byte[] generateIV() {
|
||||
final byte[] iv = new byte[IV_LENGTH];
|
||||
secureRandom.nextBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
private byte[] validateKey(String keyHex) {
|
||||
if (keyHex == null || keyHex.isEmpty()) {
|
||||
throw new SensitivePropertyProtectionException("AES Key not provided");
|
||||
}
|
||||
keyHex = formatHexKey(keyHex);
|
||||
if (!isHexKeyValid(keyHex)) {
|
||||
throw new SensitivePropertyProtectionException("AES Key not hexadecimal");
|
||||
}
|
||||
final byte[] key;
|
||||
try {
|
||||
key = Hex.decodeHex(keyHex);
|
||||
} catch (final DecoderException e) {
|
||||
throw new SensitivePropertyProtectionException("AES Key Hexadecimal decoding failed", e);
|
||||
}
|
||||
final int keyLengthBits = key.length * BITS_PER_BYTE;
|
||||
if (!VALID_KEY_LENGTHS.contains(keyLengthBits)) {
|
||||
throw new SensitivePropertyProtectionException(String.format("AES Key length not valid [%d]", keyLengthBits));
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private static String formatHexKey(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return input.replaceAll("[^0-9a-fA-F]", "").toLowerCase();
|
||||
}
|
||||
|
||||
private static boolean isHexKeyValid(String key) {
|
||||
if (key == null || key.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return key.matches("^[0-9a-fA-F]*$");
|
||||
}
|
||||
}
|
|
@ -1,109 +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.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class AesGcmSensitivePropertyProviderTest {
|
||||
private static final String HEXADECIMAL_KEY_32 = "12345678";
|
||||
|
||||
private static final String HEXADECIMAL_KEY_128 = "12345678123456788765432187654321";
|
||||
|
||||
private static final String HEXADECIMAL_KEY_256 = "1234567812345678876543218765432112345678123456788765432187654321";
|
||||
|
||||
private static final String AES_GCM_128 = "aes/gcm/128";
|
||||
|
||||
private static final String AES_GCM_256 = "aes/gcm/256";
|
||||
|
||||
private static final ProtectedPropertyContext PROPERTY_CONTEXT = ProtectedPropertyContext.defaultContext("propertyName");
|
||||
|
||||
private static final String PROPERTY_VALUE = "propertyValue";
|
||||
|
||||
private static final String DELIMITER = "||";
|
||||
|
||||
private static final String DELIMITER_PATTERN = "\\|\\|";
|
||||
|
||||
private static final int DELIMITED_ELEMENTS = 2;
|
||||
|
||||
private static final int INITIALIZATION_VECTOR_LENGTH = 12;
|
||||
|
||||
@Test
|
||||
public void testInvalidKeyLength() {
|
||||
assertThrows(SensitivePropertyProtectionException.class, () -> new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_32));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSupported() {
|
||||
final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
|
||||
assertTrue(provider.isSupported());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKeyAesGcm128() {
|
||||
final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
assertEquals(AES_GCM_128, identifierKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKeyAesGcm256() {
|
||||
final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_256);
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
assertEquals(AES_GCM_256, identifierKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtectUnprotectSuccess() {
|
||||
final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
|
||||
|
||||
final String protectedPropertyValue = provider.protect(PROPERTY_VALUE, PROPERTY_CONTEXT);
|
||||
final String unprotectedPropertyValue = provider.unprotect(protectedPropertyValue, PROPERTY_CONTEXT);
|
||||
|
||||
assertEquals(PROPERTY_VALUE, unprotectedPropertyValue);
|
||||
assertTrue(protectedPropertyValue.contains(DELIMITER));
|
||||
|
||||
final String[] elements = protectedPropertyValue.split(DELIMITER_PATTERN);
|
||||
assertEquals(DELIMITED_ELEMENTS, elements.length);
|
||||
|
||||
final String initializationVectorEncoded = elements[0];
|
||||
final byte[] initializationVector = Base64.getDecoder().decode(initializationVectorEncoded);
|
||||
assertEquals(INITIALIZATION_VECTOR_LENGTH, initializationVector.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtectUnprotectDifferentKeyFailed() {
|
||||
final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
|
||||
|
||||
final String protectedPropertyValue = provider.protect(PROPERTY_VALUE, PROPERTY_CONTEXT);
|
||||
|
||||
final AesGcmSensitivePropertyProvider secondProvider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_256);
|
||||
assertThrows(SensitivePropertyProtectionException.class, () -> secondProvider.unprotect(protectedPropertyValue, PROPERTY_CONTEXT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnprotectMinLengthRequired() {
|
||||
final AesGcmSensitivePropertyProvider provider = new AesGcmSensitivePropertyProvider(HEXADECIMAL_KEY_128);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> provider.unprotect(HEXADECIMAL_KEY_32, PROPERTY_CONTEXT));
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-factory</artifactId>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-sdk-bom</artifactId>
|
||||
<version>1.2.24</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.cloud</groupId>
|
||||
<artifactId>libraries-bom</artifactId>
|
||||
<version>${gcp.sdk.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-shared</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-aws</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-azure</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-cipher</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-gcp</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-hashicorp</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-properties</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core-http-okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.cloud</groupId>
|
||||
<artifactId>google-cloud-kms</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-security-keyvault-secrets</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core-http-netty</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-security-keyvault-keys</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core-http-netty</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>kms</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>secretsmanager</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>apache-client</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,243 +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 com.azure.security.keyvault.keys.cryptography.CryptographyClient;
|
||||
import com.azure.security.keyvault.secrets.SecretClient;
|
||||
import com.google.cloud.kms.v1.KeyManagementServiceClient;
|
||||
|
||||
import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
|
||||
import org.apache.nifi.properties.configuration.AwsKmsClientProvider;
|
||||
import org.apache.nifi.properties.configuration.AwsSecretsManagerClientProvider;
|
||||
import org.apache.nifi.properties.configuration.AzureCryptographyClientProvider;
|
||||
import org.apache.nifi.properties.configuration.AzureSecretClientProvider;
|
||||
import org.apache.nifi.properties.configuration.ClientProvider;
|
||||
import org.apache.nifi.properties.configuration.GoogleKeyManagementServiceClientProvider;
|
||||
import org.apache.nifi.properties.scheme.ProtectionScheme;
|
||||
import org.apache.nifi.util.NiFiBootstrapUtils;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import software.amazon.awssdk.services.kms.KmsClient;
|
||||
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Standard implementation of Sensitive Property Provider Factory with custom initialization for each provider
|
||||
*/
|
||||
public class StandardSensitivePropertyProviderFactory implements SensitivePropertyProviderFactory {
|
||||
private static final Logger logger = LoggerFactory.getLogger(StandardSensitivePropertyProviderFactory.class);
|
||||
|
||||
private static final List<Class<? extends SensitivePropertyProvider>> PROVIDER_CLASSES = Arrays.asList(
|
||||
AesGcmSensitivePropertyProvider.class,
|
||||
AwsKmsSensitivePropertyProvider.class,
|
||||
AwsSecretsManagerSensitivePropertyProvider.class,
|
||||
AzureKeyVaultKeySensitivePropertyProvider.class,
|
||||
AzureKeyVaultSecretSensitivePropertyProvider.class,
|
||||
GcpKmsSensitivePropertyProvider.class,
|
||||
HashiCorpVaultKeyValueSensitivePropertyProvider.class,
|
||||
HashiCorpVaultTransitSensitivePropertyProvider.class
|
||||
);
|
||||
|
||||
private Optional<String> keyHex;
|
||||
private final Supplier<BootstrapProperties> bootstrapPropertiesSupplier;
|
||||
private final Map<Class<? extends SensitivePropertyProvider>, SensitivePropertyProvider> providers;
|
||||
private Map<String, Pattern> customPropertyContextMap;
|
||||
|
||||
/**
|
||||
* Factory default constructor to support java.util.ServiceLoader
|
||||
*/
|
||||
public StandardSensitivePropertyProviderFactory() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public void setKeyHex(final String hexadecimalKey) {
|
||||
this.keyHex = Optional.ofNullable(hexadecimalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.providers = new HashMap<>();
|
||||
this.customPropertyContextMap = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Provider where Protection Scheme path starts with a prefix mapped to the Provider Class
|
||||
*
|
||||
* @param protectionScheme Protection Scheme requested
|
||||
* @return Sensitive Property Provider
|
||||
* @throws SensitivePropertyProtectionException Thrown when provider path not found for requested scheme
|
||||
*/
|
||||
@Override
|
||||
public SensitivePropertyProvider getProvider(final ProtectionScheme protectionScheme) throws SensitivePropertyProtectionException {
|
||||
final String path = Objects.requireNonNull(protectionScheme, "Protection Scheme required").getPath();
|
||||
final Collection<SensitivePropertyProvider> supportedProviders = getSupportedProviders();
|
||||
return supportedProviders.stream()
|
||||
.filter(provider -> provider.getIdentifierKey().startsWith(path))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new SensitivePropertyProtectionException(String.format("Protection Scheme [%s] not found", path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Supported Sensitive Property Providers instantiates providers as needed and checks supported status
|
||||
*
|
||||
* @return Supported Sensitive Property Providers
|
||||
*/
|
||||
@Override
|
||||
public Collection<SensitivePropertyProvider> getSupportedProviders() {
|
||||
return PROVIDER_CLASSES
|
||||
.stream()
|
||||
.map(this::getProvider)
|
||||
.filter(SensitivePropertyProvider::isSupported)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtectedPropertyContext getPropertyContext(final String groupIdentifier, final String propertyName) {
|
||||
if (customPropertyContextMap == null) {
|
||||
populateCustomPropertyContextMap();
|
||||
}
|
||||
final String contextName = customPropertyContextMap.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().matcher(groupIdentifier).find())
|
||||
.map(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return ProtectedPropertyContext.contextFor(propertyName, contextName);
|
||||
}
|
||||
|
||||
private void populateCustomPropertyContextMap() {
|
||||
final BootstrapProperties bootstrapProperties = getBootstrapProperties();
|
||||
customPropertyContextMap = new HashMap<>();
|
||||
final String contextMappingKeyPrefix = BootstrapPropertyKey.CONTEXT_MAPPING_PREFIX.getKey();
|
||||
bootstrapProperties.getPropertyKeys().stream()
|
||||
.filter(k -> k.contains(contextMappingKeyPrefix))
|
||||
.forEach(k -> customPropertyContextMap.put(StringUtils.substringAfter(k, contextMappingKeyPrefix), Pattern.compile(bootstrapProperties.getProperty(k))));
|
||||
}
|
||||
|
||||
private String getKeyHex() {
|
||||
return keyHex.orElseGet(() -> getBootstrapProperties().getProperty(BootstrapPropertyKey.SENSITIVE_KEY)
|
||||
.orElseThrow(() -> new SensitivePropertyProtectionException("Could not read root key from bootstrap.conf")));
|
||||
}
|
||||
|
||||
private BootstrapProperties getBootstrapProperties() {
|
||||
return Optional.ofNullable(bootstrapPropertiesSupplier.get()).orElseGet(() -> {
|
||||
try {
|
||||
return NiFiBootstrapUtils.loadBootstrapProperties();
|
||||
} catch (final IOException e) {
|
||||
logger.debug("Bootstrap Properties loading failed", e);
|
||||
return BootstrapProperties.EMPTY;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> Properties getClientProperties(final ClientProvider<T> clientProvider) {
|
||||
final Optional<Properties> clientProperties = clientProvider.getClientProperties(getBootstrapProperties());
|
||||
return clientProperties.orElse(null);
|
||||
}
|
||||
|
||||
private SensitivePropertyProvider getProvider(final Class<? extends SensitivePropertyProvider> providerClass) throws SensitivePropertyProtectionException {
|
||||
SensitivePropertyProvider provider = providers.get(providerClass);
|
||||
if (provider == null) {
|
||||
if (AesGcmSensitivePropertyProvider.class.equals(providerClass)) {
|
||||
final String hexadecimalKey = getKeyHex();
|
||||
provider = new AesGcmSensitivePropertyProvider(hexadecimalKey);
|
||||
} else if (AwsKmsSensitivePropertyProvider.class.equals(providerClass)) {
|
||||
final AwsKmsClientProvider clientProvider = new AwsKmsClientProvider();
|
||||
final Properties clientProperties = getClientProperties(clientProvider);
|
||||
final Optional<KmsClient> kmsClient = clientProvider.getClient(clientProperties);
|
||||
provider = new AwsKmsSensitivePropertyProvider(kmsClient.orElse(null), clientProperties);
|
||||
} else if (AwsSecretsManagerSensitivePropertyProvider.class.equals(providerClass)) {
|
||||
final AwsSecretsManagerClientProvider clientProvider = new AwsSecretsManagerClientProvider();
|
||||
final Properties clientProperties = getClientProperties(clientProvider);
|
||||
final Optional<SecretsManagerClient> secretsManagerClient = clientProvider.getClient(clientProperties);
|
||||
provider = new AwsSecretsManagerSensitivePropertyProvider(secretsManagerClient.orElse(null));
|
||||
} else if (AzureKeyVaultKeySensitivePropertyProvider.class.equals(providerClass)) {
|
||||
final AzureCryptographyClientProvider clientProvider = new AzureCryptographyClientProvider();
|
||||
final Properties clientProperties = getClientProperties(clientProvider);
|
||||
final Optional<CryptographyClient> cryptographyClient = clientProvider.getClient(clientProperties);
|
||||
provider = new AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient.orElse(null), clientProperties);
|
||||
} else if (AzureKeyVaultSecretSensitivePropertyProvider.class.equals(providerClass)) {
|
||||
final AzureSecretClientProvider clientProvider = new AzureSecretClientProvider();
|
||||
final Properties clientProperties = getClientProperties(clientProvider);
|
||||
final Optional<SecretClient> secretClient = clientProvider.getClient(clientProperties);
|
||||
provider = new AzureKeyVaultSecretSensitivePropertyProvider(secretClient.orElse(null));
|
||||
} else if (GcpKmsSensitivePropertyProvider.class.equals(providerClass)) {
|
||||
final GoogleKeyManagementServiceClientProvider clientProvider = new GoogleKeyManagementServiceClientProvider();
|
||||
final Properties clientProperties = getClientProperties(clientProvider);
|
||||
final Optional<KeyManagementServiceClient> keyManagementServiceClient = clientProvider.getClient(clientProperties);
|
||||
provider = new GcpKmsSensitivePropertyProvider(keyManagementServiceClient.orElse(null), clientProperties);
|
||||
} else if (HashiCorpVaultKeyValueSensitivePropertyProvider.class.equals(providerClass)) {
|
||||
provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(getBootstrapProperties());
|
||||
} else if (HashiCorpVaultTransitSensitivePropertyProvider.class.equals(providerClass)) {
|
||||
provider = new HashiCorpVaultTransitSensitivePropertyProvider(getBootstrapProperties());
|
||||
}
|
||||
}
|
||||
|
||||
if (provider == null) {
|
||||
throw new UnsupportedOperationException(String.format("Provider class not supported [%s]", providerClass));
|
||||
}
|
||||
|
||||
providers.put(providerClass, provider);
|
||||
return provider;
|
||||
}
|
||||
}
|
|
@ -1,49 +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.scheme;
|
||||
|
||||
/**
|
||||
* Property Protection Schemes supported as arguments for encryption commands should not have direct references
|
||||
*/
|
||||
enum PropertyProtectionScheme implements ProtectionScheme {
|
||||
AES_GCM("aes/gcm"),
|
||||
|
||||
AWS_SECRETSMANAGER("aws/secretsmanager"),
|
||||
|
||||
AWS_KMS("aws/kms"),
|
||||
|
||||
AZURE_KEYVAULT_KEY("azure/keyvault/key"),
|
||||
|
||||
AZURE_KEYVAULT_SECRET("azure/keyvault/secret"),
|
||||
|
||||
GCP_KMS("gcp/kms"),
|
||||
|
||||
HASHICORP_VAULT_KV("hashicorp/vault/kv"),
|
||||
|
||||
HASHICORP_VAULT_TRANSIT("hashicorp/vault/transit");
|
||||
|
||||
PropertyProtectionScheme(final String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
private final String path;
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
|
@ -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.properties.scheme;
|
||||
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Standard implementation of Protection Scheme Resolver using Property Protection Scheme enumeration
|
||||
*/
|
||||
public class StandardProtectionSchemeResolver implements ProtectionSchemeResolver {
|
||||
/**
|
||||
* Get Protection Scheme based on scheme matching one the supported Protection Property Scheme enumerated values
|
||||
*
|
||||
* @param scheme Scheme name required
|
||||
* @return Protection Scheme
|
||||
*/
|
||||
@Override
|
||||
public ProtectionScheme getProtectionScheme(final String scheme) {
|
||||
Objects.requireNonNull(scheme, "Scheme required");
|
||||
return Arrays.stream(PropertyProtectionScheme.values())
|
||||
.filter(propertyProtectionScheme ->
|
||||
propertyProtectionScheme.name().equals(scheme) || scheme.startsWith(propertyProtectionScheme.getPath())
|
||||
)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new SensitivePropertyProtectionException(String.format("Protection Scheme [%s] not supported", scheme)));
|
||||
}
|
||||
|
||||
public List<String> getSupportedProtectionSchemes() {
|
||||
return Arrays.stream(PropertyProtectionScheme.values())
|
||||
.map(PropertyProtectionScheme::name)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -1,15 +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.
|
||||
org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
|
|
@ -1,15 +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.
|
||||
org.apache.nifi.properties.scheme.StandardProtectionSchemeResolver
|
|
@ -1,206 +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.io.FilenameUtils;
|
||||
import org.apache.nifi.properties.scheme.ProtectionScheme;
|
||||
import org.apache.nifi.properties.scheme.StandardProtectionScheme;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Collection;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class StandardSensitivePropertyProviderFactoryTest {
|
||||
|
||||
private SensitivePropertyProviderFactory factory;
|
||||
|
||||
private static final String BOOTSTRAP_KEY_HEX = "0123456789ABCDEFFEDCBA9876543210";
|
||||
|
||||
private static final ProtectionScheme AES_GCM = new StandardProtectionScheme("aes/gcm");
|
||||
private static final ProtectionScheme AES_GCM_128 = new StandardProtectionScheme("aes/gcm/128");
|
||||
private static final ProtectionScheme HASHICORP_VAULT_TRANSIT = new StandardProtectionScheme("hashicorp/vault/transit/testing");
|
||||
private static final ProtectionScheme HASHICORP_VAULT_KV = new StandardProtectionScheme("hashicorp/vault/kv/testing");
|
||||
|
||||
private static Path tempConfDir;
|
||||
private static Path bootstrapConf;
|
||||
private static Path hashicorpVaultBootstrapConf;
|
||||
private static Path nifiProperties;
|
||||
private static Path azureKeyVaultConf;
|
||||
private static String defaultBootstrapContents;
|
||||
|
||||
@BeforeAll
|
||||
public static void initOnce() throws IOException {
|
||||
tempConfDir = Files.createTempDirectory("conf");
|
||||
bootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
|
||||
azureKeyVaultConf = Files.createTempFile("bootstrap-azure-keyvault", ".conf").toAbsolutePath();
|
||||
hashicorpVaultBootstrapConf = Files.createTempFile("bootstrap-hashicorp-vault", ".conf").toAbsolutePath();
|
||||
|
||||
nifiProperties = Files.createTempFile("nifi", ".properties").toAbsolutePath();
|
||||
|
||||
nifiProperties = Files.move(nifiProperties, tempConfDir.resolve("nifi.properties"));
|
||||
|
||||
defaultBootstrapContents = String.format("%s=%s\n%s=%s\n%s=%s",
|
||||
"nifi.bootstrap.sensitive.key", BOOTSTRAP_KEY_HEX,
|
||||
"nifi.bootstrap.protection.azure.keyvault.conf", FilenameUtils.separatorsToUnix(azureKeyVaultConf.toString()),
|
||||
"nifi.bootstrap.protection.hashicorp.vault.conf", FilenameUtils.separatorsToUnix(hashicorpVaultBootstrapConf.toString()));
|
||||
bootstrapConf = writeDefaultBootstrapConf();
|
||||
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, FilenameUtils.separatorsToUnix(nifiProperties.toString()));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDownOnce() throws IOException {
|
||||
Files.deleteIfExists(bootstrapConf);
|
||||
Files.deleteIfExists(azureKeyVaultConf);
|
||||
Files.deleteIfExists(hashicorpVaultBootstrapConf);
|
||||
Files.deleteIfExists(nifiProperties);
|
||||
Files.deleteIfExists(tempConfDir);
|
||||
System.clearProperty(NiFiProperties.PROPERTIES_FILE_PATH);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setFactory() {
|
||||
factory = StandardSensitivePropertyProviderFactory.withDefaults();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPropertyContextNotConfigured() {
|
||||
assertEquals("default/prop", factory.getPropertyContext("ldap-provider", "prop").getContextKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPropertyContext() throws IOException {
|
||||
writeBootstrapConf(defaultBootstrapContents + "\n" +
|
||||
"nifi.bootstrap.protection.context.mapping.ldap=ldap-.*");
|
||||
try {
|
||||
assertEquals("ldap/prop", factory.getPropertyContext("ldap-provider", "prop").getContextKey());
|
||||
assertEquals("ldap/prop", factory.getPropertyContext("ldap-user-group-provider", "prop").getContextKey());
|
||||
} finally {
|
||||
writeDefaultBootstrapConf();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedProviders() {
|
||||
final Collection<SensitivePropertyProvider> providers = factory.getSupportedProviders();
|
||||
assertFalse(providers.isEmpty());
|
||||
|
||||
final boolean aesProviderFound = providers.stream()
|
||||
.anyMatch(provider -> provider instanceof AesGcmSensitivePropertyProvider);
|
||||
assertTrue(aesProviderFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAzureKeyVaultSecret() throws IOException {
|
||||
final Properties properties = new Properties();
|
||||
properties.put("azure.keyvault.uri", "https://testing.vault.azure.net");
|
||||
configureAzureKeyVault(properties);
|
||||
|
||||
final SensitivePropertyProvider provider = factory.getProvider(new StandardProtectionScheme("azure/keyvault/secret"));
|
||||
assertTrue(provider.isSupported());
|
||||
assertEquals(AzureKeyVaultSecretSensitivePropertyProvider.class, provider.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashiCorpVaultKeyVaultSupported() throws IOException {
|
||||
final Properties properties = new Properties();
|
||||
properties.put("vault.kv.path", "testing");
|
||||
properties.put("vault.uri", "http://localhost:8200");
|
||||
properties.put("vault.token", "test-token");
|
||||
configureHashicorpVault(properties);
|
||||
|
||||
final SensitivePropertyProvider provider = factory.getProvider(HASHICORP_VAULT_KV);
|
||||
assertTrue(provider.isSupported());
|
||||
assertEquals(HashiCorpVaultKeyValueSensitivePropertyProvider.class, provider.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashiCorpVaultTransitSupported() throws IOException {
|
||||
final Properties properties = new Properties();
|
||||
properties.put("vault.transit.path", "testing");
|
||||
properties.put("vault.uri", "http://localhost:8200");
|
||||
properties.put("vault.token", "test-token");
|
||||
configureHashicorpVault(properties);
|
||||
|
||||
final SensitivePropertyProvider provider = factory.getProvider(HASHICORP_VAULT_TRANSIT);
|
||||
assertTrue(provider.isSupported());
|
||||
assertEquals(HashiCorpVaultTransitSensitivePropertyProvider.class, provider.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashiCorpVaultTransitExceptionWhenMissingProperties() throws IOException {
|
||||
final Properties properties = new Properties();
|
||||
properties.put("vault.uri", "http://localhost:8200");
|
||||
configureHashicorpVault(properties);
|
||||
|
||||
assertThrows(SensitivePropertyProtectionException.class, () -> factory.getProvider(HASHICORP_VAULT_TRANSIT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAesGcmWithoutKeySizeSupported() {
|
||||
final SensitivePropertyProvider provider = factory.getProvider(AES_GCM);
|
||||
assertEquals(AesGcmSensitivePropertyProvider.class, provider.getClass());
|
||||
assertTrue(provider.isSupported());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAesGcm128Supported() {
|
||||
final SensitivePropertyProvider provider = factory.getProvider(AES_GCM_128);
|
||||
assertEquals(AesGcmSensitivePropertyProvider.class, provider.getClass());
|
||||
assertTrue(provider.isSupported());
|
||||
}
|
||||
|
||||
|
||||
private static Path writeDefaultBootstrapConf() throws IOException {
|
||||
return writeBootstrapConf(defaultBootstrapContents);
|
||||
}
|
||||
|
||||
private static Path writeBootstrapConf(final String contents) throws IOException {
|
||||
final Path tempBootstrapConf = Files.createTempFile("bootstrap", ".conf").toAbsolutePath();
|
||||
final Path bootstrapConf = Files.move(tempBootstrapConf, tempConfDir.resolve("bootstrap.conf"), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
Files.write(bootstrapConf, contents.getBytes(StandardCharsets.UTF_8));
|
||||
return bootstrapConf;
|
||||
}
|
||||
|
||||
private void configureHashicorpVault(final Properties properties) throws IOException {
|
||||
try (OutputStream out = new FileOutputStream(hashicorpVaultBootstrapConf.toFile())) {
|
||||
properties.store(out, hashicorpVaultBootstrapConf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void configureAzureKeyVault(final Properties properties) throws IOException {
|
||||
try (OutputStream out = new FileOutputStream(azureKeyVaultConf.toFile())) {
|
||||
properties.store(out, azureKeyVaultConf.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +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.scheme;
|
||||
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class StandardProtectionSchemeResolverTest {
|
||||
private static final String AES_GCM = "AES_GCM";
|
||||
|
||||
private static final String AES_GCM_PATH = "aes/gcm";
|
||||
|
||||
private static final String AES_GCM_256_PATH = "aes/gcm/256";
|
||||
|
||||
private static final String UNKNOWN = "UNKNOWN";
|
||||
|
||||
private StandardProtectionSchemeResolver resolver;
|
||||
|
||||
@BeforeEach
|
||||
public void setResolver() {
|
||||
resolver = new StandardProtectionSchemeResolver();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProtectionSchemeAesGcmFound() {
|
||||
final ProtectionScheme protectionScheme = resolver.getProtectionScheme(AES_GCM);
|
||||
assertNotNull(protectionScheme);
|
||||
assertEquals(AES_GCM_PATH, protectionScheme.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProtectionSchemeAesGcm256Found() {
|
||||
final ProtectionScheme protectionScheme = resolver.getProtectionScheme(AES_GCM_256_PATH);
|
||||
assertNotNull(protectionScheme);
|
||||
assertEquals(AES_GCM_PATH, protectionScheme.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProtectionSchemeUnknownNotFound() {
|
||||
final SensitivePropertyProtectionException exception = assertThrows(SensitivePropertyProtectionException.class, () -> resolver.getProtectionScheme(UNKNOWN));
|
||||
assertTrue(exception.getMessage().contains(UNKNOWN));
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-gcp</artifactId>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.cloud</groupId>
|
||||
<artifactId>libraries-bom</artifactId>
|
||||
<version>${gcp.sdk.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-shared</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.api</groupId>
|
||||
<artifactId>api-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.api</groupId>
|
||||
<artifactId>gax</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.api.grpc</groupId>
|
||||
<artifactId>proto-google-cloud-kms-v1</artifactId>
|
||||
</dependency>
|
||||
<!-- Override Guava 31.1 from google-cloud-kms -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.cloud</groupId>
|
||||
<artifactId>google-cloud-kms</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,122 +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 com.google.api.gax.rpc.ApiException;
|
||||
import com.google.cloud.kms.v1.CryptoKey;
|
||||
import com.google.cloud.kms.v1.CryptoKeyName;
|
||||
import com.google.cloud.kms.v1.CryptoKeyVersion;
|
||||
import com.google.cloud.kms.v1.DecryptResponse;
|
||||
import com.google.cloud.kms.v1.EncryptResponse;
|
||||
import com.google.cloud.kms.v1.KeyManagementServiceClient;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Google Cloud Platform Key Management Service Sensitive Property Provider
|
||||
*/
|
||||
public class GcpKmsSensitivePropertyProvider extends ClientBasedEncodedSensitivePropertyProvider<KeyManagementServiceClient> {
|
||||
protected static final String PROJECT_PROPERTY = "gcp.kms.project";
|
||||
protected static final String LOCATION_PROPERTY = "gcp.kms.location";
|
||||
protected static final String KEYRING_PROPERTY = "gcp.kms.keyring";
|
||||
protected static final String KEY_PROPERTY = "gcp.kms.key";
|
||||
|
||||
private static final String SCHEME_BASE_PATH = "gcp/kms";
|
||||
|
||||
private CryptoKeyName cryptoKeyName;
|
||||
|
||||
GcpKmsSensitivePropertyProvider(final KeyManagementServiceClient keyManagementServiceClient, final Properties properties) {
|
||||
super(keyManagementServiceClient, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return SCHEME_BASE_PATH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Client when configured
|
||||
*/
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
final KeyManagementServiceClient keyManagementServiceClient = getClient();
|
||||
if (keyManagementServiceClient == null) {
|
||||
logger.debug("GCP KMS Client not configured");
|
||||
} else {
|
||||
keyManagementServiceClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Client and Key Operations with Encryption Algorithm when configured
|
||||
*
|
||||
* @param keyManagementServiceClient Key Management Service Client
|
||||
*/
|
||||
@Override
|
||||
protected void validate(final KeyManagementServiceClient keyManagementServiceClient) {
|
||||
if (keyManagementServiceClient == null) {
|
||||
logger.debug("GCP KMS Client not configured");
|
||||
} else {
|
||||
final String project = getProperties().getProperty(PROJECT_PROPERTY);
|
||||
final String location = getProperties().getProperty(LOCATION_PROPERTY);
|
||||
final String keyring = getProperties().getProperty(KEYRING_PROPERTY);
|
||||
final String key = getProperties().getProperty(KEY_PROPERTY);
|
||||
if (StringUtils.isNoneBlank(project, location, keyring, key)) {
|
||||
cryptoKeyName = CryptoKeyName.of(project, location, keyring, key);
|
||||
try {
|
||||
final CryptoKey cryptoKey = keyManagementServiceClient.getCryptoKey(cryptoKeyName);
|
||||
final CryptoKeyVersion cryptoKeyVersion = cryptoKey.getPrimary();
|
||||
if (CryptoKeyVersion.CryptoKeyVersionState.ENABLED == cryptoKeyVersion.getState()) {
|
||||
logger.info("GCP KMS Crypto Key [{}] Validated", cryptoKeyName);
|
||||
} else {
|
||||
throw new SensitivePropertyProtectionException(String.format("GCP KMS Crypto Key [%s] Disabled", cryptoKeyName));
|
||||
}
|
||||
} catch (final ApiException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("GCP KMS Crypto Key [%s] Validation Failed", cryptoKeyName), e);
|
||||
}
|
||||
} else {
|
||||
throw new SensitivePropertyProtectionException("GCP KMS Missing Required Properties");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encrypted bytes
|
||||
*
|
||||
* @param bytes Unprotected bytes
|
||||
* @return Encrypted bytes
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getEncrypted(final byte[] bytes) {
|
||||
final EncryptResponse encryptResponse = getClient().encrypt(cryptoKeyName, ByteString.copyFrom(bytes));
|
||||
return encryptResponse.getCiphertext().toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get decrypted bytes
|
||||
*
|
||||
* @param bytes Encrypted bytes
|
||||
* @return Decrypted bytes
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getDecrypted(final byte[] bytes) {
|
||||
final DecryptResponse decryptResponse = getClient().decrypt(cryptoKeyName, ByteString.copyFrom(bytes));
|
||||
return decryptResponse.getPlaintext().toByteArray();
|
||||
}
|
||||
}
|
|
@ -1,56 +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.configuration;
|
||||
|
||||
import com.google.cloud.kms.v1.KeyManagementServiceClient;
|
||||
|
||||
import org.apache.nifi.properties.BootstrapProperties;
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Google Key Management Service Client Provider
|
||||
*/
|
||||
public class GoogleKeyManagementServiceClientProvider extends BootstrapPropertiesClientProvider<KeyManagementServiceClient> {
|
||||
public GoogleKeyManagementServiceClientProvider() {
|
||||
super(BootstrapProperties.BootstrapPropertyKey.GCP_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Configured Client using default Key Management Service Client settings
|
||||
*
|
||||
* @param clientProperties Client Properties
|
||||
* @return Key Management Service Client
|
||||
*/
|
||||
@Override
|
||||
protected KeyManagementServiceClient getConfiguredClient(final Properties clientProperties) {
|
||||
try {
|
||||
return KeyManagementServiceClient.create();
|
||||
} catch (final IOException e) {
|
||||
throw new SensitivePropertyProtectionException("Google Key Management Service Create Failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getRequiredPropertyNames() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-hashicorp</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-hashicorp-vault-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-hashicorp-vault</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,124 +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.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
|
||||
import org.apache.nifi.vault.hashicorp.HashiCorpVaultCommunicationService;
|
||||
import org.apache.nifi.vault.hashicorp.StandardHashiCorpVaultCommunicationService;
|
||||
import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultConfiguration;
|
||||
import org.apache.nifi.vault.hashicorp.config.HashiCorpVaultConfiguration.VaultConfigurationKey;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public abstract class AbstractHashiCorpVaultSensitivePropertyProvider implements SensitivePropertyProvider {
|
||||
private static final String VAULT_PREFIX = "vault";
|
||||
|
||||
private final String path;
|
||||
private final HashiCorpVaultCommunicationService vaultCommunicationService;
|
||||
private final BootstrapProperties vaultBootstrapProperties;
|
||||
|
||||
AbstractHashiCorpVaultSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
|
||||
final String vaultBootstrapConfFilename = bootstrapProperties
|
||||
.getProperty(BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF).orElse(null);
|
||||
vaultBootstrapProperties = getVaultBootstrapProperties(vaultBootstrapConfFilename);
|
||||
path = getSecretsEnginePath(vaultBootstrapProperties);
|
||||
if (hasRequiredVaultProperties()) {
|
||||
try {
|
||||
vaultCommunicationService = new StandardHashiCorpVaultCommunicationService(getVaultPropertySource(vaultBootstrapConfFilename));
|
||||
} catch (final IOException e) {
|
||||
throw new SensitivePropertyProtectionException("Error configuring HashiCorpVaultCommunicationService", e);
|
||||
}
|
||||
} else {
|
||||
vaultCommunicationService = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured Secrets Engine path for this sensitive property provider.
|
||||
* @param vaultBootstrapProperties The Properties from the file located at bootstrap.protection.hashicorp.vault.conf
|
||||
* @return The Secrets Engine path
|
||||
*/
|
||||
protected abstract String getSecretsEnginePath(final BootstrapProperties vaultBootstrapProperties);
|
||||
|
||||
private static BootstrapProperties getVaultBootstrapProperties(final String vaultBootstrapConfFilename) {
|
||||
final BootstrapProperties vaultBootstrapProperties;
|
||||
if (vaultBootstrapConfFilename != null) {
|
||||
try {
|
||||
vaultBootstrapProperties = AbstractBootstrapPropertiesLoader.loadBootstrapProperties(
|
||||
Paths.get(vaultBootstrapConfFilename), VAULT_PREFIX);
|
||||
} catch (final IOException e) {
|
||||
throw new SensitivePropertyProtectionException("Could not load " + vaultBootstrapConfFilename, e);
|
||||
}
|
||||
} else {
|
||||
vaultBootstrapProperties = null;
|
||||
}
|
||||
return vaultBootstrapProperties;
|
||||
}
|
||||
|
||||
private PropertySource<?> getVaultPropertySource(final String vaultBootstrapConfFilename) throws IOException {
|
||||
return HashiCorpVaultConfiguration.createPropertiesFileSource(vaultBootstrapConfFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Secrets Engine path.
|
||||
* @return The Secrets Engine path
|
||||
*/
|
||||
protected String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
protected HashiCorpVaultCommunicationService getVaultCommunicationService() {
|
||||
if (vaultCommunicationService == null) {
|
||||
throw new SensitivePropertyProtectionException("Vault Protection Scheme missing required properties");
|
||||
}
|
||||
return vaultCommunicationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return hasRequiredVaultProperties();
|
||||
}
|
||||
|
||||
private boolean hasRequiredVaultProperties() {
|
||||
return vaultBootstrapProperties != null
|
||||
&& (vaultBootstrapProperties.getProperty(VaultConfigurationKey.URI.getKey()) != null)
|
||||
&& hasRequiredSecretsEngineProperties(vaultBootstrapProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the relevant Secrets Engine-specific properties are configured.
|
||||
* @param vaultBootstrapProperties The Vault-specific bootstrap properties
|
||||
* @return true if the relevant Secrets Engine-specific properties are configured
|
||||
*/
|
||||
protected boolean hasRequiredSecretsEngineProperties(final BootstrapProperties vaultBootstrapProperties) {
|
||||
return getSecretsEnginePath(vaultBootstrapProperties) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* No cleanup necessary
|
||||
*/
|
||||
@Override
|
||||
public void cleanUp() { }
|
||||
|
||||
protected void requireNotBlank(final String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
throw new IllegalArgumentException("Property value is null or empty");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.properties;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Uses the HashiCorp Vault Key/Value version 1 Secrets Engine to store sensitive values.
|
||||
*/
|
||||
public class HashiCorpVaultKeyValueSensitivePropertyProvider extends AbstractHashiCorpVaultSensitivePropertyProvider {
|
||||
|
||||
private static final String KEY_VALUE_PATH = "vault.kv.path";
|
||||
|
||||
private static final String IDENTIFIER_KEY_FORMAT = "hashicorp/vault/kv/%s";
|
||||
|
||||
HashiCorpVaultKeyValueSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
|
||||
super(bootstrapProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSecretsEnginePath(final BootstrapProperties vaultBootstrapProperties) {
|
||||
if (vaultBootstrapProperties == null) {
|
||||
return null;
|
||||
}
|
||||
return vaultBootstrapProperties.getProperty(KEY_VALUE_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return String.format(IDENTIFIER_KEY_FORMAT, getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the sensitive value in Vault and returns a description of the secret.
|
||||
*
|
||||
* @param unprotectedValue the sensitive value
|
||||
* @param context The property context, unused in this provider
|
||||
* @return the value to persist in the {@code nifi.properties} file
|
||||
* @throws SensitivePropertyProtectionException if there is an exception writing the secret
|
||||
*/
|
||||
@Override
|
||||
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
requireNotBlank(unprotectedValue);
|
||||
Objects.requireNonNull(context, "Context is required to protect a value");
|
||||
|
||||
getVaultCommunicationService().writeKeyValueSecret(getPath(), context.getContextKey(), unprotectedValue);
|
||||
return String.format("%s/%s", getPath(), context.getContextKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secret value, as read from Vault.
|
||||
*
|
||||
* @param protectedValue The value read from {@code nifi.properties} file. Ignored in this provider.
|
||||
* @param context The property context, from which the Vault secret name is pulled
|
||||
* @return the raw value to be used by the application
|
||||
* @throws SensitivePropertyProtectionException if there is an error retrieving the secret
|
||||
*/
|
||||
@Override
|
||||
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
Objects.requireNonNull(context, "Context is required to unprotect a value");
|
||||
|
||||
return getVaultCommunicationService().readKeyValueSecret(getPath(), context.getContextKey())
|
||||
.orElseThrow(() -> new SensitivePropertyProtectionException(String
|
||||
.format("Secret [%s] not found in Vault Key/Value engine at [%s]", context.getContextKey(), getPath())));
|
||||
}
|
||||
}
|
|
@ -1,75 +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.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Uses the HashiCorp Vault Transit Secrets Engine to encrypt sensitive values at rest.
|
||||
*/
|
||||
public class HashiCorpVaultTransitSensitivePropertyProvider extends AbstractHashiCorpVaultSensitivePropertyProvider {
|
||||
private static final Charset PROPERTY_CHARSET = StandardCharsets.UTF_8;
|
||||
private static final String TRANSIT_PATH = "vault.transit.path";
|
||||
|
||||
private static final String IDENTIFIER_KEY_FORMAT = "hashicorp/vault/transit/%s";
|
||||
|
||||
HashiCorpVaultTransitSensitivePropertyProvider(final BootstrapProperties bootstrapProperties) {
|
||||
super(bootstrapProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSecretsEnginePath(final BootstrapProperties vaultBootstrapProperties) {
|
||||
if (vaultBootstrapProperties == null) {
|
||||
return null;
|
||||
}
|
||||
return vaultBootstrapProperties.getProperty(TRANSIT_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifierKey() {
|
||||
return String.format(IDENTIFIER_KEY_FORMAT, getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encrypted cipher text.
|
||||
*
|
||||
* @param unprotectedValue the sensitive value
|
||||
* @param context The property context, unused in this provider
|
||||
* @return the value to persist in the {@code nifi.properties} file
|
||||
* @throws SensitivePropertyProtectionException if there is an exception encrypting the value
|
||||
*/
|
||||
@Override
|
||||
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
requireNotBlank(unprotectedValue);
|
||||
return getVaultCommunicationService().encrypt(getPath(), unprotectedValue.getBytes(PROPERTY_CHARSET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decrypted plaintext.
|
||||
*
|
||||
* @param protectedValue the cipher text read from the {@code nifi.properties} file
|
||||
* @param context The property context, unused in this provider
|
||||
* @return the raw value to be used by the application
|
||||
* @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
|
||||
*/
|
||||
@Override
|
||||
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
|
||||
requireNotBlank(protectedValue);
|
||||
return new String(getVaultCommunicationService().decrypt(getPath(), protectedValue), PROPERTY_CHARSET);
|
||||
}
|
||||
}
|
|
@ -1,55 +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.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class HashiCorpVaultKeyValueSensitivePropertyProviderTest {
|
||||
private static final String IDENTIFIER_KEY = "hashicorp/vault/kv/null";
|
||||
|
||||
@Mock
|
||||
private BootstrapProperties bootstrapProperties;
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKeyPropertiesNotFound() {
|
||||
when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
|
||||
final HashiCorpVaultKeyValueSensitivePropertyProvider provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(bootstrapProperties);
|
||||
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
|
||||
assertEquals(IDENTIFIER_KEY, identifierKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSupportedPropertiesNotFound() {
|
||||
when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
|
||||
final HashiCorpVaultKeyValueSensitivePropertyProvider provider = new HashiCorpVaultKeyValueSensitivePropertyProvider(bootstrapProperties);
|
||||
|
||||
assertFalse(provider.isSupported());
|
||||
}
|
||||
}
|
|
@ -1,55 +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.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class HashiCorpVaultTransitSensitivePropertyProviderTest {
|
||||
private static final String IDENTIFIER_KEY = "hashicorp/vault/transit/null";
|
||||
|
||||
@Mock
|
||||
private BootstrapProperties bootstrapProperties;
|
||||
|
||||
@Test
|
||||
public void testGetIdentifierKeyPropertiesNotFound() {
|
||||
when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
|
||||
final HashiCorpVaultTransitSensitivePropertyProvider provider = new HashiCorpVaultTransitSensitivePropertyProvider(bootstrapProperties);
|
||||
|
||||
final String identifierKey = provider.getIdentifierKey();
|
||||
|
||||
assertEquals(IDENTIFIER_KEY, identifierKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSupportedPropertiesNotFound() {
|
||||
when(bootstrapProperties.getProperty(eq(BootstrapProperties.BootstrapPropertyKey.HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF))).thenReturn(Optional.empty());
|
||||
final HashiCorpVaultTransitSensitivePropertyProvider provider = new HashiCorpVaultTransitSensitivePropertyProvider(bootstrapProperties);
|
||||
|
||||
assertFalse(provider.isSupported());
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-loader</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,303 +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.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 final T protectedProperties;
|
||||
|
||||
private final 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 -> 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 (isNotBlank(protection) && isNotBlank(getProperty(key))) {
|
||||
traditionalProtectedProperties.put(key, protection);
|
||||
}
|
||||
}
|
||||
|
||||
return traditionalProtectedProperties;
|
||||
}
|
||||
|
||||
@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) && isNotBlank(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()) {
|
||||
final String failed = failedKeys.size() == 1 ? failedKeys.iterator().next() : String.join(", ", failedKeys);
|
||||
throw new SensitivePropertyProtectionException(String.format("Failed unprotected properties: %s", failed));
|
||||
}
|
||||
|
||||
return protectedProperties.createApplicationProperties(rawProperties);
|
||||
} else {
|
||||
logger.debug("No protected properties");
|
||||
return protectedProperties.getApplicationProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSensitivePropertyProvider(final SensitivePropertyProvider sensitivePropertyProvider) {
|
||||
Objects.requireNonNull(sensitivePropertyProvider, "Provider required");
|
||||
|
||||
final String identifierKey = sensitivePropertyProvider.getIdentifierKey();
|
||||
if (localProviderCache.containsKey(identifierKey)) {
|
||||
throw new UnsupportedOperationException(String.format("Sensitive Property Provider Identifier [%s] override not supported", identifierKey));
|
||||
}
|
||||
|
||||
localProviderCache.put(identifierKey, sensitivePropertyProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s Properties [%d]", getClass().getSimpleName(), getPropertyKeys().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 protectionSchemePath = getProperty(getProtectionKey(key));
|
||||
|
||||
try {
|
||||
final SensitivePropertyProvider provider = findProvider(protectionSchemePath);
|
||||
return provider.unprotect(retrievedValue, ProtectedPropertyContext.defaultContext(key));
|
||||
} catch (final RuntimeException e) {
|
||||
throw new SensitivePropertyProtectionException(String.format("Property [%s] unprotect failed", key), e);
|
||||
}
|
||||
}
|
||||
return retrievedValue;
|
||||
}
|
||||
|
||||
private SensitivePropertyProvider findProvider(final String protectionSchemePath) {
|
||||
Objects.requireNonNull(protectionSchemePath, "Protection Scheme Path required");
|
||||
return localProviderCache.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> protectionSchemePath.startsWith(entry.getKey()))
|
||||
.findFirst()
|
||||
.map(Map.Entry::getValue)
|
||||
.orElseThrow(() -> new UnsupportedOperationException(String.format("Protection Scheme Path [%s] Provider not found", protectionSchemePath)));
|
||||
}
|
||||
|
||||
private boolean isNotBlank(final String string) {
|
||||
return string != null && string.length() > 0;
|
||||
}
|
||||
}
|
|
@ -1,65 +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.property.protection.loader;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Property Protection URL Class Loader uses the Current Thread Class Loader as the parent and loads libraries from a standard directory
|
||||
*/
|
||||
public class PropertyProtectionURLClassLoader extends URLClassLoader {
|
||||
private static final String STANDARD_DIRECTORY = "lib/properties";
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyProtectionURLClassLoader.class);
|
||||
|
||||
public PropertyProtectionURLClassLoader(final ClassLoader parentClassLoader) {
|
||||
super(getPropertyProtectionUrls(), parentClassLoader);
|
||||
}
|
||||
|
||||
private static URL[] getPropertyProtectionUrls() {
|
||||
final Path standardDirectory = Paths.get(STANDARD_DIRECTORY);
|
||||
if (Files.exists(standardDirectory)) {
|
||||
try (final Stream<Path> files = Files.list(standardDirectory)) {
|
||||
return files.map(Path::toUri)
|
||||
.map(uri -> {
|
||||
try {
|
||||
return uri.toURL();
|
||||
} catch (final MalformedURLException e) {
|
||||
throw new UncheckedIOException(String.format("Processing Property Protection libraries failed [%s]", standardDirectory), e);
|
||||
}
|
||||
})
|
||||
.toArray(URL[]::new);
|
||||
} catch (final IOException e) {
|
||||
throw new UncheckedIOException(String.format("Loading Property Protection libraries failed [%s]", standardDirectory), e);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Property Protection libraries directory [{}] not found", standardDirectory);
|
||||
return new URL[0];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +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.property.protection.loader;
|
||||
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
import org.apache.nifi.properties.SensitivePropertyProviderFactory;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Loader for Sensitive Property Provider Factory
|
||||
*/
|
||||
public class PropertyProviderFactoryLoader {
|
||||
/**
|
||||
* Get Sensitive Property Provider Factory using ServiceLoader to find available implementations from META-INF directories
|
||||
*
|
||||
* @return Sensitive Property Provider Factory
|
||||
* @throws SensitivePropertyProtectionException Thrown when no implementations found
|
||||
*/
|
||||
public SensitivePropertyProviderFactory getPropertyProviderFactory() {
|
||||
final ServiceLoader<SensitivePropertyProviderFactory> serviceLoader = ServiceLoader.load(SensitivePropertyProviderFactory.class);
|
||||
final Iterator<SensitivePropertyProviderFactory> factories = serviceLoader.iterator();
|
||||
|
||||
if (factories.hasNext()) {
|
||||
return factories.next();
|
||||
} else {
|
||||
throw new SensitivePropertyProtectionException(String.format("No implementations found [%s]", SensitivePropertyProviderFactory.class.getName()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +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.property.protection.loader;
|
||||
|
||||
import org.apache.nifi.properties.SensitivePropertyProtectionException;
|
||||
import org.apache.nifi.properties.scheme.ProtectionSchemeResolver;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Loader for Protection Scheme Resolver
|
||||
*/
|
||||
public class ProtectionSchemeResolverLoader {
|
||||
/**
|
||||
* Get Protection Scheme Resolver using ServiceLoader to find available implementations from META-INF directories
|
||||
*
|
||||
* @return Protection Scheme Resolver
|
||||
* @throws SensitivePropertyProtectionException Thrown when no implementations found
|
||||
*/
|
||||
public ProtectionSchemeResolver getProtectionSchemeResolver() {
|
||||
final ServiceLoader<ProtectionSchemeResolver> serviceLoader = ServiceLoader.load(ProtectionSchemeResolver.class);
|
||||
final Iterator<ProtectionSchemeResolver> factories = serviceLoader.iterator();
|
||||
|
||||
if (factories.hasNext()) {
|
||||
return factories.next();
|
||||
} else {
|
||||
throw new SensitivePropertyProtectionException(String.format("No implementations found [%s]", ProtectionSchemeResolver.class.getName()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?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>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>nifi-property-protection-shared</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-protection-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-property-utils</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,92 +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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Client-Based extension of Encoded Sensitive Property Provider
|
||||
*
|
||||
* @param <T> Client Type
|
||||
*/
|
||||
public abstract class ClientBasedEncodedSensitivePropertyProvider<T> extends EncodedSensitivePropertyProvider {
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final T client;
|
||||
|
||||
private final Properties properties;
|
||||
|
||||
public ClientBasedEncodedSensitivePropertyProvider(final T client,
|
||||
final Properties properties) {
|
||||
this.client = client;
|
||||
this.properties = properties;
|
||||
validate(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Provider supported based on client status
|
||||
*
|
||||
* @return Provider supported status
|
||||
*/
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return client != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources should be overridden when client requires shutdown
|
||||
*/
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
logger.debug("Cleanup Started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Client Properties
|
||||
*
|
||||
* @return Client Properties
|
||||
*/
|
||||
protected Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Client
|
||||
*
|
||||
* @return Client can be null when not configured
|
||||
*/
|
||||
protected T getClient() {
|
||||
if (client == null) {
|
||||
throw new IllegalStateException("Client not configured");
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Provider and Client configuration
|
||||
*
|
||||
* @param configuredClient Configured Client
|
||||
*/
|
||||
protected void validate(final T configuredClient) {
|
||||
if (configuredClient == null) {
|
||||
logger.debug("Client not configured");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.properties;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Encoded Sensitive Property Provider handles Base64 encoding and decoding of property values
|
||||
*/
|
||||
public abstract class EncodedSensitivePropertyProvider implements SensitivePropertyProvider {
|
||||
private static final Charset VALUE_CHARACTER_SET = StandardCharsets.UTF_8;
|
||||
|
||||
private static final Base64.Encoder ENCODER = Base64.getEncoder().withoutPadding();
|
||||
|
||||
private static final Base64.Decoder DECODER = Base64.getDecoder();
|
||||
|
||||
/**
|
||||
* Protect property value and return Base64-encoded representation of encrypted bytes
|
||||
*
|
||||
* @param unprotectedValue Unprotected property value to be encrypted
|
||||
* @param context Property Context
|
||||
* @return Base64-encoded representation of encrypted bytes
|
||||
*/
|
||||
@Override
|
||||
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) {
|
||||
Objects.requireNonNull(unprotectedValue, "Value required");
|
||||
Objects.requireNonNull(context, "Context required");
|
||||
try {
|
||||
final byte[] bytes = unprotectedValue.getBytes(VALUE_CHARACTER_SET);
|
||||
final byte[] encrypted = getEncrypted(bytes);
|
||||
return ENCODER.encodeToString(encrypted);
|
||||
} catch (final RuntimeException e) {
|
||||
final String message = String.format("Property [%s] Encryption Failed", context.getContextKey());
|
||||
throw new SensitivePropertyProtectionException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unprotect Base64-encoded representation of encrypted property value and return string
|
||||
*
|
||||
* @param protectedValue Base64-encoded representation of encrypted bytes
|
||||
* @param context Property Context
|
||||
* @return Decrypted property value string
|
||||
*/
|
||||
@Override
|
||||
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) {
|
||||
Objects.requireNonNull(protectedValue, "Value required");
|
||||
Objects.requireNonNull(context, "Context required");
|
||||
try {
|
||||
final byte[] decoded = DECODER.decode(protectedValue);
|
||||
final byte[] decrypted = getDecrypted(decoded);
|
||||
return new String(decrypted, VALUE_CHARACTER_SET);
|
||||
} catch (final RuntimeException e) {
|
||||
final String message = String.format("Property [%s] Decryption Failed", context.getContextKey());
|
||||
throw new SensitivePropertyProtectionException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encrypted byte array representation of bytes
|
||||
*
|
||||
* @param bytes Unprotected bytes
|
||||
* @return Encrypted bytes
|
||||
*/
|
||||
protected abstract byte[] getEncrypted(byte[] bytes);
|
||||
|
||||
/**
|
||||
* Get decrypted byte array representation of encrypted bytes
|
||||
*
|
||||
* @param bytes Encrypted bytes
|
||||
* @return Decrypted bytes
|
||||
*/
|
||||
protected abstract byte[] getDecrypted(byte[] bytes);
|
||||
}
|
|
@ -1,113 +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.configuration;
|
||||
|
||||
import org.apache.nifi.properties.BootstrapProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Shared Client Provider for reading Client Properties from file referenced in configured Bootstrap Property Key
|
||||
*
|
||||
* @param <T> Client Type
|
||||
*/
|
||||
public abstract class BootstrapPropertiesClientProvider<T> implements ClientProvider<T> {
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final BootstrapProperties.BootstrapPropertyKey bootstrapPropertyKey;
|
||||
|
||||
public BootstrapPropertiesClientProvider(final BootstrapProperties.BootstrapPropertyKey bootstrapPropertyKey) {
|
||||
this.bootstrapPropertyKey = Objects.requireNonNull(bootstrapPropertyKey, "Bootstrap Property Key required");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Client using Client Properties
|
||||
*
|
||||
* @param clientProperties Client Properties can be null
|
||||
* @return Configured Client or empty when Client Properties object is null
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> getClient(final Properties clientProperties) {
|
||||
return isMissingProperties(clientProperties) ? Optional.empty() : Optional.of(getConfiguredClient(clientProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Client Properties from file referenced in Bootstrap Properties
|
||||
*
|
||||
* @param bootstrapProperties Bootstrap Properties
|
||||
* @return Client Properties or empty when not configured
|
||||
*/
|
||||
@Override
|
||||
public Optional<Properties> getClientProperties(final BootstrapProperties bootstrapProperties) {
|
||||
Objects.requireNonNull(bootstrapProperties, "Bootstrap Properties required");
|
||||
final String clientBootstrapPropertiesPath = bootstrapProperties.getProperty(bootstrapPropertyKey).orElse(null);
|
||||
if (clientBootstrapPropertiesPath == null || clientBootstrapPropertiesPath.isEmpty()) {
|
||||
logger.debug("Client Properties [{}] not configured", bootstrapPropertyKey);
|
||||
return Optional.empty();
|
||||
} else {
|
||||
final Path propertiesPath = Paths.get(clientBootstrapPropertiesPath);
|
||||
if (Files.exists(propertiesPath)) {
|
||||
try {
|
||||
final Properties clientProperties = new Properties();
|
||||
try (final InputStream inputStream = Files.newInputStream(propertiesPath)) {
|
||||
clientProperties.load(inputStream);
|
||||
}
|
||||
return Optional.of(clientProperties);
|
||||
} catch (final IOException e) {
|
||||
final String message = String.format("Loading Client Properties Failed [%s]", propertiesPath);
|
||||
throw new UncheckedIOException(message, e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Client Properties [{}] Path [{}] not found", bootstrapPropertyKey, propertiesPath);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Configured Client using Client Properties
|
||||
*
|
||||
* @param clientProperties Client Properties
|
||||
* @return Configured Client
|
||||
*/
|
||||
protected abstract T getConfiguredClient(final Properties clientProperties);
|
||||
|
||||
/**
|
||||
* Get Property Names required for initializing client in order to perform initial validation
|
||||
*
|
||||
* @return Set of required client property names
|
||||
*/
|
||||
protected abstract Set<String> getRequiredPropertyNames();
|
||||
|
||||
private boolean isMissingProperties(final Properties clientProperties) {
|
||||
return clientProperties == null || getRequiredPropertyNames().stream().anyMatch(propertyName -> {
|
||||
final String property = clientProperties.getProperty(propertyName);
|
||||
return property == null || property.isEmpty();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,45 +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.configuration;
|
||||
|
||||
import org.apache.nifi.properties.BootstrapProperties;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Client Provider responsible for reading Client Properties and instantiating client services
|
||||
*
|
||||
* @param <T> Client Type
|
||||
*/
|
||||
public interface ClientProvider<T> {
|
||||
/**
|
||||
* Get Client Properties from Bootstrap Properties
|
||||
*
|
||||
* @param bootstrapProperties Bootstrap Properties
|
||||
* @return Client Properties or empty when not configured
|
||||
*/
|
||||
Optional<Properties> getClientProperties(BootstrapProperties bootstrapProperties);
|
||||
|
||||
/**
|
||||
* Get Client using Client Properties
|
||||
*
|
||||
* @param properties Client Properties
|
||||
* @return Client or empty when not configured
|
||||
*/
|
||||
Optional<T> getClient(Properties properties);
|
||||
}
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.apache.nifi.properties;
|
||||
|
||||
import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -58,17 +57,6 @@ public abstract class AbstractBootstrapPropertiesLoader {
|
|||
*/
|
||||
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
|
||||
|
@ -100,24 +88,6 @@ public abstract class AbstractBootstrapPropertiesLoader {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getProperty(BootstrapPropertyKey.SENSITIVE_KEY).orElseGet(() -> {
|
||||
logger.warn("No encryption key present in the bootstrap.conf file at {}", bootstrapProperties.getConfigFilePath());
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file for bootstrap.conf.
|
||||
*
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.nio.file.Paths;
|
|||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -31,29 +30,6 @@ import java.util.Set;
|
|||
public class BootstrapProperties extends StandardReadableProperties {
|
||||
private static final String PROPERTY_KEY_FORMAT = "%s.%s";
|
||||
|
||||
public enum BootstrapPropertyKey {
|
||||
SENSITIVE_KEY("bootstrap.sensitive.key"),
|
||||
HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF("bootstrap.protection.hashicorp.vault.conf"),
|
||||
AWS_SENSITIVE_PROPERTY_PROVIDER_CONF("bootstrap.protection.aws.conf"),
|
||||
AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF("bootstrap.protection.azure.keyvault.conf"),
|
||||
GCP_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF("bootstrap.protection.gcp.kms.conf"),
|
||||
CONTEXT_MAPPING_PREFIX("bootstrap.protection.context.mapping.");
|
||||
|
||||
private final String key;
|
||||
|
||||
BootstrapPropertyKey(final String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property key.
|
||||
* @return The property key
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
private final String propertyPrefix;
|
||||
private final Path configFilePath;
|
||||
|
||||
|
@ -65,7 +41,6 @@ public class BootstrapProperties extends StandardReadableProperties {
|
|||
this.configFilePath = configFilePath;
|
||||
|
||||
this.filterProperties(properties);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,15 +94,6 @@ public class BootstrapProperties extends StandardReadableProperties {
|
|||
return String.format(PROPERTY_KEY_FORMAT, propertyPrefix, subKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the optional property value with the given BootstrapPropertyKey.
|
||||
* @param key A BootstrapPropertyKey, representing properties in bootstrap.conf
|
||||
* @return The property value
|
||||
*/
|
||||
public Optional<String> getProperty(final BootstrapPropertyKey key) {
|
||||
return Optional.ofNullable(getProperty(getPropertyKey(key.key)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Bootstrap properties [%s] with prefix [%s]", configFilePath, propertyPrefix);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue