mirror of https://github.com/apache/nifi.git
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:
parent
3719a6268c
commit
4e4aa54c69
|
@ -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}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue