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:
thenatog 2018-08-31 19:50:15 -04:00 committed by Andy LoPresto
parent 4f0e2f5562
commit fc1461298a
No known key found for this signature in database
GPG Key ID: 6EC293152D90B61D
4 changed files with 144 additions and 0 deletions

View File

@ -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));

View File

@ -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>

View File

@ -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() {
}
}

View File

@ -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"));
}
}