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:
exceptionfactory 2024-06-18 14:40:05 -05:00 committed by Joseph Witt
parent b7a48f766a
commit 49a0401058
No known key found for this signature in database
GPG Key ID: 9093BF854F811A1A
181 changed files with 77 additions and 11967 deletions

View File

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

View File

@ -120,7 +120,7 @@ public class BootstrapFileProvider {
}
public BootstrapProperties getProtectedBootstrapProperties() {
return BootstrapPropertiesLoader.loadProtectedProperties(bootstrapConfigFile).getApplicationProperties();
return BootstrapPropertiesLoader.loadProtectedProperties(bootstrapConfigFile);
}
public Properties getStatusProperties() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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% %* ""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,54 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,89 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.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);
}

View File

@ -1,52 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]*$");
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,52 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.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());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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