[ 
https://issues.apache.org/jira/browse/NIFI-4701?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16306969#comment-16306969
 ] 

ASF GitHub Bot commented on NIFI-4701:
--------------------------------------

Github user alopresto commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/2350#discussion_r159130193
  
    --- Diff: 
nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
 ---
    @@ -2506,92 +2604,789 @@ 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)
    +
    +        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
    +        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 testShouldDecryptAuthorizersWithMultipleElementsPerLine() {
    +        // Arrange
    +        String authorizersPath = 
"src/test/resources/authorizers-populated-encrypted-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_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)}\""
     
    -        String[] args = ["-n", inputPropertiesFile.path, "-l", 
inputLIPFile.path, "-b", bootstrapFile.path, "-i", outputLIPFile.path, "-o", 
outputPropertiesFile.path, "-k", KEY_HEX, "-v"]
    +        def lines = workingFile.readLines()
    +        logger.info("Read lines: \n${lines.join("\n")}")
     
             AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
     
    -        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"))
    +        // Act
    +        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
    +        logger.info("Encrypted lines: \n${encryptedLines.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
    -                }
    +        // 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
    +        }
    +    }
     
    -                // 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())
    -                }
    +    @Test
    +    void testShouldEncryptAuthorizersWithEmptySensitiveElements() {
    +        // Arrange
    +        String authorizersPath = 
"src/test/resources/authorizers-populated-empty.xml"
    +        File authorizersFile = new File(authorizersPath)
     
    -                final String updatedXmlContent = outputLIPFile.text
    -                logger.info("Updated XML content: ${updatedXmlContent}")
    +        setupTmpDir()
     
    -                // 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
    -                }
    +        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 encryptedValues = updatedParsedXml.provider.find {
    -                    it.identifier == 'ldap-provider'
    -                }.property.findAll {
    -                    it.@name =~ "Password" && it.@encryption =~ 
"aes/gcm/\\d{3}"
    -                }
    +        tool.keyHex = KEY_HEX
    +        String encryptionScheme = 
"encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\""
     
    -                encryptedValues.each {
    -                    assert spp.unprotect(it.text()) == PASSWORD
    -                }
    +        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
    --- End diff --
    
    This should just be `KEY_HEX` so the JCE determines which key length to 
use. 


> Support encrypted properties in authorizers.xml
> -----------------------------------------------
>
>                 Key: NIFI-4701
>                 URL: https://issues.apache.org/jira/browse/NIFI-4701
>             Project: Apache NiFi
>          Issue Type: Improvement
>          Components: Configuration
>            Reporter: Kevin Doran
>            Assignee: Kevin Doran
>             Fix For: 1.5.0
>
>
> Since the addition of LdapUserGroupProvider (see NIFI-4059) in v1.4.0, 
> authorizers.xml can now contain properties for LDAP Server credentials. 
> This ticket is to enable properties in authorizers.xml to be encrypted, so 
> that the LDAP Server Manager credentials can be protected similar to 
> LdapProvider which is configured via login-identity-providers.xml.
> The main changes are in nifi-authorizers are:
> * authorizers.xsd to add an encryption attribute to Property
> * to PropertyAuthorizerFactoryBean to check for that attribute and decrypt 
> the property value if necessary when creating the the configuration context
> Additionally, support for creating an encrypted authorizers.xml, protected by 
> the NiFi master key, should be added to the Encrypt Tool in NiFi Toolkit.



--
This message was sent by Atlassian JIRA
(v6.4.14#64029)

Reply via email to