This is an automated email from the ASF dual-hosted git repository.
pvillard 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 a13e6c66e8 NIFI-14381: Add flow analysis rule to identify unset SSL
Context Service controller services
a13e6c66e8 is described below
commit a13e6c66e849285b72d1fbfed0c1962455d528fe
Author: Matt Burgess <[email protected]>
AuthorDate: Thu Mar 20 19:15:37 2025 -0400
NIFI-14381: Add flow analysis rule to identify unset SSL Context Service
controller services
Signed-off-by: Pierre Villard <[email protected]>
This closes #9813.
---
.../nifi-standard-rules/pom.xml | 8 ++-
.../rules/RequireServerSSLContextService.java | 84 ++++++++++++++++++++++
.../org.apache.nifi.flowanalysis.FlowAnalysisRule | 1 +
.../additionalDetails.md | 27 +++++++
.../rules/AbstractFlowAnalaysisRuleTest.java | 25 +++++++
.../rules/RequireServerSSLContextServiceTest.java | 60 ++++++++++++++++
.../RequireServerSSLContextService.json | 1 +
...RequireServerSSLContextService_noViolation.json | 1 +
8 files changed, 206 insertions(+), 1 deletion(-)
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/pom.xml
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/pom.xml
index dbebfb42e9..ff47668e79 100644
--- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/pom.xml
+++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/pom.xml
@@ -23,7 +23,13 @@
<artifactId>nifi-standard-rules</artifactId>
<packaging>jar</packaging>
- <dependencies />
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-ssl-context-service-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
<build>
<plugins>
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/java/org/apache/nifi/flowanalysis/rules/RequireServerSSLContextService.java
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/java/org/apache/nifi/flowanalysis/rules/RequireServerSSLContextService.java
new file mode 100644
index 0000000000..68588b2f2c
--- /dev/null
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/java/org/apache/nifi/flowanalysis/rules/RequireServerSSLContextService.java
@@ -0,0 +1,84 @@
+/*
+ * 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.flowanalysis.rules;
+
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.documentation.UseCase;
+import org.apache.nifi.flow.VersionedComponent;
+import org.apache.nifi.flow.VersionedConfigurableExtension;
+import org.apache.nifi.flowanalysis.AbstractFlowAnalysisRule;
+import org.apache.nifi.flowanalysis.ComponentAnalysisResult;
+import org.apache.nifi.flowanalysis.FlowAnalysisRuleContext;
+import org.apache.nifi.util.StringUtils;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+
+@Tags({"component", "processor", "controller service", "type", "ssl", "tls",
"listen"})
+@CapabilityDescription("Produces rule violations for each component (i.e.
processors or controller services) having a property "
+ + "identifying an SSLContextService that is not set.")
+@UseCase(
+ description = "Ensure that an SSL Context has been configured for the
specified components. This helps avoid ports being opened for insecure
(plaintext, e.g.) communications.",
+ configuration = """
+ To avoid the violation, ensure that the "SSL Context Service"
property is set for the specified component(s).
+ """
+)
+public class RequireServerSSLContextService extends AbstractFlowAnalysisRule {
+
+ private final static List<String> COMPONENT_TYPES = List.of(
+ "org.apache.nifi.processors.standard.ListenFTP",
+ "org.apache.nifi.processors.standard.ListenHTTP",
+ "org.apache.nifi.processors.standard.ListenTCP",
+ "org.apache.nifi.processors.standard.ListenSyslog",
+ "org.apache.nifi.processors.standard.HandleHttpRequest",
+ "org.apache.nifi.websocket.jetty.JettyWebSocketServer"
+ );
+
+ @Override
+ public Collection<ComponentAnalysisResult>
analyzeComponent(VersionedComponent component, FlowAnalysisRuleContext context)
{
+ Collection<ComponentAnalysisResult> results = new HashSet<>();
+
+ if (component instanceof VersionedConfigurableExtension
versionedConfigurableExtension) {
+
+ String encounteredComponentType =
versionedConfigurableExtension.getType();
+
+ if (COMPONENT_TYPES.contains(encounteredComponentType)) {
+ // Loop over the properties for this component looking for an
SSLContextService
+
versionedConfigurableExtension.getProperties().forEach((propertyName,
propertyValue) -> {
+
+ // If the SSL Context property exists and the value is not
set, report a violation
+ if (("SSL Context Service".equalsIgnoreCase(propertyName)
|| "ssl-context-service".equalsIgnoreCase(propertyName))
+ && StringUtils.isEmpty(propertyValue)) {
+
+ String encounteredSimpleComponentType =
encounteredComponentType.substring(encounteredComponentType.lastIndexOf(".") +
1);
+ ComponentAnalysisResult result = new
ComponentAnalysisResult(
+ component.getInstanceIdentifier(),
+ "'" + encounteredSimpleComponentType + "' must
specify an SSL Context Service"
+ );
+
+ results.add(result);
+
+ }
+ });
+ }
+ }
+ return results;
+ }
+}
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/resources/META-INF/services/org.apache.nifi.flowanalysis.FlowAnalysisRule
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/resources/META-INF/services/org.apache.nifi.flowanalysis.FlowAnalysisRule
index f5f1df7eb4..165b44830c 100644
---
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/resources/META-INF/services/org.apache.nifi.flowanalysis.FlowAnalysisRule
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/resources/META-INF/services/org.apache.nifi.flowanalysis.FlowAnalysisRule
@@ -14,5 +14,6 @@
# limitations under the License.
org.apache.nifi.flowanalysis.rules.DisallowComponentType
+org.apache.nifi.flowanalysis.rules.RequireServerSSLContextService
org.apache.nifi.flowanalysis.rules.RestrictBackpressureSettings
org.apache.nifi.flowanalysis.rules.RestrictFlowFileExpiration
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/resources/docs/org.apache.nifi.flowanalysis.rules.RequireCustomSSLContext/additionalDetails.md
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/resources/docs/org.apache.nifi.flowanalysis.rules.RequireCustomSSLContext/additionalDetails.md
new file mode 100644
index 0000000000..56be33fab7
--- /dev/null
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/main/resources/docs/org.apache.nifi.flowanalysis.rules.RequireCustomSSLContext/additionalDetails.md
@@ -0,0 +1,27 @@
+<!--
+ 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.
+-->
+
+# RequireCustomSSLContext
+
+## Usage Information
+
+This rule should be applied to prevent flow designers from opening ports for
insecure connections. The list of components to which this rule applies is as
follows:
+
+- **ListenFTP**
+- **ListenHTTP**
+- **ListenTCP**
+- **ListenSyslog**
+- **HandleHttpRequest**
+- **JettyWebSocketServer**
\ No newline at end of file
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/java/org/apache/nifi/flowanalysis/rules/AbstractFlowAnalaysisRuleTest.java
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/java/org/apache/nifi/flowanalysis/rules/AbstractFlowAnalaysisRuleTest.java
index 04b2d38acb..25b1f087c4 100644
---
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/java/org/apache/nifi/flowanalysis/rules/AbstractFlowAnalaysisRuleTest.java
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/java/org/apache/nifi/flowanalysis/rules/AbstractFlowAnalaysisRuleTest.java
@@ -23,7 +23,9 @@ import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
import org.apache.nifi.flowanalysis.AbstractFlowAnalysisRule;
+import org.apache.nifi.flowanalysis.ComponentAnalysisResult;
import org.apache.nifi.flowanalysis.FlowAnalysisRuleContext;
import org.apache.nifi.flowanalysis.GroupAnalysisResult;
import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
@@ -39,6 +41,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -62,6 +65,7 @@ public abstract class AbstractFlowAnalaysisRuleTest<T extends
AbstractFlowAnalys
@BeforeEach
public void setup() {
+ properties.clear();
rule = initializeRule();
Mockito.lenient().when(flowAnalysisRuleContext.getProperty(any())).thenAnswer(invocation
-> {
return properties.get(invocation.getArgument(0));
@@ -72,6 +76,9 @@ public abstract class AbstractFlowAnalaysisRuleTest<T extends
AbstractFlowAnalys
Mockito.lenient().when(validationContext.getProperty(any())).thenAnswer(invocation
-> {
return properties.get(invocation.getArgument(0));
});
+
Mockito.lenient().when(flowAnalysisRuleContext.getProperties()).thenAnswer(invocation
-> {
+ return properties;
+ });
}
protected void setProperty(PropertyDescriptor propertyDescriptor, String
value) {
@@ -87,4 +94,22 @@ public abstract class AbstractFlowAnalaysisRuleTest<T
extends AbstractFlowAnalys
final Collection<GroupAnalysisResult> actual =
rule.analyzeProcessGroup(getProcessGroup(flowDefinition),
flowAnalysisRuleContext);
assertIterableEquals(expected, actual.stream().map(r ->
r.getComponent().get().getInstanceIdentifier()).sorted().toList());
}
+
+
+ protected void testAnalyzeProcessors(String flowDefinition,
List<ComponentAnalysisResult> expected) throws Exception {
+ VersionedProcessGroup rootPG = getProcessGroup(flowDefinition);
+ for (VersionedProcessor processor : rootPG.getProcessors()) {
+ final Collection<ComponentAnalysisResult> actual =
rule.analyzeComponent(processor, flowAnalysisRuleContext);
+ final int expectedSize = (expected == null) ? 0 : expected.size();
+ final int actualSize = (actual == null) ? 0 : actual.size();
+ assertEquals(expectedSize, actualSize);
+ if (actualSize > 0) {
+ final ComponentAnalysisResult[] actualArray =
actual.toArray(new ComponentAnalysisResult[actualSize]);
+
+ for (int i = 0; i < expectedSize; i++) {
+ assertEquals(expected.get(i).getIssueId(),
actualArray[i].getIssueId());
+ }
+ }
+ }
+ }
}
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/java/org/apache/nifi/flowanalysis/rules/RequireServerSSLContextServiceTest.java
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/java/org/apache/nifi/flowanalysis/rules/RequireServerSSLContextServiceTest.java
new file mode 100644
index 0000000000..ebe6176659
--- /dev/null
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/java/org/apache/nifi/flowanalysis/rules/RequireServerSSLContextServiceTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.flowanalysis.rules;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.flowanalysis.ComponentAnalysisResult;
+import org.apache.nifi.ssl.SSLContextProvider;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class RequireServerSSLContextServiceTest extends
AbstractFlowAnalaysisRuleTest<RequireServerSSLContextService> {
+
+ public static final PropertyDescriptor SSL_CONTEXT_SERVICE = new
PropertyDescriptor.Builder()
+ .name("SSL Context Service")
+ .description("SSL Context Service enables support for HTTPS")
+ .required(false)
+ .identifiesControllerService(SSLContextProvider.class)
+ .build();
+ @Override
+ protected RequireServerSSLContextService initializeRule() {
+ return new RequireServerSSLContextService();
+ }
+
+ @Test
+ public void testNoViolations() throws Exception {
+ setProperty(SSL_CONTEXT_SERVICE,
"9c50e433-c2aa-3d19-aae6-20299f4ac38c");
+ testAnalyzeProcessors(
+
"src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService_noViolation.json",
+ List.of()
+ );
+ }
+
+ @Test
+ public void testViolations() throws Exception {
+ setProperty(SSL_CONTEXT_SERVICE, null);
+
+ ComponentAnalysisResult expectedResult = new
ComponentAnalysisResult("b5734be4-0195-1000-0e75-bc0f150d06bd", "'ListenHTTP'
is not allowed");
+ testAnalyzeProcessors(
+
"src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService.json",
+ List.of(
+ expectedResult // processor ListenHttp with no
SSLContextService set
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService.json
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService.json
new file mode 100644
index 0000000000..2ede157d3a
--- /dev/null
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService.json
@@ -0,0 +1 @@
+{"flowContents":{"identifier":"5d78473b-43d3-33b6-beed-83d2ae71a73d","instanceIdentifier":"b56b0714-0195-1000-2a87-e8aa1ca3849f","name":"NiFi
Flow","comments":"","position":{"x":0.0,"y":0.0},"processGroups":[],"remoteProcessGroups":[],"processors":[{"identifier":"da98e020-2712-38fa-b2c9-88e0d3bb25b8","instanceIdentifier":"b5734be4-0195-1000-0e75-bc0f150d06bd","name":"ListenHTTP","comments":"","position":{"x":-51.5,"y":-241.0},"type":"org.apache.nifi.processors.standard.ListenHTTP","bundl
[...]
\ No newline at end of file
diff --git
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService_noViolation.json
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService_noViolation.json
new file mode 100644
index 0000000000..98c1aba573
--- /dev/null
+++
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-rules/src/test/resources/RequireServerSSLContextService/RequireServerSSLContextService_noViolation.json
@@ -0,0 +1 @@
+{"flowContents":{"identifier":"5d78473b-43d3-33b6-beed-83d2ae71a73d","instanceIdentifier":"b56b0714-0195-1000-2a87-e8aa1ca3849f","name":"NiFi
Flow","comments":"","position":{"x":0.0,"y":0.0},"processGroups":[],"remoteProcessGroups":[],"processors":[{"identifier":"da98e020-2712-38fa-b2c9-88e0d3bb25b8","instanceIdentifier":"b5734be4-0195-1000-0e75-bc0f150d06bd","name":"ListenHTTP","comments":"","position":{"x":-51.5,"y":-241.0},"type":"org.apache.nifi.processors.standard.ListenHTTP","bundl
[...]
\ No newline at end of file