You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jg...@apache.org on 2021/10/01 19:55:33 UTC

[nifi] branch main updated: NIFI-9254 Updated default Stateless Sensitive Property configuration

This is an automated email from the ASF dual-hosted git repository.

jgresock pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new a94b47e  NIFI-9254 Updated default Stateless Sensitive Property configuration
a94b47e is described below

commit a94b47ecf8d97778c7375e34d07964a6ab326cc5
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Wed Sep 29 12:33:09 2021 -0500

    NIFI-9254 Updated default Stateless Sensitive Property configuration
    
    - Set NIFI_PBKDF2_AES_GCM_256 as property encryption method
    - Replaced static default sensitive properties key with random UUID
    - Added unit test for PropertiesFileEngineConfigurationParser
    - Added random encryption key generation method
    - Changed Stateless to use PropertyEncryptionMethod enum
    
    Signed-off-by: Joe Gresock <jg...@gmail.com>
    
    This closes #5424
---
 .../nifi/encrypt/PropertyEncryptionMethod.java     |   2 +-
 .../PropertiesFileEngineConfigurationParser.java   |  29 +++++-
 ...ropertiesFileEngineConfigurationParserTest.java | 107 +++++++++++++++++++++
 .../flow/StandardStatelessDataflowFactory.java     |  21 +---
 4 files changed, 140 insertions(+), 19 deletions(-)

diff --git a/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/PropertyEncryptionMethod.java b/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/PropertyEncryptionMethod.java
index 27e4f0e..4206843 100644
--- a/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/PropertyEncryptionMethod.java
+++ b/nifi-commons/nifi-property-encryptor/src/main/java/org/apache/nifi/encrypt/PropertyEncryptionMethod.java
@@ -22,7 +22,7 @@ import org.apache.nifi.security.util.KeyDerivationFunction;
 /**
  * Property Encryption Method enumerates supported values in addition to {@link org.apache.nifi.security.util.EncryptionMethod}
  */
-enum PropertyEncryptionMethod {
+public enum PropertyEncryptionMethod {
     NIFI_ARGON2_AES_GCM_128(KeyDerivationFunction.ARGON2, EncryptionMethod.AES_GCM,128),
 
     NIFI_ARGON2_AES_GCM_256(KeyDerivationFunction.ARGON2, EncryptionMethod.AES_GCM, 256),
diff --git a/nifi-stateless/nifi-stateless-api/src/main/java/org/apache/nifi/stateless/config/PropertiesFileEngineConfigurationParser.java b/nifi-stateless/nifi-stateless-api/src/main/java/org/apache/nifi/stateless/config/PropertiesFileEngineConfigurationParser.java
index 22d30eb..19eb7d7 100644
--- a/nifi-stateless/nifi-stateless-api/src/main/java/org/apache/nifi/stateless/config/PropertiesFileEngineConfigurationParser.java
+++ b/nifi-stateless/nifi-stateless-api/src/main/java/org/apache/nifi/stateless/config/PropertiesFileEngineConfigurationParser.java
@@ -24,9 +24,14 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.security.SecureRandom;
 import java.util.ArrayList;
+import java.util.Base64;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -56,10 +61,10 @@ public class PropertiesFileEngineConfigurationParser {
     private static final String KRB5_FILE = PREFIX + "kerberos.krb5.file";
 
     private static final String DEFAULT_KRB5_FILENAME = "/etc/krb5.conf";
-    private static final String DEFAULT_ENCRYPTION_PASSWORD = "nifi-stateless";
 
     private static final Pattern EXTENSION_CLIENT_PATTERN = Pattern.compile("\\Qnifi.stateless.extension.client.\\E(.*?)\\.(.+)");
 
+    private static final int PROPERTIES_KEY_LENGTH = 24;
 
     public StatelessEngineConfiguration parseEngineConfiguration(final File propertiesFile) throws IOException, StatelessConfigurationException {
         if (!propertiesFile.exists()) {
@@ -93,7 +98,7 @@ public class PropertiesFileEngineConfigurationParser {
         final String krb5Filename = properties.getProperty(KRB5_FILE, DEFAULT_KRB5_FILENAME);
         final File krb5File = new File(krb5Filename);
 
-        final String sensitivePropsKey = properties.getProperty(SENSITIVE_PROPS_KEY, DEFAULT_ENCRYPTION_PASSWORD);
+        final String sensitivePropsKey = getSensitivePropsKey(propertiesFile, properties);
         final SslContextDefinition sslContextDefinition = parseSslContextDefinition(properties);
 
         final List<ExtensionClientDefinition> extensionClients = parseExtensionClients(properties);
@@ -218,4 +223,24 @@ public class PropertiesFileEngineConfigurationParser {
         return propertyValue.trim();
     }
 
+    private String getSensitivePropsKey(final File propertiesFile, final Properties properties) {
+        String sensitivePropsKey = properties.getProperty(SENSITIVE_PROPS_KEY);
+        if (sensitivePropsKey == null || sensitivePropsKey.isEmpty()) {
+            logger.warn("Generating Random Properties Encryption Key [{}]", SENSITIVE_PROPS_KEY);
+            final SecureRandom secureRandom = new SecureRandom();
+            final byte[] sensitivePropertiesKeyBinary = new byte[PROPERTIES_KEY_LENGTH];
+            secureRandom.nextBytes(sensitivePropertiesKeyBinary);
+            final Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
+            sensitivePropsKey = encoder.encodeToString(sensitivePropertiesKeyBinary);
+
+            properties.put(SENSITIVE_PROPS_KEY, sensitivePropsKey);
+            try (final OutputStream outputStream = new FileOutputStream(propertiesFile)) {
+                properties.store(outputStream, StatelessEngineConfiguration.class.getSimpleName());
+            } catch (final IOException e) {
+                final String message = String.format("Store Configuration Properties [%s] Failed", propertiesFile);
+                throw new UncheckedIOException(message, e);
+            }
+        }
+        return sensitivePropsKey;
+    }
 }
diff --git a/nifi-stateless/nifi-stateless-api/src/test/java/org/apache/nifi/stateless/config/PropertiesFileEngineConfigurationParserTest.java b/nifi-stateless/nifi-stateless-api/src/test/java/org/apache/nifi/stateless/config/PropertiesFileEngineConfigurationParserTest.java
new file mode 100644
index 0000000..a21fac2
--- /dev/null
+++ b/nifi-stateless/nifi-stateless-api/src/test/java/org/apache/nifi/stateless/config/PropertiesFileEngineConfigurationParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.stateless.config;
+
+import org.apache.nifi.stateless.engine.StatelessEngineConfiguration;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class PropertiesFileEngineConfigurationParserTest {
+    private PropertiesFileEngineConfigurationParser parser;
+
+    private static Path narDirectory;
+
+    private static Path workingDirectory;
+
+    @BeforeAll
+    public static void setDirectories() throws IOException {
+        narDirectory = Files.createTempDirectory(PropertiesFileEngineConfigurationParserTest.class.getSimpleName());
+        workingDirectory = Files.createTempDirectory(PropertiesFileEngineConfigurationParserTest.class.getSimpleName());
+    }
+
+    @AfterAll
+    public static void deleteDirectories() throws IOException {
+        Files.deleteIfExists(narDirectory);
+        Files.deleteIfExists(workingDirectory);
+    }
+
+    @BeforeEach
+    public void setParser() {
+        parser = new PropertiesFileEngineConfigurationParser();
+    }
+
+    @Test
+    public void testParseEngineConfigurationRequiredProperties() throws IOException, StatelessConfigurationException {
+        final Properties properties = getRequiredProperties();
+        final File propertiesFile = getPropertiesFile(properties);
+
+        final StatelessEngineConfiguration configuration = parser.parseEngineConfiguration(propertiesFile);
+        assertNotNull(configuration);
+        assertEquals(narDirectory.toFile(), configuration.getNarDirectory());
+        assertEquals(workingDirectory.toFile(), configuration.getWorkingDirectory());
+    }
+
+    @Test
+    public void testParseEngineConfigurationRandomSensitivePropsKey() throws IOException, StatelessConfigurationException {
+        final Properties properties = getRequiredProperties();
+        final File propertiesFile = getPropertiesFile(properties);
+
+        final StatelessEngineConfiguration configuration = parser.parseEngineConfiguration(propertiesFile);
+        assertNotNull(configuration);
+
+        final String sensitivePropsKey = configuration.getSensitivePropsKey();
+        assertNotNull(sensitivePropsKey);
+        assertFalse(sensitivePropsKey.isEmpty());
+
+        final StatelessEngineConfiguration reloadedConfiguration = parser.parseEngineConfiguration(propertiesFile);
+        assertEquals(sensitivePropsKey, reloadedConfiguration.getSensitivePropsKey());
+    }
+
+    private Properties getRequiredProperties() {
+        final Properties properties = new Properties();
+
+        properties.setProperty("nifi.stateless.nar.directory", narDirectory.toString());
+        properties.setProperty("nifi.stateless.working.directory", workingDirectory.toString());
+
+        return properties;
+    }
+
+    private File getPropertiesFile(final Properties properties) throws IOException {
+        final File file = File.createTempFile(getClass().getSimpleName(), ".properties");
+        file.deleteOnExit();
+
+        try (final OutputStream outputStream = new FileOutputStream(file)) {
+            properties.store(outputStream, null);
+        }
+
+        return file;
+    }
+}
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java
index 6978a09..2440fb6 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/flow/StandardStatelessDataflowFactory.java
@@ -30,8 +30,9 @@ import org.apache.nifi.controller.repository.metrics.RingBufferEventRepository;
 import org.apache.nifi.controller.scheduling.StatelessProcessScheduler;
 import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.controller.service.StandardControllerServiceProvider;
+import org.apache.nifi.encrypt.PropertyEncryptionMethod;
 import org.apache.nifi.encrypt.PropertyEncryptor;
-import org.apache.nifi.encrypt.PropertyEncryptorFactory;
+import org.apache.nifi.encrypt.PropertyEncryptorBuilder;
 import org.apache.nifi.events.EventReporter;
 import org.apache.nifi.events.VolatileBulletinRepository;
 import org.apache.nifi.extensions.ExtensionClient;
@@ -51,7 +52,6 @@ import org.apache.nifi.registry.flow.InMemoryFlowRegistry;
 import org.apache.nifi.registry.flow.StandardFlowRegistryClient;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
 import org.apache.nifi.reporting.BulletinRepository;
-import org.apache.nifi.security.util.EncryptionMethod;
 import org.apache.nifi.stateless.bootstrap.ExtensionDiscovery;
 import org.apache.nifi.stateless.config.ExtensionClientDefinition;
 import org.apache.nifi.stateless.config.SslConfigurationUtil;
@@ -73,7 +73,6 @@ import org.apache.nifi.stateless.repository.StatelessFileSystemContentRepository
 import org.apache.nifi.stateless.repository.StatelessFlowFileRepository;
 import org.apache.nifi.stateless.repository.StatelessProvenanceRepository;
 import org.apache.nifi.stateless.repository.StatelessRepositoryContextFactory;
-import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -83,14 +82,11 @@ import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 
 public class StandardStatelessDataflowFactory implements StatelessDataflowFactory<VersionedFlowSnapshot> {
     private static final Logger logger = LoggerFactory.getLogger(StandardStatelessDataflowFactory.class);
-    private static final EncryptionMethod ENCRYPTION_METHOD = EncryptionMethod.MD5_256AES;
 
     @Override
     public StatelessDataflow createDataflow(final StatelessEngineConfiguration engineConfiguration, final DataflowDefinition<VersionedFlowSnapshot> dataflowDefinition)
@@ -167,7 +163,9 @@ public class StandardStatelessDataflowFactory implements StatelessDataflowFactor
                         return created;
                     }
 
-                    created = getPropertyEncryptor(engineConfiguration.getSensitivePropsKey());
+                    created = new PropertyEncryptorBuilder(engineConfiguration.getSensitivePropsKey())
+                            .setAlgorithm(PropertyEncryptionMethod.NIFI_PBKDF2_AES_GCM_256.toString())
+                            .build();
                     return created;
                 }
             };
@@ -342,13 +340,4 @@ public class StandardStatelessDataflowFactory implements StatelessDataflowFactor
 
         return Integer.parseInt(javaVersion.substring(0, dotIndex));
     }
-
-    private PropertyEncryptor getPropertyEncryptor(final String sensitivePropertiesKey) {
-        final Map<String, String> properties = new HashMap<>();
-        properties.put(NiFiProperties.SENSITIVE_PROPS_ALGORITHM, ENCRYPTION_METHOD.getAlgorithm());
-        properties.put(NiFiProperties.SENSITIVE_PROPS_PROVIDER, ENCRYPTION_METHOD.getProvider());
-        properties.put(NiFiProperties.SENSITIVE_PROPS_KEY, sensitivePropertiesKey);
-        final NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, properties);
-        return PropertyEncryptorFactory.getPropertyEncryptor(niFiProperties);
-    }
 }