aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2015-01-31 23:04:52 +0100
committerSimon Brandhof <simon.brandhof@sonarsource.com>2015-02-02 21:47:45 +0100
commitb50745d4f87179c3b77f383efbe3e5b934915c1a (patch)
tree05b925f2637d3566ebae6dcdca456b0c0871107e
parent94e710210a75da9354fe7c1e93ed69d1ca96971c (diff)
downloadsonarqube-b50745d4f87179c3b77f383efbe3e5b934915c1a.tar.gz
sonarqube-b50745d4f87179c3b77f383efbe3e5b934915c1a.zip
SONAR-6140 Ability to restrict list of HTTPS ciphers with the new propert sonar.web.https.ciphers
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java74
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java78
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java104
-rw-r--r--sonar-application/src/main/assembly/conf/sonar.properties9
7 files changed, 276 insertions, 8 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java b/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java
index a402f4a8aa8..ff6557bcfc1 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java
@@ -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);
- }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
index 049b2d208fa..7d33ad00f11 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
@@ -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
index 00000000000..2e94f2c2d99
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java
@@ -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));
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java b/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java
index f858128b4b0..e31e3a0e0c9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java
@@ -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
index 00000000000..3dd916816e6
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
@@ -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
index 00000000000..03bbbc06085
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java
@@ -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;
+ }
+}
diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties
index 2b9c3dbdedd..84feed3c14e 100644
--- a/sonar-application/src/main/assembly/conf/sonar.properties
+++ b/sonar-application/src/main/assembly/conf/sonar.properties
@@ -154,6 +154,15 @@
# 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