diff options
author | alain <108417558+alain-kermis-sonarsource@users.noreply.github.com> | 2022-11-29 14:12:45 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-11-29 20:02:49 +0000 |
commit | 78237483c658e35f35bf5044e2c4aa0b8993ba2a (patch) | |
tree | 8683b6b4b2ac68fa83111e90d85fb9034b2f6f8f /server | |
parent | 2d5f569fbd355600f35d53a3158f59feeb6fa919 (diff) | |
download | sonarqube-78237483c658e35f35bf5044e2c4aa0b8993ba2a.tar.gz sonarqube-78237483c658e35f35bf5044e2c4aa0b8993ba2a.zip |
SONAR-17619 Fix SSF-157
Diffstat (limited to 'server')
3 files changed, 161 insertions, 0 deletions
diff --git a/server/sonar-web/public/WEB-INF/web.xml b/server/sonar-web/public/WEB-INF/web.xml index da4795bfbef..6ee3d23b5fa 100644 --- a/server/sonar-web/public/WEB-INF/web.xml +++ b/server/sonar-web/public/WEB-INF/web.xml @@ -57,6 +57,11 @@ <filter-class>org.sonar.server.platform.web.CacheControlFilter</filter-class> <async-supported>true</async-supported> </filter> + <filter> + <filter-name>CspFilter</filter-name> + <filter-class>org.sonar.server.platform.web.CspFilter</filter-class> + <async-supported>true</async-supported> + </filter> <!-- order of execution is important --> <filter-mapping> @@ -84,6 +89,10 @@ <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> + <filter-name>CspFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + <filter-mapping> <filter-name>UserSessionFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java new file mode 100644 index 00000000000..2cdd606e5c4 --- /dev/null +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.web; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +public class CspFilter implements Filter { + + private final List<String> cspHeaders = new ArrayList<>(); + private String policies = null; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + cspHeaders.add("Content-Security-Policy"); + cspHeaders.add("X-Content-Security-Policy"); + cspHeaders.add("X-WebKit-CSP"); + + List<String> cspPolicies = new ArrayList<>(); + cspPolicies.add("default-src 'self'"); + cspPolicies.add("base-uri 'none'"); + cspPolicies.add("connect-src 'self' http: https:"); + cspPolicies.add("img-src * data: blob:"); + cspPolicies.add("object-src 'none'"); + cspPolicies.add("script-src 'self' 'unsafe-inline' 'unsafe-eval'"); + cspPolicies.add("style-src 'self' 'unsafe-inline'"); + cspPolicies.add("worker-src 'none'"); + this.policies = String.join("; ", cspPolicies).trim(); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + // Add policies to all HTTP headers + for (String header : this.cspHeaders) { + ((HttpServletResponse) response).setHeader(header, this.policies); + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // Not used + } + +} diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java new file mode 100644 index 00000000000..c73365dae8e --- /dev/null +++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.web; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.RETURNS_MOCKS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CspFilterTest { + + private static final String TEST_CONTEXT = "/sonarqube"; + private static final String EXPECTED = "default-src 'self'; " + + "base-uri 'none'; " + + "connect-src 'self' http: https:; " + + "img-src * data: blob:; " + + "object-src 'none'; " + + "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + + "style-src 'self' 'unsafe-inline'; " + + "worker-src 'none'"; + private final ServletContext servletContext = mock(ServletContext.class, RETURNS_MOCKS); + private final HttpServletResponse response = mock(HttpServletResponse.class); + private final FilterChain chain = mock(FilterChain.class); + private final CspFilter underTest = new CspFilter(); + FilterConfig config = mock(FilterConfig.class); + + @Before + public void setUp() throws ServletException { + when(servletContext.getContextPath()).thenReturn(TEST_CONTEXT); + } + + @Test + public void set_content_security_headers() throws Exception { + doInit(); + HttpServletRequest request = newRequest("/"); + underTest.doFilter(request, response, chain); + verify(response).setHeader("Content-Security-Policy", EXPECTED); + verify(response).setHeader("X-Content-Security-Policy", EXPECTED); + verify(response).setHeader("X-WebKit-CSP", EXPECTED); + verify(chain).doFilter(request, response); + } + + private void doInit() throws ServletException { + underTest.init(config); + } + + private HttpServletRequest newRequest(String path) { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getMethod()).thenReturn("GET"); + when(req.getRequestURI()).thenReturn(path); + when(req.getContextPath()).thenReturn(""); + when(req.getServletContext()).thenReturn(this.servletContext); + return req; + } +} |