You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2021/06/21 19:40:28 UTC

[nifi] branch main updated: NIFI-8683 support Expression Language for the Truststore/Keystore properties of SSLContextService

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

exceptionfactory 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 02b4e33  NIFI-8683 support Expression Language for the Truststore/Keystore properties of SSLContextService
02b4e33 is described below

commit 02b4e33aa6cbcba4e3dea706aa9b20e8b501b06f
Author: Chris Sampson <ch...@gmail.com>
AuthorDate: Fri Jun 11 12:13:24 2021 +0100

    NIFI-8683 support Expression Language for the Truststore/Keystore properties of SSLContextService
    
    This closes #5147
    
    Signed-off-by: David Handermann <ex...@apache.org>
---
 .../apache/nifi/ssl/StandardSSLContextService.java |  33 +++-
 .../org/apache/nifi/ssl/SSLContextServiceTest.java | 172 ++++++++++-----------
 2 files changed, 112 insertions(+), 93 deletions(-)

diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java
index af304ea..d4d2f6c 100644
--- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java
+++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java
@@ -27,8 +27,10 @@ import org.apache.nifi.components.ValidationResult;
 import org.apache.nifi.components.Validator;
 import org.apache.nifi.components.resource.ResourceCardinality;
 import org.apache.nifi.components.resource.ResourceType;
+import org.apache.nifi.context.PropertyContext;
 import org.apache.nifi.controller.AbstractControllerService;
 import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.reporting.InitializationException;
@@ -49,6 +51,7 @@ import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -63,6 +66,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme
     public static final PropertyDescriptor TRUSTSTORE = new PropertyDescriptor.Builder()
             .name("Truststore Filename")
             .description("The fully-qualified filename of the Truststore")
+            .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
             .defaultValue(null)
             .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE)
             .sensitive(false)
@@ -85,6 +89,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme
     public static final PropertyDescriptor KEYSTORE = new PropertyDescriptor.Builder()
             .name("Keystore Filename")
             .description("The fully-qualified filename of the Keystore")
+            .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
             .defaultValue(null)
             .identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE)
             .sensitive(false)
@@ -149,8 +154,10 @@ public class StandardSSLContextService extends AbstractControllerService impleme
         configContext = context;
 
         final Collection<ValidationResult> results = new ArrayList<>();
-        results.addAll(validateStore(context.getProperties(), KeystoreValidationGroup.KEYSTORE));
-        results.addAll(validateStore(context.getProperties(), KeystoreValidationGroup.TRUSTSTORE));
+
+        final Map<PropertyDescriptor, String> properties = evaluateProperties(context);
+        results.addAll(validateStore(properties, KeystoreValidationGroup.KEYSTORE));
+        results.addAll(validateStore(properties, KeystoreValidationGroup.TRUSTSTORE));
 
         if (!results.isEmpty()) {
             final StringBuilder sb = new StringBuilder(this + " is not valid due to:");
@@ -185,14 +192,26 @@ public class StandardSSLContextService extends AbstractControllerService impleme
             }
         }
 
-        results.addAll(validateStore(validationContext.getProperties(), KeystoreValidationGroup.KEYSTORE));
-        results.addAll(validateStore(validationContext.getProperties(), KeystoreValidationGroup.TRUSTSTORE));
+        final Map<PropertyDescriptor, String> properties = evaluateProperties(validationContext);
+        results.addAll(validateStore(properties, KeystoreValidationGroup.KEYSTORE));
+        results.addAll(validateStore(properties, KeystoreValidationGroup.TRUSTSTORE));
 
         isValidated = results.isEmpty();
 
         return results;
     }
 
+    private Map<PropertyDescriptor, String> evaluateProperties(final PropertyContext context) {
+        final Map<PropertyDescriptor, String> evaluatedProperties = new HashMap<>(getSupportedPropertyDescriptors().size(), 1);
+        for (final PropertyDescriptor pd : getSupportedPropertyDescriptors()) {
+            final PropertyValue pv = pd.isExpressionLanguageSupported()
+                    ? context.getProperty(pd).evaluateAttributeExpressions()
+                    : context.getProperty(pd);
+            evaluatedProperties.put(pd, pv.isSet() ? pv.getValue() : null);
+        }
+        return evaluatedProperties;
+    }
+
     private void resetValidationCache() {
         validationCacheCount = 0;
         isValidated = false;
@@ -289,7 +308,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme
 
     @Override
     public String getTrustStoreFile() {
-        return configContext.getProperty(TRUSTSTORE).getValue();
+        return configContext.getProperty(TRUSTSTORE).evaluateAttributeExpressions().getValue();
     }
 
     @Override
@@ -310,7 +329,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme
 
     @Override
     public String getKeyStoreFile() {
-        return configContext.getProperty(KEYSTORE).getValue();
+        return configContext.getProperty(KEYSTORE).evaluateAttributeExpressions().getValue();
     }
 
     @Override
@@ -582,6 +601,6 @@ public class StandardSSLContextService extends AbstractControllerService impleme
             allowableValues.add(new AllowableValue(supportedProtocol, supportedProtocol, description));
         }
 
-        return allowableValues.toArray(new AllowableValue[allowableValues.size()]);
+        return allowableValues.toArray(new AllowableValue[0]);
     }
 }
diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java
index 0bd7a87..0437706 100644
--- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java
+++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java
@@ -137,6 +137,29 @@ public class SSLContextServiceTest {
     }
 
     @Test
+    public void testGoodWithEL() throws InitializationException {
+        final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
+        SSLContextService service = new StandardSSLContextService();
+        runner.addControllerService("test-good1", service);
+        runner.setVariable("keystore", KEYSTORE_PATH);
+        runner.setVariable("truststore", TRUSTSTORE_PATH);
+        runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), "${keystore}");
+        runner.setProperty(service, StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
+        runner.setProperty(service, StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
+        runner.setProperty(service, StandardSSLContextService.TRUSTSTORE.getName(), "${truststore}");
+        runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
+        runner.setProperty(service, StandardSSLContextService.TRUSTSTORE_TYPE.getName(), JKS_TYPE);
+        runner.enableControllerService(service);
+
+        runner.setProperty("SSL Context Svc ID", "test-good1");
+        runner.assertValid(service);
+        service = (SSLContextService) runner.getProcessContext().getControllerServiceLookup().getControllerService("test-good1");
+        Assert.assertNotNull(service);
+        SSLContextService sslService = service;
+        sslService.createContext();
+    }
+
+    @Test
     public void testWithChanges() throws InitializationException {
         final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
         SSLContextService service = new StandardSSLContextService();
@@ -180,7 +203,7 @@ public class SSLContextServiceTest {
         Files.copy(originalTruststore.toPath(), tmpTruststore.toPath(), StandardCopyOption.REPLACE_EXISTING);
 
         final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
-        SSLContextService service = new StandardSSLContextService();
+        StandardSSLContextService service = new StandardSSLContextService();
         final String serviceIdentifier = "test-should-expire";
         runner.addControllerService(serviceIdentifier, service);
         runner.setProperty(service, StandardSSLContextService.KEYSTORE.getName(), tmpKeystore.getAbsolutePath());
@@ -194,8 +217,6 @@ public class SSLContextServiceTest {
         runner.setProperty("SSL Context Svc ID", serviceIdentifier);
         runner.assertValid(service);
 
-        final StandardSSLContextService sslContextService = (StandardSSLContextService) service;
-
         // Act
         boolean isDeleted = tmpKeystore.delete();
         assert isDeleted;
@@ -208,68 +229,57 @@ public class SSLContextServiceTest {
         final ValidationContext validationContext = new MockValidationContext(processContext, null, null);
 
         // Even though the keystore file is no longer present, because no property changed, the cached result is still valid
-        Collection<ValidationResult> validationResults = sslContextService.customValidate(validationContext);
+        Collection<ValidationResult> validationResults = service.customValidate(validationContext);
         assertTrue("validation results is not empty", validationResults.isEmpty());
         logger.info("(1) StandardSSLContextService#customValidate() returned true even though the keystore file is no longer available");
 
         // Assert
 
         // Have to exhaust the cached result by checking n-1 more times
-        for (int i = 2; i < sslContextService.getValidationCacheExpiration(); i++) {
-            validationResults = sslContextService.customValidate(validationContext);
+        for (int i = 2; i < service.getValidationCacheExpiration(); i++) {
+            validationResults = service.customValidate(validationContext);
             assertTrue("validation results is not empty", validationResults.isEmpty());
             logger.info("(" + i + ") StandardSSLContextService#customValidate() returned true even though the keystore file is no longer available");
         }
 
-        validationResults = sslContextService.customValidate(validationContext);
+        validationResults = service.customValidate(validationContext);
         assertFalse("validation results is empty", validationResults.isEmpty());
-        logger.info("(" + sslContextService.getValidationCacheExpiration() + ") StandardSSLContextService#customValidate() returned false because the cache expired");
+        logger.info("(" + service.getValidationCacheExpiration() + ") StandardSSLContextService#customValidate() returned false because the cache expired");
     }
 
     @Test
-    public void testGoodTrustOnly() {
-        try {
-            TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
-            SSLContextService service = new StandardSSLContextService();
-            HashMap<String, String> properties = new HashMap<>();
-            properties.put(StandardSSLContextService.TRUSTSTORE.getName(), TRUSTSTORE_PATH);
-            properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
-            properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), JKS_TYPE);
-            runner.addControllerService("test-good2", service, properties);
-            runner.enableControllerService(service);
-
-            runner.setProperty("SSL Context Svc ID", "test-good2");
-            runner.assertValid();
-            Assert.assertNotNull(service);
-            assertTrue(service instanceof StandardSSLContextService);
-            service.createContext();
-        } catch (InitializationException e) {
-        }
+    public void testGoodTrustOnly() throws InitializationException {
+        TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
+        SSLContextService service = new StandardSSLContextService();
+        HashMap<String, String> properties = new HashMap<>();
+        properties.put(StandardSSLContextService.TRUSTSTORE.getName(), TRUSTSTORE_PATH);
+        properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
+        properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), JKS_TYPE);
+        runner.addControllerService("test-good2", service, properties);
+        runner.enableControllerService(service);
+
+        runner.setProperty("SSL Context Svc ID", "test-good2");
+        runner.assertValid();
+        Assert.assertNotNull(service);
+        service.createContext();
     }
 
     @Test
     @Deprecated
-    public void testGoodKeyOnly() {
-        try {
-            TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
-            SSLContextService service = new StandardSSLContextService();
-            HashMap<String, String> properties = new HashMap<>();
-            properties.put(StandardSSLContextService.KEYSTORE.getName(), KEYSTORE_PATH);
-            properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
-            properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
-            runner.addControllerService("test-good3", service, properties);
-            runner.enableControllerService(service);
-
-            runner.setProperty("SSL Context Svc ID", "test-good3");
-            runner.assertValid();
-            Assert.assertNotNull(service);
-            assertTrue(service instanceof StandardSSLContextService);
-            SSLContextService sslService = service;
-            sslService.createContext();
-        } catch (Exception e) {
-            System.out.println(e);
-            Assert.fail("Should not have thrown a exception " + e.getMessage());
-        }
+    public void testGoodKeyOnly() throws Exception {
+        TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
+        SSLContextService service = new StandardSSLContextService();
+        HashMap<String, String> properties = new HashMap<>();
+        properties.put(StandardSSLContextService.KEYSTORE.getName(), KEYSTORE_PATH);
+        properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
+        properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
+        runner.addControllerService("test-good3", service, properties);
+        runner.enableControllerService(service);
+
+        runner.setProperty("SSL Context Svc ID", "test-good3");
+        runner.assertValid();
+        Assert.assertNotNull(service);
+        service.createContext();
     }
 
     /**
@@ -278,29 +288,24 @@ public class SSLContextServiceTest {
      * set on individual keys will fail this test.
      */
     @Test
-    public void testDifferentKeyPassword() {
-        try {
-            final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
-            final SSLContextService service = new StandardSSLContextService();
-            final Map<String, String> properties = new HashMap<>();
-            properties.put(StandardSSLContextService.KEYSTORE.getName(), KEYSTORE_WITH_KEY_PASSWORD_PATH);
-            properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
-            properties.put(StandardSSLContextService.KEY_PASSWORD.getName(), "keypassword");
-            properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
-            properties.put(StandardSSLContextService.TRUSTSTORE.getName(), TRUSTSTORE_PATH);
-            properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
-            properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), JKS_TYPE);
-            runner.addControllerService("test-diff-keys", service, properties);
-            runner.enableControllerService(service);
-
-            runner.setProperty("SSL Context Svc ID", "test-diff-keys");
-            runner.assertValid();
-            Assert.assertNotNull(service);
-            service.createContext();
-        } catch (Exception e) {
-            System.out.println(e);
-            Assert.fail("Should not have thrown a exception " + e.getMessage());
-        }
+    public void testDifferentKeyPassword() throws Exception {
+        final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
+        final SSLContextService service = new StandardSSLContextService();
+        final Map<String, String> properties = new HashMap<>();
+        properties.put(StandardSSLContextService.KEYSTORE.getName(), KEYSTORE_WITH_KEY_PASSWORD_PATH);
+        properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
+        properties.put(StandardSSLContextService.KEY_PASSWORD.getName(), "keypassword");
+        properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
+        properties.put(StandardSSLContextService.TRUSTSTORE.getName(), TRUSTSTORE_PATH);
+        properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
+        properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), JKS_TYPE);
+        runner.addControllerService("test-diff-keys", service, properties);
+        runner.enableControllerService(service);
+
+        runner.setProperty("SSL Context Svc ID", "test-diff-keys");
+        runner.assertValid();
+        Assert.assertNotNull(service);
+        service.createContext();
     }
 
     /**
@@ -309,21 +314,16 @@ public class SSLContextServiceTest {
      * set on individual keys will fail this test.
      */
     @Test
-    public void testDifferentKeyPasswordWithoutSpecifyingKeyPassword() {
-        try {
-            final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
-            final SSLContextService service = new StandardSSLContextService();
-            final Map<String, String> properties = new HashMap<>();
-            properties.put(StandardSSLContextService.KEYSTORE.getName(), KEYSTORE_WITH_KEY_PASSWORD_PATH);
-            properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
-            properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
-            runner.addControllerService("test-diff-keys", service, properties);
-
-            // Assert the service is not valid due to an internal "cannot recover key" because the key password is missing
-            runner.assertNotValid(service);
-        } catch (Exception e) {
-            System.out.println(e);
-            Assert.fail("Should not have thrown a exception " + e.getMessage());
-        }
+    public void testDifferentKeyPasswordWithoutSpecifyingKeyPassword() throws Exception {
+        final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
+        final SSLContextService service = new StandardSSLContextService();
+        final Map<String, String> properties = new HashMap<>();
+        properties.put(StandardSSLContextService.KEYSTORE.getName(), KEYSTORE_WITH_KEY_PASSWORD_PATH);
+        properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
+        properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
+        runner.addControllerService("test-diff-keys", service, properties);
+
+        // Assert the service is not valid due to an internal "cannot recover key" because the key password is missing
+        runner.assertNotValid(service);
     }
 }