http://git-wip-us.apache.org/repos/asf/nifi/blob/482f3719/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy index a5e4103..6b056d7 100644 --- a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy +++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy @@ -80,6 +80,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { final String FLOW_PASSWORD = isUnlimitedStrengthCryptoAvailable() ? FLOW_PASSWORD_256 : FLOW_PASSWORD_128 private static final int LIP_PASSWORD_LINE_COUNT = 3 + private static final int AUTHORIZERS_PASSWORD_LINE_COUNT = 3 private final String PASSWORD_PROP_REGEX = "<property[^>]* name=\".* Password\"" private static final String DEFAULT_ALGORITHM = "PBEWITHMD5AND256BITAES-CBC-OPENSSL" @@ -88,7 +89,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { private final String DEFAULT_LEGACY_SENSITIVE_PROPS_KEY = "nififtw!" @BeforeClass - public static void setUpOnce() throws Exception { + static void setUpOnce() throws Exception { Security.addProvider(new BouncyCastleProvider()) logger.metaClass.methodMissing = { String name, args -> @@ -99,17 +100,17 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @AfterClass - public static void tearDownOnce() throws Exception { + static void tearDownOnce() throws Exception { File tmpDir = new File("target/tmp/") tmpDir.delete() } @Before - public void setUp() throws Exception { + void setUp() throws Exception { } @After - public void tearDown() throws Exception { + void tearDown() throws Exception { TestAppender.reset() } @@ -320,6 +321,60 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test + void testShouldParseAuthorizersArgument() { + // Arrange + def flags = ["-a", "--authorizers"] + String authorizersPath = "src/test/resources/authorizers.xml" + ConfigEncryptionTool tool = new ConfigEncryptionTool() + + // Act + flags.each { String arg -> + tool.parse([arg, authorizersPath] as String[]) + logger.info("Parsed authorizers.xml location: ${tool.authorizersPath}") + + // Assert + assert tool.authorizersPath == authorizersPath + assert tool.handlingAuthorizers + } + } + + @Test + void testShouldParseOutputAuthorizersArgument() { + // Arrange + def flags = ["-u", "--outputAuthorizers"] + String authorizersPath = "src/test/resources/authorizers.xml" + ConfigEncryptionTool tool = new ConfigEncryptionTool() + + // Act + flags.each { String arg -> + final outputAuthorizersPath = authorizersPath.reverse() + tool.parse([arg, outputAuthorizersPath, "-a", authorizersPath] as String[]) + logger.info("Parsed output authorizers.xml location: ${tool.outputAuthorizersPath}") + + // Assert + assert tool.outputAuthorizersPath == outputAuthorizersPath + } + } + + @Test + void testParseShouldWarnIfAuthorizersWillBeOverwritten() { + // Arrange + String authorizersPath = "conf/authorizers.xml" + ConfigEncryptionTool tool = new ConfigEncryptionTool() + + // Act + tool.parse("-a ${authorizersPath} -u ${authorizersPath}".split(" ") as String[]) + logger.info("Parsed authorizers.xml location: ${tool.authorizersPath}") + logger.info("Parsed output authorizers.xml location: ${tool.outputAuthorizersPath}") + + // Assert + assert !TestAppender.events.isEmpty() + assert TestAppender.events.any { + it.message =~ "The source authorizers.xml and destination authorizers.xml are identical \\[.*\\] so the original will be overwritten" + } + } + + @Test void testShouldParseKeyArgument() { // Arrange def flags = ["-k", "--key"] @@ -351,7 +406,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test - void testParseShouldFailIfNiFiPropertiesAndLoginIdentityProviderBothMissing() { + void testParseShouldFailIfPropertiesAndProvidersMissing() { // Arrange ConfigEncryptionTool tool = new ConfigEncryptionTool() @@ -360,16 +415,22 @@ class ConfigEncryptionToolTest extends GroovyTestCase { "-v -s password", "-n", "-l", - "-o output-nifi.properties -i output-login-identity-providers.xml", + "-a", + "-o output-nifi.properties -i output-login-identity-providers.xml -u output-authorizers.xml", "-f flow.xml.gz", ] - final String NO_NFP_OR_LIP = "One or both of '-n'/'--${ConfigEncryptionTool.NIFI_PROPERTIES_ARG}' or '-l'/'--${ConfigEncryptionTool.LOGIN_IDENTITY_PROVIDERS_ARG}' must be provided unless '-x'/--'${ConfigEncryptionTool.DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG}' is specified" + final String NO_NFP_OR_LIP = "One or more of [" + + "'-n'/'--${ConfigEncryptionTool.NIFI_PROPERTIES_ARG}', " + + "'-l'/'--${ConfigEncryptionTool.LOGIN_IDENTITY_PROVIDERS_ARG}', " + + "'-a'/'--${ConfigEncryptionTool.AUTHORIZERS_ARG}'" + + "] must be provided unless '-x'/--'${ConfigEncryptionTool.DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG}' is specified" final String MISSING_NFP_ARGUMENT = "Error parsing command line. (Missing argument for option: n)" final String MISSING_LIP_ARGUMENT = "Error parsing command line. (Missing argument for option: l)" + final String MISSING_A_ARGUMENT = "Error parsing command line. (Missing argument for option: a)" final String MIGRATE_NEEDS_NFP = "In order to migrate a flow.xml.gz, a nifi.properties file must also be specified via '-n'/'--niFiProperties'." - def ACCEPTABLE_ERROR_MSGS = [NO_NFP_OR_LIP, MISSING_NFP_ARGUMENT, MISSING_LIP_ARGUMENT, MIGRATE_NEEDS_NFP] + def ACCEPTABLE_ERROR_MSGS = [NO_NFP_OR_LIP, MISSING_NFP_ARGUMENT, MISSING_LIP_ARGUMENT, MISSING_A_ARGUMENT, MIGRATE_NEEDS_NFP] // Act invalidArgs.each { String badArgs -> @@ -1457,7 +1518,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { String[] args = ["-n", inputPropertiesFile.path, "-b", bootstrapFile.path, "-o", outputPropertiesFile.path, "-k", KEY_HEX] exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final List<String> updatedPropertiesLines = outputPropertiesFile.readLines() logger.info("Updated nifi.properties:") logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) @@ -1489,7 +1550,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { bootstrapFile.deleteOnExit() tmpDir.deleteOnExit() } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -1539,7 +1600,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { String[] args = ["-n", inputPropertiesFile.path, "-b", bootstrapFile.path, "-o", outputPropertiesFile.path, "-p", PASSWORD] exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final List<String> updatedPropertiesLines = outputPropertiesFile.readLines() logger.info("Updated nifi.properties:") logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) @@ -1571,7 +1632,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { bootstrapFile.deleteOnExit() tmpDir.deleteOnExit() } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -1633,7 +1694,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { outputPropertiesFile.text = outputPropertiesFile.text.replace("nifi.sensitive.props.additional.keys=", "nifi.sensitive.props.additional.keys=nifi.ui.banner.text") exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final List<String> updatedPropertiesLines = outputPropertiesFile.readLines() logger.info("Updated nifi.properties:") logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) @@ -1665,7 +1726,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { bootstrapFile.deleteOnExit() tmpDir.deleteOnExit() } - }); + }) logger.info("Invoked #main second time with ${args.join(" ")}") ConfigEncryptionTool.main(args) @@ -1752,7 +1813,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { logger.info("Running [${scenario}] with args: ${localArgs}") exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { assert outputPropertiesFile.exists() final List<String> updatedPropertiesLines = outputPropertiesFile.readLines() logger.info("Updated nifi.properties:") @@ -1785,7 +1846,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { bootstrapFile.deleteOnExit() tmpDir.deleteOnExit() } - }); + }) logger.info("Migrating key (${scenario}) with ${localArgs.join(" ")}") ConfigEncryptionTool.main(localArgs as String[]) @@ -1799,7 +1860,7 @@ 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. */ @Test - public void testShouldMigrateFromPasswordToPassword() { + void testShouldMigrateFromPasswordToPassword() { // Arrange String scenario = "password to password" def args = ["-w", PASSWORD, "-p", PASSWORD.reverse()] @@ -1813,7 +1874,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test - public void testShouldMigrateFromPasswordToKey() { + void testShouldMigrateFromPasswordToKey() { // Arrange String scenario = "password to key" def args = ["-w", PASSWORD, "-k", KEY_HEX] @@ -1827,7 +1888,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test - public void testShouldMigrateFromKeyToPassword() { + void testShouldMigrateFromKeyToPassword() { // Arrange String scenario = "key to password" def args = ["-e", PASSWORD_KEY_HEX, "-p", PASSWORD.reverse()] @@ -1841,7 +1902,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test - public void testShouldMigrateFromKeyToKey() { + void testShouldMigrateFromKeyToKey() { // Arrange String scenario = "key to key" def args = ["-e", PASSWORD_KEY_HEX, "-k", KEY_HEX] @@ -2313,6 +2374,44 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test + void testSerializeLoginIdentityProvidersAndPreserveFormatShouldHandleManyProviders() { + // Arrange + String loginIdentityProvidersPath = "src/test/resources/login-identity-providers-populated-with-many-providers.xml" + File loginIdentityProvidersFile = new File(loginIdentityProvidersPath) + + File tmpDir = setupTmpDir() + + File workingFile = new File("target/tmp/tmp-login-identity-providers.xml") + workingFile.delete() + Files.copy(loginIdentityProvidersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX_128 + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + String plainXml = workingFile.text + String encryptedXml = tool.encryptLoginIdentityProviders(plainXml, KEY_HEX) + logger.info("Encrypted XML: \n${encryptedXml}") + + // Act + def serializedLines = tool.serializeLoginIdentityProvidersAndPreserveFormat(encryptedXml, workingFile) + logger.info("Serialized lines: \n${serializedLines.join("\n")}") + + // Assert + + // Some empty lines will be removed + def trimmedLines = lines.collect { it.trim() }.findAll { it } + def trimmedSerializedLines = serializedLines.collect { it.trim() }.findAll { it } + assert trimmedLines.size() == trimmedSerializedLines.size() + + // Ensure the replacement actually occurred + assert trimmedSerializedLines.findAll { it =~ "encryption=" }.size() == LIP_PASSWORD_LINE_COUNT + } + + @Test void testSerializeLoginIdentityProvidersAndPreserveFormatShouldHandleEmptyFile() { // Arrange File tmpDir = setupTmpDir() @@ -2344,6 +2443,52 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test + void testWriteLoginIdentityProvidersShouldHandleUnreadableFile() { + // Arrange + String providersPath = "src/test/resources/login-identity-providers-populated.xml" + File providersFile = new File(providersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-login-identity-providers.xml") + workingFile.delete() + Files.copy(providersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + tool.loginIdentityProvidersPath = workingFile.path + String writtenPath = "target/tmp/tmp-login-identity-providers-written.xml" + tool.outputLoginIdentityProvidersPath = writtenPath + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + String plainXml = workingFile.text + String encryptedXml = tool.encryptLoginIdentityProviders(plainXml, KEY_HEX) + logger.info("Encrypted XML: \n${encryptedXml}") + + tool.loginIdentityProviders = encryptedXml + + // Remove the working file (simulating an external process) + workingFile.delete() + + // Act + tool.writeLoginIdentityProviders() + + // Assert + File writtenFile = new File(writtenPath) + List<String> writtenLines = writtenFile.readLines() + logger.info("Written lines: \n${writtenLines.join("\n")}") + + // The output should contain only what was explicitly serialized in this operation (no pre-existing content) + assert writtenLines.join("\n") == encryptedXml.trim() + + // Ensure the replacement actually occurred + assert writtenLines.findAll { it =~ "encryption=" }.size() == LIP_PASSWORD_LINE_COUNT + } + + @Test void testShouldPerformFullOperationForLoginIdentityProviders() { // Arrange exit.expectSystemExitWithStatus(0) @@ -2376,7 +2521,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final String updatedXmlContent = outputLIPFile.text logger.info("Updated XML content: ${updatedXmlContent}") @@ -2413,7 +2558,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { bootstrapFile.deleteOnExit() tmpDir.deleteOnExit() } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -2459,7 +2604,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(PASSWORD_KEY_HEX) exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final String updatedXmlContent = outputLIPFile.text logger.info("Updated XML content: ${updatedXmlContent}") @@ -2494,7 +2639,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { bootstrapFile.deleteOnExit() tmpDir.deleteOnExit() } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -2506,92 +2651,835 @@ class ConfigEncryptionToolTest extends GroovyTestCase { } @Test - void testShouldPerformFullOperationForNiFiPropertiesAndLoginIdentityProviders() { + void testShouldDecryptAuthorizers() { // Arrange - exit.expectSystemExitWithStatus(0) + String authorizersPath = "src/test/resources/authorizers-populated-encrypted.xml" + File authorizersFile = new File(authorizersPath) - File tmpDir = setupTmpDir() + setupTmpDir() - File emptyKeyFile = new File("src/test/resources/bootstrap_with_empty_master_key.conf") - File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf") - bootstrapFile.delete() + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() - 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 + // Sanity check for decryption + String cipherText = "q4r7WIgN0MaxdAKM||SGgdCTPGSFEcuH4RraMYEdeyVbOx93abdWTVSWvh1w+klA" + String EXPECTED_PASSWORD = "thisIsABadPassword" + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX_128) + assert spp.unprotect(cipherText) == EXPECTED_PASSWORD - final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + KEY_HEX + tool.keyHex = KEY_HEX_128 - // Set up the NFP file - File inputPropertiesFile = new File("src/test/resources/nifi_with_sensitive_properties_unprotected.properties") - File outputPropertiesFile = new File("target/tmp/tmp_nifi.properties") - outputPropertiesFile.delete() + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") - 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}") + // Act + def decryptedLines = tool.decryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Decrypted lines: \n${decryptedLines.join("\n")}") - // Set up the LIP file - File inputLIPFile = new File("src/test/resources/login-identity-providers-populated.xml") - File outputLIPFile = new File("target/tmp/tmp-lip.xml") - outputLIPFile.delete() + // Assert + def passwordLines = decryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + assert passwordLines.every { it =~ ">thisIsABadPassword<" } + // Some lines were not encrypted originally so the encryption attribute would not have been updated + assert passwordLines.any { it =~ "encryption=\"none\"" } + } - String originalXmlContent = inputLIPFile.text - logger.info("Original XML content: ${originalXmlContent}") + @Test + void testShouldDecryptAuthorizersWithMultilineElements() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated-encrypted-multiline.xml" + File authorizersFile = new File(authorizersPath) - String[] args = ["-n", inputPropertiesFile.path, "-l", inputLIPFile.path, "-b", bootstrapFile.path, "-i", outputLIPFile.path, "-o", outputPropertiesFile.path, "-k", KEY_HEX, "-v"] + setupTmpDir() - AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true - exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { - final List<String> updatedPropertiesLines = outputPropertiesFile.readLines() - logger.info("Updated nifi.properties:") - logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) + tool.keyHex = KEY_HEX_128 - // Check that the output values for sensitive properties are not the same as the original (i.e. it was encrypted) - NiFiProperties updatedProperties = new NiFiPropertiesLoader().readProtectedPropertiesFromDisk(outputPropertiesFile) - assert updatedProperties.size() >= inputProperties.size() - originalSensitiveValues.every { String key, String originalValue -> - assert updatedProperties.getProperty(key) != originalValue - } + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") - // Check that the new NiFiProperties instance matches the output file (values still encrypted) - updatedProperties.getPropertyKeys().every { String key -> - assert updatedPropertiesLines.contains("${key}=${updatedProperties.getProperty(key)}".toString()) - } + // Act + def decryptedLines = tool.decryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Decrypted lines: \n${decryptedLines.join("\n")}") - final String updatedXmlContent = outputLIPFile.text - logger.info("Updated XML content: ${updatedXmlContent}") + // Assert + def passwordLines = decryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + assert passwordLines.every { it =~ ">thisIsABadPassword<" } + // Some lines were not encrypted originally so the encryption attribute would not have been updated + assert passwordLines.any { it =~ "encryption=\"none\"" } + } - // Check that the output values for sensitive properties are not the same as the original (i.e. it was encrypted) - def originalParsedXml = new XmlSlurper().parseText(originalXmlContent) - def updatedParsedXml = new XmlSlurper().parseText(updatedXmlContent) - assert originalParsedXml != updatedParsedXml - assert originalParsedXml.'**'.findAll { it.@encryption } != updatedParsedXml.'**'.findAll { - it.@encryption - } + @Test + void testShouldDecryptAuthorizersWithMultipleElementsPerLine() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated-encrypted-multiple-per-line.xml" + File authorizersFile = new File(authorizersPath) - def encryptedValues = updatedParsedXml.provider.find { - it.identifier == 'ldap-provider' - }.property.findAll { - it.@name =~ "Password" && it.@encryption =~ "aes/gcm/\\d{3}" - } + setupTmpDir() - encryptedValues.each { - assert spp.unprotect(it.text()) == PASSWORD - } + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true - // Check that the comments are still there - def trimmedLines = inputLIPFile.readLines().collect { it.trim() }.findAll { it } - def trimmedSerializedLines = updatedXmlContent.split("\n").collect { it.trim() }.findAll { it } - assert trimmedLines.size() == trimmedSerializedLines.size() + tool.keyHex = KEY_HEX_128 + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + // Act + def decryptedLines = tool.decryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Decrypted lines: \n${decryptedLines.join("\n")}") + + // Assert + def passwordLines = decryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + assert passwordLines.every { it =~ ">thisIsABadPassword<" } + // Some lines were not encrypted originally so the encryption attribute would not have been updated + assert passwordLines.any { it =~ "encryption=\"none\"" } + } + + + @Test + void testDecryptAuthorizersShouldHandleCommentedElements() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-commented.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX_128 + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + // Act + def decryptedLines = tool.decryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Decrypted lines: \n${decryptedLines.join("\n")}") + + // Assert + + // If no encrypted properties are found, the original input text is just returned (comments and formatting in tact) + assert decryptedLines == lines + } + + @Test + void testShouldEncryptAuthorizers() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + String encryptionScheme = "encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\"" + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + + // Act + def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + + // Assert + def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + assert passwordLines.every { !it.contains(">thisIsABadPassword<") } + assert passwordLines.every { it.contains(encryptionScheme) } + passwordLines.each { + String ct = (it =~ ">(.*)</property>")[0][1] + logger.info("Cipher text: ${ct}") + assert spp.unprotect(ct) == PASSWORD + } + } + + @Test + void testShouldEncryptAuthorizersWithEmptySensitiveElements() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated-empty.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + String encryptionScheme = "encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\"" + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + + // Act + def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + + // Assert + def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + def populatedPasswordLines = passwordLines.findAll { it =~ />.+</ } + assert populatedPasswordLines.every { !it.contains(">thisIsABadPassword<") } + assert populatedPasswordLines.every { it.contains(encryptionScheme) } + populatedPasswordLines.each { + String ct = (it =~ ">(.*)</property>")[0][1] + logger.info("Cipher text: ${ct}") + assert spp.unprotect(ct) == PASSWORD + } + } + + @Test + void testShouldEncryptAuthorizersWithMultilineElements() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated-multiline.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + String encryptionScheme = "encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\"" + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + + // Act + def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + + // Assert + def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + assert passwordLines.every { !it.contains(">thisIsABadPassword<") } + assert passwordLines.every { it.contains(encryptionScheme) } + passwordLines.each { + String ct = (it =~ ">(.*)</property>")[0][1] + logger.info("Cipher text: ${ct}") + assert spp.unprotect(ct) == PASSWORD + } + } + + @Test + void testShouldEncryptAuthorizersWithMultipleElementsPerLine() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated-multiple-per-line.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + String encryptionScheme = "encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\"" + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + + // Act + def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + + // Assert + def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + assert passwordLines.every { !it.contains(">thisIsABadPassword<") } + assert passwordLines.every { it.contains(encryptionScheme) } + passwordLines.each { + String ct = (it =~ ">(.*)</property>")[0][1] + logger.info("Cipher text: ${ct}") + assert spp.unprotect(ct) == PASSWORD + } + } + + @Test + void testShouldEncryptAuthorizersWithRenamedProvider() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated-renamed.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + String encryptionScheme = "encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\"" + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + assert lines.findAll { it =~ "ldap-user-group-provider" }.empty + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + + // Act + def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + + // Assert + def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX } + assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + def populatedPasswordLines = passwordLines.findAll { it =~ />.+</ } + assert populatedPasswordLines.every { !it.contains(">thisIsABadPassword<") } + assert populatedPasswordLines.every { it.contains(encryptionScheme) } + populatedPasswordLines.each { + String ct = (it =~ ">(.*)</property>")[0][1] + logger.info("Cipher text: ${ct}") + assert spp.unprotect(ct) == PASSWORD + } + } + + @Test + void testEncryptAuthorizersShouldHandleCommentedElements() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-commented.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX_128 + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + // Act + def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + + // Assert + + // If no sensitive properties are found, the original input text is just returned (comments and formatting in tact) + assert encryptedLines == lines + } + + @Test + void testSerializeAuthorizersAndPreserveFormatShouldRespectComments() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + String plainXml = workingFile.text + String encryptedXml = tool.encryptAuthorizers(plainXml, KEY_HEX) + logger.info("Encrypted XML: \n${encryptedXml}") + + // Act + def serializedLines = tool.serializeAuthorizersAndPreserveFormat(encryptedXml, workingFile) + logger.info("Serialized lines: \n${serializedLines.join("\n")}") + + // Assert + + // Some empty lines will be removed + def trimmedLines = lines.collect { it.trim() }.findAll { it } + def trimmedSerializedLines = serializedLines.collect { it.trim() }.findAll { it } + assert trimmedLines.size() == trimmedSerializedLines.size() + + // Ensure the replacement actually occurred + assert trimmedSerializedLines.findAll { it =~ "encryption=" }.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + } + + @Test + void testSerializeAuthorizersAndPreserveFormatShouldHandleRenamedProvider() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated-renamed.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + assert lines.findAll { it =~ "ldap-user-group-provider" }.empty + + String plainXml = workingFile.text + String encryptedXml = tool.encryptAuthorizers(plainXml, KEY_HEX) + logger.info("Encrypted XML: \n${encryptedXml}") + + // Act + def serializedLines = tool.serializeAuthorizersAndPreserveFormat(encryptedXml, workingFile) + logger.info("Serialized lines: \n${serializedLines.join("\n")}") + + // Assert + + // Some empty lines will be removed + def trimmedLines = lines.collect { it.trim() }.findAll { it } + def trimmedSerializedLines = serializedLines.collect { it.trim() }.findAll { it } + assert trimmedLines.size() == trimmedSerializedLines.size() + + // Ensure the replacement actually occurred + assert trimmedSerializedLines.findAll { it =~ "encryption=" }.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + } + + @Test + void testSerializeAuthorizersAndPreserveFormatShouldHandleCommentedFile() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-commented.xml" + File authorizersFile = new File(authorizersPath) + + File tmpDir = setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + // If no sensitive properties are found, the original input text is just returned (comments and formatting in tact) + def encryptedLines = tool.encryptAuthorizers(lines.join("\n")).split("\n") + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + assert encryptedLines == lines + + // Act + def serializedLines = ConfigEncryptionTool.serializeAuthorizersAndPreserveFormat(encryptedLines.join("\n"), workingFile) + logger.info("Serialized lines: \n${serializedLines.join("\n")}") + + // Assert + assert serializedLines == encryptedLines + assert TestAppender.events.any { + it.renderedMessage =~ "No provider element with class org.apache.nifi.ldap.tenants.LdapUserGroupProvider found in XML content; " + + "the file could be empty or the element may be missing or commented out" + } + } + + @Test + void testSerializeAuthorizersAndPreserveFormatShouldHandleEmptyFile() { + // Arrange + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + workingFile.createNewFile() + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + // If no sensitive properties are found, the original input text is just returned (comments and formatting in tact) + def encryptedLines = lines + logger.info("Encrypted lines: \n${encryptedLines.join("\n")}") + + // Act + def serializedLines = ConfigEncryptionTool.serializeAuthorizersAndPreserveFormat(encryptedLines.join("\n"), workingFile) + logger.info("Serialized lines: \n${serializedLines.join("\n")}") + + // Assert + assert serializedLines.findAll { it }.isEmpty() + assert TestAppender.events.any { + it.renderedMessage =~ "No provider element with class org.apache.nifi.ldap.tenants.LdapUserGroupProvider found in XML content; " + + "the file could be empty or the element may be missing or commented out" + } + } + + @Test + void testWriteAuthorizersShouldHandleUnreadableFile() { + // Arrange + String authorizersPath = "src/test/resources/authorizers-populated.xml" + File authorizersFile = new File(authorizersPath) + + setupTmpDir() + + File workingFile = new File("target/tmp/tmp-authorizers.xml") + workingFile.delete() + Files.copy(authorizersFile.toPath(), workingFile.toPath()) + ConfigEncryptionTool tool = new ConfigEncryptionTool() + tool.isVerbose = true + + tool.keyHex = KEY_HEX + tool.authorizersPath = workingFile.path + String writtenPath = "target/tmp/tmp-authorizers-written.xml" + tool.outputAuthorizersPath = writtenPath + + def lines = workingFile.readLines() + logger.info("Read lines: \n${lines.join("\n")}") + + String plainXml = workingFile.text + String encryptedXml = tool.encryptAuthorizers(plainXml, KEY_HEX) + logger.info("Encrypted XML: \n${encryptedXml}") + + tool.authorizers = encryptedXml + + // Remove the working file (simulating an external process) + workingFile.delete() + + // Act + tool.writeAuthorizers() + + // Assert + File writtenFile = new File(writtenPath) + List<String> writtenLines = writtenFile.readLines() + logger.info("Written lines: \n${writtenLines.join("\n")}") + + // The output should contain only what was explicitly serialized in this operation (no pre-existing content) + assert writtenLines.join("\n") == encryptedXml.trim() + + // Ensure the replacement actually occurred + assert writtenLines.findAll { it =~ "encryption=" }.size() == AUTHORIZERS_PASSWORD_LINE_COUNT + } + + @Test + void testShouldPerformFullOperationForAuthorizers() { + // Arrange + exit.expectSystemExitWithStatus(0) + + File tmpDir = setupTmpDir() + + 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 + + final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + KEY_HEX + + File inputAuthorizersFile = new File("src/test/resources/authorizers-populated.xml") + File outputAuthorizersFile = new File("target/tmp/tmp-authorizers.xml") + outputAuthorizersFile.delete() + + String originalXmlContent = inputAuthorizersFile.text + logger.info("Original XML content: ${originalXmlContent}") + + String[] args = ["-a", inputAuthorizersFile.path, "-b", bootstrapFile.path, "-u", outputAuthorizersFile.path, "-k", KEY_HEX, "-v"] + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + + exit.checkAssertionAfterwards(new Assertion() { + void checkAssertion() { + final String updatedXmlContent = outputAuthorizersFile.text + logger.info("Updated XML content: ${updatedXmlContent}") + + // Check that the output values for sensitive properties are not the same as the original (i.e. it was encrypted) + def originalParsedXml = new XmlSlurper().parseText(originalXmlContent) + def updatedParsedXml = new XmlSlurper().parseText(updatedXmlContent) + assert originalParsedXml != updatedParsedXml + assert originalParsedXml.'**'.findAll { it.@encryption } != updatedParsedXml.'**'.findAll { + it.@encryption + } + + def encryptedValues = updatedParsedXml.userGroupProvider.find { + it.identifier == 'ldap-user-group-provider' + }.property.findAll { + it.@name =~ "Password" && it.@encryption =~ "aes/gcm/\\d{3}" + } + + encryptedValues.each { + assert spp.unprotect(it.text()) == PASSWORD + } + + // Check that the key was persisted to the bootstrap.conf + final List<String> updatedBootstrapLines = bootstrapFile.readLines() + String updatedKeyLine = updatedBootstrapLines.find { + it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX) + } + logger.info("Updated key line: ${updatedKeyLine}") + + assert updatedKeyLine == EXPECTED_KEY_LINE + assert originalBootstrapLines.size() == updatedBootstrapLines.size() + + // Clean up + outputAuthorizersFile.deleteOnExit() + bootstrapFile.deleteOnExit() + tmpDir.deleteOnExit() + } + }) + + // Act + ConfigEncryptionTool.main(args) + logger.info("Invoked #main with ${args.join(" ")}") + + // Assert + + // Assertions defined above + } + + @Test + void testShouldPerformFullOperationMigratingAuthorizers() { + // Arrange + exit.expectSystemExitWithStatus(0) + + File tmpDir = setupTmpDir() + + // Start with 128-bit encryption and go to whatever is supported on this system + File emptyKeyFile = new File("src/test/resources/bootstrap_with_master_key_128.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 + KEY_HEX_128 + + final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX + + File inputAuthorizersFile = new File("src/test/resources/authorizers-populated-encrypted.xml") + File outputAuthorizersFile = new File("target/tmp/tmp-authorizers.xml") + outputAuthorizersFile.delete() + + String originalXmlContent = inputAuthorizersFile.text + logger.info("Original XML content: ${originalXmlContent}") + + // Migrate from KEY_HEX_128 to PASSWORD_KEY_HEX + String[] args = ["-a", inputAuthorizersFile.path, "-b", bootstrapFile.path, "-u", outputAuthorizersFile.path, "-m", "-e", KEY_HEX_128, "-k", PASSWORD_KEY_HEX, "-v"] + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(PASSWORD_KEY_HEX) + + exit.checkAssertionAfterwards(new Assertion() { + void checkAssertion() { + final String updatedXmlContent = outputAuthorizersFile.text + logger.info("Updated XML content: ${updatedXmlContent}") + + // Check that the output values for sensitive properties are not the same as the original (i.e. it was encrypted) + def originalParsedXml = new XmlSlurper().parseText(originalXmlContent) + def updatedParsedXml = new XmlSlurper().parseText(updatedXmlContent) + assert originalParsedXml != updatedParsedXml +// assert originalParsedXml.'**'.findAll { it.@encryption } != updatedParsedXml.'**'.findAll { it.@encryption } + + def encryptedValues = updatedParsedXml.userGroupProvider.find { + it.identifier == 'ldap-user-group-provider' + }.property.findAll { + it.@name =~ "Password" && it.@encryption =~ "aes/gcm/\\d{3}" + } + + encryptedValues.each { + assert spp.unprotect(it.text()) == PASSWORD + } + + // Check that the key was persisted to the bootstrap.conf + final List<String> updatedBootstrapLines = bootstrapFile.readLines() + String updatedKeyLine = updatedBootstrapLines.find { + it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX) + } + logger.info("Updated key line: ${updatedKeyLine}") + + assert updatedKeyLine == EXPECTED_KEY_LINE + assert originalBootstrapLines.size() == updatedBootstrapLines.size() + + // Clean up + outputAuthorizersFile.deleteOnExit() + bootstrapFile.deleteOnExit() + tmpDir.deleteOnExit() + } + }) + + // Act + ConfigEncryptionTool.main(args) + logger.info("Invoked #main with ${args.join(" ")}") + + // Assert + + // Assertions defined above + } + + @Test + void testShouldPerformFullOperationForNiFiPropertiesAndLoginIdentityProvidersAndAuthorizers() { + // Arrange + exit.expectSystemExitWithStatus(0) + + File tmpDir = setupTmpDir() + + 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 + + final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + KEY_HEX + + // Set up the NFP file + File inputPropertiesFile = new File("src/test/resources/nifi_with_sensitive_properties_unprotected.properties") + File outputPropertiesFile = new File("target/tmp/tmp_nifi.properties") + outputPropertiesFile.delete() + + 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}") + + // Set up the LIP file + File inputLIPFile = new File("src/test/resources/login-identity-providers-populated.xml") + File outputLIPFile = new File("target/tmp/tmp-lip.xml") + outputLIPFile.delete() + + String originalLipXmlContent = inputLIPFile.text + logger.info("Original LIP XML content: ${originalLipXmlContent}") + + // Set up the Authorizers file + File inputAuthorizersFile = new File("src/test/resources/authorizers-populated.xml") + File outputAuthorizersFile = new File("target/tmp/tmp-authorizers.xml") + outputAuthorizersFile.delete() + + String originalAuthorizersXmlContent = inputAuthorizersFile.text + logger.info("Original Authorizers XML content: ${originalAuthorizersXmlContent}") + + String[] args = [ + "-n", inputPropertiesFile.path, + "-l", inputLIPFile.path, + "-a", inputAuthorizersFile.path, + "-b", bootstrapFile.path, + "-o", outputPropertiesFile.path, + "-i", outputLIPFile.path, + "-u", outputAuthorizersFile.path, + "-k", KEY_HEX, + "-v"] + + AESSensitivePropertyProvider spp = new AESSensitivePropertyProvider(KEY_HEX) + + exit.checkAssertionAfterwards(new Assertion() { + void checkAssertion() { + + /*** NiFi Properties Assertions ***/ + + final List<String> updatedPropertiesLines = outputPropertiesFile.readLines() + logger.info("Updated nifi.properties:") + logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) + + // Check that the output values for sensitive properties are not the same as the original (i.e. it was encrypted) + NiFiProperties updatedProperties = new NiFiPropertiesLoader().readProtectedPropertiesFromDisk(outputPropertiesFile) + assert updatedProperties.size() >= inputProperties.size() + originalSensitiveValues.every { String key, String originalValue -> + assert updatedProperties.getProperty(key) != originalValue + } + + // Check that the new NiFiProperties instance matches the output file (values still encrypted) + updatedProperties.getPropertyKeys().every { String key -> + assert updatedPropertiesLines.contains("${key}=${updatedProperties.getProperty(key)}".toString()) + } + + /*** Login Identity Providers Assertions ***/ + + final String updatedLipXmlContent = outputLIPFile.text + logger.info("Updated LIP XML content: ${updatedLipXmlContent}") + // Check that the output values for sensitive properties are not the same as the original (i.e. it was encrypted) + def originalLipParsedXml = new XmlSlurper().parseText(originalLipXmlContent) + def updatedLipParsedXml = new XmlSlurper().parseText(updatedLipXmlContent) + assert originalLipParsedXml != updatedLipParsedXml + assert originalLipParsedXml.'**'.findAll { it.@encryption } != updatedLipParsedXml.'**'.findAll { + it.@encryption + } + def lipEncryptedValues = updatedLipParsedXml.provider.find { + it.identifier == 'ldap-provider' + }.property.findAll { + it.@name =~ "Password" && it.@encryption =~ "aes/gcm/\\d{3}" + } + lipEncryptedValues.each { + assert spp.unprotect(it.text()) == PASSWORD + } + // Check that the comments are still there + def lipTrimmedLines = inputLIPFile.readLines().collect { it.trim() }.findAll { it } + def lipTrimmedSerializedLines = updatedLipXmlContent.split("\n").collect { it.trim() }.findAll { it } + assert lipTrimmedLines.size() == lipTrimmedSerializedLines.size() + + /*** Authorizers Assertions ***/ + + final String updatedAuthorizersXmlContent = outputAuthorizersFile.text + logger.info("Updated Authorizers XML content: ${updatedAuthorizersXmlContent}") + // Check that the output values for sensitive properties are not the same as the original (i.e. it was encrypted) + def originalAuthorizersParsedXml = new XmlSlurper().parseText(originalAuthorizersXmlContent) + def updatedAuthorizersParsedXml = new XmlSlurper().parseText(updatedAuthorizersXmlContent) + assert originalAuthorizersParsedXml != updatedAuthorizersParsedXml + assert originalAuthorizersParsedXml.'**'.findAll { it.@encryption } != updatedAuthorizersParsedXml.'**'.findAll { + it.@encryption + } + def authorizersEncryptedValues = updatedAuthorizersParsedXml.userGroupProvider.find { + it.identifier == 'ldap-user-group-provider' + }.property.findAll { + it.@name =~ "Password" && it.@encryption =~ "aes/gcm/\\d{3}" + } + authorizersEncryptedValues.each { + assert spp.unprotect(it.text()) == PASSWORD + } + // Check that the comments are still there + def authorizersTrimmedLines = inputAuthorizersFile.readLines().collect { it.trim() }.findAll { it } + def authorizersTrimmedSerializedLines = updatedAuthorizersXmlContent.split("\n").collect { it.trim() }.findAll { it } + assert authorizersTrimmedLines.size() == authorizersTrimmedSerializedLines.size() + + /*** Bootstrap assertions ***/ // Check that the key was persisted to the bootstrap.conf final List<String> updatedBootstrapLines = bootstrapFile.readLines() @@ -2606,10 +3494,11 @@ class ConfigEncryptionToolTest extends GroovyTestCase { // Clean up outputPropertiesFile.deleteOnExit() outputLIPFile.deleteOnExit() + outputAuthorizersFile.deleteOnExit() bootstrapFile.deleteOnExit() tmpDir.deleteOnExit() } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -2726,7 +3615,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-x", "-v", "-s", newFlowPassword] exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines() logger.info("Updated nifi.properties:") logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) @@ -2769,7 +3658,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { assert ConfigEncryptionTool.decryptFlowElement(it, newFlowPassword) == "thisIsABadPassword" } } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -2834,7 +3723,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-x", "-v", "-s", newFlowPassword] exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines() logger.info("Updated nifi.properties:") logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) @@ -2874,7 +3763,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { assert ConfigEncryptionTool.decryptFlowElement(it, newFlowPassword) == "thisIsABadPassword" } } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -2950,7 +3839,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-b", bootstrapFile.path, "-x", "-v", "-s", newFlowPassword] exit.checkAssertionAfterwards(new Assertion() { - public void checkAssertion() { + void checkAssertion() { final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines() logger.info("Updated nifi.properties:") logger.info("\n" * 2 + updatedPropertiesLines.join("\n")) @@ -3015,7 +3904,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase { assert ConfigEncryptionTool.decryptFlowElement(it, newFlowPassword) == "thisIsABadPassword" } } - }); + }) // Act ConfigEncryptionTool.main(args) @@ -3550,19 +4439,19 @@ class ConfigEncryptionToolTest extends GroovyTestCase { // TODO: Test with 128/256-bit available } -public class TestAppender extends AppenderSkeleton { - static final List<LoggingEvent> events = new ArrayList<>(); +class TestAppender extends AppenderSkeleton { + static final List<LoggingEvent> events = new ArrayList<>() @Override protected void append(LoggingEvent e) { synchronized (events) { - events.add(e); + events.add(e) } } - public static void reset() { + static void reset() { synchronized (events) { - events.clear(); + events.clear() } }
http://git-wip-us.apache.org/repos/asf/nifi/blob/482f3719/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml ---------------------------------------------------------------------- diff --git a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml new file mode 100644 index 0000000..3808404 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml @@ -0,0 +1,309 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + 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. +--> +<!-- + This file lists the userGroupProviders, accessPolicyProviders, and authorizers to use when running securely. In order + to use a specific authorizer it must be configured here and it's identifier must be specified in the nifi.properties file. + If the authorizer is a managedAuthorizer, it may need to be configured with an accessPolicyProvider and an userGroupProvider. + This file allows for configuration of them, but they must be configured in order: + + ... + all userGroupProviders + all accessPolicyProviders + all Authorizers + ... +--> +<authorizers> + + <!-- + The FileUserGroupProvider will provide support for managing users and groups which is backed by a file + on the local file system. + + - Users File - The file where the FileUserGroupProvider will store users and groups. + + - Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically + be used to load the users and groups into the Users File. + + - Initial User Identity [unique key] - The identity of a users and systems to seed the Users File. The name of + each property must be unique, for example: "Initial User Identity A", "Initial User Identity B", + "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3" + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities, + so the values should be the unmapped identities (i.e. full DN from a certificate). + --> + <userGroupProvider> + <identifier>file-user-group-provider</identifier> + <class>org.apache.nifi.authorization.FileUserGroupProvider</class> + <property name="Users File">./conf/users.xml</property> + <property name="Legacy Authorized Users File"></property> + + <property name="Initial User Identity 1"></property> + </userGroupProvider> + + <!-- + The LdapUserGroupProvider will retrieve users and groups from an LDAP server. The users and groups + are not configurable. + + 'Authentication Strategy' - How the connection to the LDAP server is authenticated. Possible + values are ANONYMOUS, SIMPLE, LDAPS, or START_TLS. + + 'Manager DN' - The DN of the manager that is used to bind to the LDAP server to search for users. + 'Manager Password' - The password of the manager that is used to bind to the LDAP server to + search for users. + + 'TLS - Keystore' - Path to the Keystore that is used when connecting to LDAP using LDAPS or START_TLS. + 'TLS - Keystore Password' - Password for the Keystore that is used when connecting to LDAP + using LDAPS or START_TLS. + 'TLS - Keystore Type' - Type of the Keystore that is used when connecting to LDAP using + LDAPS or START_TLS (i.e. JKS or PKCS12). + 'TLS - Truststore' - Path to the Truststore that is used when connecting to LDAP using LDAPS or START_TLS. + 'TLS - Truststore Password' - Password for the Truststore that is used when connecting to + LDAP using LDAPS or START_TLS. + 'TLS - Truststore Type' - Type of the Truststore that is used when connecting to LDAP using + LDAPS or START_TLS (i.e. JKS or PKCS12). + 'TLS - Client Auth' - Client authentication policy when connecting to LDAP using LDAPS or START_TLS. + Possible values are REQUIRED, WANT, NONE. + 'TLS - Protocol' - Protocol to use when connecting to LDAP using LDAPS or START_TLS. (i.e. TLS, + TLSv1.1, TLSv1.2, etc). + 'TLS - Shutdown Gracefully' - Specifies whether the TLS should be shut down gracefully + before the target context is closed. Defaults to false. + + 'Referral Strategy' - Strategy for handling referrals. Possible values are FOLLOW, IGNORE, THROW. + 'Connect Timeout' - Duration of connect timeout. (i.e. 10 secs). + 'Read Timeout' - Duration of read timeout. (i.e. 10 secs). + + 'Url' - Space-separated list of URLs of the LDAP servers (i.e. ldap://<hostname>:<port>). + 'Page Size' - Sets the page size when retrieving users and groups. If not specified, no paging is performed. + 'Sync Interval' - Duration of time between syncing users and groups (i.e. 30 mins). Minimum allowable value is 10 secs. + + 'User Search Base' - Base DN for searching for users (i.e. ou=users,o=nifi). Required to search users. + 'User Object Class' - Object class for identifying users (i.e. person). Required if searching users. + 'User Search Scope' - Search scope for searching users (ONE_LEVEL, OBJECT, or SUBTREE). Required if searching users. + 'User Search Filter' - Filter for searching for users against the 'User Search Base' (i.e. (memberof=cn=team1,ou=groups,o=nifi) ). Optional. + 'User Identity Attribute' - Attribute to use to extract user identity (i.e. cn). Optional. If not set, the entire DN is used. + 'User Group Name Attribute' - Attribute to use to define group membership (i.e. memberof). Optional. If not set + group membership will not be calculated through the users. Will rely on group membership being defined + through 'Group Member Attribute' if set. The value of this property is the name of the attribute in the user ldap entry that + associates them with a group. The value of that user attribute could be a dn or group name for instance. What value is expected + is configured in the 'User Group Name Attribute - Referenced Group Attribute'. + 'User Group Name Attribute - Referenced Group Attribute' - If blank, the value of the attribute defined in 'User Group Name Attribute' + is expected to be the full dn of the group. If not blank, this property will define the attribute of the group ldap entry that + the value of the attribute defined in 'User Group Name Attribute' is referencing (i.e. name). Use of this property requires that + 'Group Search Base' is also configured. + + 'Group Search Base' - Base DN for searching for groups (i.e. ou=groups,o=nifi). Required to search groups. + 'Group Object Class' - Object class for identifying groups (i.e. groupOfNames). Required if searching groups. + 'Group Search Scope' - Search scope for searching groups (ONE_LEVEL, OBJECT, or SUBTREE). Required if searching groups. + 'Group Search Filter' - Filter for searching for groups against the 'Group Search Base'. Optional. + 'Group Name Attribute' - Attribute to use to extract group name (i.e. cn). Optional. If not set, the entire DN is used. + 'Group Member Attribute' - Attribute to use to define group membership (i.e. member). Optional. If not set + group membership will not be calculated through the groups. Will rely on group membership being defined + through 'User Group Name Attribute' if set. The value of this property is the name of the attribute in the group ldap entry that + associates them with a user. The value of that group attribute could be a dn or memberUid for instance. What value is expected + is configured in the 'Group Member Attribute - Referenced User Attribute'. (i.e. member: cn=User 1,ou=users,o=nifi vs. memberUid: user1) + 'Group Member Attribute - Referenced User Attribute' - If blank, the value of the attribute defined in 'Group Member Attribute' + is expected to be the full dn of the user. If not blank, this property will define the attribute of the user ldap entry that + the value of the attribute defined in 'Group Member Attribute' is referencing (i.e. uid). Use of this property requires that + 'User Search Base' is also configured. (i.e. member: cn=User 1,ou=users,o=nifi vs. memberUid: user1) + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities. + Group names are not mapped. + --> + <!-- To enable the ldap-user-group-provider remove 2 lines. This is 1 of 2. + <userGroupProvider> + <identifier>ldap-user-group-provider</identifier> + <class>org.apache.nifi.ldap.tenants.LdapUserGroupProvider</class> + <property name="Authentication Strategy">START_TLS</property> + + <property name="Manager DN"></property> + <property name="Manager Password"></property> + + <property name="TLS - Keystore"></property> + <property name="TLS - Keystore Password"></property> + <property name="TLS - Keystore Type"></property> + <property name="TLS - Truststore"></property> + <property name="TLS - Truststore Password"></property> + <property name="TLS - Truststore Type"></property> + <property name="TLS - Client Auth"></property> + <property name="TLS - Protocol"></property> + <property name="TLS - Shutdown Gracefully"></property> + + <property name="Referral Strategy">FOLLOW</property> + <property name="Connect Timeout">10 secs</property> + <property name="Read Timeout">10 secs</property> + + <property name="Url"></property> + <property name="Page Size"></property> + <property name="Sync Interval">30 mins</property> + + <property name="User Search Base"></property> + <property name="User Object Class">person</property> + <property name="User Search Scope">ONE_LEVEL</property> + <property name="User Search Filter"></property> + <property name="User Identity Attribute"></property> + <property name="User Group Name Attribute"></property> + <property name="User Group Name Attribute - Referenced Group Attribute"></property> + + <property name="Group Search Base"></property> + <property name="Group Object Class">group</property> + <property name="Group Search Scope">ONE_LEVEL</property> + <property name="Group Search Filter"></property> + <property name="Group Name Attribute"></property> + <property name="Group Member Attribute"></property> + <property name="Group Member Attribute - Referenced User Attribute"></property> + </userGroupProvider> + To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. --> + + <!-- + The CompositeUserGroupProvider will provide support for retrieving users and groups from multiple sources. + + - User Group Provider [unique key] - The identifier of user group providers to load from. The name of + each property must be unique, for example: "User Group Provider A", "User Group Provider B", + "User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3" + + NOTE: Any identity mapping rules specified in nifi.properties are not applied in this implementation. This behavior + would need to be applied by the base implementation. + --> + <!-- To enable the composite-user-group-provider remove 2 lines. This is 1 of 2. + <userGroupProvider> + <identifier>composite-user-group-provider</identifier> + <class>org.apache.nifi.authorization.CompositeUserGroupProvider</class> + <property name="User Group Provider 1"></property> + </userGroupProvider> + To enable the composite-user-group-provider remove 2 lines. This is 2 of 2. --> + + <!-- + The CompositeConfigurableUserGroupProvider will provide support for retrieving users and groups from multiple sources. + Additionally, a single configurable user group provider is required. Users from the configurable user group provider + are configurable, however users loaded from one of the User Group Provider [unique key] will not be. + + - Configurable User Group Provider - A configurable user group provider. + + - User Group Provider [unique key] - The identifier of user group providers to load from. The name of + each property must be unique, for example: "User Group Provider A", "User Group Provider B", + "User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3" + + NOTE: Any identity mapping rules specified in nifi.properties are not applied in this implementation. This behavior + would need to be applied by the base implementation. + --> + <!-- To enable the composite-configurable-user-group-provider remove 2 lines. This is 1 of 2. + <userGroupProvider> + <identifier>composite-configurable-user-group-provider</identifier> + <class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class> + <property name="Configurable User Group Provider">file-user-group-provider</property> + <property name="User Group Provider 1"></property> + </userGroupProvider> + To enable the composite-configurable-user-group-provider remove 2 lines. This is 2 of 2. --> + + <!-- + The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file + on the local file system. + + - User Group Provider - The identifier for an User Group Provider defined above that will be used to access + users and groups for use in the managed access policies. + + - Authorizations File - The file where the FileAccessPolicyProvider will store policies. + + - Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and + given the ability to create additional users, groups, and policies. The value of this property could be + a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there + are no other policies defined. If this property is specified then a Legacy Authorized Users File can not be specified. + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the initial admin identity, + so the value should be the unmapped identity. This identity must be found in the configured User Group Provider. + + - Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically + converted to the new authorizations model. If this property is specified then an Initial Admin Identity can + not be specified, and this property will only be used when there are no other users, groups, and policies defined. + + NOTE: Any users in the legacy users file must be found in the configured User Group Provider. + + - Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node + should be defined, so that every node knows about every other node. If not clustered these properties can be ignored. + The name of each property must be unique, for example for a three node cluster: + "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3" + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities, + so the values should be the unmapped identities (i.e. full DN from a certificate). This identity must be found + in the configured User Group Provider. + --> + <accessPolicyProvider> + <identifier>file-access-policy-provider</identifier> + <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class> + <property name="User Group Provider">file-user-group-provider</property> + <property name="Authorizations File">./conf/authorizations.xml</property> + <property name="Initial Admin Identity"></property> + <property name="Legacy Authorized Users File"></property> + + <property name="Node Identity 1"></property> + </accessPolicyProvider> + + <!-- + The StandardManagedAuthorizer. This authorizer implementation must be configured with the + Access Policy Provider which it will use to access and manage users, groups, and policies. + These users, groups, and policies will be used to make all access decisions during authorization + requests. + + - Access Policy Provider - The identifier for an Access Policy Provider defined above. + --> + <authorizer> + <identifier>managed-authorizer</identifier> + <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class> + <property name="Access Policy Provider">file-access-policy-provider</property> + </authorizer> + + <!-- + NOTE: This Authorizer has been replaced with the more granular approach configured above with the Standard + Managed Authorizer. However, it is still available for backwards compatibility reasons. + + The FileAuthorizer is NiFi's provided authorizer and has the following properties: + + - Authorizations File - The file where the FileAuthorizer will store policies. + + - Users File - The file where the FileAuthorizer will store users and groups. + + - Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and + given the ability to create additional users, groups, and policies. The value of this property could be + a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there + are no other users, groups, and policies defined. If this property is specified then a Legacy Authorized + Users File can not be specified. + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the initial admin identity, + so the value should be the unmapped identity. + + - Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically + converted to the new authorizations model. If this property is specified then an Initial Admin Identity can + not be specified, and this property will only be used when there are no other users, groups, and policies defined. + + - Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node + should be defined, so that every node knows about every other node. If not clustered these properties can be ignored. + The name of each property must be unique, for example for a three node cluster: + "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3" + + NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities, + so the values should be the unmapped identities (i.e. full DN from a certificate). + --> + <!-- <authorizer> + <identifier>file-provider</identifier> + <class>org.apache.nifi.authorization.FileAuthorizer</class> + <property name="Authorizations File">./conf/authorizations.xml</property> + <property name="Users File">./conf/users.xml</property> + <property name="Initial Admin Identity"></property> + <property name="Legacy Authorized Users File"></property> + + <property name="Node Identity 1"></property> + </authorizer> + --> +</authorizers> \ No newline at end of file
