You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by tp...@apache.org on 2022/11/29 16:19:45 UTC
[nifi] branch main updated: NIFI-10871 Skipped CSRF processing for replicated HTTP requests
This is an automated email from the ASF dual-hosted git repository.
tpalfy 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 d55fb91b0f NIFI-10871 Skipped CSRF processing for replicated HTTP requests
d55fb91b0f is described below
commit d55fb91b0f2f62b081a8732e66dbbd3ea66961a6
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Wed Nov 23 16:30:20 2022 -0600
NIFI-10871 Skipped CSRF processing for replicated HTTP requests
- Updated Security Filter Configuration to avoid unnecessary CSRF Request Token generation for requests replicated between cluster nodes
This closes #6715.
Signed-off-by: Tamas Palfy <tp...@apache.org>
---
.../nifi/web/NiFiWebApiSecurityConfiguration.java | 2 +
.../security/csrf/SkipReplicatedCsrfFilter.java | 61 +++++++++++
.../csrf/SkipReplicatedCsrfFilterTest.java | 113 +++++++++++++++++++++
3 files changed, 176 insertions(+)
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
index d43824d122..2108b09abe 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
@@ -20,6 +20,7 @@ import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.StandardAuthenticationEntryPoint;
import org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationFilter;
import org.apache.nifi.web.security.csrf.CsrfCookieRequestMatcher;
+import org.apache.nifi.web.security.csrf.SkipReplicatedCsrfFilter;
import org.apache.nifi.web.security.csrf.StandardCookieCsrfTokenRepository;
import org.apache.nifi.web.security.knox.KnoxAuthenticationFilter;
import org.apache.nifi.web.security.log.AuthenticationUserFilter;
@@ -109,6 +110,7 @@ public class NiFiWebApiSecurityConfiguration {
).permitAll()
.anyRequest().authenticated()
)
+ .addFilterBefore(new SkipReplicatedCsrfFilter(), CsrfFilter.class)
.csrf(csrf -> csrf
.csrfTokenRepository(
new StandardCookieCsrfTokenRepository()
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/csrf/SkipReplicatedCsrfFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/csrf/SkipReplicatedCsrfFilter.java
new file mode 100644
index 0000000000..e3637fdb55
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/csrf/SkipReplicatedCsrfFilter.java
@@ -0,0 +1,61 @@
+/*
+ * 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.security.csrf;
+
+import org.springframework.security.web.csrf.CsrfFilter;
+import org.springframework.security.web.util.matcher.AndRequestMatcher;
+import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Skip Replicated Cross-Site Request Forgery Filter disables subsequent filtering for matched requests
+ */
+public class SkipReplicatedCsrfFilter extends OncePerRequestFilter {
+ /** RequestReplicator.REQUEST_TRANSACTION_ID_HEADER applied to replicated cluster requests */
+ protected static final String REPLICATED_REQUEST_HEADER = "X-RequestTransactionId";
+
+ /** Requests containing replicated header and not containing authorization cookies will be skipped */
+ private static final RequestMatcher REQUEST_MATCHER = new AndRequestMatcher(
+ new RequestHeaderRequestMatcher(REPLICATED_REQUEST_HEADER),
+ new NegatedRequestMatcher(new CsrfCookieRequestMatcher())
+ );
+
+ /**
+ * Set request attribute to disable standard CSRF Filter when request matches
+ *
+ * @param request HTTP Servlet Request
+ * @param response HTTP Servlet Response
+ * @param filterChain Filter Chain
+ * @throws ServletException Thrown on FilterChain.doFilter()
+ * @throws IOException Thrown on FilterChain.doFilter()
+ */
+ @Override
+ protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
+ if (REQUEST_MATCHER.matches(request)) {
+ CsrfFilter.skipRequest(request);
+ }
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/csrf/SkipReplicatedCsrfFilterTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/csrf/SkipReplicatedCsrfFilterTest.java
new file mode 100644
index 0000000000..a2e0727c44
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/csrf/SkipReplicatedCsrfFilterTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.security.csrf;
+
+import org.apache.nifi.web.security.http.SecurityCookieName;
+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 org.springframework.http.HttpMethod;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.web.csrf.CsrfFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class SkipReplicatedCsrfFilterTest {
+ private static final String SHOULD_NOT_FILTER = String.format("SHOULD_NOT_FILTER%s", CsrfFilter.class.getName());
+
+ MockHttpServletRequest httpServletRequest;
+
+ MockHttpServletResponse httpServletResponse;
+
+ @Mock
+ FilterChain filterChain;
+
+ SkipReplicatedCsrfFilter filter;
+
+ @BeforeEach
+ void setHandler() {
+ filter = new SkipReplicatedCsrfFilter();
+ httpServletRequest = new MockHttpServletRequest();
+ httpServletResponse = new MockHttpServletResponse();
+ }
+
+ @Test
+ void testDoFilterInternalNotSkipped() throws ServletException, IOException {
+ httpServletRequest.setMethod(HttpMethod.GET.name());
+
+ filter.doFilterInternal(httpServletRequest, httpServletResponse, filterChain);
+
+ assertCsrfFilterNotSkipped();
+ }
+
+ @Test
+ void testDoFilterInternalBearerCookieNotSkipped() throws ServletException, IOException {
+ httpServletRequest.setMethod(HttpMethod.GET.name());
+ final Cookie bearerCookie = new Cookie(SecurityCookieName.AUTHORIZATION_BEARER.getName(), UUID.randomUUID().toString());
+ httpServletRequest.setCookies(bearerCookie);
+
+ filter.doFilterInternal(httpServletRequest, httpServletResponse, filterChain);
+
+ assertCsrfFilterNotSkipped();
+ }
+
+ @Test
+ void testDoFilterInternalReplicatedHeaderAndBearerCookieNotSkipped() throws ServletException, IOException {
+ httpServletRequest.setMethod(HttpMethod.GET.name());
+ httpServletRequest.addHeader(SkipReplicatedCsrfFilter.REPLICATED_REQUEST_HEADER, UUID.randomUUID().toString());
+ final Cookie bearerCookie = new Cookie(SecurityCookieName.AUTHORIZATION_BEARER.getName(), UUID.randomUUID().toString());
+ httpServletRequest.setCookies(bearerCookie);
+
+ filter.doFilterInternal(httpServletRequest, httpServletResponse, filterChain);
+
+ assertCsrfFilterNotSkipped();
+ }
+
+ @Test
+ void testDoFilterInternalReplicatedHeaderSkipped() throws ServletException, IOException {
+ httpServletRequest.setMethod(HttpMethod.GET.name());
+ httpServletRequest.addHeader(SkipReplicatedCsrfFilter.REPLICATED_REQUEST_HEADER, UUID.randomUUID().toString());
+
+ filter.doFilterInternal(httpServletRequest, httpServletResponse, filterChain);
+
+ final Object shouldNotFilter = httpServletRequest.getAttribute(SHOULD_NOT_FILTER);
+
+ assertEquals(Boolean.TRUE, shouldNotFilter);
+ verify(filterChain).doFilter(eq(httpServletRequest), eq(httpServletResponse));
+ }
+
+ private void assertCsrfFilterNotSkipped() throws ServletException, IOException {
+ final Object shouldNotFilter = httpServletRequest.getAttribute(SHOULD_NOT_FILTER);
+
+ assertNotEquals(Boolean.TRUE, shouldNotFilter);
+ verify(filterChain).doFilter(eq(httpServletRequest), eq(httpServletResponse));
+ }
+}