blob: 1361a270d6c4477d433eed3d7780cf63a446bcc5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.properties
import org.apache.nifi.properties.ApplicationPropertiesProtector
import org.apache.nifi.properties.MultipleSensitivePropertyProtectionException
import org.apache.nifi.properties.PropertyProtectionScheme
import org.apache.nifi.properties.ProtectedPropertyContext
import org.apache.nifi.properties.SensitivePropertyProtectionException
import org.apache.nifi.properties.SensitivePropertyProvider
import org.apache.nifi.properties.StandardSensitivePropertyProviderFactory
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.After
import org.junit.AfterClass
import org.junit.Assume
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import javax.crypto.Cipher
import java.security.Security
@RunWith(JUnit4.class)
class ProtectedNiFiRegistryPropertiesGroovyTest extends GroovyTestCase {
private static final Logger logger = LoggerFactory.getLogger(ProtectedNiFiRegistryPropertiesGroovyTest.class)
private static final String KEYSTORE_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_KEYSTORE_PASSWD
private static final String KEY_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_KEY_PASSWD
private static final String TRUSTSTORE_PASSWORD_KEY = NiFiRegistryProperties.SECURITY_TRUSTSTORE_PASSWD
private static final def DEFAULT_SENSITIVE_PROPERTIES = [
KEYSTORE_PASSWORD_KEY,
KEY_PASSWORD_KEY,
TRUSTSTORE_PASSWORD_KEY
]
private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
private static final String KEY_HEX_256 = KEY_HEX_128 * 2
private static final String KEY_HEX = Cipher.getMaxAllowedKeyLength("AES") < 256 ? KEY_HEX_128 : KEY_HEX_256
@BeforeClass
static void setUpOnce() throws Exception {
Security.addProvider(new BouncyCastleProvider())
logger.metaClass.methodMissing = { String name, args ->
logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
}
}
@Before
void setUp() throws Exception {
}
@After
void tearDown() throws Exception {
}
@AfterClass
static void tearDownOnce() {
}
private static ProtectedNiFiRegistryProperties loadFromResourceFile(String propertiesFilePath) {
return loadFromResourceFile(propertiesFilePath, KEY_HEX)
}
private static ProtectedNiFiRegistryProperties loadFromResourceFile(String propertiesFilePath, String keyHex) {
File file = fileForResource(propertiesFilePath)
if (file == null || !file.exists() || !file.canRead()) {
String path = (file == null ? "missing file" : file.getAbsolutePath())
logger.error("Cannot read from '{}' -- file is missing or not readable", path)
throw new IllegalArgumentException("NiFi Registry properties file missing or unreadable")
}
FileReader reader = new FileReader(file)
try {
final Properties props = new Properties()
props.load(reader)
NiFiRegistryProperties properties = new NiFiRegistryProperties(props)
logger.info("Loaded {} properties from {}", properties.size(), file.getAbsolutePath())
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(properties)
// If it has protected keys, inject the SPP
if (protectedNiFiProperties.hasProtectedKeys()) {
protectedNiFiProperties.addSensitivePropertyProvider(StandardSensitivePropertyProviderFactory.withKey(keyHex)
.getProvider(PropertyProtectionScheme.AES_GCM))
}
return protectedNiFiProperties
} catch (final Exception ex) {
logger.error("Cannot load properties file due to " + ex.getLocalizedMessage())
throw new RuntimeException("Cannot load properties file due to " +
ex.getLocalizedMessage(), ex)
}
}
private static File fileForResource(String resourcePath) {
String filePath
try {
URL resourceURL = ProtectedNiFiRegistryPropertiesGroovyTest.class.getResource(resourcePath)
if (!resourceURL) {
throw new RuntimeException("File ${resourcePath} not found in class resources, cannot load.")
}
filePath = resourceURL.toURI().getPath()
} catch (URISyntaxException ex) {
throw new RuntimeException("Cannot load resource file due to " + ex.getLocalizedMessage(), ex)
}
File file = new File(filePath)
return file
}
@Test
void testShouldDetectIfPropertyIsSensitive() throws Exception {
// Arrange
final String INSENSITIVE_PROPERTY_KEY = "nifi.registry.web.http.port"
final String SENSITIVE_PROPERTY_KEY = "nifi.registry.security.keystorePasswd"
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
// Act
boolean bannerIsSensitive = properties.isPropertySensitive(INSENSITIVE_PROPERTY_KEY)
logger.info("${INSENSITIVE_PROPERTY_KEY} is ${bannerIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
boolean passwordIsSensitive = properties.isPropertySensitive(SENSITIVE_PROPERTY_KEY)
logger.info("${SENSITIVE_PROPERTY_KEY} is ${passwordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
// Assert
assert !bannerIsSensitive
assert passwordIsSensitive
}
@Test
void testShouldGetDefaultSensitiveProperties() throws Exception {
// Arrange
logger.info("${DEFAULT_SENSITIVE_PROPERTIES.size()} default sensitive properties: ${DEFAULT_SENSITIVE_PROPERTIES.join(", ")}")
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
// Act
List defaultSensitiveProperties = properties.getSensitivePropertyKeys()
logger.info("${defaultSensitiveProperties.size()} default sensitive properties: ${defaultSensitiveProperties.join(", ")}")
// Assert
assert defaultSensitiveProperties.size() == DEFAULT_SENSITIVE_PROPERTIES.size()
assert defaultSensitiveProperties.containsAll(DEFAULT_SENSITIVE_PROPERTIES)
}
@Test
void testShouldGetAdditionalSensitiveProperties() throws Exception {
// Arrange
def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.registry.web.http.port", "nifi.registry.web.http.host"]
logger.info("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_additional_sensitive_keys.properties")
// Act
List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
// Assert
assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
}
@Test
void testGetAdditionalSensitivePropertiesShouldNotIncludeSelf() throws Exception {
// Arrange
def completeSensitiveProperties = DEFAULT_SENSITIVE_PROPERTIES + ["nifi.registry.web.http.port", "nifi.registry.web.http.host"]
logger.info("${completeSensitiveProperties.size()} total sensitive properties: ${completeSensitiveProperties.join(", ")}")
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_additional_sensitive_keys.properties")
// Act
List retrievedSensitiveProperties = properties.getSensitivePropertyKeys()
logger.info("${retrievedSensitiveProperties.size()} retrieved sensitive properties: ${retrievedSensitiveProperties.join(", ")}")
// Assert
assert retrievedSensitiveProperties.size() == completeSensitiveProperties.size()
assert retrievedSensitiveProperties.containsAll(completeSensitiveProperties)
}
/**
* In the default (no protection enabled) scenario, a call to retrieve a sensitive property should return the raw value transparently.
* @throws Exception
*/
@Test
void testShouldGetUnprotectedValueOfSensitiveProperty() throws Exception {
// Arrange
final String expectedKeystorePassword = "thisIsABadKeystorePassword"
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected.properties")
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
// Act
String retrievedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
// Assert
assert retrievedKeystorePassword == expectedKeystorePassword
assert isSensitive
assert !isProtected
}
/**
* In the default (no protection enabled) scenario, a call to retrieve a sensitive property (which is empty) should return the raw value transparently.
* @throws Exception
*/
@Test
void testShouldGetEmptyUnprotectedValueOfSensitiveProperty() throws Exception {
// Arrange
final String expectedTruststorePassword = ""
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected.properties")
boolean isSensitive = properties.isPropertySensitive(TRUSTSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(TRUSTSTORE_PASSWORD_KEY)
logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
// Act
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedTruststorePassword = unprotectedProperties.getProperty(TRUSTSTORE_PASSWORD_KEY)
logger.info("${TRUSTSTORE_PASSWORD_KEY}: ${retrievedTruststorePassword}")
// Assert
assert retrievedTruststorePassword == expectedTruststorePassword
assert isSensitive
assert !isProtected
}
/**
* The new model no longer needs to maintain the protected state -- it is used as a wrapper/decorator during load to unprotect the sensitive properties and then return an instance of raw properties.
*
* @throws Exception
*/
@Test
void testShouldGetUnprotectedValueOfSensitivePropertyWhenProtected() throws Exception {
// Arrange
final String expectedKeystorePassword = "thisIsABadPassword"
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
// Act
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
// Assert
assert retrievedKeystorePassword == expectedKeystorePassword
assert isSensitive
assert isProtected
}
/**
* In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is protected with an unknown protection scheme.
* @throws Exception
*/
@Test
void testGetValueOfSensitivePropertyShouldHandleUnknownProtectionScheme() throws Exception {
// Arrange
// Raw properties
Properties rawProperties = new Properties()
rawProperties.load(new FileReader(fileForResource("/conf/nifi-registry.with_sensitive_props_protected_unknown.properties")))
final String expectedKeystorePassword = rawProperties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("Raw value for ${KEYSTORE_PASSWORD_KEY}: ${expectedKeystorePassword}")
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_unknown.properties")
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
// While the value is "protected", the scheme is not registered
logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
// Act
def msg = shouldFail(IllegalStateException) {
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
}
// Assert
assert msg == "No provider available for nifi.registry.security.keyPasswd"
assert isSensitive
assert isProtected
}
/**
* In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is unable to be unprotected due to a malformed value.
* @throws Exception
*/
@Test
void testGetValueOfSensitivePropertyShouldHandleSingleMalformedValue() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_single_malformed.properties", KEY_HEX_128)
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
// Act
def msg = shouldFail(SensitivePropertyProtectionException) {
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
}
logger.info(msg)
// Assert
assert msg =~ "Failed to unprotect key ${KEYSTORE_PASSWORD_KEY}"
assert isSensitive
assert isProtected
}
/**
* In the protection enabled scenario, a call to retrieve a sensitive property should handle if the property is unable to be unprotected due to a malformed value.
* @throws Exception
*/
@Test
void testGetValueOfSensitivePropertyShouldHandleMultipleMalformedValues() throws Exception {
// Arrange
// Raw properties
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_multiple_malformed.properties", KEY_HEX_128)
// Iterate over the protected keys and track the ones that fail to decrypt
SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX_128)
.getProvider(PropertyProtectionScheme.AES_GCM)
Set<String> malformedKeys = properties.getProtectedPropertyKeys()
.findAll { String key, String scheme -> scheme == spp.identifierKey }
.keySet()
.findAll { String key ->
try {
spp.unprotect(properties.getProperty(key), ProtectedPropertyContext.defaultContext(key))
return false
} catch (SensitivePropertyProtectionException e) {
logger.expected("Caught a malformed value for ${key}")
return true
}
}
logger.expected("Malformed keys: ${malformedKeys.join(", ")}")
// Act
def e = groovy.test.GroovyAssert.shouldFail(SensitivePropertyProtectionException) {
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
}
logger.expected(e.getMessage())
// Assert
assert e instanceof MultipleSensitivePropertyProtectionException
assert e.getMessage() =~ "Failed to unprotect keys"
assert e.getFailedKeys() == malformedKeys
}
/**
* In the protection enabled scenario, a call to retrieve a sensitive property should handle if the internal cache of providers is empty.
* @throws Exception
*/
@Test
void testGetValueOfSensitivePropertyShouldHandleInvalidatedInternalCache() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
final String expectedKeystorePassword = properties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("Read raw value from properties: ${expectedKeystorePassword}")
// Overwrite the internal cache
properties.getSensitivePropertyProviders().clear()
boolean isSensitive = properties.isPropertySensitive(KEYSTORE_PASSWORD_KEY)
boolean isProtected = properties.isPropertyProtected(KEYSTORE_PASSWORD_KEY)
logger.info("The property is ${isSensitive ? "sensitive" : "not sensitive"} and ${isProtected ? "protected" : "not protected"}")
// Act
def msg = shouldFail(IllegalStateException) {
NiFiRegistryProperties unprotectedProperties = properties.getUnprotectedProperties()
String retrievedKeystorePassword = unprotectedProperties.getProperty(KEYSTORE_PASSWORD_KEY)
logger.info("${KEYSTORE_PASSWORD_KEY}: ${retrievedKeystorePassword}")
}
// Assert
assert msg == "No provider available for nifi.registry.security.keyPasswd"
assert isSensitive
assert isProtected
}
@Test
void testShouldDetectIfPropertyIsProtected() throws Exception {
// Arrange
final String unprotectedPropertyKey = TRUSTSTORE_PASSWORD_KEY
final String protectedPropertyKey = KEYSTORE_PASSWORD_KEY
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
// Act
boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(unprotectedPropertyKey)
boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(unprotectedPropertyKey)
logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
boolean protectedPasswordIsSensitive = properties.isPropertySensitive(protectedPropertyKey)
boolean protectedPasswordIsProtected = properties.isPropertyProtected(protectedPropertyKey)
logger.info("${protectedPropertyKey} is ${protectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
logger.info("${protectedPropertyKey} is ${protectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
// Assert
assert unprotectedPasswordIsSensitive
assert !unprotectedPasswordIsProtected
assert protectedPasswordIsSensitive
assert protectedPasswordIsProtected
}
@Test
void testShouldDetectIfPropertyWithEmptyProtectionSchemeIsProtected() throws Exception {
// Arrange
final String unprotectedPropertyKey = KEY_PASSWORD_KEY
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_unprotected_extra_line.properties")
// Act
boolean unprotectedPasswordIsSensitive = properties.isPropertySensitive(unprotectedPropertyKey)
boolean unprotectedPasswordIsProtected = properties.isPropertyProtected(unprotectedPropertyKey)
logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsSensitive ? "SENSITIVE" : "NOT SENSITIVE"}")
logger.info("${unprotectedPropertyKey} is ${unprotectedPasswordIsProtected ? "PROTECTED" : "NOT PROTECTED"}")
// Assert
assert unprotectedPasswordIsSensitive
assert !unprotectedPasswordIsProtected
}
@Test
void testShouldGetPercentageOfSensitivePropertiesProtected_0() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
// Act
double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
// Assert
assert percentProtected == 0.0
}
private static double getPercentOfSensitivePropertiesProtected(final ProtectedNiFiRegistryProperties properties) {
return (int) Math.round(properties.getProtectedPropertyKeys().size() / ((double) properties.getPopulatedSensitivePropertyKeys().size()) * 100);
}
@Test
void testShouldGetPercentageOfSensitivePropertiesProtected_75() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
// Act
double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
// Assert
assert percentProtected == 67.0
}
@Test
void testShouldGetPercentageOfSensitivePropertiesProtected_100() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_fully_protected_aes_128.properties", KEY_HEX_128)
logger.info("Sensitive property keys: ${properties.getSensitivePropertyKeys()}")
logger.info("Protected property keys: ${properties.getProtectedPropertyKeys().keySet()}")
// Act
double percentProtected = getPercentOfSensitivePropertiesProtected(properties)
logger.info("${percentProtected}% (${properties.getProtectedPropertyKeys().size()} of ${properties.getPopulatedSensitivePropertyKeys().size()}) protected")
// Assert
assert percentProtected == 100.0
}
@Test
void testInstanceWithNoProtectedPropertiesShouldNotLoadSPP() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = loadFromResourceFile("/conf/nifi-registry.properties")
assert properties.getSensitivePropertyProviders().isEmpty()
logger.info("Has protected properties: ${properties.hasProtectedKeys()}")
assert !properties.hasProtectedKeys()
// Act
Map localCache = properties.getSensitivePropertyProviders()
logger.info("Internal cache ${localCache} has ${localCache.size()} providers loaded")
// Assert
assert localCache.isEmpty()
}
@Test
void testShouldAddSensitivePropertyProvider() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
assert properties.getSensitivePropertyProviders().isEmpty()
SensitivePropertyProvider mockProvider =
[unprotect : { String input ->
logger.mock("Mock call to #unprotect(${input})")
input.reverse()
},
getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
// Act
properties.addSensitivePropertyProvider(mockProvider)
// Assert
assert properties.getSensitivePropertyProviders().size() == 1
}
@Test
void testShouldNotAddNullSensitivePropertyProvider() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
assert properties.getSensitivePropertyProviders().isEmpty()
// Act
def msg = shouldFail(NullPointerException) {
properties.addSensitivePropertyProvider(null)
}
logger.info(msg)
// Assert
assert properties.getSensitivePropertyProviders().size() == 0
assert msg == "Cannot add null SensitivePropertyProvider"
}
@Test
void testShouldNotAllowOverwriteOfProvider() throws Exception {
// Arrange
ProtectedNiFiRegistryProperties properties = new ProtectedNiFiRegistryProperties()
assert properties.getSensitivePropertyProviders().isEmpty()
SensitivePropertyProvider mockProvider =
[unprotect : { String input ->
logger.mock("Mock call to 1#unprotect(${input})")
input.reverse()
},
getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
properties.addSensitivePropertyProvider(mockProvider)
assert properties.getSensitivePropertyProviders().size() == 1
SensitivePropertyProvider mockProvider2 =
[unprotect : { String input ->
logger.mock("Mock call to 2#unprotect(${input})")
input.reverse()
},
getIdentifierKey: { -> "mockProvider" }] as SensitivePropertyProvider
// Act
def msg = shouldFail(UnsupportedOperationException) {
properties.addSensitivePropertyProvider(mockProvider2)
}
logger.info(msg)
// Assert
assert msg == "Cannot overwrite existing sensitive property provider registered for mockProvider"
assert properties.getSensitivePropertyProviders().size() == 1
}
@Test
void testGetUnprotectedPropertiesShouldReturnInternalInstanceWhenNoneProtected() {
// Arrange
ProtectedNiFiRegistryProperties protectedNiFiProperties = loadFromResourceFile("/conf/nifi-registry.properties")
logger.info("Loaded ${protectedNiFiProperties.size()} properties from conf/nifi.properties")
int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
logger.info("Hash code of internal instance: ${hashCode}")
// Act
NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
// Assert
assert unprotectedNiFiProperties.size() == protectedNiFiProperties.size()
assert unprotectedNiFiProperties.getPropertyKeys().every {
!unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() == hashCode
}
@Test
void testGetUnprotectedPropertiesShouldDecryptProtectedProperties() {
// Arrange
ProtectedNiFiRegistryProperties protectedNiFiProperties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_128.properties", KEY_HEX_128)
int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
int protectionSchemeCount = protectedNiFiProperties
.getPropertyKeysIncludingProtectionSchemes()
.findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
.size()
int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
String protectedProps = protectedNiFiProperties
.getProtectedPropertyKeys()
.collectEntries {
[(it.key): protectedNiFiProperties.getProperty(it.key)]
}.entrySet()
.join("\n")
logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
logger.info("Protected properties: \n${protectedProps}")
logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
logger.info("Hash code of internal instance: ${hashCode}")
// Act
NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
// Assert
assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
assert unprotectedNiFiProperties.getPropertyKeys().every {
!unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() != hashCode
}
@Test
void testGetUnprotectedPropertiesShouldDecryptProtectedPropertiesWith256Bit() {
// Arrange
Assume.assumeTrue("JCE unlimited strength crypto policy must be installed for this test", Cipher.getMaxAllowedKeyLength("AES") > 128)
ProtectedNiFiRegistryProperties protectedNiFiProperties =
loadFromResourceFile("/conf/nifi-registry.with_sensitive_props_protected_aes_256.properties", KEY_HEX_256)
int protectedPropertyCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
int protectionSchemeCount = protectedNiFiProperties
.getPropertyKeysIncludingProtectionSchemes()
.findAll { it.endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX) }
.size()
int expectedUnprotectedPropertyCount = protectedNiFiProperties.size()
String protectedProps = protectedNiFiProperties
.getProtectedPropertyKeys()
.collectEntries {
[(it.key): protectedNiFiProperties.getProperty(it.key)]
}.entrySet()
.join("\n")
logger.info("Detected ${protectedPropertyCount} protected properties and ${protectionSchemeCount} protection scheme properties")
logger.info("Protected properties: \n${protectedProps}")
logger.info("Expected unprotected property count: ${expectedUnprotectedPropertyCount}")
int hashCode = protectedNiFiProperties.getApplicationProperties().hashCode()
logger.info("Hash code of internal instance: ${hashCode}")
// Act
NiFiRegistryProperties unprotectedNiFiProperties = protectedNiFiProperties.getUnprotectedProperties()
logger.info("Unprotected ${unprotectedNiFiProperties.size()} properties")
// Assert
assert unprotectedNiFiProperties.size() == expectedUnprotectedPropertyCount
assert unprotectedNiFiProperties.getPropertyKeys().every {
!unprotectedNiFiProperties.getProperty(it).endsWith(ApplicationPropertiesProtector.PROTECTED_KEY_SUFFIX)
}
logger.info("Hash code from returned unprotected instance: ${unprotectedNiFiProperties.hashCode()}")
assert unprotectedNiFiProperties.hashCode() != hashCode
}
@Test
void testShouldCalculateSize() {
// Arrange
Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
// Act
int protectedSize = protectedNiFiProperties.size()
logger.info("Protected properties (${protectedNiFiProperties.size()}): " +
"${protectedNiFiProperties.getPropertyKeys().join(", ")}")
// Assert
assert protectedSize == rawProperties.size() - 1
}
@Test
void testGetPropertyKeysShouldMatchSize() {
// Arrange
Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
// Act
def filteredKeys = protectedNiFiProperties.getPropertyKeys()
logger.info("Protected properties (${protectedNiFiProperties.size()}): ${filteredKeys.join(", ")}")
// Assert
assert protectedNiFiProperties.size() == rawProperties.size() - 1
assert filteredKeys == rawProperties.getPropertyKeys() - "key.protected"
}
@Test
void testShouldGetPropertyKeysIncludingProtectionSchemes() {
// Arrange
Properties props = [key: "protectedValue", "key.protected": "scheme", "key2": "value2"] as Properties
NiFiRegistryProperties rawProperties = new NiFiRegistryProperties(props)
ProtectedNiFiRegistryProperties protectedNiFiProperties = new ProtectedNiFiRegistryProperties(rawProperties)
logger.info("Raw properties (${rawProperties.size()}): ${rawProperties.getPropertyKeys().join(", ")}")
// Act
def allKeys = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes()
logger.info("Protected properties with schemes (${allKeys.size()}): ${allKeys.join(", ")}")
// Assert
assert allKeys.size() == rawProperties.size()
assert allKeys == rawProperties.getPropertyKeys()
}
}