NIFI-5116 Implemented logic to translate nifi.properties file to CLI properties format.

Added unit tests.

This closes #2660.

Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
Andy LoPresto 2018-04-25 18:27:05 -04:00 committed by Bryan Bende
parent 3719a6268c
commit 4e4aa54c69
No known key found for this signature in database
GPG Key ID: A0DDA9ED50711C39
2 changed files with 521 additions and 23 deletions

View File

@ -99,6 +99,7 @@ class ConfigEncryptionTool {
private boolean handlingFlowXml = false
private boolean ignorePropertiesFiles = false
private boolean queryingCurrentHashParams = false
private boolean translatingCli = false
private static final String HELP_ARG = "help"
private static final String VERBOSE_ARG = "verbose"
@ -124,6 +125,7 @@ class ConfigEncryptionTool {
private static final String NEW_FLOW_ALGORITHM_ARG = "newFlowAlgorithm"
private static final String NEW_FLOW_PROVIDER_ARG = "newFlowProvider"
private static final String CURRENT_HASH_PARAMS_ARG = "currentHashParams"
private static final String TRANSLATE_CLI_ARG = "translateCli"
// Static holder to avoid re-generating the options object multiple times in an invocation
private static Options staticOptions
@ -198,7 +200,17 @@ class ConfigEncryptionTool {
private static final String DEFAULT_PROVIDER = BouncyCastleProvider.PROVIDER_NAME
private static final String DEFAULT_FLOW_ALGORITHM = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"
static private final int AMBARI_COMPATIBLE_SCRYPT_HASH_LENGTH = 256
private static final int AMBARI_COMPATIBLE_SCRYPT_HASH_LENGTH = 256
private static final Map<String, String> PROPERTY_KEY_MAP = [
"nifi.security.keystore": "keystore",
"nifi.security.keystoreType": "keystoreType",
"nifi.security.keystorePasswd": "keystorePasswd",
"nifi.security.keyPasswd": "keyPasswd",
"nifi.security.truststore": "truststore",
"nifi.security.truststoreType": "truststoreType",
"nifi.security.truststorePasswd": "truststorePasswd",
]
private static String buildHeader(String description = DEFAULT_DESCRIPTION) {
"${SEP}${description}${SEP * 2}"
@ -247,6 +259,7 @@ class ConfigEncryptionTool {
options.addOption(Option.builder("A").longOpt(NEW_FLOW_ALGORITHM_ARG).hasArg(true).argName("algorithm").desc("The algorithm to use to encrypt the sensitive processor properties in flow.xml.gz").build())
options.addOption(Option.builder("P").longOpt(NEW_FLOW_PROVIDER_ARG).hasArg(true).argName("algorithm").desc("The security provider to use to encrypt the sensitive processor properties in flow.xml.gz").build())
options.addOption(Option.builder().longOpt(CURRENT_HASH_PARAMS_ARG).hasArg(false).desc("Returns the current salt and cost params used to store the hashed key/password").build())
options.addOption(Option.builder("c").longOpt(TRANSLATE_CLI_ARG).hasArg(false).desc("Translates the nifi.properties file to a format suitable for the NiFi CLI tool").build())
options
}
@ -302,8 +315,44 @@ class ConfigEncryptionTool {
}
}
// If this flag is present, ensure no other options are present and then fail/return
if (commandLine.hasOption(TRANSLATE_CLI_ARG)) {
translatingCli = true
if (commandLineHasActionFlags(commandLine, [TRANSLATE_CLI_ARG, BOOTSTRAP_CONF_ARG, NIFI_PROPERTIES_ARG])) {
printUsageAndThrow("When '-c'/'--${TRANSLATE_CLI_ARG}' is specified, only '-h', '-v', and '-n'/'-b' with the relevant files are allowed", ExitCode.INVALID_ARGS)
}
}
bootstrapConfPath = commandLine.getOptionValue(BOOTSTRAP_CONF_ARG)
// This needs to occur even if the nifi.properties won't be encrypted
if (commandLine.hasOption(NIFI_PROPERTIES_ARG)) {
boolean ignoreFlagPresent = commandLine.hasOption(DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG)
if (isVerbose && !ignoreFlagPresent) {
logger.info("Handling encryption of nifi.properties")
}
niFiPropertiesPath = commandLine.getOptionValue(NIFI_PROPERTIES_ARG)
outputNiFiPropertiesPath = commandLine.getOptionValue(OUTPUT_NIFI_PROPERTIES_ARG, niFiPropertiesPath)
handlingNiFiProperties = !ignoreFlagPresent
if (niFiPropertiesPath == outputNiFiPropertiesPath) {
// TODO: Add confirmation pause and provide -y flag to offer no-interaction mode?
logger.warn("The source nifi.properties and destination nifi.properties are identical [${outputNiFiPropertiesPath}] so the original will be overwritten")
}
}
// If translating nifi.properties to CLI format, none of the remaining parsing is necessary
if (translatingCli) {
// If the nifi.properties isn't present, throw an exception
// If the nifi.properties is encrypted and the bootstrap.conf isn't present, we will throw an error later when the encryption is detected
if (!niFiPropertiesPath) {
printUsageAndThrow("When '-c'/'--translateCli' is specified, '-n'/'--niFiProperties' is required (and '-b'/'--bootstrapConf' is required if the properties are encrypted)", ExitCode.INVALID_ARGS)
}
return commandLine
}
// If this flag is provided, the nifi.properties is necessary to read/write the flow encryption key, but the encryption process will not actually be applied to nifi.properties / login-identity-providers.xml
if (commandLine.hasOption(DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG)) {
handlingNiFiProperties = false
@ -339,22 +388,6 @@ class ConfigEncryptionTool {
}
}
// This needs to occur even if the nifi.properties won't be encrypted
if (commandLine.hasOption(NIFI_PROPERTIES_ARG)) {
boolean ignoreFlagPresent = commandLine.hasOption(DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG)
if (isVerbose && !ignoreFlagPresent) {
logger.info("Handling encryption of nifi.properties")
}
niFiPropertiesPath = commandLine.getOptionValue(NIFI_PROPERTIES_ARG)
outputNiFiPropertiesPath = commandLine.getOptionValue(OUTPUT_NIFI_PROPERTIES_ARG, niFiPropertiesPath)
handlingNiFiProperties = !ignoreFlagPresent
if (niFiPropertiesPath == outputNiFiPropertiesPath) {
// TODO: Add confirmation pause and provide -y flag to offer no-interaction mode?
logger.warn("The source nifi.properties and destination nifi.properties are identical [${outputNiFiPropertiesPath}] so the original will be overwritten")
}
}
if (commandLine.hasOption(FLOW_XML_ARG)) {
if (isVerbose) {
logger.info("Handling encryption of flow.xml.gz")
@ -479,6 +512,13 @@ class ConfigEncryptionTool {
return commandLine
}
/**
* Returns true if the {@code commandLine} object has flags other than the {@code help} or {@code verbose} flags or any of the acceptable args provided in an optional parameter. This is used to detect incompatible arguments for specific modes.
*
* @param commandLine the commandLine object
* @param acceptableOptionStrings an optional list of acceptable options that can be present without returning true
* @return true if incompatible flags are present
*/
boolean commandLineHasActionFlags(CommandLine commandLine, List<String> acceptableOptionStrings = []) {
// Resolve the list of Option objects corresponding to "help" and "verbose"
final List<Option> ALWAYS_ACCEPTABLE_OPTIONS = resolveOptions([HELP_ARG, VERBOSE_ARG])
@ -1650,6 +1690,26 @@ class ConfigEncryptionTool {
System.exit(ExitCode.SUCCESS.ordinal())
}
// Handle the translate CLI case
if (tool.translatingCli) {
if (tool.bootstrapConfPath) {
// Check to see if bootstrap.conf has a master key
tool.keyHex = NiFiPropertiesLoader.extractKeyFromBootstrapFile(tool.bootstrapConfPath)
}
if (!tool.keyHex) {
logger.info("No master key detected in ${tool.bootstrapConfPath} -- if ${tool.niFiPropertiesPath} is encrypted, the translation will fail")
}
// Load the existing properties (decrypting if necessary)
tool.niFiProperties = tool.loadNiFiProperties(tool.keyHex)
String cliOutput = tool.translateNiFiPropertiesToCLI()
System.out.println(cliOutput)
System.exit(ExitCode.SUCCESS.ordinal())
}
boolean existingNiFiPropertiesAreEncrypted = tool.niFiPropertiesAreEncrypted()
if (!tool.ignorePropertiesFiles || (tool.handlingFlowXml && existingNiFiPropertiesAreEncrypted)) {
// If we are handling the flow.xml.gz and nifi.properties is already encrypted, try getting the key from bootstrap.conf rather than the console
@ -1820,4 +1880,27 @@ class ConfigEncryptionTool {
System.exit(ExitCode.SUCCESS.ordinal())
}
String translateNiFiPropertiesToCLI() {
// Assemble the baseUrl
String baseUrl = determineBaseUrl(niFiProperties)
// Copy the relevant properties to a Map using the "CLI" keys
List<String> cliOutput = ["baseUrl=${baseUrl}"]
PROPERTY_KEY_MAP.each { String nfpKey, String cliKey ->
cliOutput << "${cliKey}=${niFiProperties.getProperty(nfpKey)}"
}
cliOutput << "proxiedEntity="
cliOutput.join("\n")
}
static String determineBaseUrl(NiFiProperties niFiProperties) {
String protocol = niFiProperties.isHTTPSConfigured() ? "https" : "http"
String host = niFiProperties.isHTTPSConfigured() ? niFiProperties.getProperty(NiFiProperties.WEB_HTTPS_HOST) : niFiProperties.getProperty(NiFiProperties.WEB_HTTP_HOST)
String port = niFiProperties.getConfiguredHttpOrHttpsPort()
"${protocol}://${host}:${port}"
}
}

View File

@ -2221,7 +2221,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
/**
* Ideally all of the combination tests would be a single test with iterative argument lists, but due to the System.exit(), it can only be captured once per test.
*/
@Ignore // TODO re-enable once this is passing on all platforms
@Ignore
// TODO re-enable once this is passing on all platforms
@Test
void testShouldMigrateFromHashedPasswordToPassword() {
// Arrange
@ -2236,7 +2237,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Assertions in common method above
}
@Ignore // TODO re-enable once this is passing on all platforms
@Ignore
// TODO re-enable once this is passing on all platforms
@Test
void testShouldMigrateFromHashedPasswordToKey() {
// Arrange
@ -2251,7 +2253,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Assertions in common method above
}
@Ignore // TODO re-enable once this is passing on all platforms
@Ignore
// TODO re-enable once this is passing on all platforms
@Test
void testShouldMigrateFromHashedKeyToPassword() {
// Arrange
@ -2266,7 +2269,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Assertions in common method above
}
@Ignore // TODO re-enable once this is passing on all platforms
@Ignore
// TODO re-enable once this is passing on all platforms
@Test
void testShouldMigrateFromHashedKeyToKey() {
// Arrange
@ -2281,7 +2285,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
// Assertions in common method above
}
@Ignore // TODO re-enable once this is passing on all platforms
@Ignore
// TODO re-enable once this is passing on all platforms
@Test
void testShouldFailToMigrateFromIncorrectHashedPasswordToPassword() {
// Arrange
@ -5205,7 +5210,8 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
}
@Ignore // TODO re-enable once this is passing on all platforms
@Ignore
// TODO re-enable once this is passing on all platforms
@Test
void testShouldReturnCurrentHashParams() {
// Arrange
@ -5349,6 +5355,415 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
}
}
@Test
void testShouldTranslateCliWithPlaintextInput() {
// Arrange
exit.expectSystemExitWithStatus(0)
final Map<String, String> EXPECTED_CLI_OUTPUT = [
"baseUrl" : "https://nifi.nifi.apache.org:8443",
"keystore" : "/path/to/keystore.jks",
"keystoreType" : "JKS",
"keystorePasswd" : "thisIsABadKeystorePassword",
"keyPasswd" : "thisIsABadKeyPassword",
"truststore" : "",
"truststoreType" : "",
"truststorePasswd": "",
"proxiedEntity" : "",
]
File tmpDir = new File("target/tmp/")
tmpDir.mkdirs()
setFilePermissions(tmpDir, [PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE, PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE])
File emptyKeyFile = new File("src/test/resources/bootstrap_with_empty_master_key.conf")
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
bootstrapFile.delete()
Files.copy(emptyKeyFile.toPath(), bootstrapFile.toPath())
final List<String> originalBootstrapLines = bootstrapFile.readLines()
String originalKeyLine = originalBootstrapLines.find {
it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
}
logger.info("Original key line from bootstrap.conf: ${originalKeyLine}")
assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
File inputPropertiesFile = new File("src/test/resources/nifi_with_sensitive_properties_unprotected.properties")
NiFiProperties inputProperties = new NiFiPropertiesLoader().load(inputPropertiesFile)
logger.info("Loaded ${inputProperties.size()} properties from input file")
ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
logger.info("Original sensitive values: ${originalSensitiveValues}")
String[] args = ["-n", inputPropertiesFile.path, "-b", bootstrapFile.path, "-c"]
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
final String standardOutput = systemOutRule.getLog()
List<String> lines = standardOutput.split("\n")
// The SystemRule log also includes STDERR, so truncate after 9 lines
def stdoutLines = lines[0..<EXPECTED_CLI_OUTPUT.size()]
logger.info("STDOUT:\n\t${stdoutLines.join("\n\t")}")
// Split the output into lines and create a map of the keys and values
def parsedCli = stdoutLines.collectEntries { String line ->
def components = line.split("=", 2)
components.size() > 1 ? [(components[0]): components[1]] : [(components[0]): ""]
}
assert parsedCli.size() == EXPECTED_CLI_OUTPUT.size()
assert EXPECTED_CLI_OUTPUT.every { String k, String v -> parsedCli.get(k) == v }
// Clean up
bootstrapFile.deleteOnExit()
tmpDir.deleteOnExit()
}
})
// Act
ConfigEncryptionTool.main(args)
logger.info("Invoked #main with ${args.join(" ")}")
// Assert
// Assertions defined above
}
@Test
void testShouldTranslateCliWithPlaintextInputWithoutBootstrapConf() {
// Arrange
exit.expectSystemExitWithStatus(0)
final Map<String, String> EXPECTED_CLI_OUTPUT = [
"baseUrl" : "https://nifi.nifi.apache.org:8443",
"keystore" : "/path/to/keystore.jks",
"keystoreType" : "JKS",
"keystorePasswd" : "thisIsABadKeystorePassword",
"keyPasswd" : "thisIsABadKeyPassword",
"truststore" : "",
"truststoreType" : "",
"truststorePasswd": "",
"proxiedEntity" : "",
]
File tmpDir = new File("target/tmp/")
tmpDir.mkdirs()
setFilePermissions(tmpDir, [PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE, PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE])
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
bootstrapFile.delete()
File inputPropertiesFile = new File("src/test/resources/nifi_with_sensitive_properties_unprotected.properties")
NiFiProperties inputProperties = new NiFiPropertiesLoader().load(inputPropertiesFile)
logger.info("Loaded ${inputProperties.size()} properties from input file")
ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
logger.info("Original sensitive values: ${originalSensitiveValues}")
String[] args = ["-n", inputPropertiesFile.path, "-c"]
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
final String standardOutput = systemOutRule.getLog()
List<String> lines = standardOutput.split("\n")
// The SystemRule log also includes STDERR, so truncate after 9 lines
def stdoutLines = lines[0..<EXPECTED_CLI_OUTPUT.size()]
logger.info("STDOUT:\n\t${stdoutLines.join("\n\t")}")
// Split the output into lines and create a map of the keys and values
def parsedCli = stdoutLines.collectEntries { String line ->
def components = line.split("=", 2)
components.size() > 1 ? [(components[0]): components[1]] : [(components[0]): ""]
}
assert parsedCli.size() == EXPECTED_CLI_OUTPUT.size()
assert EXPECTED_CLI_OUTPUT.every { String k, String v -> parsedCli.get(k) == v }
// Clean up
bootstrapFile.deleteOnExit()
tmpDir.deleteOnExit()
}
})
// Act
ConfigEncryptionTool.main(args)
logger.info("Invoked #main with ${args.join(" ")}")
// Assert
// Assertions defined above
}
@Test
void testShouldTranslateCliWithEncryptedInput() {
// Arrange
exit.expectSystemExitWithStatus(0)
final Map<String, String> EXPECTED_CLI_OUTPUT = [
"baseUrl" : "https://nifi.nifi.apache.org:8443",
"keystore" : "/path/to/keystore.jks",
"keystoreType" : "JKS",
"keystorePasswd" : "thisIsABadKeystorePassword",
"keyPasswd" : "thisIsABadKeyPassword",
"truststore" : "",
"truststoreType" : "",
"truststorePasswd": "",
"proxiedEntity" : "",
]
File tmpDir = new File("target/tmp/")
tmpDir.mkdirs()
setFilePermissions(tmpDir, [PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE, PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE])
File masterKeyFile = new File("src/test/resources/bootstrap_with_master_key.conf")
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
bootstrapFile.delete()
Files.copy(masterKeyFile.toPath(), bootstrapFile.toPath())
File inputPropertiesFile = new File("src/test/resources/nifi_with_sensitive_properties_protected_aes.properties")
NiFiProperties inputProperties = NiFiPropertiesLoader.withKey(KEY_HEX).load(inputPropertiesFile)
logger.info("Loaded ${inputProperties.size()} properties from input file")
ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
logger.info("Original sensitive values: ${originalSensitiveValues}")
String[] args = ["-n", inputPropertiesFile.path, "-b", bootstrapFile.path, "-c"]
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
final String standardOutput = systemOutRule.getLog()
List<String> lines = standardOutput.split("\n")
// The SystemRule log also includes STDERR, so truncate after 9 lines
def stdoutLines = lines[0..<EXPECTED_CLI_OUTPUT.size()]
logger.info("STDOUT:\n\t${stdoutLines.join("\n\t")}")
// Split the output into lines and create a map of the keys and values
def parsedCli = stdoutLines.collectEntries { String line ->
def components = line.split("=", 2)
components.size() > 1 ? [(components[0]): components[1]] : [(components[0]): ""]
}
assert parsedCli.size() == EXPECTED_CLI_OUTPUT.size()
assert EXPECTED_CLI_OUTPUT.every { String k, String v -> parsedCli.get(k) == v }
// Clean up
bootstrapFile.deleteOnExit()
tmpDir.deleteOnExit()
}
})
// Act
ConfigEncryptionTool.main(args)
logger.info("Invoked #main with ${args.join(" ")}")
// Assert
// Assertions defined above
}
@Test
void testTranslateCliWithEncryptedInputShouldNotIntersperseVerboseOutput() {
// Arrange
exit.expectSystemExitWithStatus(0)
final Map<String, String> EXPECTED_CLI_OUTPUT = [
"baseUrl" : "https://nifi.nifi.apache.org:8443",
"keystore" : "/path/to/keystore.jks",
"keystoreType" : "JKS",
"keystorePasswd" : "thisIsABadKeystorePassword",
"keyPasswd" : "thisIsABadKeyPassword",
"truststore" : "",
"truststoreType" : "",
"truststorePasswd": "",
"proxiedEntity" : "",
]
File tmpDir = new File("target/tmp/")
tmpDir.mkdirs()
setFilePermissions(tmpDir, [PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE, PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE])
File masterKeyFile = new File("src/test/resources/bootstrap_with_master_key.conf")
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
bootstrapFile.delete()
Files.copy(masterKeyFile.toPath(), bootstrapFile.toPath())
File inputPropertiesFile = new File("src/test/resources/nifi_with_sensitive_properties_protected_aes.properties")
NiFiProperties inputProperties = NiFiPropertiesLoader.withKey(KEY_HEX).load(inputPropertiesFile)
logger.info("Loaded ${inputProperties.size()} properties from input file")
ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
logger.info("Original sensitive values: ${originalSensitiveValues}")
String[] args = ["-n", inputPropertiesFile.path, "-b", bootstrapFile.path, "-c", "-v"]
exit.checkAssertionAfterwards(new Assertion() {
void checkAssertion() {
final String standardOutput = systemOutRule.getLog()
List<String> lines = standardOutput.split("\n")
// The SystemRule log also includes STDERR, so truncate after 9 lines
def stdoutLines = lines[0..<EXPECTED_CLI_OUTPUT.size()]
logger.info("STDOUT:\n\t${stdoutLines.join("\n\t")}")
// Split the output into lines and create a map of the keys and values
def parsedCli = stdoutLines.collectEntries { String line ->
def components = line.split("=", 2)
components.size() > 1 ? [(components[0]): components[1]] : [(components[0]): ""]
}
assert parsedCli.size() == EXPECTED_CLI_OUTPUT.size()
assert EXPECTED_CLI_OUTPUT.every { String k, String v -> parsedCli.get(k) == v }
// Clean up
bootstrapFile.deleteOnExit()
tmpDir.deleteOnExit()
}
})
// Act
ConfigEncryptionTool.main(args)
logger.info("Invoked #main with ${args.join(" ")}")
// Assert
// Assertions defined above
}
@Test
void testShouldTranslateCli() {
// Arrange
final Map<String, String> EXPECTED_CLI_OUTPUT = [
"baseUrl" : "https://nifi.nifi.apache.org:8443",
"keystore" : "/path/to/keystore.jks",
"keystoreType" : "JKS",
"keystorePasswd" : "thisIsABadKeystorePassword",
"keyPasswd" : "thisIsABadKeyPassword",
"truststore" : "",
"truststoreType" : "",
"truststorePasswd": "",
"proxiedEntity" : "",
]
String originalNiFiPropertiesPath = "src/test/resources/nifi_with_sensitive_properties_unprotected.properties"
NiFiProperties plainProperties = NiFiPropertiesLoader.withKey(KEY_HEX).load(originalNiFiPropertiesPath)
logger.info("Loaded NiFiProperties from ${originalNiFiPropertiesPath}")
ConfigEncryptionTool tool = new ConfigEncryptionTool()
tool.translatingCli = true
tool.niFiProperties = plainProperties
// Act
String cliOutput = tool.translateNiFiPropertiesToCLI()
logger.info("Translated to CLI format: \n${cliOutput}")
// Assert
def parsedCli = cliOutput.split("\n").collectEntries { String line ->
def components = line.split("=", 2)
[(components[0]): components[1]]
}
assert parsedCli.size() == EXPECTED_CLI_OUTPUT.size()
assert EXPECTED_CLI_OUTPUT.every { String k, String v -> parsedCli.get(k) == v }
}
@Test
void testShouldFailOnCliTranslateIfConflictingFlagsPresent() {
// Arrange
ConfigEncryptionTool tool = new ConfigEncryptionTool()
def validOpts = [
"-n nifi.properties",
"--niFiProperties nifi.properties",
"--verbose -n nifi.properties -b bootstrap.conf",
]
// These values won't cause an error in #commandLineHasActionFlags() but will throw an error later in #parse()
// Don't test with -h/--help because it will cause a System.exit()
def incompleteOpts = [
"",
"-v",
"--verbose",
// "-h",
// "--help",
"-b bootstrap.conf",
"--bootstrapConf bootstrap.conf",
]
def invalidOpts = [
"--migrate",
"-o output",
"-x \$s0\$"
]
// Act
validOpts.each { String valid ->
tool = new ConfigEncryptionTool()
def args = (valid + " -c").split(" ")
logger.info("Testing with ${args}")
tool.parse(args as String[])
}
incompleteOpts.each { String incomplete ->
tool = new ConfigEncryptionTool()
def args = (incomplete + " -c").split(" ")
logger.info("Testing with ${args}")
def msg = shouldFail(CommandLineParseException) {
tool.parse(args as String[])
}
// Assert
assert msg == "When '-c'/'--translateCli' is specified, '-n'/'--niFiProperties' is required (and '-b'/'--bootstrapConf' is required if the properties are encrypted)"
assert systemOutRule.getLog().contains("usage: org.apache.nifi.properties.ConfigEncryptionTool [")
}
invalidOpts.each { String invalid ->
tool = new ConfigEncryptionTool()
def args = (invalid + " -c").split(" ")
logger.info("Testing with ${args}")
def msg = shouldFail(CommandLineParseException) {
tool.parse(args as String[])
}
// Assert
assert msg == "When '-c'/'--translateCli' is specified, only '-h', '-v', and '-n'/'-b' with the relevant files are allowed"
assert systemOutRule.getLog().contains("usage: org.apache.nifi.properties.ConfigEncryptionTool [")
}
}
@Test
void testTranslateCliShouldFailIfMissingNecessaryFlags() {
// Arrange
ConfigEncryptionTool tool = new ConfigEncryptionTool()
// Bootstrap alone is insufficient; nifi.properties alone is ok if it is in plaintext
def invalidOpts = [
"-b bootstrap.conf",
]
// Act
invalidOpts.each { String invalid ->
def args = (invalid + " -c").split(" ")
logger.info("Testing with ${args}")
def msg = shouldFail(CommandLineParseException) {
tool.parse(args as String[])
}
// Assert
assert msg == "When '-c'/'--translateCli' is specified, '-n'/'--niFiProperties' is required (and '-b'/'--bootstrapConf' is required if the properties are encrypted)"
assert systemOutRule.getLog().contains("usage: org.apache.nifi.properties.ConfigEncryptionTool [")
}
}
// TODO: Test with 128/256-bit available
}