--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+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.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This servlet filter sets response headers that enable browser protection against several classes if Web attacks.
+ * The list of headers is mirrored in environment.rb as a workaround to Rack swallowing the headers..
+ */
+public class SecurityServletFilter implements Filter {
+
+ private static final Set<String> ALLOWED_HTTP_METHODS = ImmutableSet.of("DELETE", "GET", "HEAD", "POST", "PUT");
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // nothing
+ }
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
+ doHttpFilter((HttpServletRequest) req, (HttpServletResponse) resp, chain);
+ }
+
+ private static void doHttpFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws IOException, ServletException {
+ // SONAR-6881 Disable OPTIONS and TRACE methods
+ if (!ALLOWED_HTTP_METHODS.contains(httpRequest.getMethod())) {
+ httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+
+ chain.doFilter(httpRequest, httpResponse);
+
+ // Clickjacking protection
+ // See https://www.owasp.org/index.php/Clickjacking_Protection_for_Java_EE
+ httpResponse.addHeader("X-Frame-Options", "SAMEORIGIN");
+
+ // Cross-site scripting
+ // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers
+ httpResponse.addHeader("X-XSS-Protection", "1; mode=block");
+
+ // MIME-sniffing
+ // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers
+ httpResponse.addHeader("X-Content-Type-Options", "nosniff");
+ }
+
+ @Override
+ public void destroy() {
+ // nothing
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Test;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.startsWith;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SecurityServletFilterTest {
+
+ SecurityServletFilter underTest = new SecurityServletFilter();
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ FilterChain chain = mock(FilterChain.class);
+
+ @Test
+ public void allow_GET_method() throws IOException, ServletException {
+ assertThatMethodIsAllowed("GET");
+ }
+
+ @Test
+ public void allow_HEAD_method() throws IOException, ServletException {
+ assertThatMethodIsAllowed("HEAD");
+ }
+
+ @Test
+ public void allow_PUT_method() throws IOException, ServletException {
+ assertThatMethodIsAllowed("PUT");
+ }
+
+ @Test
+ public void allow_POST_method() throws IOException, ServletException {
+ assertThatMethodIsAllowed("POST");
+ }
+
+ private void assertThatMethodIsAllowed(String httpMethod) throws IOException, ServletException {
+ HttpServletRequest request = newRequest(httpMethod);
+ underTest.doFilter(request, response, chain);
+ verify(response, never()).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ verify(chain).doFilter(request, response);
+ }
+
+ @Test
+ public void deny_OPTIONS_method() throws IOException, ServletException {
+ assertThatMethodIsDenied("OPTIONS");
+ }
+
+ @Test
+ public void deny_TRACE_method() throws IOException, ServletException {
+ assertThatMethodIsDenied("TRACE");
+ }
+
+ private void assertThatMethodIsDenied(String httpMethod) throws IOException, ServletException {
+ underTest.doFilter(newRequest(httpMethod), response, chain);
+ verify(response).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ }
+
+ @Test
+ public void set_secured_headers() throws ServletException, IOException {
+ underTest.init(mock(FilterConfig.class));
+ HttpServletRequest request = newRequest("GET");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response, times(3)).addHeader(startsWith("X-"), anyString());
+
+ underTest.destroy();
+ }
+
+ private HttpServletRequest newRequest(String httpMethod) {
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getMethod()).thenReturn(httpMethod);
+ return req;
+ }
+}
end
end
+#
+# Put response headers on all HTTP calls. This is done by the Java SecurityServlerFilter,
+# but for some reason Rack swallows the headers set on Java side.
+# See middleware configuration below.
+#
+class SecurityHeaders
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+
+ # Clickjacking protection
+ # See https://www.owasp.org/index.php/Clickjacking_Protection_for_Java_EE
+ headers['X-Frame-Options']='SAMEORIGIN'
+
+ # Cross-site scripting
+ # See https://www.owasp.org/index.php/List_of_useful_HTTP_headers
+ headers['X-XSS-Protection']='1; mode=block'
+
+ # MIME-sniffing
+ # See https://www.owasp.org/index.php/List_of_useful_HTTP_headers
+ headers['X-Content-Type-Options']='nosniff';
+
+ [status, headers, body]
+ end
+end
+
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# Activate observers that should always be running
# Please note that observers generated using script/generate observer need to have an _observer suffix
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Add security related headers
+ config.middleware.use SecurityHeaders
end
<filter-name>RackFilter</filter-name>
<filter-class>org.jruby.rack.RackFilter</filter-class>
</filter>
+ <filter>
+ <filter-name>SecurityFilter</filter-name>
+ <filter-class>org.sonar.server.platform.SecurityServletFilter</filter-class>
+ </filter>
<filter>
<filter-name>ProfilingFilter</filter-name>
<filter-class>org.sonar.server.platform.ProfilingFilter</filter-class>
<filter-name>ServletFilters</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
+ <filter-mapping>
+ <filter-name>SecurityFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
<filter-mapping>
<filter-name>RackFilter</filter-name>
<url-pattern>/*</url-pattern>