]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6140 Ability to restrict list of HTTPS ciphers with the new propert sonar.web...
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Sat, 31 Jan 2015 22:04:52 +0000 (23:04 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 4 Feb 2015 09:45:54 +0000 (10:45 +0100)
server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java
server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java
server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java [new file with mode: 0644]
sonar-application/src/main/assembly/conf/sonar.properties

index 8bbbef093119ba028101b825beee0637932b5bd9..073199b428e5072b88d77a938d3c514011571103 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.app;
 
 import org.apache.catalina.connector.Connector;
 import org.apache.catalina.startup.Tomcat;
-import org.slf4j.LoggerFactory;
 import org.sonar.process.Props;
 
 import javax.annotation.Nullable;
@@ -33,8 +32,13 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+/**
+ * Configuration of Tomcat connectors
+ */
 class Connectors {
 
+  public static final String PROP_HTTPS_CIPHERS = "sonar.web.https.ciphers";
+
   private static final int DISABLED_PORT = -1;
   static final String HTTP_PROTOCOL = "HTTP/1.1";
   static final String AJP_PROTOCOL = "AJP/1.3";
@@ -77,7 +81,6 @@ class Connectors {
     if (port > DISABLED_PORT) {
       connector = newConnector(props, HTTP_PROTOCOL, "http");
       connector.setPort(port);
-      info("HTTP connector is enabled on port " + port);
     }
     return connector;
   }
@@ -89,7 +92,6 @@ class Connectors {
     if (port > DISABLED_PORT) {
       connector = newConnector(props, AJP_PROTOCOL, "http");
       connector.setPort(port);
-      info("AJP connector is enabled on port " + port);
     }
     return connector;
   }
@@ -115,12 +117,12 @@ class Connectors {
       setConnectorAttribute(connector, "truststoreType", props.value("sonar.web.https.truststoreType", "JKS"));
       setConnectorAttribute(connector, "truststoreProvider", props.value("sonar.web.https.truststoreProvider"));
       setConnectorAttribute(connector, "clientAuth", props.value("sonar.web.https.clientAuth", "false"));
+      setConnectorAttribute(connector, "ciphers", props.value(PROP_HTTPS_CIPHERS));
       // SSLv3 must not be enable because of Poodle vulnerability
       // See https://jira.codehaus.org/browse/SONAR-5860
       setConnectorAttribute(connector, "sslEnabledProtocols", "TLSv1,TLSv1.1,TLSv1.2");
       setConnectorAttribute(connector, "sslProtocol", "TLS");
       setConnectorAttribute(connector, "SSLEnabled", true);
-      info("HTTPS connector is enabled on port " + port);
     }
     return connector;
   }
@@ -152,8 +154,4 @@ class Connectors {
       c.setAttribute(key, value);
     }
   }
-
-  private static void info(String message) {
-    LoggerFactory.getLogger(Connectors.class).info(message);
-  }
 }
index 01a5305c53dd46d2fd96912047e768de8af64ea0..371c2685f26275d5af866bdb1470be718a4992de 100644 (file)
@@ -59,12 +59,16 @@ class EmbeddedTomcat {
     webappContext = Webapp.configure(tomcat, props);
     try {
       tomcat.start();
+      new StartupLogs(LoggerFactory.getLogger(getClass())).log(tomcat);
     } catch (LifecycleException e) {
       Throwables.propagate(e);
     }
   }
 
   boolean isReady() {
+    if (webappContext == null) {
+      return false;
+    }
     switch (webappContext.getState()) {
       case NEW:
       case INITIALIZING:
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java b/server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java
new file mode 100644 (file)
index 0000000..1f69e0d
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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.app;
+
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.commons.lang.StringUtils;
+import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
+import org.slf4j.Logger;
+
+class StartupLogs {
+
+  private final Logger log;
+
+  StartupLogs(Logger log) {
+    this.log = log;
+  }
+
+  void log(Tomcat tomcat) {
+    Connector[] connectors = tomcat.getService().findConnectors();
+    for (Connector connector : connectors) {
+      if (StringUtils.containsIgnoreCase(connector.getProtocol(), "AJP")) {
+        logAjp(connector);
+      } else if (StringUtils.equalsIgnoreCase(connector.getScheme(), "https")) {
+        logHttps(connector);
+      } else if (StringUtils.equalsIgnoreCase(connector.getScheme(), "http")) {
+        logHttp(connector);
+      } else {
+        throw new IllegalArgumentException("Unsupported connector: " + connector);
+      }
+    }
+  }
+
+  private void logAjp(Connector connector) {
+    log.info(String.format("%s connector enabled on port %d", connector.getProtocol(), connector.getPort()));
+  }
+
+  private void logHttp(Connector connector) {
+    log.info(String.format("HTTP connector enabled on port %d", connector.getPort()));
+  }
+
+  private void logHttps(Connector connector) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("HTTPS connector enabled on port ").append(connector.getPort());
+
+    AbstractHttp11JsseProtocol protocol = (AbstractHttp11JsseProtocol) connector.getProtocolHandler();
+    sb.append(" | ciphers=");
+    String ciphers = protocol.getCiphers();
+    if (StringUtils.isBlank(ciphers)) {
+      sb.append("JVM defaults");
+    } else {
+      sb.append(ciphers);
+    }
+
+    log.info(sb.toString());
+  }
+}
index 8445efb22be9514e151ed17668650bf4ae879761..15f0e5860bef0d418acc9b85461d8eca54af8d54 100644 (file)
@@ -29,6 +29,9 @@ import org.sonar.process.Props;
 import java.io.File;
 import java.util.Map;
 
+/**
+ * Configures webapp into Tomcat
+ */
 class Webapp {
 
   private static final String JRUBY_MAX_RUNTIMES = "jruby.max.runtimes";
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
new file mode 100644 (file)
index 0000000..4338c13
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.app;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.Props;
+
+import java.io.File;
+import java.net.ConnectException;
+import java.net.Inet4Address;
+import java.net.URL;
+import java.util.Properties;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class EmbeddedTomcatTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void start() throws Exception {
+    Props props = new Props(new Properties());
+
+    // prepare file system
+    File home = temp.newFolder();
+    File webDir = new File(home, "web");
+    FileUtils.write(new File(home, "web/WEB-INF/web.xml"), "<web-app/>");
+    props.set("sonar.path.home", home.getAbsolutePath());
+    props.set("sonar.path.web", webDir.getAbsolutePath());
+
+    // start server on a random port
+    int httpPort = NetworkUtils.freePort();
+    int ajpPort = NetworkUtils.freePort();
+    props.set("sonar.web.port", String.valueOf(httpPort));
+    props.set("sonar.ajp.port", String.valueOf(ajpPort));
+    EmbeddedTomcat tomcat = new EmbeddedTomcat(props);
+    assertThat(tomcat.isReady()).isFalse();
+    tomcat.start();
+    assertThat(tomcat.isReady()).isTrue();
+
+    // check that http connector accepts requests
+    URL url = new URL("http://" + Inet4Address.getLocalHost().getHostAddress() + ":" + httpPort);
+    url.openConnection().connect();
+
+    // stop server
+    tomcat.terminate();
+    // tomcat.isReady() must not be called. It is used to wait for server startup, not shutdown.
+    try {
+      url.openConnection().connect();
+      fail();
+    } catch (ConnectException e) {
+      // expected
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java
new file mode 100644 (file)
index 0000000..03bbbc0
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.app;
+
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.coyote.http11.Http11Protocol;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class StartupLogsTest {
+
+  Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
+  Logger logger = mock(Logger.class);
+  StartupLogs sut = new StartupLogs(logger);
+
+  @Test
+  public void logAjp() throws Exception {
+    Connector connector = newConnector("AJP/1.3", "http");
+    when(tomcat.getService().findConnectors()).thenReturn(new Connector[] {connector});
+
+    sut.log(tomcat);
+
+    verify(logger).info("AJP/1.3 connector enabled on port 1234");
+    verifyNoMoreInteractions(logger);
+  }
+
+  @Test
+  public void logHttp() throws Exception {
+    Connector connector = newConnector("HTTP/1.1", "http");
+    when(tomcat.getService().findConnectors()).thenReturn(new Connector[] {connector});
+
+    sut.log(tomcat);
+
+    verify(logger).info("HTTP connector enabled on port 1234");
+    verifyNoMoreInteractions(logger);
+  }
+
+  @Test
+  public void logHttps_default_ciphers() throws Exception {
+    Connector connector = newConnector("HTTP/1.1", "https");
+    when(tomcat.getService().findConnectors()).thenReturn(new Connector[] {connector});
+
+    sut.log(tomcat);
+
+    verify(logger).info("HTTPS connector enabled on port 1234 | ciphers=JVM defaults");
+    verifyNoMoreInteractions(logger);
+  }
+
+  @Test
+  public void logHttps_overridden_ciphers() throws Exception {
+    Connector connector = newConnector("HTTP/1.1", "https");
+    connector.setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");
+    ((Http11Protocol) connector.getProtocolHandler()).setCiphers("SSL_RSA,TLS_RSA_WITH_RC4");
+    when(tomcat.getService().findConnectors()).thenReturn(new Connector[] {connector});
+
+    sut.log(tomcat);
+
+    verify(logger).info("HTTPS connector enabled on port 1234 | ciphers=SSL_RSA,TLS_RSA_WITH_RC4");
+    verifyNoMoreInteractions(logger);
+  }
+
+  @Test
+  public void unsupported_connector() throws Exception {
+    Connector connector = mock(Connector.class, Mockito.RETURNS_DEEP_STUBS);
+    when(connector.getProtocol()).thenReturn("SPDY/1.1");
+    when(connector.getScheme()).thenReturn("spdy");
+    when(tomcat.getService().findConnectors()).thenReturn(new Connector[] {connector});
+    try {
+      sut.log(tomcat);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  private Connector newConnector(String protocol, String schema) {
+    Connector httpConnector = new Connector(protocol);
+    httpConnector.setScheme(schema);
+    httpConnector.setPort(1234);
+    return httpConnector;
+  }
+}
index 7e87e5c3fedd2ab99501a283cc28069f4600e013..804f187807a74b4dffd686ec8e9ab99b37139a6b 100644 (file)
@@ -87,6 +87,13 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000
 # TCP port for incoming HTTPS connections. Disabled when value is -1 (default).
 #sonar.web.https.port=-1
 
+#
+# Recommendation for HTTPS
+#    SonarQube natively supports HTTPS. However using a reverse proxy
+#    infrastructure is the recommended way to set up your SonarQube installation
+#    on production environments which need to be highly secured.
+#    This allows to fully master all the security parameters that you want.
+
 # HTTPS - the alias used to for the server certificate in the keystore.
 # If not specified the first key read in the keystore is used.
 #sonar.web.https.keyAlias=
@@ -136,6 +143,15 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000
 # and 'true' (certificates are required).
 #sonar.web.https.clientAuth=false
 
+# HTTPS - comma separated list of encryption ciphers to support for HTTPS connections.
+# If specified, only the ciphers that are listed and supported by the SSL implementation will be used.
+# By default, the default ciphers for the JVM will be used. Note that this usually means that the weak
+# export grade ciphers, for instance RC4, will be included in the list of available ciphers.
+# The ciphers are specified using the JSSE cipher naming convention (see
+# https://www.openssl.org/docs/apps/ciphers.html)
+# Example: sonar.web.https.ciphers=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
+#sonar.web.https.ciphers=
+
 # The maximum number of connections that the server will accept and process at any given time.
 # When this number has been reached, the server will not accept any more connections until
 # the number of connections falls below this value. The operating system may still accept connections