]> 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>
Mon, 2 Feb 2015 20:47:45 +0000 (21:47 +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 a402f4a8aa8ecff6abffc31ccc9b963fa0cce297..ff6557bcfc1c884d24301b223c5dc3e280cc90c7 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,6 +32,9 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+/**
+ * Configuration of Tomcat connectors
+ */
 class Connectors {
 
   private static final int DISABLED_PORT = -1;
@@ -77,7 +79,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 +90,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 +115,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("sonar.web.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;
   }
@@ -153,8 +153,4 @@ class Connectors {
       c.setAttribute(key, value);
     }
   }
-
-  private static void info(String message) {
-    LoggerFactory.getLogger(Connectors.class).info(message);
-  }
 }
index 049b2d208fa385d13fe7e7f9ec39b24fd992e5fc..7d33ad00f11ec854175f69c41cc9746c2f5ea70d 100644 (file)
@@ -60,12 +60,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..2e94f2c
--- /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.ProtocolHandler;
+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 additional = new StringBuilder();
+    ProtocolHandler protocol = connector.getProtocolHandler();
+    if (protocol instanceof AbstractHttp11JsseProtocol) {
+      additional.append("| ciphers=");
+      String ciphers = ((AbstractHttp11JsseProtocol) protocol).getCiphers();
+      if (StringUtils.isBlank(ciphers)) {
+        additional.append("JVM defaults");
+      } else {
+        additional.append(ciphers);
+      }
+    }
+    log.info(String.format("HTTPS connector enabled on port %d %s", connector.getPort(), additional));
+  }
+}
index f858128b4b0dca70ab359df74ae64b5bb126e7a2..e31e3a0e0c9c37975d3b573ab1a454ef258c5548 100644 (file)
@@ -30,6 +30,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..3dd9168
--- /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.assertj.core.api.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 2b9c3dbdedd45227de59142b10e08b0596809d3f..84feed3c14efdf54380feb323281d5c25fc7adb0 100644 (file)
 # 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 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