mirror of https://github.com/apache/nifi.git
NIFI-5366 - Added ContentSecurityPolicyFilter which stops framing of NiFi resources. It applies the Content-Security-Policy header. This protects against clickjacking.
NIFI-5366 - Added unit test. Added single quotes around 'self' for frame-ancestors CSP header. NIFI-5366 - Fixed dependencies. This closes #2989. Signed-off-by: Andy LoPresto <alopresto@apache.org>
This commit is contained in:
parent
4f0e2f5562
commit
fc1461298a
|
@ -74,6 +74,7 @@ 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.ContentSecurityPolicyFilter;
|
||||
import org.eclipse.jetty.annotations.AnnotationConfiguration;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
|
@ -502,6 +503,11 @@ public class JettyServer implements NiFiServer {
|
|||
// add a filter to set the X-Frame-Options filter
|
||||
webappContext.addFilter(new FilterHolder(FRAME_OPTIONS_FILTER), "/*", EnumSet.allOf(DispatcherType.class));
|
||||
|
||||
// add a filter to set the Content Security Policy frame-ancestors directive
|
||||
FilterHolder cspFilter = new FilterHolder(new ContentSecurityPolicyFilter());
|
||||
cspFilter.setName(ContentSecurityPolicyFilter.class.getSimpleName());
|
||||
webappContext.addFilter(cspFilter, "/*", EnumSet.allOf(DispatcherType.class));
|
||||
|
||||
try {
|
||||
// configure the class loader - webappClassLoader -> jetty nar -> web app's nar -> ...
|
||||
webappContext.setClassLoader(new WebAppClassLoader(parentClassLoader, webappContext));
|
||||
|
|
|
@ -154,5 +154,16 @@
|
|||
<artifactId>jettison</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>5.0.6.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterConfig;
|
||||
|
||||
/**
|
||||
* A filter to apply the Content Security Policy (which supersedes the X-Frame-Options header).
|
||||
*
|
||||
*/
|
||||
public class ContentSecurityPolicyFilter implements Filter {
|
||||
private static final String HEADER = "Content-Security-Policy";
|
||||
private static final String POLICY = "frame-ancestors 'self'";
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContentSecurityPolicyFilter.class);
|
||||
|
||||
@Override
|
||||
public void doFilter(final ServletRequest req, final ServletResponse resp, final FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
final HttpServletResponse response = (HttpServletResponse) resp;
|
||||
response.setHeader(HEADER, POLICY);
|
||||
|
||||
filterChain.doFilter(req, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(final FilterConfig config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ContentSecurityPolicyFilterTest {
|
||||
|
||||
@Test
|
||||
public void testCSPHeaderApplied() throws ServletException, IOException {
|
||||
// Arrange
|
||||
|
||||
FilterHolder originFilter = new FilterHolder(new ContentSecurityPolicyFilter());
|
||||
|
||||
// Set up request
|
||||
HttpServletRequest mockRequest = Mockito.mock(HttpServletRequest.class);
|
||||
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||
FilterChain mockFilterChain = Mockito.mock(FilterChain.class);
|
||||
|
||||
// Action
|
||||
originFilter.getFilter().doFilter(mockRequest, mockResponse, mockFilterChain);
|
||||
|
||||
// Verify
|
||||
assertEquals("frame-ancestors 'self'", mockResponse.getHeader("Content-Security-Policy"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCSPHeaderAppliedOnlyOnce() throws ServletException, IOException {
|
||||
// Arrange
|
||||
|
||||
FilterHolder originFilter = new FilterHolder(new ContentSecurityPolicyFilter());
|
||||
|
||||
// Set up request
|
||||
HttpServletRequest mockRequest = Mockito.mock(HttpServletRequest.class);
|
||||
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||
FilterChain mockFilterChain = Mockito.mock(FilterChain.class);
|
||||
|
||||
// Action
|
||||
originFilter.getFilter().doFilter(mockRequest, mockResponse, mockFilterChain);
|
||||
originFilter.getFilter().doFilter(mockRequest, mockResponse, mockFilterChain);
|
||||
|
||||
// Verify
|
||||
assertEquals("frame-ancestors 'self'", mockResponse.getHeader("Content-Security-Policy"));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue