blob: 4809923f0537530e937cdca1ae5c72189a20c54c [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.properties
import org.apache.commons.lang3.SystemUtils
import org.apache.nifi.util.NiFiBootstrapUtils
import org.apache.nifi.util.NiFiProperties
import org.apache.nifi.util.file.FileUtils
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.After
import org.junit.AfterClass
import org.junit.Assume
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import javax.crypto.Cipher
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermission
import java.security.Security
@RunWith(JUnit4.class)
class NiFiPropertiesLoaderGroovyTest extends GroovyTestCase {
private static final Logger logger = LoggerFactory.getLogger(NiFiPropertiesLoaderGroovyTest.class)
final def DEFAULT_SENSITIVE_PROPERTIES = [
"nifi.sensitive.props.key",
"nifi.security.keystorePasswd",
"nifi.security.keyPasswd",
"nifi.security.truststorePasswd"
]
final def COMMON_ADDITIONAL_SENSITIVE_PROPERTIES = [
"nifi.sensitive.props.algorithm",
"nifi.kerberos.service.principal",
"nifi.kerberos.krb5.file",
"nifi.kerberos.keytab.location"
]
private static final String KEY_HEX_128 = "0123456789ABCDEFFEDCBA9876543210"
private static final String KEY_HEX_256 = KEY_HEX_128 * 2
public static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
private static final String PASSWORD_KEY_HEX_128 = "2C576A9585DB862F5ECBEE5B4FFFCCA1"
private static String originalPropertiesPath = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)
private
final Set<PosixFilePermission> ownerReadWrite = [PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_READ]
private static boolean isUnlimitedStrengthCryptoAvailable() {
Cipher.getMaxAllowedKeyLength("AES") > 128
}
@BeforeClass
static void setUpOnce() throws Exception {
Assume.assumeTrue("Test only runs on *nix", !SystemUtils.IS_OS_WINDOWS)
Security.addProvider(new BouncyCastleProvider())
logger.metaClass.methodMissing = { String name, args ->
logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
}
}
@Before
void setUp() throws Exception {
}
@After
void tearDown() throws Exception {
// Clear the sensitive property providers between runs
// if (ProtectedNiFiProperties.@localProviderCache) {
// ProtectedNiFiProperties.@localProviderCache = [:]
// }
}
@AfterClass
static void tearDownOnce() {
if (originalPropertiesPath) {
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, originalPropertiesPath)
}
}
@Test
void testConstructorShouldCreateNewInstance() throws Exception {
// Arrange
// Act
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Assert
assert !niFiPropertiesLoader.@instance
assert !niFiPropertiesLoader.@keyHex
}
@Test
void testShouldCreateInstanceWithKey() throws Exception {
// Arrange
// Act
NiFiPropertiesLoader niFiPropertiesLoader = NiFiPropertiesLoader.withKey(KEY_HEX)
// Assert
assert !niFiPropertiesLoader.@instance
assert niFiPropertiesLoader.@keyHex == KEY_HEX
}
@Test
void testShouldLoadUnprotectedPropertiesFromFile() throws Exception {
// Arrange
File unprotectedFile = new File("src/test/resources/conf/nifi.properties")
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Act
NiFiProperties niFiProperties = niFiPropertiesLoader.load(unprotectedFile)
// Assert
assert niFiProperties.size() > 0
// Ensure it is not a ProtectedNiFiProperties
assert !(niFiProperties instanceof ProtectedNiFiProperties)
}
@Test
void testShouldLoadUnprotectedPropertiesFromFileWithoutBootstrap() throws Exception {
// Arrange
File unprotectedFile = new File("src/test/resources/conf/nifi.properties")
// Set the system property to the test file
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, unprotectedFile.absolutePath)
logger.info("Set ${NiFiProperties.PROPERTIES_FILE_PATH} to ${unprotectedFile.absolutePath}")
// Act
NiFiProperties niFiProperties = NiFiPropertiesLoader.loadDefaultWithKeyFromBootstrap()
// Assert
assert niFiProperties.size() > 0
// Ensure it is not a ProtectedNiFiProperties
assert !(niFiProperties instanceof ProtectedNiFiProperties)
}
@Test
void testShouldNotLoadProtectedPropertiesFromFileWithoutBootstrap() throws Exception {
// Arrange
File protectedFile = new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes.properties")
// Set the system property to the test file
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, protectedFile.absolutePath)
logger.info("Set ${NiFiProperties.PROPERTIES_FILE_PATH} to ${protectedFile.absolutePath}")
// Act
def msg = shouldFail(SensitivePropertyProtectionException) {
NiFiProperties niFiProperties = NiFiPropertiesLoader.loadDefaultWithKeyFromBootstrap()
}
logger.expected(msg)
// Assert
assert msg =~ "Could not read root key from bootstrap.conf"
}
@Test
void testShouldLoadUnprotectedPropertiesFromPathWithGeneratedSensitivePropertiesKey() throws Exception {
// Arrange
final File propertiesFile = File.createTempFile("nifi.without.key", ".properties")
propertiesFile.deleteOnExit()
final OutputStream outputStream = new FileOutputStream(propertiesFile)
final InputStream inputStream = getClass().getResourceAsStream("/conf/nifi.without.key.properties")
FileUtils.copy(inputStream, outputStream)
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, propertiesFile.absolutePath);
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Act
NiFiProperties niFiProperties = niFiPropertiesLoader.get()
// Assert
final String sensitivePropertiesKey = niFiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY)
assert sensitivePropertiesKey.length() == 32
}
@Test
void testShouldNotLoadUnprotectedPropertiesFromPathWithBlankKeyForClusterNode() throws Exception {
// Arrange
final File propertiesFile = File.createTempFile("nifi.without.key", ".properties")
propertiesFile.deleteOnExit()
final OutputStream outputStream = new FileOutputStream(propertiesFile)
final InputStream inputStream = getClass().getResourceAsStream("/conf/nifi.cluster.without.key.properties")
FileUtils.copy(inputStream, outputStream)
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, propertiesFile.absolutePath);
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Act
shouldFail(SensitivePropertyProtectionException) {
niFiPropertiesLoader.get()
}
}
@Test
void testShouldNotLoadUnprotectedPropertiesFromNullFile() throws Exception {
// Arrange
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Act
def msg = shouldFail(IllegalArgumentException) {
NiFiProperties niFiProperties = niFiPropertiesLoader.load(null as File)
}
logger.expected(msg)
// Assert
assert msg == "NiFi properties file missing or unreadable"
}
@Test
void testShouldNotLoadUnprotectedPropertiesFromMissingFile() throws Exception {
// Arrange
File missingFile = new File("src/test/resources/conf/nifi_missing.properties")
assert !missingFile.exists()
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Act
def msg = shouldFail(IllegalArgumentException) {
NiFiProperties niFiProperties = niFiPropertiesLoader.load(missingFile)
}
logger.expected(msg)
// Assert
assert msg == "NiFi properties file missing or unreadable"
}
@Test
void testShouldNotLoadUnprotectedPropertiesFromUnreadableFile() throws Exception {
// Arrange
File unreadableFile = new File("src/test/resources/conf/nifi_no_permissions.properties")
Files.setPosixFilePermissions(unreadableFile.toPath(), [] as Set)
assert !unreadableFile.canRead()
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Act
def msg = shouldFail(IllegalArgumentException) {
NiFiProperties niFiProperties = niFiPropertiesLoader.load(unreadableFile)
}
logger.expected(msg)
// Assert
assert msg == "NiFi properties file missing or unreadable"
// Clean up to allow for indexing, etc.
Files.setPosixFilePermissions(unreadableFile.toPath(), ownerReadWrite)
}
@Test
void testShouldLoadUnprotectedPropertiesFromPath() throws Exception {
// Arrange
File unprotectedFile = new File("src/test/resources/conf/nifi.properties")
NiFiPropertiesLoader niFiPropertiesLoader = new NiFiPropertiesLoader()
// Act
NiFiProperties niFiProperties = niFiPropertiesLoader.load(unprotectedFile.path)
// Assert
assert niFiProperties.size() > 0
// Ensure it is not a ProtectedNiFiProperties
assert !(niFiProperties instanceof ProtectedNiFiProperties)
}
@Test
void testShouldLoadUnprotectedPropertiesFromProtectedFile() throws Exception {
// Arrange
File protectedFile = new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes.properties")
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, protectedFile.path)
NiFiPropertiesLoader niFiPropertiesLoader = NiFiPropertiesLoader.withKey(KEY_HEX)
final def EXPECTED_PLAIN_VALUES = [
(NiFiProperties.SENSITIVE_PROPS_KEY): "thisIsABadSensitiveKeyPassword",
(NiFiProperties.SECURITY_KEYSTORE_PASSWD): "thisIsABadKeystorePassword",
(NiFiProperties.SECURITY_KEY_PASSWD): "thisIsABadKeyPassword",
]
// This method is covered in tests above, so safe to use here to retrieve protected properties
ProtectedNiFiProperties protectedNiFiProperties = niFiPropertiesLoader.readProtectedPropertiesFromDisk(protectedFile)
int totalKeysCount = protectedNiFiProperties.getPropertyKeysIncludingProtectionSchemes().size()
int protectedKeysCount = protectedNiFiProperties.getProtectedPropertyKeys().size()
logger.info("Read ${totalKeysCount} total properties (${protectedKeysCount} protected) from ${protectedFile.canonicalPath}")
// Act
NiFiProperties niFiProperties = niFiPropertiesLoader.load(protectedFile)
// Assert
assert niFiProperties.size() == totalKeysCount - protectedKeysCount
// Ensure that any key marked as protected above is different in this instance
protectedNiFiProperties.getProtectedPropertyKeys().keySet().each { String key ->
String plainValue = niFiProperties.getProperty(key)
String protectedValue = protectedNiFiProperties.getProperty(key)
logger.info("Checking that [${protectedValue}] -> [${plainValue}] == [${EXPECTED_PLAIN_VALUES[key]}]")
assert plainValue == EXPECTED_PLAIN_VALUES[key]
assert plainValue != protectedValue
assert plainValue.length() <= protectedValue.length()
}
// Ensure it is not a ProtectedNiFiProperties
assert !(niFiProperties instanceof ProtectedNiFiProperties)
}
@Test
void testShouldExtractKeyFromBootstrapFile() throws Exception {
// Arrange
def defaultNiFiPropertiesFilePath = "src/test/resources/bootstrap_tests/conf/nifi.properties"
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath)
// Act
String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile()
// Assert
assert key == KEY_HEX
}
@Test
void testShouldNotExtractKeyFromBootstrapFileWithoutKeyLine() throws Exception {
// Arrange
def defaultNiFiPropertiesFilePath = "src/test/resources/bootstrap_tests/missing_key_line/nifi.properties"
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath)
// Act
String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile()
// Assert
assert key == ""
}
@Test
void testShouldNotExtractKeyFromBootstrapFileWithoutKey() throws Exception {
// Arrange
def defaultNiFiPropertiesFilePath = "src/test/resources/bootstrap_tests/missing_key_line/nifi.properties"
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath)
// Act
String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile()
// Assert
assert key == ""
}
@Test
void testShouldNotExtractKeyFromMissingBootstrapFile() throws Exception {
// Arrange
def defaultNiFiPropertiesFilePath = "src/test/resources/bootstrap_tests/missing_bootstrap/nifi.properties"
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath)
// Act
def msg = shouldFail(IOException) {
String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile()
}
logger.expected(msg)
// Assert
assert msg =~ "Cannot read from .*bootstrap.conf"
}
@Test
void testShouldNotExtractKeyFromUnreadableBootstrapFile() throws Exception {
// Arrange
File unreadableFile = new File("src/test/resources/bootstrap_tests/unreadable_bootstrap/bootstrap.conf")
Set<PosixFilePermission> originalPermissions = Files.getPosixFilePermissions(unreadableFile.toPath())
Files.setPosixFilePermissions(unreadableFile.toPath(), [] as Set)
assert !unreadableFile.canRead()
def defaultNiFiPropertiesFilePath = "src/test/resources/bootstrap_tests/unreadable_bootstrap/nifi.properties"
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath)
// Act
def msg = shouldFail(IOException) {
String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile()
}
logger.expected(msg)
// Assert
assert msg =~ "Cannot read from .*bootstrap.conf"
// Clean up to allow for indexing, etc.
Files.setPosixFilePermissions(unreadableFile.toPath(), originalPermissions)
}
@Ignore("Unreadable conf directory breaks build")
@Test
void testShouldNotExtractKeyFromUnreadableConfDir() throws Exception {
// Arrange
File unreadableDir = new File("src/test/resources/bootstrap_tests/unreadable_conf")
Set<PosixFilePermission> originalPermissions = Files.getPosixFilePermissions(unreadableDir.toPath())
Files.setPosixFilePermissions(unreadableDir.toPath(), [] as Set)
assert !unreadableDir.canRead()
def defaultNiFiPropertiesFilePath = "src/test/resources/bootstrap_tests/unreadable_conf/nifi.properties"
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, defaultNiFiPropertiesFilePath)
// Act
def msg = shouldFail(IOException) {
String key = NiFiBootstrapUtils.extractKeyFromBootstrapFile()
}
logger.expected(msg)
// Assert
assert msg == "Cannot read from bootstrap.conf"
// Clean up to allow for indexing, etc.
Files.setPosixFilePermissions(unreadableDir.toPath(), originalPermissions)
}
@Test
void testShouldLoadUnprotectedPropertiesFromProtectedDefaultFileAndUseBootstrapKey() throws Exception {
// Arrange
File protectedFile = new File("src/test/resources/bootstrap_tests/conf/nifi_with_sensitive_properties_protected_aes.properties")
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, protectedFile.path)
NiFiPropertiesLoader niFiPropertiesLoader = NiFiPropertiesLoader.withKey(KEY_HEX)
NiFiProperties normalReadProperties = niFiPropertiesLoader.load(protectedFile)
logger.info("Read ${normalReadProperties.size()} total properties from ${protectedFile.canonicalPath}")
// Act
NiFiProperties niFiProperties = NiFiPropertiesLoader.loadDefaultWithKeyFromBootstrap()
// Assert
assert niFiProperties.size() == normalReadProperties.size()
def readPropertiesAndValues = niFiProperties.getPropertyKeys().collectEntries {
[(it): niFiProperties.getProperty(it)]
}
def expectedPropertiesAndValues = normalReadProperties.getPropertyKeys().collectEntries {
[(it): normalReadProperties.getProperty(it)]
}
assert readPropertiesAndValues == expectedPropertiesAndValues
}
@Test
void testShouldUpdateKeyInFactory() throws Exception {
// Arrange
File originalKeyFile = new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes_128.properties")
File passwordKeyFile = new File("src/test/resources/conf/nifi_with_sensitive_properties_protected_aes_128_password.properties")
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, originalKeyFile.path)
NiFiPropertiesLoader niFiPropertiesLoader = NiFiPropertiesLoader.withKey(KEY_HEX_128)
NiFiProperties niFiProperties = niFiPropertiesLoader.load(originalKeyFile)
logger.info("Read ${niFiProperties.size()} total properties from ${originalKeyFile.canonicalPath}")
// Act
NiFiPropertiesLoader passwordNiFiPropertiesLoader = NiFiPropertiesLoader.withKey(PASSWORD_KEY_HEX_128)
NiFiProperties passwordProperties = passwordNiFiPropertiesLoader.load(passwordKeyFile)
logger.info("Read ${passwordProperties.size()} total properties from ${passwordKeyFile.canonicalPath}")
// Assert
assert niFiProperties.size() == passwordProperties.size()
def readPropertiesAndValues = niFiProperties.getPropertyKeys().collectEntries {
[(it): niFiProperties.getProperty(it)]
}
def readPasswordPropertiesAndValues = passwordProperties.getPropertyKeys().collectEntries {
[(it): passwordProperties.getProperty(it)]
}
assert readPropertiesAndValues == readPasswordPropertiesAndValues
}
}