mirror of https://github.com/apache/nifi.git
NIFI-11488 Removed Deprecated Sensitive Properties Algorithms
Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com> This closes #7198.
This commit is contained in:
parent
d54940c397
commit
ee03db0e8f
|
@ -28,10 +28,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=test-host
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -28,10 +28,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=1.2.3.4
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=12345
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=8080
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/existing-keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/existing-keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -24,10 +24,6 @@ nifi.web.http.port=
|
|||
nifi.web.https.host=
|
||||
nifi.web.https.port=8443
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=./src/test/resources/keystore.p12
|
||||
nifi.security.keystoreType=PKCS12
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
*/
|
||||
package org.apache.nifi.flow.encryptor.command;
|
||||
|
||||
import org.apache.nifi.encrypt.PropertyEncryptionMethod;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptor;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
|
||||
import org.apache.nifi.flow.encryptor.FlowEncryptor;
|
||||
import org.apache.nifi.flow.encryptor.StandardFlowEncryptor;
|
||||
import org.apache.nifi.security.util.EncryptionMethod;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -61,7 +61,7 @@ class FlowEncryptorCommand implements Runnable {
|
|||
|
||||
private static final String GZ_EXTENSION = ".gz";
|
||||
|
||||
private static final String DEFAULT_PROPERTIES_ALGORITHM = EncryptionMethod.MD5_256AES.getAlgorithm();
|
||||
private static final String DEFAULT_PROPERTIES_ALGORITHM = PropertyEncryptionMethod.NIFI_PBKDF2_AES_GCM_256.name();
|
||||
|
||||
private static final String DEFAULT_PROPERTIES_KEY = "nififtw!";
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
*/
|
||||
package org.apache.nifi.flow.encryptor;
|
||||
|
||||
import org.apache.nifi.encrypt.PropertyEncryptionMethod;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptor;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
|
||||
import org.apache.nifi.security.util.EncryptionMethod;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -55,8 +55,8 @@ public class StandardFlowEncryptorTest {
|
|||
|
||||
@BeforeEach
|
||||
public void setEncryptors() {
|
||||
inputEncryptor = getPropertyEncryptor(INPUT_KEY, EncryptionMethod.MD5_256AES.getAlgorithm());
|
||||
outputEncryptor = getPropertyEncryptor(OUTPUT_KEY, EncryptionMethod.SHA256_256AES.getAlgorithm());
|
||||
inputEncryptor = getPropertyEncryptor(INPUT_KEY, PropertyEncryptionMethod.NIFI_PBKDF2_AES_GCM_256.name());
|
||||
outputEncryptor = getPropertyEncryptor(OUTPUT_KEY, PropertyEncryptionMethod.NIFI_ARGON2_AES_GCM_256.name());
|
||||
flowEncryptor = new StandardFlowEncryptor();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.apache.nifi.flow.encryptor.command;
|
||||
|
||||
import org.apache.nifi.stream.io.GZIPOutputStream;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -34,6 +33,7 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -56,8 +56,6 @@ public class FlowEncryptorCommandTest {
|
|||
|
||||
private static final String POPULATED_PROPERTIES = "/populated.nifi.properties";
|
||||
|
||||
private static final String LEGACY_BLANK_PROPERTIES = "/legacy-blank.nifi.properties";
|
||||
|
||||
private static final String REQUESTED_ALGORITHM = "NIFI_PBKDF2_AES_GCM_256";
|
||||
|
||||
@AfterEach
|
||||
|
@ -85,20 +83,6 @@ public class FlowEncryptorCommandTest {
|
|||
assertPropertiesKeyUpdated(propertiesPath, propertiesKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunPropertiesKeyLegacyPropertiesWithoutFlowJson() throws IOException, URISyntaxException {
|
||||
final Path propertiesPath = getLegacyNiFiPropertiesWithoutFlowJson();
|
||||
System.setProperty(FlowEncryptorCommand.PROPERTIES_FILE_PATH, propertiesPath.toString());
|
||||
|
||||
final FlowEncryptorCommand command = new FlowEncryptorCommand();
|
||||
|
||||
final String propertiesKey = UUID.randomUUID().toString();
|
||||
command.setRequestedPropertiesKey(propertiesKey);
|
||||
command.run();
|
||||
|
||||
assertPropertiesKeyUpdated(propertiesPath, propertiesKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunPropertiesAlgorithmWithPropertiesKeyPopulatedProperties() throws IOException, URISyntaxException {
|
||||
final Path propertiesPath = getPopulatedNiFiProperties();
|
||||
|
@ -150,11 +134,6 @@ public class FlowEncryptorCommandTest {
|
|||
return getNiFiProperties(flowConfiguration, flowConfigurationJson, POPULATED_PROPERTIES);
|
||||
}
|
||||
|
||||
protected static Path getLegacyNiFiPropertiesWithoutFlowJson() throws IOException, URISyntaxException {
|
||||
final Path flowConfiguration = getFlowConfiguration(FLOW_CONTENTS_XML, XML_GZ);
|
||||
return getNiFiProperties(flowConfiguration, null, LEGACY_BLANK_PROPERTIES);
|
||||
}
|
||||
|
||||
private static Path getNiFiProperties(
|
||||
final Path flowConfigurationPath,
|
||||
final Path flowConfigurationJsonPath,
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# 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.
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.algorithm=
|
||||
nifi.flow.configuration.file=
|
|
@ -71,7 +71,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -69,7 +69,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -71,7 +71,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -24,17 +24,9 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-security-utils</artifactId>
|
||||
<artifactId>nifi-security-crypto-key</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
|
|
|
@ -18,15 +18,12 @@ package org.apache.nifi.encrypt;
|
|||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* Cipher Property Encryptor provides hexadecimal encoding and decoding around cipher operations
|
||||
|
@ -34,10 +31,6 @@ import java.security.Security;
|
|||
abstract class CipherPropertyEncryptor implements PropertyEncryptor {
|
||||
private static final Charset PROPERTY_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt property and encode as a hexadecimal string
|
||||
*
|
||||
|
@ -52,7 +45,7 @@ abstract class CipherPropertyEncryptor implements PropertyEncryptor {
|
|||
final Cipher cipher = getEncryptionCipher(encodedParameters);
|
||||
try {
|
||||
final byte[] encrypted = cipher.doFinal(binary);
|
||||
return Hex.encodeHexString(Arrays.concatenate(encodedParameters, encrypted));
|
||||
return Hex.encodeHexString(getConcatenatedBinary(encodedParameters, encrypted));
|
||||
} catch (final BadPaddingException | IllegalBlockSizeException e) {
|
||||
final String message = String.format("Encryption Failed with Algorithm [%s]", cipher.getAlgorithm());
|
||||
throw new EncryptionException(message, e);
|
||||
|
@ -87,6 +80,17 @@ abstract class CipherPropertyEncryptor implements PropertyEncryptor {
|
|||
}
|
||||
}
|
||||
|
||||
private byte[] getConcatenatedBinary(final byte[] encodedParameters, final byte[] encrypted) {
|
||||
final int encodedParametersLength = encodedParameters.length;
|
||||
final int encryptedLength = encrypted.length;
|
||||
final int concatenatedLength = encodedParametersLength + encryptedLength;
|
||||
|
||||
final byte[] concatenated = new byte[concatenatedLength];
|
||||
System.arraycopy(encodedParameters, 0, concatenated, 0, encodedParametersLength);
|
||||
System.arraycopy(encrypted, 0, concatenated, encodedParametersLength, encryptedLength);
|
||||
return concatenated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Encoded Parameters based on cipher implementation
|
||||
*
|
||||
|
|
|
@ -24,10 +24,6 @@ public class EncryptionException extends RuntimeException {
|
|||
|
||||
private static final long serialVersionUID = 19802342398832L;
|
||||
|
||||
public EncryptionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public EncryptionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.encrypt;
|
||||
|
||||
import org.apache.nifi.security.util.EncryptionMethod;
|
||||
import org.apache.nifi.security.util.crypto.CipherUtility;
|
||||
import org.apache.nifi.security.util.crypto.PBECipherProvider;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Property Encryptor implementation using Password Based Encryption Cipher Provider
|
||||
*/
|
||||
class PasswordBasedCipherPropertyEncryptor extends CipherPropertyEncryptor {
|
||||
private static final int ARRAY_START = 0;
|
||||
|
||||
private static final boolean ENCRYPT = true;
|
||||
|
||||
private static final boolean DECRYPT = false;
|
||||
|
||||
private final PBECipherProvider cipherProvider;
|
||||
|
||||
private final EncryptionMethod encryptionMethod;
|
||||
|
||||
private final String password;
|
||||
|
||||
private final int keyLength;
|
||||
|
||||
private final int saltLength;
|
||||
|
||||
private final String description;
|
||||
|
||||
protected PasswordBasedCipherPropertyEncryptor(final PBECipherProvider cipherProvider,
|
||||
final EncryptionMethod encryptionMethod,
|
||||
final String password) {
|
||||
this.cipherProvider = cipherProvider;
|
||||
this.encryptionMethod = encryptionMethod;
|
||||
this.password = password;
|
||||
this.keyLength = CipherUtility.parseKeyLengthFromAlgorithm(encryptionMethod.getAlgorithm());
|
||||
this.saltLength = CipherUtility.getSaltLengthForAlgorithm(encryptionMethod.getAlgorithm());
|
||||
this.description = String.format("%s Encryption Method [%s] Key Length [%d] Salt Length [%d]",
|
||||
getClass().getSimpleName(),
|
||||
encryptionMethod.getAlgorithm(),
|
||||
keyLength,
|
||||
saltLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cipher for Decryption based on encrypted binary
|
||||
*
|
||||
* @param encryptedBinary Encrypted Binary
|
||||
* @return Cipher for Decryption
|
||||
*/
|
||||
@Override
|
||||
protected Cipher getDecryptionCipher(final byte[] encryptedBinary) {
|
||||
final byte[] salt = readSalt(encryptedBinary);
|
||||
return getCipher(salt, DECRYPT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cipher for Encryption using encoded parameters containing random salt generated for encoding parameters
|
||||
*
|
||||
* @param encodedParameters Binary encoded parameters containing random salt generated from Cipher Provider
|
||||
* @return Cipher for Encryption
|
||||
*/
|
||||
@Override
|
||||
protected Cipher getEncryptionCipher(byte[] encodedParameters) {
|
||||
return getCipher(encodedParameters, ENCRYPT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cipher Binary from encrypted binary
|
||||
*
|
||||
* @param encryptedBinary Encrypted Binary containing cipher binary and other information
|
||||
* @return Cipher Binary for decryption
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getCipherBinary(byte[] encryptedBinary) {
|
||||
return Arrays.copyOfRange(encryptedBinary, saltLength, encryptedBinary.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Encoded Parameters returns a random salt generated from the Cipher Provider
|
||||
*
|
||||
* @return Random Salt for encoded parameters
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected byte[] getEncodedParameters() {
|
||||
final byte[] salt;
|
||||
|
||||
if (cipherProvider instanceof org.apache.nifi.security.util.crypto.NiFiLegacyCipherProvider) {
|
||||
salt = ((org.apache.nifi.security.util.crypto.NiFiLegacyCipherProvider) cipherProvider).generateSalt(encryptionMethod);
|
||||
} else {
|
||||
salt = cipherProvider.generateSalt();
|
||||
}
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
private Cipher getCipher(final byte[] salt, final boolean encrypt) {
|
||||
try {
|
||||
return cipherProvider.getCipher(encryptionMethod, password, salt, keyLength, encrypt);
|
||||
} catch (final Exception e) {
|
||||
final String message = String.format("Failed to get Cipher for Algorithm [%s]", encryptionMethod.getAlgorithm());
|
||||
throw new EncryptionException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readSalt(final byte[] binary) {
|
||||
final byte[] salt = new byte[saltLength];
|
||||
System.arraycopy(binary, ARRAY_START, salt, ARRAY_START, saltLength);
|
||||
return salt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return object equality based on Encryption Method and Password
|
||||
*
|
||||
* @param object Object for comparison
|
||||
* @return Object equality status
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object object) {
|
||||
boolean equals = false;
|
||||
if (this == object) {
|
||||
equals = true;
|
||||
} else if (object instanceof PasswordBasedCipherPropertyEncryptor) {
|
||||
final PasswordBasedCipherPropertyEncryptor encryptor = (PasswordBasedCipherPropertyEncryptor) object;
|
||||
equals = Objects.equals(encryptionMethod, encryptor.encryptionMethod) && Objects.equals(password, encryptor.password);
|
||||
}
|
||||
return equals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return hash code based on Encryption Method and Password
|
||||
*
|
||||
* @return Hash Code based on Encryption Method and Password
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(encryptionMethod, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return String containing object description
|
||||
*
|
||||
* @return Object description
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return description;
|
||||
}
|
||||
}
|
|
@ -16,61 +16,40 @@
|
|||
*/
|
||||
package org.apache.nifi.encrypt;
|
||||
|
||||
import org.apache.nifi.security.util.EncryptionMethod;
|
||||
import org.apache.nifi.security.util.KeyDerivationFunction;
|
||||
|
||||
/**
|
||||
* Property Encryption Method enumerates supported values in addition to {@link org.apache.nifi.security.util.EncryptionMethod}
|
||||
* Property Encryption Method enumerates supported values
|
||||
*/
|
||||
public enum PropertyEncryptionMethod {
|
||||
NIFI_ARGON2_AES_GCM_128(KeyDerivationFunction.ARGON2, EncryptionMethod.AES_GCM,128),
|
||||
NIFI_ARGON2_AES_GCM_256(256),
|
||||
|
||||
NIFI_ARGON2_AES_GCM_256(KeyDerivationFunction.ARGON2, EncryptionMethod.AES_GCM, 256),
|
||||
NIFI_PBKDF2_AES_GCM_256(256);
|
||||
|
||||
NIFI_BCRYPT_AES_GCM_128(KeyDerivationFunction.BCRYPT, EncryptionMethod.AES_GCM, 128),
|
||||
|
||||
NIFI_BCRYPT_AES_GCM_256(KeyDerivationFunction.BCRYPT, EncryptionMethod.AES_GCM, 256),
|
||||
|
||||
NIFI_PBKDF2_AES_GCM_128(KeyDerivationFunction.PBKDF2, EncryptionMethod.AES_GCM, 128),
|
||||
|
||||
NIFI_PBKDF2_AES_GCM_256(KeyDerivationFunction.PBKDF2, EncryptionMethod.AES_GCM, 256),
|
||||
|
||||
NIFI_SCRYPT_AES_GCM_128(KeyDerivationFunction.SCRYPT, EncryptionMethod.AES_GCM, 128),
|
||||
|
||||
NIFI_SCRYPT_AES_GCM_256(KeyDerivationFunction.SCRYPT, EncryptionMethod.AES_GCM, 256);
|
||||
|
||||
private static final int HASH_LENGTH_DIVISOR = 8;
|
||||
|
||||
private final KeyDerivationFunction keyDerivationFunction;
|
||||
|
||||
private final EncryptionMethod encryptionMethod;
|
||||
private static final int BYTE_LENGTH_DIVISOR = 8;
|
||||
|
||||
private final int keyLength;
|
||||
|
||||
private final int hashLength;
|
||||
private final int derivedKeyLength;
|
||||
|
||||
PropertyEncryptionMethod(final KeyDerivationFunction keyDerivationFunction,
|
||||
final EncryptionMethod encryptionMethod,
|
||||
final int keyLength) {
|
||||
this.keyDerivationFunction = keyDerivationFunction;
|
||||
this.encryptionMethod = encryptionMethod;
|
||||
PropertyEncryptionMethod(final int keyLength) {
|
||||
this.keyLength = keyLength;
|
||||
this.hashLength = keyLength / HASH_LENGTH_DIVISOR;
|
||||
}
|
||||
|
||||
public KeyDerivationFunction getKeyDerivationFunction() {
|
||||
return keyDerivationFunction;
|
||||
}
|
||||
|
||||
public EncryptionMethod getEncryptionMethod() {
|
||||
return encryptionMethod;
|
||||
this.derivedKeyLength = keyLength / BYTE_LENGTH_DIVISOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key length in bits
|
||||
*
|
||||
* @return Key length in bites
|
||||
*/
|
||||
public int getKeyLength() {
|
||||
return keyLength;
|
||||
}
|
||||
|
||||
public int getHashLength() {
|
||||
return hashLength;
|
||||
/**
|
||||
* Get derived key length in bytes
|
||||
*
|
||||
* @return Derived key length in bytes
|
||||
*/
|
||||
public int getDerivedKeyLength() {
|
||||
return derivedKeyLength;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,6 @@
|
|||
*/
|
||||
package org.apache.nifi.encrypt;
|
||||
|
||||
import org.apache.nifi.security.util.EncryptionMethod;
|
||||
import org.apache.nifi.security.util.crypto.PBECipherProvider;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -61,24 +58,8 @@ public class PropertyEncryptorBuilder {
|
|||
*/
|
||||
public PropertyEncryptor build() {
|
||||
final PropertyEncryptionMethod propertyEncryptionMethod = findPropertyEncryptionAlgorithm(algorithm);
|
||||
if (propertyEncryptionMethod == null) {
|
||||
return getPasswordBasedCipherPropertyEncryptor();
|
||||
} else {
|
||||
final SecretKey secretKey = SECRET_KEY_PROVIDER.getSecretKey(propertyEncryptionMethod, password);
|
||||
return new KeyedCipherPropertyEncryptor(secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private PasswordBasedCipherPropertyEncryptor getPasswordBasedCipherPropertyEncryptor() {
|
||||
final EncryptionMethod encryptionMethod = findEncryptionMethod(algorithm);
|
||||
if (encryptionMethod.isPBECipher()) {
|
||||
final PBECipherProvider cipherProvider = new org.apache.nifi.security.util.crypto.NiFiLegacyCipherProvider();
|
||||
return new PasswordBasedCipherPropertyEncryptor(cipherProvider, encryptionMethod, password);
|
||||
} else {
|
||||
final String message = String.format("Algorithm [%s] not supported for Sensitive Properties", encryptionMethod.getAlgorithm());
|
||||
throw new UnsupportedOperationException(message);
|
||||
}
|
||||
final SecretKey secretKey = SECRET_KEY_PROVIDER.getSecretKey(propertyEncryptionMethod, password);
|
||||
return new KeyedCipherPropertyEncryptor(secretKey);
|
||||
}
|
||||
|
||||
private PropertyEncryptionMethod findPropertyEncryptionAlgorithm(final String algorithm) {
|
||||
|
@ -91,15 +72,11 @@ public class PropertyEncryptorBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
if (foundPropertyEncryptionMethod == null) {
|
||||
final String message = String.format("Algorithm [%s] not supported for Sensitive Properties", algorithm);
|
||||
throw new EncryptionException(message);
|
||||
}
|
||||
|
||||
return foundPropertyEncryptionMethod;
|
||||
}
|
||||
|
||||
private EncryptionMethod findEncryptionMethod(final String algorithm) {
|
||||
final EncryptionMethod encryptionMethod = EncryptionMethod.forAlgorithm(algorithm);
|
||||
if (encryptionMethod == null) {
|
||||
final String message = String.format("Encryption Method not found for Algorithm [%s]", algorithm);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
return encryptionMethod;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,20 +16,18 @@
|
|||
*/
|
||||
package org.apache.nifi.encrypt;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.security.util.KeyDerivationFunction;
|
||||
import org.apache.nifi.security.util.crypto.Argon2SecureHasher;
|
||||
import org.apache.nifi.security.util.crypto.KeyDerivationBcryptSecureHasher;
|
||||
import org.apache.nifi.security.util.crypto.PBKDF2SecureHasher;
|
||||
import org.apache.nifi.security.util.crypto.ScryptSecureHasher;
|
||||
import org.apache.nifi.security.util.crypto.SecureHasher;
|
||||
import org.apache.nifi.security.crypto.key.DerivedKey;
|
||||
import org.apache.nifi.security.crypto.key.DerivedKeySpec;
|
||||
import org.apache.nifi.security.crypto.key.StandardDerivedKeySpec;
|
||||
import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyParameterSpec;
|
||||
import org.apache.nifi.security.crypto.key.argon2.Argon2DerivedKeyProvider;
|
||||
import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyParameterSpec;
|
||||
import org.apache.nifi.security.crypto.key.pbkdf2.Pbkdf2DerivedKeyProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
@ -38,7 +36,14 @@ import java.util.Objects;
|
|||
class StandardPropertySecretKeyProvider implements PropertySecretKeyProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(StandardPropertySecretKeyProvider.class);
|
||||
|
||||
private static final Charset PASSWORD_CHARSET = StandardCharsets.UTF_8;
|
||||
/** Standard Application Salt supporting deterministic encrypted property comparison */
|
||||
private static final byte[] APPLICATION_SALT = new byte[]{'N', 'i', 'F', 'i', ' ', 'S', 't', 'a', 't', 'i', 'c', ' ', 'S', 'a', 'l', 't'};
|
||||
|
||||
/** Argon2 Parameter Specification configured with settings introduced in 1.12.0 */
|
||||
private static final Argon2DerivedKeyParameterSpec ARGON2_PARAMETER_SPEC = new Argon2DerivedKeyParameterSpec(65536, 5, 8, APPLICATION_SALT);
|
||||
|
||||
/** PBKDF2 Parameter Specification configured with settings introduced in 0.5.0 */
|
||||
private static final Pbkdf2DerivedKeyParameterSpec PBKDF2_PARAMETER_SPEC = new Pbkdf2DerivedKeyParameterSpec(160000, APPLICATION_SALT);
|
||||
|
||||
private static final int MINIMUM_PASSWORD_LENGTH = 12;
|
||||
|
||||
|
@ -46,45 +51,44 @@ class StandardPropertySecretKeyProvider implements PropertySecretKeyProvider {
|
|||
|
||||
private static final String SECRET_KEY_ALGORITHM = "AES";
|
||||
|
||||
private static final Argon2DerivedKeyProvider argon2DerivedKeyProvider = new Argon2DerivedKeyProvider();
|
||||
|
||||
private static final Pbkdf2DerivedKeyProvider pbkdf2DerivedKeyProvider = new Pbkdf2DerivedKeyProvider();
|
||||
|
||||
/**
|
||||
* Get Secret Key using Property Encryption Method with provided password
|
||||
*
|
||||
* @param propertyEncryptionMethod Property Encryption Method
|
||||
* @param password Password used to derive Secret Key
|
||||
* @param password Password used to derive Secret Key
|
||||
* @return Derived Secret Key
|
||||
*/
|
||||
@Override
|
||||
public SecretKey getSecretKey(final PropertyEncryptionMethod propertyEncryptionMethod, final String password) {
|
||||
Objects.requireNonNull(propertyEncryptionMethod, "Property Encryption Method is required");
|
||||
Objects.requireNonNull(password, "Password is required");
|
||||
if (StringUtils.length(password) < MINIMUM_PASSWORD_LENGTH) {
|
||||
if (password.length() < MINIMUM_PASSWORD_LENGTH) {
|
||||
throw new EncryptionException(PASSWORD_LENGTH_MESSAGE);
|
||||
}
|
||||
|
||||
final KeyDerivationFunction keyDerivationFunction = propertyEncryptionMethod.getKeyDerivationFunction();
|
||||
final int keyLength = propertyEncryptionMethod.getKeyLength();
|
||||
LOGGER.debug("Generating [{}-{}] Secret Key using [{}]", SECRET_KEY_ALGORITHM, keyLength, keyDerivationFunction.getKdfName());
|
||||
LOGGER.debug("Generating [{}-{}] Secret Key using [{}]", SECRET_KEY_ALGORITHM, keyLength, propertyEncryptionMethod.name());
|
||||
|
||||
final SecureHasher secureHasher = getSecureHasher(propertyEncryptionMethod);
|
||||
final byte[] passwordBinary = password.getBytes(PASSWORD_CHARSET);
|
||||
final byte[] hash = secureHasher.hashRaw(passwordBinary);
|
||||
return new SecretKeySpec(hash, SECRET_KEY_ALGORITHM);
|
||||
final DerivedKey derivedKey = getDerivedKey(propertyEncryptionMethod, password);
|
||||
return new SecretKeySpec(derivedKey.getEncoded(), SECRET_KEY_ALGORITHM);
|
||||
}
|
||||
|
||||
private static SecureHasher getSecureHasher(final PropertyEncryptionMethod propertyEncryptionMethod) {
|
||||
final KeyDerivationFunction keyDerivationFunction = propertyEncryptionMethod.getKeyDerivationFunction();
|
||||
final int hashLength = propertyEncryptionMethod.getHashLength();
|
||||
private DerivedKey getDerivedKey(final PropertyEncryptionMethod propertyEncryptionMethod, final String password) {
|
||||
final char[] characters = password.toCharArray();
|
||||
final int derivedKeyLength = propertyEncryptionMethod.getDerivedKeyLength();
|
||||
|
||||
if (KeyDerivationFunction.ARGON2.equals(keyDerivationFunction)) {
|
||||
return new Argon2SecureHasher(hashLength);
|
||||
} else if (KeyDerivationFunction.BCRYPT.equals(keyDerivationFunction)) {
|
||||
return new KeyDerivationBcryptSecureHasher(hashLength);
|
||||
} else if (KeyDerivationFunction.PBKDF2.equals(keyDerivationFunction)) {
|
||||
return new PBKDF2SecureHasher(hashLength);
|
||||
} else if (KeyDerivationFunction.SCRYPT.equals(keyDerivationFunction)) {
|
||||
return new ScryptSecureHasher(hashLength);
|
||||
if (PropertyEncryptionMethod.NIFI_ARGON2_AES_GCM_256 == propertyEncryptionMethod) {
|
||||
final DerivedKeySpec<Argon2DerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(characters, derivedKeyLength, SECRET_KEY_ALGORITHM, ARGON2_PARAMETER_SPEC);
|
||||
return argon2DerivedKeyProvider.getDerivedKey(derivedKeySpec);
|
||||
} else if (PropertyEncryptionMethod.NIFI_PBKDF2_AES_GCM_256 == propertyEncryptionMethod) {
|
||||
final DerivedKeySpec<Pbkdf2DerivedKeyParameterSpec> derivedKeySpec = new StandardDerivedKeySpec<>(characters, derivedKeyLength, SECRET_KEY_ALGORITHM, PBKDF2_PARAMETER_SPEC);
|
||||
return pbkdf2DerivedKeyProvider.getDerivedKey(derivedKeySpec);
|
||||
} else {
|
||||
final String message = String.format("Key Derivation Function [%s] not supported", keyDerivationFunction.getKdfName());
|
||||
final String message = String.format("Property Encryption Method [%s] not supported", propertyEncryptionMethod);
|
||||
throw new EncryptionException(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.apache.nifi.encrypt;
|
|||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -40,7 +39,7 @@ public class KeyedCipherPropertyEncryptorTest {
|
|||
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
|
||||
private static final byte[] STATIC_KEY = StringUtils.repeat("KEY", 8).getBytes(DEFAULT_CHARSET);
|
||||
private static final byte[] STATIC_KEY = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6};
|
||||
|
||||
private static final SecretKey SECRET_KEY = new SecretKeySpec(STATIC_KEY, KEY_ALGORITHM);
|
||||
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.encrypt;
|
||||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.nifi.security.util.EncryptionMethod;
|
||||
import org.apache.nifi.security.util.crypto.PBECipherProvider;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class PasswordBasedCipherPropertyEncryptorTest {
|
||||
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
private static final String PROPERTY = String.class.getName();
|
||||
|
||||
private static final int ENCRYPTED_BINARY_LENGTH = 48;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static final PBECipherProvider CIPHER_PROVIDER = new org.apache.nifi.security.util.crypto.NiFiLegacyCipherProvider();
|
||||
|
||||
private static final EncryptionMethod ENCRYPTION_METHOD = EncryptionMethod.MD5_256AES;
|
||||
|
||||
private static final String PASSWORD = String.class.getName();
|
||||
|
||||
private PasswordBasedCipherPropertyEncryptor encryptor;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
encryptor = new PasswordBasedCipherPropertyEncryptor(CIPHER_PROVIDER, ENCRYPTION_METHOD, PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptDecrypt() {
|
||||
final String encrypted = encryptor.encrypt(PROPERTY);
|
||||
final String decrypted = encryptor.decrypt(encrypted);
|
||||
assertEquals(PROPERTY, decrypted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptHexadecimalEncoded() throws DecoderException {
|
||||
final String encrypted = encryptor.encrypt(PROPERTY);
|
||||
final byte[] decoded = Hex.decodeHex(encrypted);
|
||||
assertEquals(ENCRYPTED_BINARY_LENGTH, decoded.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecryptEncryptionException() {
|
||||
final String encodedProperty = Hex.encodeHexString(PROPERTY.getBytes(DEFAULT_CHARSET));
|
||||
assertThrows(EncryptionException.class, () -> encryptor.decrypt(encodedProperty));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsHashCode() {
|
||||
final PasswordBasedCipherPropertyEncryptor equivalentEncryptor = new PasswordBasedCipherPropertyEncryptor(CIPHER_PROVIDER, ENCRYPTION_METHOD, PASSWORD);
|
||||
assertEquals(encryptor, equivalentEncryptor);
|
||||
assertEquals(encryptor.hashCode(), equivalentEncryptor.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsHashCodeDifferentPassword() {
|
||||
final PasswordBasedCipherPropertyEncryptor differentEncryptor = new PasswordBasedCipherPropertyEncryptor(CIPHER_PROVIDER, ENCRYPTION_METHOD, String.class.getSimpleName());
|
||||
assertNotEquals(encryptor, differentEncryptor);
|
||||
assertNotEquals(encryptor.hashCode(), differentEncryptor.hashCode());
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.encrypt;
|
||||
|
||||
import org.apache.nifi.security.util.EncryptionMethod;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class PropertyEncryptorFactoryTest {
|
||||
private static final EncryptionMethod ENCRYPTION_METHOD = EncryptionMethod.MD5_256AES;
|
||||
|
||||
@Test
|
||||
public void testGetPropertyEncryptorUnsupportedEncryptionMethod() {
|
||||
final Properties properties = new Properties();
|
||||
properties.setProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM, EncryptionMethod.AES_CBC_NO_PADDING.getAlgorithm());
|
||||
properties.setProperty(NiFiProperties.SENSITIVE_PROPS_KEY, String.class.getName());
|
||||
final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> PropertyEncryptorFactory.getPropertyEncryptor(niFiProperties));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPropertyEncryptorPropertiesBlankPassword() {
|
||||
final Properties properties = new Properties();
|
||||
properties.setProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM, ENCRYPTION_METHOD.getAlgorithm());
|
||||
properties.setProperty(NiFiProperties.SENSITIVE_PROPS_KEY, StringUtils.EMPTY);
|
||||
final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> PropertyEncryptorFactory.getPropertyEncryptor(niFiProperties));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPropertyEncryptorPropertiesKeyedCipherPropertyEncryptor() {
|
||||
final Properties properties = new Properties();
|
||||
properties.setProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM, PropertyEncryptionMethod.NIFI_ARGON2_AES_GCM_256.toString());
|
||||
properties.setProperty(NiFiProperties.SENSITIVE_PROPS_KEY, String.class.getName());
|
||||
final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
|
||||
|
||||
final PropertyEncryptor encryptor = PropertyEncryptorFactory.getPropertyEncryptor(niFiProperties);
|
||||
assertNotNull(encryptor);
|
||||
assertEquals(KeyedCipherPropertyEncryptor.class, encryptor.getClass());
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ public class StandardPropertySecretKeyProviderTest {
|
|||
final SecretKey secretKey = provider.getSecretKey(propertyEncryptionMethod, SEED);
|
||||
final int secretKeyLength = secretKey.getEncoded().length;
|
||||
final String message = String.format("Method [%s] Key Length not matched", propertyEncryptionMethod);
|
||||
assertEquals(propertyEncryptionMethod.getHashLength(), secretKeyLength, message);
|
||||
assertEquals(propertyEncryptionMethod.getDerivedKeyLength(), secretKeyLength, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,10 +34,8 @@ public class SecureHasherFactory {
|
|||
|
||||
static {
|
||||
registeredSecureHashers = new HashMap<>();
|
||||
registeredSecureHashers.put(KeyDerivationFunction.PBKDF2, PBKDF2SecureHasher.class);
|
||||
registeredSecureHashers.put(KeyDerivationFunction.BCRYPT, BcryptSecureHasher.class);
|
||||
registeredSecureHashers.put(KeyDerivationFunction.SCRYPT, ScryptSecureHasher.class);
|
||||
registeredSecureHashers.put(KeyDerivationFunction.ARGON2, Argon2SecureHasher.class);
|
||||
registeredSecureHashers.put(KeyDerivationFunction.PBKDF2, PBKDF2SecureHasher.class);
|
||||
}
|
||||
|
||||
public static SecureHasher getSecureHasher(final String algorithm) {
|
||||
|
|
|
@ -26,46 +26,22 @@ public class SecureHasherFactoryTest {
|
|||
|
||||
@Test
|
||||
public void testSecureHasherFactoryArgon2() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("NIFI_ARGON2_AES_GCM_128");
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("NIFI_ARGON2_AES_GCM_256");
|
||||
assertEquals(Argon2SecureHasher.class, hasher.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureHasherFactoryPBKDF2() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("NIFI_PBKDF2_AES_GCM_128");
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("NIFI_PBKDF2_AES_GCM_256");
|
||||
assertEquals(PBKDF2SecureHasher.class, hasher.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureHasherFactoryBCrypt() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("NIFI_BCRYPT_AES_GCM_128");
|
||||
assertEquals(BcryptSecureHasher.class, hasher.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureHasherFactorySCrypt() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("NIFI_SCRYPT_AES_GCM_128");
|
||||
assertEquals(ScryptSecureHasher.class, hasher.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureHasherFactoryArgon2ShortName() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("ARGON2");
|
||||
assertEquals(Argon2SecureHasher.class, hasher.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureHasherFactorySCryptShortName() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("SCRYPT");
|
||||
assertEquals(ScryptSecureHasher.class, hasher.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureHasherFactoryLowerCaseName() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("scrypt");
|
||||
assertEquals(ScryptSecureHasher.class, hasher.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureHasherFactoryArgon2SimilarName() {
|
||||
SecureHasher hasher = SecureHasherFactory.getSecureHasher("ARGON_2");
|
||||
|
|
|
@ -83,10 +83,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -68,10 +68,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.apache.nifi.encrypt;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.nifi.util.NiFiProperties;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -38,7 +37,7 @@ public class PropertyEncryptorFactory {
|
|||
final String algorithm = properties.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM);
|
||||
String password = properties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY);
|
||||
|
||||
if (StringUtils.isBlank(password)) {
|
||||
if (password == null || password.isEmpty()) {
|
||||
throw new IllegalArgumentException(KEY_REQUIRED);
|
||||
}
|
||||
|
|
@ -33,7 +33,6 @@ import org.apache.nifi.controller.ReportingTaskNode;
|
|||
import org.apache.nifi.controller.repository.FlowFileEventRepository;
|
||||
import org.apache.nifi.controller.status.history.StatusHistoryRepository;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptor;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorFactory;
|
||||
import org.apache.nifi.nar.ExtensionDiscoveringManager;
|
||||
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
|
||||
import org.apache.nifi.nar.SystemBundle;
|
||||
|
@ -80,7 +79,7 @@ public class TestStandardReportingContext {
|
|||
otherProps.put("nifi.remote.input.socket.port", "");
|
||||
otherProps.put("nifi.remote.input.secure", "");
|
||||
nifiProperties = NiFiProperties.createBasicNiFiProperties(propsFile, otherProps);
|
||||
encryptor = PropertyEncryptorFactory.getPropertyEncryptor(nifiProperties);
|
||||
encryptor = Mockito.mock(PropertyEncryptor.class);
|
||||
|
||||
// use the system bundle
|
||||
systemBundle = SystemBundle.create(nifiProperties);
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.apache.nifi.controller.ProcessorNode;
|
|||
import org.apache.nifi.controller.repository.FlowFileEventRepository;
|
||||
import org.apache.nifi.controller.status.history.StatusHistoryRepository;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptor;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorFactory;
|
||||
import org.apache.nifi.nar.ExtensionDiscoveringManager;
|
||||
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
|
||||
import org.apache.nifi.nar.SystemBundle;
|
||||
|
@ -88,7 +87,7 @@ public class StandardFlowSerializerTest {
|
|||
otherProps.put("nifi.remote.input.socket.port", "");
|
||||
otherProps.put("nifi.remote.input.secure", "");
|
||||
final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(propsFile, otherProps);
|
||||
final PropertyEncryptor encryptor = PropertyEncryptorFactory.getPropertyEncryptor(nifiProperties);
|
||||
final PropertyEncryptor encryptor = Mockito.mock(PropertyEncryptor.class);
|
||||
|
||||
// use the system bundle
|
||||
systemBundle = SystemBundle.create(nifiProperties);
|
||||
|
|
|
@ -70,10 +70,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -77,8 +77,8 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./target/flowcontrollertest/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.key=SENSITIVE_PROPS_KEY
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -141,12 +141,6 @@ nifi.web.max.header.size=16 KB
|
|||
nifi.web.proxy.context.path=
|
||||
nifi.web.proxy.host=
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.key.protected=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -141,12 +141,6 @@ nifi.web.max.header.size=16 KB
|
|||
nifi.web.proxy.context.path=
|
||||
nifi.web.proxy.host=
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.key.protected=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -74,8 +74,8 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./target/lifecycletest/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.key=SENSITIVE_PROPS_KEY
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -69,8 +69,8 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.key=SENSITIVE_PROPS_KEY
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -74,8 +74,8 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./target/standardflowserializertest/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.key=SENSITIVE_PROPS_KEY
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -74,8 +74,8 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./target/standardflowsynchronizerspec/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.key=SENSITIVE_PROPS_KEY
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -74,8 +74,8 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./target/standardprocessschedulertest/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.key=SENSITIVE_PROPS_KEY
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
|
|
|
@ -71,10 +71,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -71,10 +71,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -71,10 +71,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -71,10 +71,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=
|
||||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=key
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -129,12 +129,6 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./work/jetty
|
||||
nifi.web.jetty.threads=200
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.key.protected=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -43,7 +43,7 @@ import org.apache.nifi.diagnostics.DiagnosticsFactory;
|
|||
import org.apache.nifi.diagnostics.ThreadDumpTask;
|
||||
import org.apache.nifi.diagnostics.bootstrap.BootstrapDiagnosticsFactory;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptor;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorFactory;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
|
||||
import org.apache.nifi.events.VolatileBulletinRepository;
|
||||
import org.apache.nifi.nar.ExtensionManagerHolder;
|
||||
import org.apache.nifi.nar.ExtensionMapping;
|
||||
|
@ -127,7 +127,9 @@ public class HeadlessNiFiServer implements NiFiServer {
|
|||
}
|
||||
};
|
||||
|
||||
PropertyEncryptor encryptor = PropertyEncryptorFactory.getPropertyEncryptor(props);
|
||||
final String propertiesKey = props.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY);
|
||||
final String propertiesAlgorithm = props.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM);
|
||||
final PropertyEncryptor encryptor = new PropertyEncryptorBuilder(propertiesKey).setAlgorithm(propertiesAlgorithm).build();
|
||||
VariableRegistry variableRegistry = new FileBasedVariableRegistry(props.getVariableRegistryPropertiesPaths());
|
||||
BulletinRepository bulletinRepository = new VolatileBulletinRepository();
|
||||
|
||||
|
|
|
@ -129,12 +129,6 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./work/jetty
|
||||
nifi.web.jetty.threads=200
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.key.protected=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -132,7 +132,7 @@ nifi.web.jetty.threads=200
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=dQU402Mz4J+t+e18||6+ictR0Nssq3/rR/d8fq5CFAKmpakr9jCyPIJYxG7n6D86gxsu2TRp4M48ugUw==
|
||||
nifi.sensitive.props.key.protected=aes/gcm/256
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=nifi.ui.banner.text
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -128,11 +128,6 @@ nifi.web.https.port=
|
|||
nifi.web.jetty.working.directory=./target/work/jetty
|
||||
nifi.web.jetty.threads=200
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=
|
||||
nifi.security.keystoreType=
|
||||
nifi.security.keystorePasswd=
|
||||
|
|
|
@ -34,8 +34,6 @@ nifi.flowcontroller.graceful.shutdown.seconds=10
|
|||
nifi.nar.library.directory=./lib
|
||||
nifi.nar.working.directory=./work/nar/
|
||||
nifi.flowservice.writedelay.seconds=2
|
||||
nifi.sensitive.props.key=REPLACE_ME
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.h2.repository.maxmemoryrows=100000
|
||||
nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
|
||||
nifi.h2.max.connections=20
|
||||
|
|
|
@ -84,10 +84,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=8443
|
||||
nifi.web.jetty.working.directory=target/test-classes/access-control/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=REPLACE_ME
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=target/test-classes/access-control/keystore.jks
|
||||
nifi.security.keystoreType=JKS
|
||||
nifi.security.keystorePasswd=passwordpassword
|
||||
|
|
|
@ -84,10 +84,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=8443
|
||||
nifi.web.jetty.working.directory=target/test-classes/access-control/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=REPLACE_ME
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=target/test-classes/access-control/keystore.jks
|
||||
nifi.security.keystoreType=JKS
|
||||
nifi.security.keystorePasswd=passwordpassword
|
||||
|
|
|
@ -90,10 +90,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=8443
|
||||
nifi.web.jetty.working.directory=target/test-classes/access-control/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=REPLACE_ME
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=target/test-classes/access-control/keystore.jks
|
||||
nifi.security.keystoreType=JKS
|
||||
nifi.security.keystorePasswd=passwordpassword
|
||||
|
|
|
@ -84,10 +84,6 @@ nifi.web.https.host=
|
|||
nifi.web.https.port=8443
|
||||
nifi.web.jetty.working.directory=target/test-classes/access-control/jetty
|
||||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=REPLACE_ME
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
|
||||
nifi.security.keystore=target/test-classes/access-control/keystore.jks
|
||||
nifi.security.keystoreType=JKS
|
||||
nifi.security.keystorePasswd=passwordpassword
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.apache.nifi.tests.system.clustering;
|
|||
import org.apache.nifi.controller.serialization.FlowEncodingVersion;
|
||||
import org.apache.nifi.controller.serialization.FlowFromDOMFactory;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptor;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorFactory;
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
|
||||
import org.apache.nifi.tests.system.InstanceConfiguration;
|
||||
import org.apache.nifi.tests.system.NiFiInstance;
|
||||
import org.apache.nifi.tests.system.NiFiInstanceFactory;
|
||||
|
@ -246,7 +246,10 @@ public class JoinClusterWithDifferentFlow extends NiFiSystemIT {
|
|||
|
||||
private PropertyEncryptor createEncryptorFromProperties(Properties properties) {
|
||||
final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
|
||||
return PropertyEncryptorFactory.getPropertyEncryptor(niFiProperties);
|
||||
|
||||
final String propertiesKey = niFiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY);
|
||||
final String propertiesAlgorithm = niFiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM);
|
||||
return new PropertyEncryptorBuilder(propertiesKey).setAlgorithm(propertiesAlgorithm).build();
|
||||
}
|
||||
|
||||
private String readFlow(final File file) throws IOException {
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.apache.commons.cli.Options
|
|||
import org.apache.commons.cli.ParseException
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import org.apache.nifi.encrypt.PropertyEncryptor
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorFactory
|
||||
import org.apache.nifi.encrypt.PropertyEncryptorBuilder
|
||||
import org.apache.nifi.flow.encryptor.FlowEncryptor
|
||||
import org.apache.nifi.flow.encryptor.StandardFlowEncryptor
|
||||
import org.apache.nifi.properties.scheme.ProtectionScheme
|
||||
|
@ -205,7 +205,7 @@ class ConfigEncryptionTool {
|
|||
private static final String XML_DECLARATION_REGEX = /<\?xml version="1.0" encoding="UTF-8"\?>/
|
||||
private static final String WRAPPED_FLOW_XML_CIPHER_TEXT_REGEX = /enc\{[a-fA-F0-9]+?\}/
|
||||
|
||||
private static final String DEFAULT_FLOW_ALGORITHM = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"
|
||||
private static final String DEFAULT_FLOW_ALGORITHM = "NIFI_PBKDF2_AES_GCM_256"
|
||||
|
||||
private static final Map<String, String> PROPERTY_KEY_MAP = [
|
||||
"nifi.security.keystore" : "keystore",
|
||||
|
@ -711,7 +711,7 @@ class ConfigEncryptionTool {
|
|||
* @param flowXmlContent the original flow.xml.gz as an input stream
|
||||
* @param existingFlowPassword the existing value of nifi.sensitive.props.key (not a raw key, but rather a password)
|
||||
* @param newFlowPassword the password to use to for encryption (not a raw key, but rather a password)
|
||||
* @param existingAlgorithm the KDF algorithm to use (defaults to PBEWITHMD5AND256BITAES-CBC-OPENSSL)
|
||||
* @param existingAlgorithm the KDF algorithm to use
|
||||
* @param existingProvider the {@link java.security.Provider} to use (defaults to BC)
|
||||
* @return the encrypted XML content as an InputStream
|
||||
*/
|
||||
|
@ -719,18 +719,8 @@ class ConfigEncryptionTool {
|
|||
File tempFlowXmlFile = new File(getTemporaryFlowXmlFile(outputFlowXmlPath).toString())
|
||||
final OutputStream flowOutputStream = getFlowOutputStream(tempFlowXmlFile, flowXmlContent instanceof GZIPInputStream)
|
||||
|
||||
NiFiProperties inputProperties = NiFiProperties.createBasicNiFiProperties("", [
|
||||
(NiFiProperties.SENSITIVE_PROPS_KEY): existingFlowPassword,
|
||||
(NiFiProperties.SENSITIVE_PROPS_ALGORITHM): existingAlgorithm
|
||||
])
|
||||
|
||||
NiFiProperties outputProperties = NiFiProperties.createBasicNiFiProperties("", [
|
||||
(NiFiProperties.SENSITIVE_PROPS_KEY): newFlowPassword,
|
||||
(NiFiProperties.SENSITIVE_PROPS_ALGORITHM): newAlgorithm
|
||||
])
|
||||
|
||||
final PropertyEncryptor inputEncryptor = PropertyEncryptorFactory.getPropertyEncryptor(inputProperties)
|
||||
final PropertyEncryptor outputEncryptor = PropertyEncryptorFactory.getPropertyEncryptor(outputProperties)
|
||||
final PropertyEncryptor inputEncryptor = new PropertyEncryptorBuilder(existingFlowPassword).setAlgorithm(existingAlgorithm).build()
|
||||
final PropertyEncryptor outputEncryptor = new PropertyEncryptorBuilder(newFlowPassword).setAlgorithm(newAlgorithm).build()
|
||||
|
||||
final FlowEncryptor flowEncryptor = new StandardFlowEncryptor()
|
||||
flowEncryptor.processFlow(flowXmlContent, flowOutputStream, inputEncryptor, outputEncryptor)
|
||||
|
|
|
@ -22,12 +22,10 @@ import org.apache.commons.cli.CommandLineParser
|
|||
import org.apache.commons.cli.DefaultParser
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import org.apache.nifi.security.util.EncryptionMethod
|
||||
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException
|
||||
import org.apache.nifi.util.NiFiProperties
|
||||
import org.apache.nifi.util.console.TextDevice
|
||||
import org.apache.nifi.util.console.TextDevices
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.junit.After
|
||||
import org.junit.AfterClass
|
||||
import org.junit.Assume
|
||||
|
@ -52,7 +50,6 @@ import java.nio.charset.StandardCharsets
|
|||
import java.nio.file.Files
|
||||
import java.nio.file.attribute.PosixFilePermission
|
||||
import java.security.KeyException
|
||||
import java.security.Security
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
class ConfigEncryptionToolTest extends GroovyLogTestCase {
|
||||
|
@ -71,7 +68,6 @@ class ConfigEncryptionToolTest extends GroovyLogTestCase {
|
|||
private static final String KEY_HEX_256 = KEY_HEX_128 * 2
|
||||
public static final String KEY_HEX = isUnlimitedStrengthCryptoAvailable() ? KEY_HEX_256 : KEY_HEX_128
|
||||
private static final String PASSWORD = "thisIsABadPassword"
|
||||
private static final String ANOTHER_PASSWORD = "thisIsAnotherBadPassword"
|
||||
|
||||
private static final SensitivePropertyProviderFactory DEFAULT_PROVIDER_FACTORY = StandardSensitivePropertyProviderFactory.withKey(KEY_HEX)
|
||||
|
||||
|
@ -95,14 +91,11 @@ class ConfigEncryptionToolTest extends GroovyLogTestCase {
|
|||
private final String PASSWORD_PROP_REGEX = "<property[^>]* name=\".* Password\""
|
||||
private final String SECRET_PROP_REGEX = "<property[^>]* name=\".* Secret\""
|
||||
|
||||
private static final EncryptionMethod DEFAULT_ENCRYPTION_METHOD = EncryptionMethod.MD5_256AES
|
||||
private static final String WFXCTR = ConfigEncryptionTool.WRAPPED_FLOW_XML_CIPHER_TEXT_REGEX
|
||||
private final String DEFAULT_LEGACY_SENSITIVE_PROPS_KEY = "nififtw!"
|
||||
|
||||
@BeforeClass
|
||||
static void setUpOnce() throws Exception {
|
||||
Assume.assumeTrue("Test only runs on *nix", !SystemUtils.IS_OS_WINDOWS)
|
||||
Security.addProvider(new BouncyCastleProvider())
|
||||
|
||||
logger.metaClass.methodMissing = { String name, args ->
|
||||
logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
|
||||
|
@ -3410,800 +3403,6 @@ class ConfigEncryptionToolTest extends GroovyLogTestCase {
|
|||
assert msg == "In order to migrate a flow.xml.gz, a nifi.properties file must also be specified via '-n'/'--niFiProperties'." as String
|
||||
}
|
||||
|
||||
// TODO: Test different algs/providers
|
||||
// TODO: Test reading sensitive props key from console
|
||||
// TODO: All combo scenarios
|
||||
@Test
|
||||
void testShouldPerformFullOperationOnFlowXmlWithoutEncryptedNiFiProperties() {
|
||||
// Arrange
|
||||
exit.expectSystemExitWithStatus(0)
|
||||
|
||||
File tmpDir = setupTmpDir()
|
||||
|
||||
File emptyKeyFile = new File("src/test/resources/bootstrap_with_empty_root_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
|
||||
|
||||
// Not "handling" NFP, so update in place (not source test resource)
|
||||
String niFiPropertiesTemplatePath = "src/test/resources/nifi_default.properties"
|
||||
File niFiPropertiesFile = new File(niFiPropertiesTemplatePath)
|
||||
|
||||
File workingNiFiPropertiesFile = new File("target/tmp/tmp-nifi.properties")
|
||||
workingNiFiPropertiesFile.delete()
|
||||
Files.copy(niFiPropertiesFile.toPath(), workingNiFiPropertiesFile.toPath())
|
||||
|
||||
File flowXmlFile = new File("src/test/resources/flow.xml.gz")
|
||||
File workingFlowXmlFile = new File("target/tmp/tmp-flow.xml.gz")
|
||||
workingFlowXmlFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFlowXmlFile.toPath())
|
||||
|
||||
// Read the uncompressed version to compare later
|
||||
File originalFlowXmlFile = new File("src/test/resources/flow.xml")
|
||||
final String ORIGINAL_FLOW_XML_CONTENT = originalFlowXmlFile.text
|
||||
def originalFlowCipherTexts = ORIGINAL_FLOW_XML_CONTENT.findAll(WFXCTR)
|
||||
final int CIPHER_TEXT_COUNT = originalFlowCipherTexts.size()
|
||||
|
||||
NiFiProperties inputProperties = new NiFiPropertiesLoader().load(workingNiFiPropertiesFile)
|
||||
logger.info("Loaded ${inputProperties.size()} properties from input file")
|
||||
ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
|
||||
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
|
||||
logger.info("Original sensitive values: ${originalSensitiveValues}")
|
||||
|
||||
String newFlowPassword = FLOW_PASSWORD
|
||||
|
||||
String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-x", "-v", "-s", newFlowPassword]
|
||||
|
||||
exit.checkAssertionAfterwards(new Assertion() {
|
||||
void checkAssertion() {
|
||||
final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines()
|
||||
logger.info("Updated nifi.properties:")
|
||||
logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
|
||||
|
||||
// Check that the output values for everything is the same except the sensitive props key
|
||||
NiFiProperties updatedProperties = new NiFiPropertiesLoader().loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
assert updatedProperties.size() == inputProperties.size()
|
||||
assert updatedProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY) == newFlowPassword
|
||||
originalSensitiveValues.every { String key, String originalValue ->
|
||||
if (key != NiFiProperties.SENSITIVE_PROPS_KEY) {
|
||||
assert updatedProperties.getProperty(key) == originalValue
|
||||
}
|
||||
}
|
||||
|
||||
// Check that bootstrap.conf did not change
|
||||
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()
|
||||
|
||||
// Verify the flow definition
|
||||
def verifyTool = new ConfigEncryptionTool()
|
||||
verifyTool.isVerbose = true
|
||||
InputStream updatedFlowXmlContent = verifyTool.loadFlowXml(workingFlowXmlFile.path)
|
||||
|
||||
// Check that the flow.xml.gz content changed
|
||||
assert updatedFlowXmlContent != ORIGINAL_FLOW_XML_CONTENT
|
||||
|
||||
// Verify that the cipher texts decrypt correctly
|
||||
logger.info("Original flow.xml.gz cipher texts: ${originalFlowCipherTexts}")
|
||||
|
||||
def updatedFlowCipherTexts = findFieldsInStream(updatedFlowXmlContent, WFXCTR)
|
||||
logger.info("Updated flow.xml.gz cipher texts: ${updatedFlowCipherTexts}")
|
||||
assert updatedFlowCipherTexts.size() == CIPHER_TEXT_COUNT
|
||||
}
|
||||
})
|
||||
|
||||
// Act
|
||||
ConfigEncryptionTool.main(args)
|
||||
logger.info("Invoked #main with ${args.join(" ")}")
|
||||
|
||||
// Assert
|
||||
|
||||
// Assertions defined above
|
||||
}
|
||||
|
||||
/**
|
||||
* In this scenario, the nifi.properties is not encrypted and the flow.xml.gz is "migrated" from Key X to the same key (the default key).
|
||||
*/
|
||||
@Test
|
||||
void testShouldPerformFullOperationOnFlowXmlWithSameSensitivePropsKey() {
|
||||
exit.expectSystemExitWithStatus(0)
|
||||
|
||||
File emptyKeyFile = new File("src/test/resources/bootstrap_with_empty_root_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)
|
||||
}
|
||||
assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
|
||||
|
||||
final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
|
||||
|
||||
// Not "handling" NFP, so update in place (not source test resource)
|
||||
String niFiPropertiesTemplatePath = "src/test/resources/nifi_default.properties"
|
||||
File niFiPropertiesFile = new File(niFiPropertiesTemplatePath)
|
||||
|
||||
File workingNiFiPropertiesFile = new File("target/tmp/tmp-nifi.properties")
|
||||
workingNiFiPropertiesFile.delete()
|
||||
Files.copy(niFiPropertiesFile.toPath(), workingNiFiPropertiesFile.toPath())
|
||||
|
||||
File flowXmlFile = new File("src/test/resources/flow_default_key.xml.gz")
|
||||
File workingFlowXmlFile = new File("target/tmp/tmp-flow.xml.gz")
|
||||
workingFlowXmlFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFlowXmlFile.toPath())
|
||||
|
||||
// Read the uncompressed version to compare later
|
||||
File originalFlowXmlFile = new File("src/test/resources/flow_default_key.xml")
|
||||
final String ORIGINAL_FLOW_XML_CONTENT = originalFlowXmlFile.text
|
||||
def originalFlowCipherTexts = ORIGINAL_FLOW_XML_CONTENT.findAll(WFXCTR)
|
||||
final int CIPHER_TEXT_COUNT = originalFlowCipherTexts.size()
|
||||
|
||||
NiFiProperties inputProperties = new NiFiPropertiesLoader().load(workingNiFiPropertiesFile)
|
||||
ProtectedNiFiProperties protectedInputProperties = new ProtectedNiFiProperties(inputProperties)
|
||||
def originalSensitiveValues = protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): protectedInputProperties.getProperty(key)] }
|
||||
|
||||
String newFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY
|
||||
|
||||
String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-x", "-v", "-s", newFlowPassword]
|
||||
|
||||
exit.checkAssertionAfterwards(new Assertion() {
|
||||
void checkAssertion() {
|
||||
final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines()
|
||||
|
||||
// Check that the output values for everything is the same including the sensitive props key
|
||||
NiFiProperties updatedProperties = new NiFiPropertiesLoader().loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
assert updatedProperties.size() == inputProperties.size()
|
||||
originalSensitiveValues.every { String key, String originalValue ->
|
||||
assert updatedProperties.getProperty(key) == originalValue
|
||||
}
|
||||
|
||||
// Check that bootstrap.conf did not change
|
||||
final List<String> updatedBootstrapLines = bootstrapFile.readLines()
|
||||
String updatedKeyLine = updatedBootstrapLines.find {
|
||||
it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
|
||||
}
|
||||
|
||||
assert updatedKeyLine == EXPECTED_KEY_LINE
|
||||
assert originalBootstrapLines.size() == updatedBootstrapLines.size()
|
||||
|
||||
// Verify the flow definition
|
||||
def verifyTool = new ConfigEncryptionTool()
|
||||
verifyTool.isVerbose = true
|
||||
InputStream migratedFlowXmlContent = verifyTool.loadFlowXml(workingFlowXmlFile.path)
|
||||
|
||||
// Check that the flow.xml.gz cipher texts did change (new salt)
|
||||
assert migratedFlowXmlContent != ORIGINAL_FLOW_XML_CONTENT
|
||||
|
||||
// Verify that the cipher texts decrypt correctly
|
||||
def migratedFlowCipherTexts = findFieldsInStream(migratedFlowXmlContent, WFXCTR)
|
||||
assert migratedFlowCipherTexts.size() == CIPHER_TEXT_COUNT
|
||||
}
|
||||
})
|
||||
|
||||
ConfigEncryptionTool.main(args)
|
||||
}
|
||||
|
||||
/**
|
||||
* In this scenario, the nifi.properties file has a sensitive key value which is already encrypted. The goal is to provide a new provide a new sensitive key value, perform the migration of the flow.xml.gz, and update nifi.properties with a new encrypted sensitive key value without modifying any other nifi.properties values.
|
||||
*/
|
||||
@Test
|
||||
void testShouldPerformFullOperationOnFlowXmlWithPreviouslyEncryptedNiFiProperties() {
|
||||
// Arrange
|
||||
exit.expectSystemExitWithStatus(0)
|
||||
|
||||
File tmpDir = setupTmpDir()
|
||||
|
||||
File passwordKeyFile = new File("src/test/resources/bootstrap_with_root_key_password_128.conf")
|
||||
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
|
||||
bootstrapFile.delete()
|
||||
|
||||
Files.copy(passwordKeyFile.toPath(), bootstrapFile.toPath())
|
||||
final List<String> originalBootstrapLines = bootstrapFile.readLines()
|
||||
String originalKeyLine = originalBootstrapLines.find {
|
||||
it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
|
||||
}
|
||||
final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX_128
|
||||
logger.info("Original key line from bootstrap.conf: ${originalKeyLine}")
|
||||
assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX_128
|
||||
|
||||
// Not "handling" NFP, so update in place (not source test resource)
|
||||
String niFiPropertiesTemplatePath = "src/test/resources/nifi_with_few_sensitive_properties_protected_aes_password_128.properties"
|
||||
File niFiPropertiesFile = new File(niFiPropertiesTemplatePath)
|
||||
|
||||
File workingNiFiPropertiesFile = new File("target/tmp/tmp-nifi.properties")
|
||||
workingNiFiPropertiesFile.delete()
|
||||
Files.copy(niFiPropertiesFile.toPath(), workingNiFiPropertiesFile.toPath())
|
||||
|
||||
// Use a flow definition that was encrypted with the hard-coded default SP key
|
||||
File flowXmlFile = new File("src/test/resources/flow.xml.gz")
|
||||
File workingFlowXmlFile = new File("target/tmp/tmp-flow.xml.gz")
|
||||
workingFlowXmlFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFlowXmlFile.toPath())
|
||||
|
||||
// Read the uncompressed version to compare later
|
||||
File originalFlowXmlFile = new File("src/test/resources/flow_default_key.xml")
|
||||
final String ORIGINAL_FLOW_XML_CONTENT = originalFlowXmlFile.text
|
||||
def originalFlowCipherTexts = ORIGINAL_FLOW_XML_CONTENT.findAll(WFXCTR)
|
||||
final int CIPHER_TEXT_COUNT = originalFlowCipherTexts.size()
|
||||
|
||||
// Load both the encrypted and decrypted properties to compare later
|
||||
NiFiPropertiesLoader niFiPropertiesLoader = NiFiPropertiesLoader.withKey(PASSWORD_KEY_HEX_128)
|
||||
NiFiProperties inputProperties = niFiPropertiesLoader.load(workingNiFiPropertiesFile)
|
||||
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}")
|
||||
|
||||
|
||||
final String SENSITIVE_PROTECTION_KEY = ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
ProtectedNiFiProperties encryptedProperties = niFiPropertiesLoader.loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
def originalEncryptedValues = encryptedProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): encryptedProperties.getProperty(key)] }
|
||||
logger.info("Original encrypted values: ${originalEncryptedValues}")
|
||||
String originalSensitiveKeyProtectionScheme = encryptedProperties.getProperty(SENSITIVE_PROTECTION_KEY)
|
||||
logger.info("Sensitive property key originally protected with ${originalSensitiveKeyProtectionScheme}")
|
||||
|
||||
String newFlowPassword = FLOW_PASSWORD
|
||||
|
||||
// Bootstrap path must be provided to decrypt nifi.properties to get SP key
|
||||
String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-b", bootstrapFile.path, "-x", "-v", "-s", newFlowPassword]
|
||||
|
||||
exit.checkAssertionAfterwards(new Assertion() {
|
||||
void checkAssertion() {
|
||||
final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines()
|
||||
logger.info("Updated nifi.properties:")
|
||||
logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
|
||||
|
||||
final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX_128)
|
||||
.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
|
||||
|
||||
// Check that the output values for everything is the same except the sensitive props key
|
||||
NiFiProperties updatedProperties = new NiFiPropertiesLoader().loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
assert updatedProperties.size() == inputProperties.size()
|
||||
String newSensitivePropertyKey = updatedProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
|
||||
// Check that the encrypted value changed
|
||||
assert newSensitivePropertyKey != originalSensitiveValues.get(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
|
||||
// Check that the decrypted value is the new password
|
||||
assert spp.unprotect(newSensitivePropertyKey, nifiPropertiesContext(NiFiProperties.SENSITIVE_PROPS_KEY)) == newFlowPassword
|
||||
|
||||
// Check that all other values stayed the same
|
||||
originalEncryptedValues.every { String key, String originalValue ->
|
||||
if (key != NiFiProperties.SENSITIVE_PROPS_KEY) {
|
||||
assert updatedProperties.getProperty(key) == originalValue
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all other (decrypted) values stayed the same
|
||||
originalSensitiveValues.every { String key, String originalValue ->
|
||||
if (key != NiFiProperties.SENSITIVE_PROPS_KEY) {
|
||||
assert spp.unprotect(updatedProperties.getProperty(key), nifiPropertiesContext(key)) == originalValue
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the protection scheme did not change
|
||||
String sensitiveKeyProtectionScheme = updatedProperties.getProperty(SENSITIVE_PROTECTION_KEY)
|
||||
logger.info("Sensitive property key currently protected with ${sensitiveKeyProtectionScheme}")
|
||||
assert sensitiveKeyProtectionScheme == originalSensitiveKeyProtectionScheme
|
||||
|
||||
// Check that bootstrap.conf did not change
|
||||
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()
|
||||
|
||||
// Verify the flow definition
|
||||
def verifyTool = new ConfigEncryptionTool()
|
||||
verifyTool.isVerbose = true
|
||||
verifyTool.flowXmlPath = workingFlowXmlFile.path
|
||||
InputStream updatedFlowXmlContent = verifyTool.loadFlowXml(workingFlowXmlFile.path)
|
||||
|
||||
def migratedFlowCipherTexts = findFieldsInStream(updatedFlowXmlContent, WFXCTR)
|
||||
|
||||
// Verify that the cipher texts decrypt correctly
|
||||
logger.info("Original flow.xml.gz cipher texts: ${originalFlowCipherTexts}")
|
||||
logger.info("Updated flow.xml.gz cipher texts: ${migratedFlowCipherTexts}")
|
||||
assert migratedFlowCipherTexts.size() == CIPHER_TEXT_COUNT
|
||||
}
|
||||
})
|
||||
|
||||
// Act
|
||||
ConfigEncryptionTool.main(args)
|
||||
logger.info("Invoked #main with ${args.join(" ")}")
|
||||
}
|
||||
|
||||
/**
|
||||
* In this scenario, the nifi.properties file has a sensitive key value which is already encrypted. The goal is to provide a new provide a new sensitive key value, perform the migration of the flow.xml.gz, and update nifi.properties with a new encrypted sensitive key value without modifying any other nifi.properties values.
|
||||
*/
|
||||
@Test
|
||||
void testShouldPerformFullOperationOnAFlowXmlWithPreviouslyEncryptedNiFiProperties() {
|
||||
// Arrange
|
||||
exit.expectSystemExitWithStatus(0)
|
||||
|
||||
File tmpDir = setupTmpDir()
|
||||
|
||||
File passwordKeyFile = new File("src/test/resources/bootstrap_with_root_key_password_128.conf")
|
||||
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
|
||||
bootstrapFile.delete()
|
||||
|
||||
Files.copy(passwordKeyFile.toPath(), bootstrapFile.toPath())
|
||||
final List<String> originalBootstrapLines = bootstrapFile.readLines()
|
||||
String originalKeyLine = originalBootstrapLines.find {
|
||||
it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
|
||||
}
|
||||
final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX_128
|
||||
logger.info("Original key line from bootstrap.conf: ${originalKeyLine}")
|
||||
assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX_128
|
||||
|
||||
// Not "handling" NFP, so update in place (not source test resource)
|
||||
String niFiPropertiesTemplatePath = "src/test/resources/nifi_with_few_sensitive_properties_protected_aes_password_128.properties"
|
||||
File niFiPropertiesFile = new File(niFiPropertiesTemplatePath)
|
||||
|
||||
File workingNiFiPropertiesFile = new File("target/tmp/tmp-nifi.properties")
|
||||
workingNiFiPropertiesFile.delete()
|
||||
Files.copy(niFiPropertiesFile.toPath(), workingNiFiPropertiesFile.toPath())
|
||||
|
||||
// Use a flow definition that was encrypted with the hard-coded default SP key
|
||||
File flowXmlFile = new File("src/test/resources/flow.xml.gz")
|
||||
File workingFlowXmlFile = new File("target/tmp/tmp-flow.xml.gz")
|
||||
workingFlowXmlFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFlowXmlFile.toPath())
|
||||
|
||||
// Get the original ciphered fields to compare later
|
||||
def verifyTool = new ConfigEncryptionTool()
|
||||
verifyTool.isVerbose = true
|
||||
def originalFlowCipherTexts = findFieldsInStream(verifyTool.loadFlowXml(flowXmlFile.path), WFXCTR)
|
||||
final int CIPHER_TEXT_COUNT = originalFlowCipherTexts.size()
|
||||
|
||||
// Load both the encrypted and decrypted properties to compare later
|
||||
NiFiPropertiesLoader niFiPropertiesLoader = NiFiPropertiesLoader.withKey(PASSWORD_KEY_HEX_128)
|
||||
NiFiProperties inputProperties = niFiPropertiesLoader.load(workingNiFiPropertiesFile)
|
||||
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}")
|
||||
|
||||
|
||||
final String SENSITIVE_PROTECTION_KEY = ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
ProtectedNiFiProperties encryptedProperties = niFiPropertiesLoader.loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
def originalEncryptedValues = encryptedProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): encryptedProperties.getProperty(key)] }
|
||||
logger.info("Original encrypted values: ${originalEncryptedValues}")
|
||||
String originalSensitiveKeyProtectionScheme = encryptedProperties.getProperty(SENSITIVE_PROTECTION_KEY)
|
||||
logger.info("Sensitive property key originally protected with ${originalSensitiveKeyProtectionScheme}")
|
||||
|
||||
String newFlowPassword = FLOW_PASSWORD
|
||||
|
||||
// Bootstrap path must be provided to decrypt nifi.properties to get SP key
|
||||
String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-b", bootstrapFile.path, "-x", "-v", "-s", newFlowPassword]
|
||||
|
||||
exit.checkAssertionAfterwards(new Assertion() {
|
||||
void checkAssertion() {
|
||||
final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines()
|
||||
logger.info("Updated nifi.properties:")
|
||||
logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
|
||||
|
||||
final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX_128)
|
||||
.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
|
||||
|
||||
// Check that the output values for everything is the same except the sensitive props key
|
||||
NiFiProperties updatedProperties = new NiFiPropertiesLoader().loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
assert updatedProperties.size() == inputProperties.size()
|
||||
String newSensitivePropertyKey = updatedProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
|
||||
// Check that the encrypted value changed
|
||||
assert newSensitivePropertyKey != originalSensitiveValues.get(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
|
||||
// Check that the decrypted value is the new password
|
||||
assert spp.unprotect(newSensitivePropertyKey, nifiPropertiesContext(NiFiProperties.SENSITIVE_PROPS_KEY)) == newFlowPassword
|
||||
|
||||
// Check that all other values stayed the same
|
||||
originalEncryptedValues.every { String key, String originalValue ->
|
||||
if (key != NiFiProperties.SENSITIVE_PROPS_KEY) {
|
||||
assert updatedProperties.getProperty(key) == originalValue
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all other (decrypted) values stayed the same
|
||||
originalSensitiveValues.every { String key, String originalValue ->
|
||||
if (key != NiFiProperties.SENSITIVE_PROPS_KEY) {
|
||||
assert spp.unprotect(updatedProperties.getProperty(key), nifiPropertiesContext(key)) == originalValue
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the protection scheme did not change
|
||||
String sensitiveKeyProtectionScheme = updatedProperties.getProperty(SENSITIVE_PROTECTION_KEY)
|
||||
logger.info("Sensitive property key currently protected with ${sensitiveKeyProtectionScheme}")
|
||||
assert sensitiveKeyProtectionScheme == originalSensitiveKeyProtectionScheme
|
||||
|
||||
// Check that bootstrap.conf did not change
|
||||
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()
|
||||
|
||||
// Verify the flow definition
|
||||
verifyTool = new ConfigEncryptionTool()
|
||||
verifyTool.isVerbose = true
|
||||
InputStream migratedFlowXmlContent = verifyTool.loadFlowXml(workingFlowXmlFile.path)
|
||||
|
||||
def migratedFlowCipherTexts = findFieldsInStream(migratedFlowXmlContent, WFXCTR)
|
||||
logger.info("Migrated flow cipher texts for: " + workingFlowXmlFile.path)
|
||||
// Verify that the cipher texts decrypt correctly
|
||||
logger.info("Original " + workingFlowXmlFile.path + " unique cipher texts: ${originalFlowCipherTexts}")
|
||||
logger.info("Migrated " + workingFlowXmlFile.path + " unique cipher texts: ${migratedFlowCipherTexts}")
|
||||
assert migratedFlowCipherTexts.size() == CIPHER_TEXT_COUNT
|
||||
}
|
||||
})
|
||||
|
||||
// Act
|
||||
ConfigEncryptionTool.main(args)
|
||||
logger.info("Invoked #main with ${args.join(" ")}")
|
||||
|
||||
// Assert
|
||||
|
||||
// Assertions defined above
|
||||
}
|
||||
|
||||
/**
|
||||
* In this scenario, the nifi.properties file has a sensitive key value which is already encrypted. The goal is to provide a new provide a new sensitive key value, perform the migration of the flow.xml.gz, and update nifi.properties with a new encrypted sensitive key value without modifying any other nifi.properties values, and repeat this process multiple times to ensure no corruption of the keys.
|
||||
*/
|
||||
@Test
|
||||
void testShouldPerformFullOperationOnFlowXmlMultipleTimes() {
|
||||
// Arrange
|
||||
exit.expectSystemExitWithStatus(0)
|
||||
|
||||
File tmpDir = setupTmpDir()
|
||||
|
||||
File passwordKeyFile = new File("src/test/resources/bootstrap_with_root_key_password_128.conf")
|
||||
File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
|
||||
bootstrapFile.delete()
|
||||
|
||||
Files.copy(passwordKeyFile.toPath(), bootstrapFile.toPath())
|
||||
final List<String> originalBootstrapLines = bootstrapFile.readLines()
|
||||
String originalKeyLine = originalBootstrapLines.find {
|
||||
it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
|
||||
}
|
||||
final String EXPECTED_KEY_LINE = ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX_128
|
||||
logger.info("Original key line from bootstrap.conf: ${originalKeyLine}")
|
||||
assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX_128
|
||||
|
||||
// Not "handling" NFP, so update in place (not source test resource)
|
||||
String niFiPropertiesTemplatePath = "src/test/resources/nifi_with_few_sensitive_properties_protected_aes_password_128.properties"
|
||||
File niFiPropertiesFile = new File(niFiPropertiesTemplatePath)
|
||||
|
||||
File workingNiFiPropertiesFile = new File("target/tmp/tmp-nifi.properties")
|
||||
workingNiFiPropertiesFile.delete()
|
||||
Files.copy(niFiPropertiesFile.toPath(), workingNiFiPropertiesFile.toPath())
|
||||
|
||||
// Use a flow definition that was encrypted with the hard-coded default SP key
|
||||
File flowXmlFile = new File("src/test/resources/flow_default_key.xml.gz")
|
||||
File workingFlowXmlFile = new File("target/tmp/tmp-flow.xml.gz")
|
||||
workingFlowXmlFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFlowXmlFile.toPath())
|
||||
|
||||
// Read the uncompressed version to compare later
|
||||
File originalFlowXmlFile = new File("src/test/resources/flow_default_key.xml")
|
||||
final String ORIGINAL_FLOW_XML_CONTENT = originalFlowXmlFile.text
|
||||
def originalFlowCipherTexts = ORIGINAL_FLOW_XML_CONTENT.findAll(WFXCTR).toSet()
|
||||
final int CIPHER_TEXT_COUNT = originalFlowCipherTexts.size()
|
||||
|
||||
// Load both the encrypted and decrypted properties to compare later
|
||||
NiFiPropertiesLoader niFiPropertiesLoader = NiFiPropertiesLoader.withKey(PASSWORD_KEY_HEX_128)
|
||||
NiFiProperties inputProperties = niFiPropertiesLoader.load(workingNiFiPropertiesFile)
|
||||
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}")
|
||||
|
||||
final String SENSITIVE_PROTECTION_KEY = ApplicationPropertiesProtector.getProtectionKey(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
ProtectedNiFiProperties encryptedProperties = niFiPropertiesLoader.loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
def originalEncryptedValues = encryptedProperties.getSensitivePropertyKeys().collectEntries { String key -> [(key): encryptedProperties.getProperty(key)] }
|
||||
logger.info("Original encrypted values: ${originalEncryptedValues}")
|
||||
String originalSensitiveKeyProtectionScheme = encryptedProperties.getProperty(SENSITIVE_PROTECTION_KEY)
|
||||
logger.info("Sensitive property key originally protected with ${originalSensitiveKeyProtectionScheme}")
|
||||
|
||||
// Create a series of passwords with which to encrypt the flow XML, starting with the current password
|
||||
def passwordProgression = [DEFAULT_LEGACY_SENSITIVE_PROPS_KEY] + (0..5).collect { "${FLOW_PASSWORD}${it}" }
|
||||
|
||||
// The root key is not changing
|
||||
final SensitivePropertyProvider spp = StandardSensitivePropertyProviderFactory.withKey(PASSWORD_KEY_HEX_128)
|
||||
.getProvider(ConfigEncryptionTool.DEFAULT_PROTECTION_SCHEME)
|
||||
|
||||
// Act
|
||||
passwordProgression.eachWithIndex { String existingFlowPassword, int i ->
|
||||
if (i < passwordProgression.size() - 1) {
|
||||
exit.expectSystemExitWithStatus(0)
|
||||
String newFlowPassword = passwordProgression[i + 1]
|
||||
logger.info("Migrating from ${existingFlowPassword} to ${newFlowPassword}")
|
||||
|
||||
// Bootstrap path must be provided to decrypt nifi.properties to get SP key
|
||||
String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", workingFlowXmlFile.path, "-b", bootstrapFile.path, "-x", "-v", "-s", newFlowPassword]
|
||||
|
||||
def msg = shouldFail {
|
||||
logger.info("Invoked #main with ${args.join(" ")}")
|
||||
ConfigEncryptionTool.main(args)
|
||||
}
|
||||
logger.expected(msg)
|
||||
|
||||
// Assert
|
||||
// Get the updated nifi.properties and check the sensitive key
|
||||
final List<String> updatedPropertiesLines = workingNiFiPropertiesFile.readLines()
|
||||
String updatedSensitiveKeyLine = updatedPropertiesLines.find {
|
||||
it.startsWith(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
}
|
||||
logger.info("Updated key line: ${updatedSensitiveKeyLine}")
|
||||
|
||||
// Check that the output values for everything are the same except the sensitive props key
|
||||
NiFiProperties updatedProperties = new NiFiPropertiesLoader().loadProtectedProperties(workingNiFiPropertiesFile)
|
||||
assert updatedProperties.size() == inputProperties.size()
|
||||
String newSensitivePropertyKey = updatedProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
|
||||
// Check that the encrypted value changed
|
||||
assert newSensitivePropertyKey != originalSensitiveValues.get(NiFiProperties.SENSITIVE_PROPS_KEY)
|
||||
|
||||
// Check that the decrypted value is the new password
|
||||
assert spp.unprotect(newSensitivePropertyKey, nifiPropertiesContext(NiFiProperties.SENSITIVE_PROPS_KEY)) == newFlowPassword
|
||||
|
||||
// Check that all other values stayed the same
|
||||
originalEncryptedValues.every { String key, String originalValue ->
|
||||
if (key != NiFiProperties.SENSITIVE_PROPS_KEY) {
|
||||
assert updatedProperties.getProperty(key) == originalValue
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all other (decrypted) values stayed the same
|
||||
originalSensitiveValues.every { String key, String originalValue ->
|
||||
if (key != NiFiProperties.SENSITIVE_PROPS_KEY) {
|
||||
assert spp.unprotect(updatedProperties.getProperty(key), nifiPropertiesContext(key)) == originalValue
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the protection scheme did not change
|
||||
String sensitiveKeyProtectionScheme = updatedProperties.getProperty(SENSITIVE_PROTECTION_KEY)
|
||||
logger.info("Sensitive property key currently protected with ${sensitiveKeyProtectionScheme}")
|
||||
assert sensitiveKeyProtectionScheme == originalSensitiveKeyProtectionScheme
|
||||
|
||||
// Check that bootstrap.conf did not change
|
||||
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()
|
||||
|
||||
// Verify the flow definition
|
||||
def verifyTool = new ConfigEncryptionTool()
|
||||
verifyTool.isVerbose = true
|
||||
InputStream updatedFlowXmlContent = verifyTool.loadFlowXml(workingFlowXmlFile.path)
|
||||
|
||||
// Check that the flow.xml.gz content changed
|
||||
// TODO assert updatedFlowXmlContent != ORIGINAL_FLOW_XML_CONTENT
|
||||
|
||||
// Verify that the cipher texts decrypt correctly
|
||||
logger.info("Original flow.xml.gz cipher texts: ${originalFlowCipherTexts}")
|
||||
def flowCipherTexts = findFieldsInStream(updatedFlowXmlContent, WFXCTR)
|
||||
logger.info("Updated flow.xml.gz cipher texts: ${flowCipherTexts}")
|
||||
assert flowCipherTexts.size() == CIPHER_TEXT_COUNT
|
||||
|
||||
// Update the "original" flow cipher texts for the next run to the current values
|
||||
originalFlowCipherTexts = flowCipherTexts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldMigrateFlowXmlContent() {
|
||||
// Arrange
|
||||
String flowXmlPath = "src/test/resources/flow.xml"
|
||||
File flowXmlFile = new File(flowXmlPath)
|
||||
|
||||
File tmpDir = setupTmpDir()
|
||||
|
||||
File workingFile = new File("target/tmp/tmp-flow.xml")
|
||||
workingFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFile.toPath())
|
||||
ConfigEncryptionTool tool = new ConfigEncryptionTool()
|
||||
tool.isVerbose = true
|
||||
tool.flowXmlPath = workingFile.path
|
||||
tool.outputFlowXmlPath = workingFile.path
|
||||
|
||||
final String SENSITIVE_VALUE = "thisIsABadPassword"
|
||||
|
||||
String existingFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY
|
||||
String newFlowPassword = FLOW_PASSWORD
|
||||
|
||||
InputStream xmlContent = new FileInputStream(workingFile.path)
|
||||
logger.info("Read flow.xml as input stream.")
|
||||
|
||||
// There are two encrypted passwords in this flow
|
||||
int cipherTextCount = findFieldsInStream(xmlContent, WFXCTR).size()
|
||||
logger.info("Found ${cipherTextCount} unique encrypted properties in the original flow.xml content")
|
||||
|
||||
// Act
|
||||
xmlContent = new FileInputStream(workingFile.path)
|
||||
tool.migrateFlowXmlContent(xmlContent, existingFlowPassword, newFlowPassword)
|
||||
logger.info("Migrated flow.xml.")
|
||||
|
||||
// Assert
|
||||
InputStream migratedFlowXmlFile = new FileInputStream(workingFile.path)
|
||||
def migratedCipherTexts = findFieldsInStream(migratedFlowXmlFile, WFXCTR)
|
||||
|
||||
assert migratedCipherTexts.size() == cipherTextCount
|
||||
|
||||
// Ensure that everything else is identical
|
||||
assertEquals(removeXmlDeclarationAndComments(flowXmlFile.text).replaceAll(WFXCTR, "").trim(),
|
||||
removeXmlDeclarationAndComments(workingFile.text).replaceAll(WFXCTR, "").trim())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testShouldMigrateFlowXmlContentMultipleTimes() {
|
||||
// Arrange
|
||||
String flowXmlPath = "src/test/resources/flow.xml"
|
||||
File flowXmlFile = new File(flowXmlPath)
|
||||
|
||||
File tmpDir = setupTmpDir()
|
||||
|
||||
File workingFile = new File("target/tmp/tmp-flow.xml")
|
||||
workingFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFile.toPath())
|
||||
ConfigEncryptionTool tool = new ConfigEncryptionTool()
|
||||
tool.isVerbose = true
|
||||
tool.outputFlowXmlPath = workingFile.path
|
||||
|
||||
final String SENSITIVE_VALUE = "thisIsABadPassword"
|
||||
|
||||
// Create a series of passwords with which to encrypt the flow XML, starting with the current password
|
||||
def passwordProgression = [DEFAULT_LEGACY_SENSITIVE_PROPS_KEY] + (0..5).collect { "${FLOW_PASSWORD}${it}" }
|
||||
|
||||
String xmlContent = workingFile.text
|
||||
// logger.info("Read flow.xml: \n${xmlContent}")
|
||||
|
||||
// There are two encrypted passwords in this flow
|
||||
final def ORIGINAL_CIPHER_TEXTS = xmlContent.findAll(WFXCTR)
|
||||
logger.info("Cipher texts: \n${ORIGINAL_CIPHER_TEXTS.join("\n")}")
|
||||
final int ORIGINAL_CIPHER_TEXT_COUNT = ORIGINAL_CIPHER_TEXTS.size()
|
||||
logger.info("Found ${ORIGINAL_CIPHER_TEXT_COUNT} encrypted properties in the original flow.xml content")
|
||||
|
||||
InputStream currentXmlContent = new ByteArrayInputStream(xmlContent.bytes)
|
||||
|
||||
// Act
|
||||
passwordProgression.eachWithIndex { String existingFlowPassword, int i ->
|
||||
if (i < passwordProgression.size() - 1) {
|
||||
String newFlowPassword = passwordProgression[i + 1]
|
||||
logger.info("Migrating from ${existingFlowPassword} to ${newFlowPassword}")
|
||||
|
||||
InputStream migratedXmlContent = tool.migrateFlowXmlContent(currentXmlContent, existingFlowPassword, newFlowPassword)
|
||||
// logger.info("Migrated flow.xml: \n${migratedXmlContent}")
|
||||
|
||||
// Assert
|
||||
def newCipherTexts = findFieldsInStream(migratedXmlContent, WFXCTR)
|
||||
logger.info("Cipher texts for iteration ${i}: \n${newCipherTexts.join("\n")}")
|
||||
|
||||
assert newCipherTexts.size() == ORIGINAL_CIPHER_TEXT_COUNT
|
||||
|
||||
// Ensure that everything else is identical
|
||||
assertEquals(removeXmlDeclarationAndComments(new File(workingFile.path).text).replaceAll(WFXCTR, "").trim(),
|
||||
removeXmlDeclarationAndComments(flowXmlFile.text).replaceAll(WFXCTR, "").trim())
|
||||
|
||||
// Update the "source" XML content for the next iteration
|
||||
currentXmlContent = tool.loadFlowXml(workingFile.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMigrateFlowXmlContentShouldUseConstantSalt() {
|
||||
// Arrange
|
||||
String flowXmlPath = "src/test/resources/flow.xml"
|
||||
File flowXmlFile = new File(flowXmlPath)
|
||||
|
||||
File tmpDir = setupTmpDir()
|
||||
|
||||
File workingFile = new File("target/tmp/tmp-flow.xml")
|
||||
workingFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFile.toPath())
|
||||
ConfigEncryptionTool tool = new ConfigEncryptionTool()
|
||||
tool.isVerbose = true
|
||||
tool.outputFlowXmlPath = workingFile.path
|
||||
|
||||
String existingFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY
|
||||
String newFlowPassword = FLOW_PASSWORD
|
||||
|
||||
String xmlContent = workingFile.text
|
||||
logger.info("Read flow.xml: \n${xmlContent}")
|
||||
|
||||
// There are two encrypted passwords in this flow
|
||||
int cipherTextCount = xmlContent.findAll(WFXCTR).size()
|
||||
logger.info("Found ${cipherTextCount} encrypted properties in the original flow.xml content")
|
||||
|
||||
// Act
|
||||
InputStream migratedXmlContent = tool.migrateFlowXmlContent(new ByteArrayInputStream(xmlContent.bytes), existingFlowPassword, newFlowPassword)
|
||||
logger.info("Migrated flow.xml.")
|
||||
|
||||
// Assert
|
||||
def newCipherTexts = findFieldsInStream(migratedXmlContent, WFXCTR)
|
||||
|
||||
assert newCipherTexts.size() == cipherTextCount
|
||||
|
||||
// Check that the same salt was used on all output
|
||||
String saltHex = newCipherTexts.first()[4..<36]
|
||||
logger.info("First detected salt: ${saltHex}")
|
||||
newCipherTexts.every {
|
||||
assert it[4..<36] == saltHex
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is scoped to the higher-level method to ensure that if a bad padding exception is thrown, the right errors are displayed.
|
||||
*/
|
||||
@Test
|
||||
void testHandleFlowXmlMigrationWithIncorrectExistingPasswordShouldProvideHelpfulErrorMessage() {
|
||||
// Arrange
|
||||
systemOutRule.clearLog()
|
||||
|
||||
String flowXmlPath = "src/test/resources/flow.xml"
|
||||
File flowXmlFile = new File(flowXmlPath)
|
||||
|
||||
// Use the wrong existing password
|
||||
String wrongExistingFlowPassword = DEFAULT_LEGACY_SENSITIVE_PROPS_KEY.reverse()
|
||||
String newFlowPassword = FLOW_PASSWORD
|
||||
|
||||
def nifiProperties = wrapNFP([(NiFiProperties.SENSITIVE_PROPS_KEY): wrongExistingFlowPassword])
|
||||
|
||||
File workingFile = new File("target/tmp/tmp-flow.xml")
|
||||
workingFile.delete()
|
||||
Files.copy(flowXmlFile.toPath(), workingFile.toPath())
|
||||
ConfigEncryptionTool tool = new ConfigEncryptionTool()
|
||||
tool.isVerbose = true
|
||||
tool.flowXmlInputStream = tool.loadFlowXml(workingFile.path)
|
||||
tool.niFiProperties = nifiProperties
|
||||
tool.flowPropertiesPassword = newFlowPassword
|
||||
tool.handlingNiFiProperties = false
|
||||
|
||||
// Act
|
||||
def message = shouldFail(Exception) {
|
||||
tool.handleFlowXml()
|
||||
logger.info("Migrated flow.xml.")
|
||||
}
|
||||
logger.expected(message)
|
||||
|
||||
// Assert
|
||||
// TODO: Assert that this message was in the log output (neither the STDOUT and STDERR buffers contain it, but it is printed)
|
||||
assert message == "Encountered an error migrating flow content"
|
||||
}
|
||||
|
||||
private static NiFiProperties wrapNFP(Map<String, String> map) {
|
||||
new NiFiProperties(
|
||||
new Properties(map))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldLoadFlowXmlContent() {
|
||||
// Arrange
|
||||
|
|
|
@ -71,7 +71,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=n2z+tTTbHuZ4V4V2||uWhdasyDXD4ZG2lMAes/vqh6u4vaz4xgL4aEbF4Y/dXevqk3ulRcOwf1vc4RDQ==
|
||||
nifi.sensitive.props.key.protected=aes/gcm/256
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=UXcrW8T1UKAPJeun||ezUJSp30AvKGsRxJOOXoPUtZonv56Lx1
|
||||
nifi.sensitive.props.key.protected=aes/gcm/128
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=thisIsABadSensitiveKeyPassword
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -72,7 +72,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=n2z+tTTbHuZ4V4V2||uWhdasyDXD4ZG2lMAes/vqh6u4vaz4xgL4aEbF4Y/dXevqk3ulRcOwf1vc4RDQ==
|
||||
nifi.sensitive.props.key.protected=aes/gcm/256
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -72,7 +72,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=xPqEWK8a34r19J4z||UOFzOfZE/NQK4Xua8WWblf1/Ld+Pf7eQ1zg0U/qYW2sPwxyhhOXWwQmrUft6qA
|
||||
nifi.sensitive.props.key.protected=aes/gcm/128
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -72,7 +72,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=n6ZO5Y9zv4CGElB2||e0SwwiJqNOZ8drLl30+dbiSYYMgd+Vx7rFjwCYEkJpF4Vh+Tx8+7Oek96kpoxQ
|
||||
nifi.sensitive.props.key.protected=aes/gcm/256
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -73,7 +73,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=xwc0atbb2krNWE8i||NLuzY6uraSVnONQEhA6hxfVntOTPhzG7yiKOysopYzfBTYY5Um1oNgnyvrCadw
|
||||
nifi.sensitive.props.key.protected=aes/gcm/128
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -71,7 +71,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=thisIsABadSensitiveKeyPassword
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -73,7 +73,7 @@ nifi.web.jetty.working.directory=./target/work/jetty
|
|||
# security properties #
|
||||
nifi.sensitive.props.key=thisIsABadSensitiveKeyPassword
|
||||
nifi.sensitive.props.key.protected=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
nifi.sensitive.props.additional.keys=
|
||||
|
||||
nifi.security.keystore=/path/to/keystore.jks
|
||||
|
|
|
@ -130,7 +130,7 @@ nifi.web.jetty.threads=200
|
|||
|
||||
# security properties #
|
||||
nifi.sensitive.props.key=
|
||||
nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
|
||||
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
|
||||
|
||||
nifi.security.keystore=./conf/localhost.jks
|
||||
nifi.security.keystoreType=jks
|
||||
|
|
Loading…
Reference in New Issue