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;
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";
if (port > DISABLED_PORT) {
connector = newConnector(props, HTTP_PROTOCOL, "http");
connector.setPort(port);
- info("HTTP connector is enabled on port " + port);
}
return connector;
}
if (port > DISABLED_PORT) {
connector = newConnector(props, AJP_PROTOCOL, "http");
connector.setPort(port);
- info("AJP connector is enabled on port " + port);
}
return connector;
}
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;
}
c.setAttribute(key, value);
}
}
-
- private static void info(String message) {
- LoggerFactory.getLogger(Connectors.class).info(message);
- }
}
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:
--- /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.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());
+ }
+}
import java.io.File;
import java.util.Map;
+/**
+ * Configures webapp into Tomcat
+ */
class Webapp {
private static final String JRUBY_MAX_RUNTIMES = "jruby.max.runtimes";
--- /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.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
+ }
+ }
+}
--- /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.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;
+ }
+}
# 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=
# 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