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 2022/01/19 21:12:08 UTC
[nifi] branch main updated: NIFI-9481 Excluded Data Transfer REST methods from DoSFilter
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 fc27b31 NIFI-9481 Excluded Data Transfer REST methods from DoSFilter
fc27b31 is described below
commit fc27b3138bba69c52d3c7e4158c77a265410981f
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Tue Jan 18 17:19:06 2022 -0600
NIFI-9481 Excluded Data Transfer REST methods from DoSFilter
- Added DataTransferDoSFilter with request URI evaluation
- Added RequestFilterProvider and implementations to abstract Jetty Filter configuration
Signed-off-by: Joe Gresock <jg...@gmail.com>
This closes #5670.
---
.../org/apache/nifi/web/server/JettyServer.java | 165 +-------
.../filter/DataTransferExcludedDoSFilter.java | 54 +++
.../nifi/web/server/filter/FilterParameter.java | 24 ++
.../web/server/filter/RequestFilterProvider.java | 35 ++
.../filter/RestApiRequestFilterProvider.java | 62 +++
.../filter/StandardRequestFilterProvider.java | 127 ++++++
.../nifi/web/server/JettyServerGroovyTest.groovy | 435 ---------------------
.../filter/DataTransferExcludedDoSFilterTest.java | 80 ++++
.../filter/RestApiRequestFilterProviderTest.java | 77 ++++
.../filter/StandardRequestFilterProviderTest.java | 108 +++++
10 files changed, 582 insertions(+), 585 deletions(-)
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index d9aefa3..b5d5b7a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -54,13 +54,10 @@ import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.ContentAccess;
import org.apache.nifi.web.NiFiWebConfigurationContext;
import org.apache.nifi.web.UiExtensionType;
-import org.apache.nifi.web.security.headers.ContentSecurityPolicyFilter;
-import org.apache.nifi.web.security.headers.StrictTransportSecurityFilter;
-import org.apache.nifi.web.security.headers.XContentTypeOptionsFilter;
-import org.apache.nifi.web.security.headers.XFrameOptionsFilter;
-import org.apache.nifi.web.security.headers.XSSProtectionFilter;
-import org.apache.nifi.web.security.requests.ContentLengthFilter;
-import org.apache.nifi.web.server.log.RequestAuthenticationFilter;
+import org.apache.nifi.web.server.filter.FilterParameter;
+import org.apache.nifi.web.server.filter.RequestFilterProvider;
+import org.apache.nifi.web.server.filter.RestApiRequestFilterProvider;
+import org.apache.nifi.web.server.filter.StandardRequestFilterProvider;
import org.apache.nifi.web.server.log.RequestLogProvider;
import org.apache.nifi.web.server.log.StandardRequestLogProvider;
import org.apache.nifi.web.server.util.TrustStoreScanner;
@@ -83,7 +80,6 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.servlets.DoSFilter;
import org.eclipse.jetty.util.ssl.KeyStoreScanner;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -99,7 +95,6 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.DispatcherType;
-import javax.servlet.Filter;
import javax.servlet.ServletContext;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -147,9 +142,9 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
private static final String CONTEXT_PATH_NIFI_API = "/nifi-api";
private static final String CONTEXT_PATH_NIFI_CONTENT_VIEWER = "/nifi-content-viewer";
private static final String CONTEXT_PATH_NIFI_DOCS = "/nifi-docs";
- private static final String RELATIVE_PATH_ACCESS_TOKEN = "/access/token";
- private static final int DOS_FILTER_REJECT_REQUEST = -1;
+ private static final RequestFilterProvider REQUEST_FILTER_PROVIDER = new StandardRequestFilterProvider();
+ private static final RequestFilterProvider REST_API_REQUEST_FILTER_PROVIDER = new RestApiRequestFilterProvider();
private static final FileFilter WAR_FILTER = pathname -> {
final String nameToTest = pathname.getName().toLowerCase();
@@ -214,15 +209,11 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
if (props.isHTTPSConfigured()) {
// Create a handler for the host header and add it to the server
final HostHeaderHandler hostHeaderHandler = new HostHeaderHandler(props);
- logger.info("Created HostHeaderHandler [{}}]", hostHeaderHandler);
// Add this before the WAR handlers
allHandlers.addHandler(hostHeaderHandler);
- } else {
- logger.info("Running in HTTP mode; host headers not restricted");
}
-
final ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
contextHandlers.addHandler(warHandlers);
allHandlers.addHandler(contextHandlers);
@@ -239,14 +230,6 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
server.setRequestLog(requestLog);
}
- /**
- * Instantiates this object but does not perform any configuration. Used for unit testing.
- */
- JettyServer(Server server, NiFiProperties properties) {
- this.server = server;
- this.props = properties;
- }
-
private Handler loadInitialWars(final Set<Bundle> bundles) {
// load WARs
@@ -633,25 +616,15 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
// configure the max form size (3x the default)
webappContext.setMaxFormContentSize(600000);
- // add HTTP security headers to all responses
- // TODO: Allow more granular path configuration (e.g. /nifi-api/site-to-site/ vs. /nifi-api/process-groups)
- ArrayList<Class<? extends Filter>> filters =
- new ArrayList<>(Arrays.asList(
- XFrameOptionsFilter.class,
- ContentSecurityPolicyFilter.class,
- XSSProtectionFilter.class,
- XContentTypeOptionsFilter.class));
+ final List<FilterHolder> requestFilters = CONTEXT_PATH_NIFI_API.equals(contextPath)
+ ? REST_API_REQUEST_FILTER_PROVIDER.getFilters(props)
+ : REQUEST_FILTER_PROVIDER.getFilters(props);
- if (props.isHTTPSConfigured()) {
- filters.add(StrictTransportSecurityFilter.class);
- filters.add(RequestAuthenticationFilter.class);
- }
- filters.forEach((filter) -> addFilters(filter, webappContext));
- addDenialOfServiceFilters(webappContext, props);
-
- if (CONTEXT_PATH_NIFI_API.equals(contextPath)) {
- addAccessTokenRequestFilter(webappContext, props);
- }
+ requestFilters.forEach(filter -> {
+ final String pathSpecification = filter.getInitParameter(FilterParameter.PATH_SPECIFICATION.name());
+ final String filterPathSpecification = pathSpecification == null ? CONTEXT_PATH_ALL : pathSpecification;
+ webappContext.addFilter(filter, filterPathSpecification, EnumSet.allOf(DispatcherType.class));
+ });
try {
// configure the class loader - webappClassLoader -> jetty nar -> web app's nar -> ...
@@ -660,16 +633,10 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
startUpFailure(ioe);
}
- logger.info("Loading WAR: " + warFile.getAbsolutePath() + " with context path set to " + contextPath);
+ logger.info("Loading WAR [{}] Context Path [{}]", warFile.getAbsolutePath(), contextPath);
return webappContext;
}
- private void addFilters(Class<? extends Filter> clazz, WebAppContext webappContext) {
- FilterHolder holder = new FilterHolder(clazz);
- holder.setName(clazz.getSimpleName());
- webappContext.addFilter(holder, CONTEXT_PATH_ALL, EnumSet.allOf(DispatcherType.class));
- }
-
private void addDocsServlets(WebAppContext docsContext) {
try {
// Load the nifi/docs directory
@@ -713,108 +680,6 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
}
/**
- * Adds configurable filters relating to preventing denial of service attacks to the given context.
- * Currently, this implementation adds
- * {@link org.eclipse.jetty.servlets.DoSFilter} and {@link ContentLengthFilter} filters.
- *
- * @param webAppContext context to which filters will be added
- * @param props the {@link NiFiProperties}
- */
- private static void addDenialOfServiceFilters(final WebAppContext webAppContext, final NiFiProperties props) {
- addWebRequestLimitingFilter(webAppContext, props.getMaxWebRequestsPerSecond(), getWebRequestTimeoutMs(props), props.getWebRequestIpWhitelist());
-
- // Only add the ContentLengthFilter if the property is explicitly set (empty by default)
- final int maxRequestSize = determineMaxRequestSize(props);
- if (maxRequestSize > 0) {
- addContentLengthFilter(webAppContext, maxRequestSize);
- } else {
- logger.debug("Not adding content-length filter because {} is not set in nifi.properties", NiFiProperties.WEB_MAX_CONTENT_SIZE);
- }
- }
-
- private static long getWebRequestTimeoutMs(final NiFiProperties props) {
- final long defaultRequestTimeout = Math.round(FormatUtils.getPreciseTimeDuration(NiFiProperties.DEFAULT_WEB_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS));
- long configuredRequestTimeout = 0L;
- try {
- configuredRequestTimeout = Math.round(FormatUtils.getPreciseTimeDuration(props.getWebRequestTimeout(), TimeUnit.MILLISECONDS));
- } catch (final NumberFormatException e) {
- logger.warn("Exception parsing property [{}]; using default value: [{}]", NiFiProperties.WEB_REQUEST_TIMEOUT, defaultRequestTimeout);
- }
-
- return configuredRequestTimeout > 0 ? configuredRequestTimeout : defaultRequestTimeout;
- }
-
- /**
- * Adds the {@link org.eclipse.jetty.servlets.DoSFilter} to the specified context and path. Limits incoming web requests to {@code maxWebRequestsPerSecond} per second.
- * In order to allow clients to make more requests than the maximum rate, clients can be added to the {@code ipWhitelist}.
- * The {@code requestTimeoutInMilliseconds} value limits requests to the given request timeout amount, and will close connections that run longer than this time.
- *
- * @param webAppContext Web Application Context where Filter will be added
- * @param maxRequestsPerSec Maximum number of allowed requests per second
- * @param maxRequestMs Maximum amount of time in milliseconds before a connection will be automatically closed
- * @param allowed Comma-separated string of IP addresses that should not be rate limited. Does not apply to request timeout
- */
- private static void addWebRequestLimitingFilter(final WebAppContext webAppContext, final int maxRequestsPerSec, final long maxRequestMs, final String allowed) {
- final FilterHolder holder = new FilterHolder(DoSFilter.class);
- holder.setInitParameters(new HashMap<String, String>() {{
- put("maxRequestsPerSec", Integer.toString(maxRequestsPerSec));
- put("maxRequestMs", Long.toString(maxRequestMs));
- put("ipWhitelist", allowed);
- }});
- holder.setName(DoSFilter.class.getSimpleName());
-
- webAppContext.addFilter(holder, CONTEXT_PATH_ALL, EnumSet.allOf(DispatcherType.class));
- logger.debug("Added DoSFilter Path [{}] Max Requests Per Second [{}] Request Timeout [{} ms] Allowed [{}]", CONTEXT_PATH_ALL, maxRequestsPerSec, maxRequestMs, allowed);
- }
-
- private static void addAccessTokenRequestFilter(final WebAppContext webAppContext, final NiFiProperties properties) {
- final int maxRequestsPerSec = properties.getMaxWebAccessTokenRequestsPerSecond();
- final long maxRequestMs = getWebRequestTimeoutMs(properties);
-
- final String webRequestAllowed = properties.getWebRequestIpWhitelist();
- final FilterHolder holder = new FilterHolder(DoSFilter.class);
- holder.setInitParameters(new HashMap<String, String>() {{
- put("maxRequestsPerSec", Integer.toString(maxRequestsPerSec));
- put("maxRequestMs", Long.toString(maxRequestMs));
- put("ipWhitelist", webRequestAllowed);
- put("maxWaitMs", Integer.toString(DOS_FILTER_REJECT_REQUEST));
- put("delayMs", Integer.toString(DOS_FILTER_REJECT_REQUEST));
- }});
- holder.setName("AccessTokenRequest-DoSFilter");
-
- webAppContext.addFilter(holder, RELATIVE_PATH_ACCESS_TOKEN, EnumSet.allOf(DispatcherType.class));
- logger.debug("Added DoSFilter Path [{}] Max Requests Per Second [{}] Request Timeout [{} ms] Allowed [{}]", RELATIVE_PATH_ACCESS_TOKEN, maxRequestsPerSec, maxRequestMs, webRequestAllowed);
- }
-
- private static int determineMaxRequestSize(NiFiProperties props) {
- try {
- final String webMaxContentSize = props.getWebMaxContentSize();
- logger.debug("Read {} as {}", NiFiProperties.WEB_MAX_CONTENT_SIZE, webMaxContentSize);
- if (StringUtils.isNotBlank(webMaxContentSize)) {
- int configuredMaxRequestSize = DataUnit.parseDataSize(webMaxContentSize, DataUnit.B).intValue();
- logger.debug("Parsed max content length as {} bytes", configuredMaxRequestSize);
- return configuredMaxRequestSize;
- } else {
- logger.debug("{} read from nifi.properties is empty", NiFiProperties.WEB_MAX_CONTENT_SIZE);
- }
- } catch (final IllegalArgumentException e) {
- logger.warn("Exception parsing property {}; disabling content length filter", NiFiProperties.WEB_MAX_CONTENT_SIZE);
- logger.debug("Error during parsing: ", e);
- }
- return -1;
- }
-
- private static void addContentLengthFilter(final WebAppContext webAppContext, int maxContentLength) {
- final FilterHolder holder = new FilterHolder(ContentLengthFilter.class);
- holder.setInitParameters(new HashMap<String, String>() {{
- put("maxContentLength", String.valueOf(maxContentLength));
- }});
- holder.setName(ContentLengthFilter.class.getSimpleName());
- logger.debug("Adding ContentLengthFilter to Path [{}] with Maximum Content Length [{}B]", CONTEXT_PATH_ALL, maxContentLength);
- webAppContext.addFilter(holder, CONTEXT_PATH_ALL, EnumSet.allOf(DispatcherType.class));
- }
-
- /**
* Returns a File object for the directory containing NIFI documentation.
* <p>
* Formerly, if the docsDirectory did not exist NIFI would fail to start
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/DataTransferExcludedDoSFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/DataTransferExcludedDoSFilter.java
new file mode 100644
index 0000000..83629dc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/DataTransferExcludedDoSFilter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.web.server.filter;
+
+import org.eclipse.jetty.servlets.DoSFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Denial-of-Service Filter extended to exclude Data Transfer operations
+ */
+public class DataTransferExcludedDoSFilter extends DoSFilter {
+ protected static final String DATA_TRANSFER_URI_ATTRIBUTE = "nifi-api-data-transfer-uri";
+
+ private static final String DATA_TRANSFER_PATH = "/nifi-api/data-transfer";
+
+ /**
+ * Handle Filter Chain and override service filter for Data Transfer requests
+ *
+ * @param filterChain Filter Chain
+ * @param request HTTP Servlet Request to be evaluated
+ * @param response HTTP Servlet Response
+ * @throws ServletException Thrown on FilterChain.doFilter() failures
+ * @throws IOException Thrown on FilterChain.doFilter() failures
+ */
+ @Override
+ protected void doFilterChain(final FilterChain filterChain, final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
+ final String requestUri = request.getRequestURI();
+ if (requestUri.startsWith(DATA_TRANSFER_PATH)) {
+ request.setAttribute(DATA_TRANSFER_URI_ATTRIBUTE, requestUri);
+ filterChain.doFilter(request, response);
+ } else {
+ super.doFilterChain(filterChain, request, response);
+ }
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/FilterParameter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/FilterParameter.java
new file mode 100644
index 0000000..8b773d5
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/FilterParameter.java
@@ -0,0 +1,24 @@
+/*
+ * 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.web.server.filter;
+
+/**
+ * Filter Parameter enumeration
+ */
+public enum FilterParameter {
+ PATH_SPECIFICATION
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/RequestFilterProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/RequestFilterProvider.java
new file mode 100644
index 0000000..4551997
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/RequestFilterProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.web.server.filter;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.eclipse.jetty.servlet.FilterHolder;
+
+import java.util.List;
+
+/**
+ * Request Filter Provider for abstracting configuration of HTTP Request Filters
+ */
+public interface RequestFilterProvider {
+ /**
+ * Get Filters using provided NiFi Properties
+ *
+ * @param properties NiFi Properties required
+ * @return List of Filter Holder
+ */
+ List<FilterHolder> getFilters(NiFiProperties properties);
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProvider.java
new file mode 100644
index 0000000..ee114fc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProvider.java
@@ -0,0 +1,62 @@
+/*
+ * 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.web.server.filter;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlets.DoSFilter;
+
+import java.util.List;
+
+/**
+ * Request Filter Provider for REST API Web Application
+ */
+public class RestApiRequestFilterProvider extends StandardRequestFilterProvider {
+ public static final String RELATIVE_PATH_ACCESS_TOKEN = "/access/token";
+
+ private static final int DOS_FILTER_REJECT_REQUEST = -1;
+
+ /**
+ * Get Filters using provided NiFi Properties and append filters for Access Token Requests
+ *
+ * @param properties NiFi Properties required
+ * @return List of Filter Holders
+ */
+ @Override
+ public List<FilterHolder> getFilters(final NiFiProperties properties) {
+ final List<FilterHolder> filters = super.getFilters(properties);
+
+ final FilterHolder accessTokenDenialOfServiceFilter = getAccessTokenDenialOfServiceFilter(properties);
+ filters.add(accessTokenDenialOfServiceFilter);
+
+ return filters;
+ }
+
+ private FilterHolder getAccessTokenDenialOfServiceFilter(final NiFiProperties properties) {
+ final FilterHolder filter = getDenialOfServiceFilter(properties, DoSFilter.class);
+
+ final int maxWebAccessTokenRequestsPerSecond = properties.getMaxWebAccessTokenRequestsPerSecond();
+ filter.setInitParameter("maxRequestsPerSec", Integer.toString(maxWebAccessTokenRequestsPerSecond));
+
+ filter.setInitParameter("maxWaitMs", Integer.toString(DOS_FILTER_REJECT_REQUEST));
+ filter.setInitParameter("delayMs", Integer.toString(DOS_FILTER_REJECT_REQUEST));
+
+ filter.setInitParameter(FilterParameter.PATH_SPECIFICATION.name(), RELATIVE_PATH_ACCESS_TOKEN);
+ filter.setName("AccessToken-DoSFilter");
+ return filter;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java
new file mode 100644
index 0000000..eab2f70
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java
@@ -0,0 +1,127 @@
+/*
+ * 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.web.server.filter;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.headers.ContentSecurityPolicyFilter;
+import org.apache.nifi.web.security.headers.StrictTransportSecurityFilter;
+import org.apache.nifi.web.security.headers.XContentTypeOptionsFilter;
+import org.apache.nifi.web.security.headers.XFrameOptionsFilter;
+import org.apache.nifi.web.security.headers.XSSProtectionFilter;
+import org.apache.nifi.web.security.requests.ContentLengthFilter;
+import org.apache.nifi.web.server.log.RequestAuthenticationFilter;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlets.DoSFilter;
+
+import javax.servlet.Filter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Standard implementation of Request Filter Provider
+ */
+public class StandardRequestFilterProvider implements RequestFilterProvider {
+ private static final int MAX_CONTENT_SIZE_DISABLED = 0;
+
+ /**
+ * Get Filters using provided NiFi Properties
+ *
+ * @param properties NiFi Properties required
+ * @return List of Filter Holders
+ */
+ @Override
+ public List<FilterHolder> getFilters(final NiFiProperties properties) {
+ Objects.requireNonNull(properties, "Properties required");
+
+ final List<FilterHolder> filters = new ArrayList<>();
+
+ filters.add(getFilterHolder(XFrameOptionsFilter.class));
+ filters.add(getFilterHolder(ContentSecurityPolicyFilter.class));
+ filters.add(getFilterHolder(XSSProtectionFilter.class));
+ filters.add(getFilterHolder(XContentTypeOptionsFilter.class));
+
+ if (properties.isHTTPSConfigured()) {
+ filters.add(getFilterHolder(StrictTransportSecurityFilter.class));
+ filters.add(getFilterHolder(RequestAuthenticationFilter.class));
+ }
+
+ final int maxContentSize = getMaxContentSize(properties);
+ if (maxContentSize > MAX_CONTENT_SIZE_DISABLED) {
+ final FilterHolder contentLengthFilter = getContentLengthFilter(maxContentSize);
+ filters.add(contentLengthFilter);
+ }
+
+ final FilterHolder denialOfServiceFilter = getDenialOfServiceFilter(properties, DataTransferExcludedDoSFilter.class);
+ filters.add(denialOfServiceFilter);
+
+ return filters;
+ }
+
+ protected FilterHolder getDenialOfServiceFilter(final NiFiProperties properties, final Class<? extends DoSFilter> filterClass) {
+ final FilterHolder filter = new FilterHolder(filterClass);
+
+ final int maxWebRequestsPerSecond = properties.getMaxWebRequestsPerSecond();
+ filter.setInitParameter("maxRequestsPerSec", Integer.toString(maxWebRequestsPerSecond));
+
+ final long webRequestTimeout = getWebRequestTimeout(properties);
+ filter.setInitParameter("maxRequestMs", Long.toString(webRequestTimeout));
+
+ final String webRequestIpWhitelist = properties.getWebRequestIpWhitelist();
+ filter.setInitParameter("ipWhitelist", webRequestIpWhitelist);
+
+ filter.setName(DoSFilter.class.getSimpleName());
+ return filter;
+ }
+
+ private FilterHolder getFilterHolder(final Class<? extends Filter> filterClass) {
+ final FilterHolder filter = new FilterHolder(filterClass);
+ filter.setName(filterClass.getSimpleName());
+ return filter;
+ }
+
+ private FilterHolder getContentLengthFilter(final int maxContentSize) {
+ final FilterHolder filter = new FilterHolder(ContentLengthFilter.class);
+ filter.setInitParameter(ContentLengthFilter.MAX_LENGTH_INIT_PARAM, Integer.toString(maxContentSize));
+ filter.setName(ContentLengthFilter.class.getSimpleName());
+ return filter;
+ }
+
+ private int getMaxContentSize(final NiFiProperties properties) {
+ final String webMaxContentSize = properties.getWebMaxContentSize();
+ try {
+ return StringUtils.isBlank(webMaxContentSize) ? MAX_CONTENT_SIZE_DISABLED : DataUnit.parseDataSize(webMaxContentSize, DataUnit.B).intValue();
+ } catch (final IllegalArgumentException e) {
+ throw new IllegalStateException(String.format("Property [%s] format invalid", NiFiProperties.WEB_MAX_CONTENT_SIZE), e);
+ }
+ }
+
+ protected long getWebRequestTimeout(final NiFiProperties properties) {
+ final String webRequestTimeout = properties.getWebRequestTimeout();
+
+ try {
+ final double webRequestTimeoutParsed = FormatUtils.getPreciseTimeDuration(webRequestTimeout, TimeUnit.MILLISECONDS);
+ return Math.round(webRequestTimeoutParsed);
+ } catch (final NumberFormatException e) {
+ throw new IllegalStateException(String.format("Property [%s] format invalid", NiFiProperties.WEB_REQUEST_TIMEOUT), e);
+ }
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy
deleted file mode 100644
index e7804d7..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy
+++ /dev/null
@@ -1,435 +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.web.server
-
-import org.apache.nifi.bundle.Bundle
-import org.apache.nifi.nar.ExtensionManagerHolder
-import org.apache.nifi.processor.DataUnit
-import org.apache.nifi.remote.io.socket.NetworkUtils
-import org.apache.nifi.security.util.StandardTlsConfiguration
-import org.apache.nifi.security.util.TemporaryKeyStoreBuilder
-import org.apache.nifi.security.util.TlsConfiguration
-import org.apache.nifi.security.util.TlsPlatform
-import org.apache.nifi.util.NiFiProperties
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.eclipse.jetty.server.Connector
-import org.eclipse.jetty.server.HttpConfiguration
-import org.eclipse.jetty.server.Server
-import org.eclipse.jetty.server.ServerConnector
-import org.eclipse.jetty.server.SslConnectionFactory
-import org.eclipse.jetty.servlet.FilterHolder
-import org.eclipse.jetty.util.ssl.SslContextFactory
-import org.eclipse.jetty.webapp.WebAppContext
-import org.junit.After
-import org.junit.Assume
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.Test
-import org.junit.contrib.java.lang.system.Assertion
-import org.junit.contrib.java.lang.system.ExpectedSystemExit
-import org.junit.contrib.java.lang.system.SystemErrRule
-import org.junit.contrib.java.lang.system.SystemOutRule
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito
-import org.mockito.invocation.InvocationOnMock
-import org.mockito.stubbing.Answer
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-import javax.net.ssl.SSLContext
-import javax.net.ssl.SSLSocket
-import javax.net.ssl.SSLSocketFactory
-import javax.servlet.DispatcherType
-import java.nio.charset.StandardCharsets
-import java.security.Security
-
-@RunWith(JUnit4.class)
-class JettyServerGroovyTest extends GroovyTestCase {
- private static final Logger logger = LoggerFactory.getLogger(JettyServerGroovyTest.class)
-
- @Rule
- public final ExpectedSystemExit exit = ExpectedSystemExit.none()
-
- @Rule
- public final SystemOutRule systemOutRule = new SystemOutRule().enableLog()
-
- @Rule
- public final SystemErrRule systemErrRule = new SystemErrRule().enableLog()
-
- private static final int DEFAULT_HTTP_PORT = 8080
- private static final int DEFAULT_HTTPS_PORT = 8443
-
- private static final int HTTPS_PORT = NetworkUtils.getAvailableTcpPort()
- private static final String HTTPS_HOSTNAME = "localhost"
-
- private static final String TLS_1_3_PROTOCOL = "TLSv1.3"
- private static final List<String> TLS_1_3_CIPHER_SUITES = ["TLS_AES_128_GCM_SHA256"]
-
- private static final TlsConfiguration TLS_CONFIGURATION = new TemporaryKeyStoreBuilder().build()
-
- // These protocol versions should not ever be supported
- static private final List<String> LEGACY_TLS_PROTOCOLS = ["TLS", "TLSv1", "TLSv1.1", "SSL", "SSLv2", "SSLv2Hello", "SSLv3"]
-
- NiFiProperties httpsProps = new NiFiProperties(new Properties([
- (NiFiProperties.WEB_HTTPS_PORT) : HTTPS_PORT as String,
- (NiFiProperties.WEB_HTTPS_HOST) : HTTPS_HOSTNAME,
- (NiFiProperties.SECURITY_KEYSTORE) : TLS_CONFIGURATION.keystorePath,
- (NiFiProperties.SECURITY_KEYSTORE_PASSWD) : TLS_CONFIGURATION.keystorePassword,
- (NiFiProperties.SECURITY_KEYSTORE_TYPE) : TLS_CONFIGURATION.keystoreType.type,
- (NiFiProperties.SECURITY_TRUSTSTORE) : TLS_CONFIGURATION.truststorePath,
- (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): TLS_CONFIGURATION.truststorePassword,
- (NiFiProperties.SECURITY_TRUSTSTORE_TYPE) : TLS_CONFIGURATION.truststoreType.type,
- ]))
-
- @BeforeClass
- static void setUpOnce() throws Exception {
- new File(TLS_CONFIGURATION.keystorePath).deleteOnExit()
- new File(TLS_CONFIGURATION.truststorePath).deleteOnExit()
-
- Security.addProvider(new BouncyCastleProvider())
-
- logger.metaClass.methodMissing = { String name, args ->
- logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
- }
- }
-
- @After
- void tearDown() throws Exception {
- // Cleans up the EMH so it can be reinitialized when a new Jetty server starts
- ExtensionManagerHolder.INSTANCE = null
- }
-
- @Test
- void testShouldDetectHttpAndHttpsConfigurationsBothPresent() {
- // Arrange
- Map badProps = [
- (NiFiProperties.WEB_HTTP_HOST) : "localhost",
- (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
- (NiFiProperties.WEB_THREADS) : NiFiProperties.DEFAULT_WEB_THREADS
- ]
- NiFiProperties mockProps = Mockito.mock(NiFiProperties.class)
- Mockito.when(mockProps.getPort()).thenReturn(DEFAULT_HTTP_PORT)
- Mockito.when(mockProps.getSslPort()).thenReturn(DEFAULT_HTTPS_PORT)
-
- Mockito.when(mockProps.getProperty(ArgumentMatchers.anyString())).thenAnswer(new Answer<Object>() {
- @Override
- Object answer(InvocationOnMock invocation) throws Throwable {
- badProps[(String) invocation.getArgument(0)] ?: "no_value"
- }
- })
-
- // Act
- boolean bothConfigsPresent = JettyServer.bothHttpAndHttpsConnectorsConfigured(mockProps)
- logger.info("Both configs present: ${bothConfigsPresent}")
-
- // Assert
- assert bothConfigsPresent
- }
-
- @Test
- void testDetectHttpAndHttpsConfigurationsShouldAllowEither() {
- // Arrange
- Map httpMap = [
- (NiFiProperties.WEB_HTTP_HOST) : "localhost",
- (NiFiProperties.WEB_HTTPS_HOST): null,
- ]
- NiFiProperties httpProps = [
- getPort : { -> DEFAULT_HTTP_PORT },
- getSslPort : { -> null },
- getProperty: { String prop ->
- String value = httpMap[prop] ?: "no_value"
- value
- },
- ] as NiFiProperties
-
- Map httpsMap = [
- (NiFiProperties.WEB_HTTP_HOST) : null,
- (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
- ]
- NiFiProperties httpsProps = [
- getPort : { -> null },
- getSslPort : { -> DEFAULT_HTTPS_PORT },
- getProperty: { String prop ->
- String value = httpsMap[prop] ?: "no_value"
- value
- },
- ] as NiFiProperties
-
- // Act
- boolean bothConfigsPresentForHttp = JettyServer.bothHttpAndHttpsConnectorsConfigured(httpProps)
- logger.info("Both configs present for HTTP properties: ${bothConfigsPresentForHttp}")
-
- boolean bothConfigsPresentForHttps = JettyServer.bothHttpAndHttpsConnectorsConfigured(httpsProps)
- logger.info("Both configs present for HTTPS properties: ${bothConfigsPresentForHttps}")
-
- // Assert
- assert !bothConfigsPresentForHttp
- assert !bothConfigsPresentForHttps
- }
-
- @Test
- void testShouldFailToStartWithHttpAndHttpsConfigurationsBothPresent() {
- // Arrange
- Map badProps = [
- (NiFiProperties.WEB_HTTP_HOST) : "localhost",
- (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
- ]
- NiFiProperties mockProps = [
- getPort : { -> DEFAULT_HTTP_PORT },
- getSslPort : { -> DEFAULT_HTTPS_PORT },
- getProperty : { String prop ->
- String value = badProps[prop] ?: "no_value"
- logger.mock("getProperty(${prop}) -> ${value}")
- value
- },
- getWebThreads : { -> NiFiProperties.DEFAULT_WEB_THREADS },
- getWebMaxHeaderSize: { -> NiFiProperties.DEFAULT_WEB_MAX_HEADER_SIZE },
- isHTTPSConfigured : { -> true }
- ] as NiFiProperties
-
- // The web server should fail to start and exit Java
- exit.expectSystemExitWithStatus(1)
- exit.checkAssertionAfterwards(new Assertion() {
- void checkAssertion() {
- final String standardErr = systemErrRule.getLog()
- List<String> errLines = standardErr.split("\n")
-
- assert errLines.any { it =~ "Failed to start web server: " }
- assert errLines.any { it =~ "Shutting down..." }
- }
- })
-
- // Act
- JettyServer jettyServer = new JettyServer()
- jettyServer.initialize(mockProps, null, [] as Set<Bundle>, null)
-
- // Assert
-
- // Assertions defined above
- }
-
- @Test
- void testShouldConfigureHTTPSConnector() {
- // Arrange
- final String externalHostname = "localhost"
-
- NiFiProperties httpsProps = new NiFiProperties(new Properties([
- (NiFiProperties.WEB_HTTPS_PORT): HTTPS_PORT as String,
- (NiFiProperties.WEB_HTTPS_HOST): externalHostname,
- ]))
-
- Server internalServer = new Server()
- JettyServer jetty = new JettyServer(internalServer, httpsProps)
-
- // Act
- jetty.configureHttpsConnector(internalServer, new HttpConfiguration())
- List<Connector> connectors = Arrays.asList(internalServer.connectors)
-
- // Assert
- assertServerConnector(connectors, externalHostname, HTTPS_PORT)
- }
-
- @Test
- void testShouldSupportTLSv1_3WhenProtocolFound() {
- // Arrange
- String[] defaultProtocols = SSLContext.getDefault().defaultSSLParameters.protocols
- Assume.assumeTrue("This test should only run when TLSv1.3 is found in the set of default protocols", defaultProtocols.contains(TLS_1_3_PROTOCOL))
-
- Server internalServer = new Server()
- JettyServer jetty = new JettyServer(internalServer, httpsProps)
-
- jetty.configureConnectors(internalServer)
- List<Connector> connectors = Arrays.asList(internalServer.connectors)
- internalServer.start()
-
- // Create a (client) socket which only supports TLSv1.3
- TlsConfiguration tls13ClientConf = StandardTlsConfiguration.fromNiFiProperties(httpsProps)
- SSLSocketFactory socketFactory = org.apache.nifi.security.util.SslContextFactory.createSSLSocketFactory(tls13ClientConf)
-
- SSLSocket socket = (SSLSocket) socketFactory.createSocket(HTTPS_HOSTNAME, HTTPS_PORT)
- socket.setEnabledProtocols([TLS_1_3_PROTOCOL] as String[])
- socket.setEnabledCipherSuites(TLS_1_3_CIPHER_SUITES as String[])
-
- // Act
- String response = makeTLSRequest(socket, "This is a TLS 1.3 request")
-
- // Assert
- assert response =~ "HTTP/1.1 400"
-
- assertServerConnector(connectors)
-
- // Clean up
- internalServer.stop()
- }
-
- @Test
- void testShouldNotSupportTLSv1_3WhenProtocolNotFound() {
- // Arrange
- Assume.assumeTrue("This test should only run when TLSv1.3 is not found in the set of default protocols", !TlsPlatform.supportedProtocols.contains(TLS_1_3_PROTOCOL))
-
- Server internalServer = new Server()
- JettyServer jetty = new JettyServer(internalServer, httpsProps)
-
- jetty.configureConnectors(internalServer)
- List<Connector> connectors = Arrays.asList(internalServer.connectors)
- internalServer.start()
-
- TlsConfiguration tlsConfiguration = StandardTlsConfiguration.fromNiFiProperties(httpsProps)
-
- // Create a "default" (client) socket (which supports TLSv1.2)
- SSLSocketFactory defaultSocketFactory = org.apache.nifi.security.util.SslContextFactory.createSSLSocketFactory(tlsConfiguration)
- SSLSocket defaultSocket = (SSLSocket) defaultSocketFactory.createSocket(HTTPS_HOSTNAME, HTTPS_PORT)
-
- // Act
- String tls12Response = makeTLSRequest(defaultSocket, "This is a default socket request")
-
- def msg = shouldFail() {
- // Create a (client) socket which only supports TLSv1.3
- SSLSocketFactory tls13SocketFactory = org.apache.nifi.security.util.SslContextFactory.createSSLSocketFactory(tlsConfiguration)
-
- SSLSocket tls13Socket = (SSLSocket) tls13SocketFactory.createSocket(HTTPS_HOSTNAME, HTTPS_PORT)
- tls13Socket.setEnabledProtocols([TLS_1_3_PROTOCOL] as String[])
- tls13Socket.setEnabledCipherSuites(TLS_1_3_CIPHER_SUITES as String[])
-
- makeTLSRequest(tls13Socket, "This is a TLSv1.3 socket request")
- }
- logger.expected(msg)
-
- // Assert
- assert tls12Response =~ "HTTP"
-
- assertServerConnector(connectors)
-
- // Clean up
- internalServer.stop()
- }
-
- /**
- * Returns the server's response body as a String. Closes the socket connection.
- *
- * @param socket
- * @param requestMessage
- * @return
- */
- private static String makeTLSRequest(Socket socket, String requestMessage) {
- InputStream socketInputStream = new BufferedInputStream(socket.getInputStream())
- OutputStream socketOutputStream = new BufferedOutputStream(socket.getOutputStream())
-
- socketOutputStream.write(requestMessage.getBytes())
- socketOutputStream.flush()
-
- byte[] data = new byte[2048]
- int len = socketInputStream.read(data)
- if (len <= 0) {
- throw new IOException("no data received")
- }
- final String trimmedResponse = new String(data, 0, len, StandardCharsets.UTF_8)
- logger.info("Client received ${len} bytes from server: \n${trimmedResponse}\n----End of response----")
- socket.close()
- trimmedResponse
- }
-
- private static void assertServerConnector(List<Connector> connectors,
- String EXPECTED_HOSTNAME = HTTPS_HOSTNAME,
- int EXPECTED_PORT = HTTPS_PORT) {
- // Assert the server connector is correct
- assert connectors.size() == 1
- ServerConnector connector = connectors.first() as ServerConnector
- assert connector.host == EXPECTED_HOSTNAME
- assert connector.port == EXPECTED_PORT
- assert connector.getProtocols() == ['ssl', 'http/1.1']
-
- SslConnectionFactory connectionFactory = connector.getConnectionFactory("ssl") as SslConnectionFactory
- SslContextFactory sslContextFactory = connectionFactory.getSslContextFactory()
- assert (sslContextFactory.getExcludeProtocols() as List<String>).containsAll(LEGACY_TLS_PROTOCOLS)
- }
-
- @Test
- void testShouldEnableContentLengthFilterIfWebMaxContentSizeSet() {
- // Arrange
- Map defaultProps = [
- (NiFiProperties.WEB_HTTP_PORT) : DEFAULT_HTTP_PORT as String,
- (NiFiProperties.WEB_HTTP_HOST) : "localhost",
- (NiFiProperties.WEB_MAX_CONTENT_SIZE): "1 MB",
- ]
- NiFiProperties mockProps = new NiFiProperties(new Properties(defaultProps))
-
- List<FilterHolder> filters = []
- def mockWebContext = [
- addFilter: { FilterHolder fh, String path, EnumSet<DispatcherType> d ->
- logger.mock("Called addFilter(${fh.name}, ${path}, ${d})")
- filters.add(fh)
- fh
- }] as WebAppContext
-
- JettyServer jettyServer = new JettyServer(new Server(), mockProps)
- logger.info("Created JettyServer: ${jettyServer.dump()}")
-
- final int MAX_CONTENT_LENGTH_BYTES = DataUnit.parseDataSize(defaultProps[NiFiProperties.WEB_MAX_CONTENT_SIZE], DataUnit.B).intValue()
-
- // Act
- jettyServer.addDenialOfServiceFilters(mockWebContext, mockProps)
-
- // Assert
- assert filters.size() == 2
- def filterNames = filters*.name
- logger.info("Web API Context has ${filters.size()} filters: ${filterNames.join(", ")}".toString())
- assert filterNames.contains("DoSFilter")
- assert filterNames.contains("ContentLengthFilter")
-
- FilterHolder clfHolder = filters.find { it.name == "ContentLengthFilter" }
- String maxContentLength = clfHolder.getInitParameter("maxContentLength")
- assert maxContentLength == MAX_CONTENT_LENGTH_BYTES as String
-
- // Filter is not instantiated just by adding it
-// ContentLengthFilter clf = filters?.find { it.className == "ContentLengthFilter" }?.filter as ContentLengthFilter
-// assert clf.getMaxContentLength() == MAX_CONTENT_LENGTH_BYTES
- }
-
- @Test
- void testShouldNotEnableContentLengthFilterIfWebMaxContentSizeEmpty() {
- // Arrange
- Map defaultProps = [
- (NiFiProperties.WEB_HTTP_PORT): DEFAULT_HTTP_PORT as String,
- (NiFiProperties.WEB_HTTP_HOST): "localhost",
- ]
- NiFiProperties mockProps = new NiFiProperties(new Properties(defaultProps))
-
- List<FilterHolder> filters = []
- def mockWebContext = [
- addFilter: { FilterHolder fh, String path, EnumSet<DispatcherType> d ->
- logger.mock("Called addFilter(${fh.name}, ${path}, ${d})")
- filters.add(fh)
- fh
- }] as WebAppContext
-
- JettyServer jettyServer = new JettyServer(new Server(), mockProps)
- logger.info("Created JettyServer: ${jettyServer.dump()}")
-
- // Act
- jettyServer.addDenialOfServiceFilters(mockWebContext, mockProps)
-
- // Assert
- assert filters.size() == 1
- def filterNames = filters*.name
- logger.info("Web API Context has ${filters.size()} filters: ${filterNames.join(", ")}".toString())
- assert filterNames.contains("DoSFilter")
- assert !filterNames.contains("ContentLengthFilter")
- }
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/DataTransferExcludedDoSFilterTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/DataTransferExcludedDoSFilterTest.java
new file mode 100644
index 0000000..5017133
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/DataTransferExcludedDoSFilterTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.web.server.filter;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class DataTransferExcludedDoSFilterTest {
+ private static final String DATA_TRANSFER_URI = "/nifi-api/data-transfer";
+
+ private static final String ACCESS_URI = "/nifi-api/access";
+
+ @Mock
+ private FilterConfig filterConfig;
+
+ @Mock
+ private FilterChain filterChain;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ private DataTransferExcludedDoSFilter filter;
+
+ @BeforeEach
+ public void setFilter() throws ServletException {
+ filter = new DataTransferExcludedDoSFilter();
+ filter.init(filterConfig);
+ }
+
+ @Test
+ public void testDoFilterChain() throws ServletException, IOException {
+ when(request.getRequestURI()).thenReturn(ACCESS_URI);
+
+ filter.doFilterChain(filterChain, request, response);
+
+ verify(request, never()).setAttribute(eq(DataTransferExcludedDoSFilter.DATA_TRANSFER_URI_ATTRIBUTE), eq(DATA_TRANSFER_URI));
+ }
+
+ @Test
+ public void testDoFilterChainDataTransfer() throws ServletException, IOException {
+ when(request.getRequestURI()).thenReturn(DATA_TRANSFER_URI);
+
+ filter.doFilterChain(filterChain, request, response);
+
+ verify(request).setAttribute(eq(DataTransferExcludedDoSFilter.DATA_TRANSFER_URI_ATTRIBUTE), eq(DATA_TRANSFER_URI));
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java
new file mode 100644
index 0000000..b00f308
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.web.server.filter;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.headers.ContentSecurityPolicyFilter;
+import org.apache.nifi.web.security.headers.XContentTypeOptionsFilter;
+import org.apache.nifi.web.security.headers.XFrameOptionsFilter;
+import org.apache.nifi.web.security.headers.XSSProtectionFilter;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlets.DoSFilter;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.servlet.Filter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class RestApiRequestFilterProviderTest {
+ private RestApiRequestFilterProvider provider;
+
+ @BeforeEach
+ public void setProvider() {
+ provider = new RestApiRequestFilterProvider();
+ }
+
+ @Test
+ public void testGetFilters() {
+ final NiFiProperties properties = getProperties(Collections.emptyMap());
+ final List<FilterHolder> filters = provider.getFilters(properties);
+
+ final Optional<FilterHolder> filterHolder = filters.stream().filter(filter -> filter.getInitParameter(FilterParameter.PATH_SPECIFICATION.name()) != null).findFirst();
+ assertTrue(filterHolder.isPresent());
+
+ final FilterHolder holder = filterHolder.get();
+ assertEquals(DoSFilter.class, holder.getHeldClass());
+
+ assertNotNull(filters);
+ assertFalse(filters.isEmpty());
+
+ assertFilterClassFound(filters, DataTransferExcludedDoSFilter.class);
+ assertFilterClassFound(filters, XFrameOptionsFilter.class);
+ assertFilterClassFound(filters, ContentSecurityPolicyFilter.class);
+ assertFilterClassFound(filters, XSSProtectionFilter.class);
+ assertFilterClassFound(filters, XContentTypeOptionsFilter.class);
+ }
+
+ private void assertFilterClassFound(final List<FilterHolder> filters, final Class<? extends Filter> filterClass) {
+ final Optional<FilterHolder> filterHolder = filters.stream().filter(filter -> filterClass.equals(filter.getHeldClass())).findFirst();
+ assertTrue(filterHolder.isPresent(), String.format("Filter Class [%s] not found", filterClass));
+ }
+
+ private NiFiProperties getProperties(final Map<String, String> properties) {
+ return NiFiProperties.createBasicNiFiProperties(null, properties);
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java
new file mode 100644
index 0000000..feb4ba0
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.web.server.filter;
+
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.headers.ContentSecurityPolicyFilter;
+import org.apache.nifi.web.security.headers.StrictTransportSecurityFilter;
+import org.apache.nifi.web.security.headers.XContentTypeOptionsFilter;
+import org.apache.nifi.web.security.headers.XFrameOptionsFilter;
+import org.apache.nifi.web.security.headers.XSSProtectionFilter;
+import org.apache.nifi.web.security.requests.ContentLengthFilter;
+import org.apache.nifi.web.server.log.RequestAuthenticationFilter;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.servlet.Filter;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StandardRequestFilterProviderTest {
+ private static final String HTTPS_PORT = "8443";
+
+ private static final String MAX_CONTENT_SIZE = "1 MB";
+
+ private StandardRequestFilterProvider provider;
+
+ @BeforeEach
+ public void setProvider() {
+ provider = new StandardRequestFilterProvider();
+ }
+
+ @Test
+ public void testGetFilters() {
+ final NiFiProperties properties = getProperties(Collections.emptyMap());
+ final List<FilterHolder> filters = provider.getFilters(properties);
+
+ assertStandardFiltersFound(filters);
+ }
+
+ @Test
+ public void testGetFiltersContentLengthEnabled() {
+ final Map<String, String> configurationProperties = new LinkedHashMap<>();
+ configurationProperties.put(NiFiProperties.WEB_MAX_CONTENT_SIZE, MAX_CONTENT_SIZE);
+
+ final NiFiProperties properties = getProperties(configurationProperties);
+ final List<FilterHolder> filters = provider.getFilters(properties);
+
+ assertStandardFiltersFound(filters);
+
+ assertFilterClassFound(filters, ContentLengthFilter.class);
+ }
+
+ @Test
+ public void testGetFiltersHttpsEnabled() {
+ final Map<String, String> configurationProperties = new LinkedHashMap<>();
+ configurationProperties.put(NiFiProperties.WEB_HTTPS_PORT, HTTPS_PORT);
+
+ final NiFiProperties properties = getProperties(configurationProperties);
+ final List<FilterHolder> filters = provider.getFilters(properties);
+
+ assertStandardFiltersFound(filters);
+
+ assertFilterClassFound(filters, RequestAuthenticationFilter.class);
+ assertFilterClassFound(filters, StrictTransportSecurityFilter.class);
+ }
+
+ private void assertStandardFiltersFound(final List<FilterHolder> filters) {
+ assertNotNull(filters);
+ assertFalse(filters.isEmpty());
+
+ assertFilterClassFound(filters, DataTransferExcludedDoSFilter.class);
+ assertFilterClassFound(filters, XFrameOptionsFilter.class);
+ assertFilterClassFound(filters, ContentSecurityPolicyFilter.class);
+ assertFilterClassFound(filters, XSSProtectionFilter.class);
+ assertFilterClassFound(filters, XContentTypeOptionsFilter.class);
+ }
+
+ private void assertFilterClassFound(final List<FilterHolder> filters, final Class<? extends Filter> filterClass) {
+ final Optional<FilterHolder> filterHolder = filters.stream().filter(filter -> filterClass.equals(filter.getHeldClass())).findFirst();
+ assertTrue(filterHolder.isPresent(), String.format("Filter Class [%s] not found", filterClass));
+ }
+
+ private NiFiProperties getProperties(final Map<String, String> properties) {
+ return NiFiProperties.createBasicNiFiProperties(null, properties);
+ }
+}