Improve docker healthcheck (#2110)

The new docker health check respects the jetty configuration and uses the configured port and context path.
It supports ssl listeners and follows redirects.

Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2022-08-30 11:02:21 +02:00
committed by GitHub
parent 6055ba145f
commit f672e175b5
17 changed files with 1261 additions and 34 deletions

View File

@@ -0,0 +1,2 @@
- type: fixed
description: Fix docker healthcheck for custom ports, https and forced base url ([#2110](https://github.com/scm-manager/scm-manager/pull/2110))

View File

@@ -69,6 +69,6 @@ EXPOSE 8080
# we us a high relative high start period,
# because the start time depends on the number of installed plugins
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/scm/api/v2 || exit 1
CMD /opt/scm-server/bin/healthcheck || exit 1
ENTRYPOINT [ "/opt/scm-server/bin/scm-server" ]

View File

@@ -50,7 +50,7 @@ COPY build/docker/opt /opt
RUN set -x \
&& apt-get update \
# libfreetype6 libfontconfig1 graphviz
&& apt-get install -y --no-install-recommends libfreetype6 libfontconfig1 graphviz mercurial bash ca-certificates wget \
&& apt-get install -y --no-install-recommends libfreetype6 libfontconfig1 graphviz mercurial bash ca-certificates \
# use gid 0 for openshift compatibility
&& useradd -d "${SCM_HOME}" -u 1000 -g 0 -m -s /bin/bash scm \
&& mkdir -p ${SCM_HOME} ${CACHE_DIR} \
@@ -68,6 +68,6 @@ EXPOSE 8080
# we us a high relative high start period,
# because the start time depends on the number of installed plugins
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/scm/api/v2 || exit 1
CMD /opt/scm-server/bin/healthcheck || exit 1
ENTRYPOINT [ "/opt/scm-server/bin/scm-server" ]

View File

@@ -90,7 +90,7 @@ task setupBuilder() {
}
task build(type: Exec) {
commandLine = ["docker", "buildx", "bake", "--builder", "scm-builder", isSnapshot ? "dev": "prod"]
commandLine = ["docker", "buildx", "bake", "--builder", "scm-builder", isSnapshot ? "dev": "prod", isSnapshot ? "--load" : ""]
environment "VERSION", dockerTag
environment "COMMIT_SHA", revision
environment "IMAGE", dockerRepository
@@ -122,7 +122,7 @@ task publish() {
}
def inspect = new JsonSlurper().parseText(stdout.toString())
def manifest = inspect.manifests.find { m -> m.platform.architecture == "arm" }
// append arm image to manifest with version and without os suffix
exec {
commandLine = ["docker", "buildx", "imagetools", "create", "--append", "-t", "${dockerRepository}:${dockerTag}", "${dockerRepository}:${dockerTag}-debian@${manifest.digest}"]

View File

@@ -65,10 +65,10 @@
</Array>
</Arg>
<Set name="host">
<SystemProperty name="jetty.host" default="0.0.0.0" />
<Env name="SCM_HTTP_HOST" default="0.0.0.0" />
</Set>
<Set name="port">
<SystemProperty name="jetty.port" default="8080" />
<Env name="SCM_HTTP_PORT" default="8080" />
</Set>
</New>
</Arg>

View File

@@ -0,0 +1,6 @@
#!/bin/sh
exec java -cp "/etc/scm:/opt/scm-server/lib/*" \
-client -Xmx64m \
-Djava.awt.headless=true \
-Dlogback.configurationFile=logging.xml \
sonia.scm.server.HealthCheck

View File

@@ -35,4 +35,13 @@ dependencies {
implementation libraries.jettyWebapp
// TODO do we need jetty jmx?
implementation libraries.jettyJmx
// tests
testImplementation libraries.junitJupiterApi
testImplementation libraries.junitJupiterParams
testRuntimeOnly libraries.junitJupiterEngine
testImplementation libraries.junitPioneer
testImplementation libraries.assertj
testImplementation libraries.guava
}

View File

@@ -1,8 +1,17 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.google.code.findbugs:jsr305:3.0.2=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.google.errorprone:error_prone_annotations:2.3.4=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.google.guava:failureaccess:1.0.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.google.guava:guava:30.1-jre=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.google.j2objc:j2objc-annotations:1.3=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
commons-daemon:commons-daemon:1.2.3=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
javax.servlet:javax.servlet-api:3.1.0=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.apiguardian:apiguardian-api:1.1.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.assertj:assertj-core:3.18.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.checkerframework:checker-qual:3.5.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.eclipse.jetty:jetty-http:9.4.44.v20210927=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.eclipse.jetty:jetty-io:9.4.44.v20210927=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.eclipse.jetty:jetty-jmx:9.4.44.v20210927=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
@@ -17,6 +26,19 @@ org.jacoco:org.jacoco.agent:0.8.7=jacocoAgentCopy,jacocoAntCopy
org.jacoco:org.jacoco.ant:0.8.7=jacocoAntCopy
org.jacoco:org.jacoco.core:0.8.7=jacocoAntCopy
org.jacoco:org.jacoco.report:0.8.7=jacocoAntCopy
org.junit-pioneer:junit-pioneer:1.6.2=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.junit.jupiter:junit-jupiter-api:5.7.0=testCompileClasspath,testCompileClasspathCopy
org.junit.jupiter:junit-jupiter-api:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
org.junit.jupiter:junit-jupiter-engine:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
org.junit.jupiter:junit-jupiter-params:5.7.0=testCompileClasspath,testCompileClasspathCopy
org.junit.jupiter:junit-jupiter-params:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
org.junit.platform:junit-platform-commons:1.7.0=testCompileClasspath,testCompileClasspathCopy
org.junit.platform:junit-platform-commons:1.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
org.junit.platform:junit-platform-engine:1.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
org.junit.platform:junit-platform-launcher:1.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
org.junit:junit-bom:5.7.0=testCompileClasspath,testCompileClasspathCopy
org.junit:junit-bom:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.ow2.asm:asm-analysis:9.1=jacocoAntCopy
org.ow2.asm:asm-commons:9.1=jacocoAntCopy
org.ow2.asm:asm-tree:9.1=jacocoAntCopy

View File

@@ -0,0 +1,153 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.server;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
public class HealthCheck implements Callable<Integer> {
private final List<Listener> listeners;
public HealthCheck(List<Listener> configuration) {
this.listeners = configuration;
}
public HealthCheck(Listener... listeners) {
this.listeners = Arrays.asList(listeners);
}
public static void main(String[] args) {
HealthCheck check = new HealthCheck(new ServerConfiguration().getListeners());
Integer exitCode = check.call();
System.exit(exitCode);
}
@Override
public Integer call() {
return listeners.stream()
.map(l -> String.format("%s://127.0.0.1:%d%s/api/v2", l.getScheme(), l.getPort(), contextPath(l)))
.mapToInt(this::checkUrl)
.max()
.orElse(0);
}
private String contextPath(Listener listener) {
if ("/".equals(listener.getContextPath())) {
return "";
}
return listener.getContextPath();
}
private Integer checkUrl(String url) {
return checkUrl(url, true);
}
private int checkUrl(String url, boolean followRedirect) {
try {
HttpURLConnection connection = createConnection(url);
int code = connection.getResponseCode();
if (isRedirect(code) && followRedirect) {
String location = connection.getHeaderField("Location");
if (location != null && !location.isEmpty()) {
return checkUrl(location, false);
} else {
return 1;
}
}
return code == 200 ? 0 : 1;
} catch (IOException e) {
return 2;
}
}
private boolean isRedirect(int code) {
return code == HttpServletResponse.SC_MOVED_PERMANENTLY
|| code == HttpServletResponse.SC_MOVED_TEMPORARILY // same as SC_FOUND
|| code == HttpServletResponse.SC_SEE_OTHER
|| code == HttpServletResponse.SC_TEMPORARY_REDIRECT
|| code == 308; // could not find SC field
}
private HttpURLConnection createConnection(String url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(15000);
connection.setRequestProperty("User-Agent", "scm-health-check/1.0");
if (connection instanceof HttpsURLConnection) {
applyHttpsConfiguration((HttpsURLConnection) connection);
}
return connection;
}
@SuppressWarnings("java:S5527")
private void applyHttpsConfiguration(HttpsURLConnection connection) {
connection.setHostnameVerifier((hostname, session) -> true);
SSLSocketFactory socketFactory = createSslSocketFactory();
if (socketFactory != null) {
connection.setSSLSocketFactory(socketFactory);
}
}
private SSLSocketFactory createSslSocketFactory() {
try {
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(null, new X509TrustManager[]{new TrustAllTrustManager()}, null);
return context.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
return null;
}
}
@SuppressWarnings("java:S4830")
private static class TrustAllTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// accept anything
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// accept anything
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.server;
public class Listener {
private final String scheme;
private final int port;
private final String contextPath;
public Listener(String scheme, int port, String contextPath) {
this.scheme = scheme;
this.port = port;
this.contextPath = contextPath;
}
public String getScheme() {
return scheme;
}
public int getPort() {
return port;
}
public String getContextPath() {
return contextPath;
}
}

View File

@@ -27,11 +27,6 @@ package sonia.scm.server;
//~--- non-JDK imports --------------------------------------------------------
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.xml.XmlConfiguration;
//~--- JDK imports ------------------------------------------------------------
import java.net.URL;
/**
*
@@ -39,10 +34,6 @@ import java.net.URL;
*/
public class ScmServer extends Thread
{
/** Field description */
public static final String CONFIGURATION = "/server-config.xml";
/** Field description */
static final int GRACEFUL_TIMEOUT = 2000;
@@ -54,25 +45,9 @@ public class ScmServer extends Thread
*/
public ScmServer()
{
URL configURL = ScmServer.class.getResource(CONFIGURATION);
if (configURL == null)
{
throw new ScmServerException("could not find server-config.xml");
}
ServerConfiguration config = new ServerConfiguration();
server = new org.eclipse.jetty.server.Server();
try
{
XmlConfiguration config = new XmlConfiguration(configURL);
config.configure(server);
}
catch (Exception ex)
{
throw new ScmServerException("error during server configuration", ex);
}
config.configure(server);
}
//~--- methods --------------------------------------------------------------

View File

@@ -0,0 +1,129 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.server;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public final class ServerConfiguration {
private static final String CONFIGURATION = "/server-config.xml";
@SuppressWarnings("java:S1075") // not a real uri
private static final String DEFAULT_CONTEXT_PATH = "/scm";
private final XmlConfiguration jettyConfiguration;
public ServerConfiguration() {
this(CONFIGURATION);
}
public ServerConfiguration(String configurationUrl) {
this.jettyConfiguration = read(configurationUrl);
}
public ServerConfiguration(Path configurationPath) {
this.jettyConfiguration = parse(Resource.newResource(configurationPath));
}
public void configure(Server server) {
try {
jettyConfiguration.configure(server);
} catch (Exception ex) {
throw new ScmServerException("error during server configuration", ex);
}
}
public List<Listener> getListeners() {
List<Listener> listeners = new ArrayList<>();
Server server = new Server();
configure(server);
String contextPath = findContextPath(server.getHandlers());
if (contextPath == null) {
contextPath = DEFAULT_CONTEXT_PATH;
}
for (Connector connector : server.getConnectors()) {
if (connector instanceof ServerConnector) {
ServerConnector serverConnector = (ServerConnector) connector;
String scheme = "http";
String protocol = serverConnector.getDefaultProtocol();
if ("SSL".equalsIgnoreCase(protocol) || "TLS".equalsIgnoreCase(protocol)) {
scheme = "https";
}
listeners.add(new Listener(scheme, serverConnector.getPort(), contextPath));
}
}
return listeners;
}
private String findContextPath(Handler[] handlers) {
for (Handler handler : handlers) {
if (handler instanceof WebAppContext) {
return ((WebAppContext) handler).getContextPath();
} else if (handler instanceof HandlerCollection) {
String contextPath = findContextPath(((HandlerCollection) handler).getHandlers());
if (contextPath != null) {
return contextPath;
}
}
}
return null;
}
private static XmlConfiguration read(String configurationUrl) {
URL configURL = ScmServer.class.getResource(configurationUrl);
if (configURL == null) {
throw new ScmServerException("could not find server-config.xml");
}
return parse(Resource.newResource(configURL));
}
private static XmlConfiguration parse(Resource resource) {
try {
return new XmlConfiguration(resource);
} catch (IOException | SAXException ex) {
throw new ScmServerException("could not read server configuration", ex);
}
}
}

View File

@@ -0,0 +1,311 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.server;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class HealthCheckTest {
private List<Server> servers;
@Test
void shouldReturnZero() throws Exception {
int port = startHttpServer("/scm");
HealthCheck check = new HealthCheck(new Listener("http", port, "/scm"));
assertThat(check.call()).isZero();
}
@Test
void shouldReturnNonZeroForWrongContextPath() throws Exception {
int port = startHttpServer("/scm");
HealthCheck check = new HealthCheck(new Listener("http", port, "/myscm"));
assertThat(check.call()).isPositive();
}
@Test
void shouldReturnNonZeroForWrongPort() throws Exception {
int port = startHttpServer("/scm");
HealthCheck check = new HealthCheck(new Listener("http", port + 1, "/scm"));
assertThat(check.call()).isPositive();
}
@Test
void shouldReturnZeroForMultipleListeners() throws Exception {
int one = startHttpServer("/scm");
int two = startHttpServer("/scm");
HealthCheck check = new HealthCheck(
new Listener("http", one, "/scm"),
new Listener("http", two, "/scm")
);
assertThat(check.call()).isZero();
}
@Test
void shouldReturnNonZeroIfOneFails() throws Exception {
int one = startHttpServer("/myscm");
int two = startHttpServer("/scm");
HealthCheck check = new HealthCheck(
new Listener("http", one, "/myscm"),
new Listener("http", two, "/myscm")
);
assertThat(check.call()).isPositive();
}
@Test
void shouldHandleHttps(@TempDir Path directory) throws Exception {
int port = startHttpsServer(directory, "/scm");
HealthCheck check = new HealthCheck(new Listener("https", port, "/scm"));
assertThat(check.call()).isZero();
}
@Test
void shouldHandleHttpAndHttps(@TempDir Path directory) throws Exception {
int http = startHttpServer("/scm");
int https = startHttpsServer(directory, "/scm");
HealthCheck check = new HealthCheck(
new Listener("http", http, "/scm"),
new Listener("https", https, "/scm")
);
assertThat(check.call()).isZero();
}
@Test
void shouldFollowRedirect() throws Exception {
int http = startHttpServer("/scm");
int redirector = startHttpRedirector("/scm", "http", http);
HealthCheck check = new HealthCheck(
new Listener("http", redirector, "/scm")
);
assertThat(check.call()).isZero();
}
@Test
void shouldFailWithInvalidRedirect() throws Exception {
int redirector = startHttpRedirector("/scm", "http", 9999);
HealthCheck check = new HealthCheck(
new Listener("http", redirector, "/scm")
);
assertThat(check.call()).isPositive();
}
@Test
void shouldFailOnRedirectWithouLocation() throws Exception {
int redirector = startInvalidRedirector("/scm");
HealthCheck check = new HealthCheck(
new Listener("http", redirector, "/scm")
);
assertThat(check.call()).isPositive();
}
@Test
void shouldFollowRedirectFromHttpToHttps(@TempDir Path directory) throws Exception {
int https = startHttpsServer(directory,"/scm");
int redirector = startHttpRedirector("/scm", "https", https);
HealthCheck check = new HealthCheck(
new Listener("http", redirector, "/scm")
);
assertThat(check.call()).isZero();
}
@BeforeEach
private void setUp() {
servers = new ArrayList<>();
}
@AfterEach
private void shutdown() {
for (Server server : servers) {
try {
server.stop();
} catch (Exception e) {
// do nothing
}
}
}
private int startHttpServer(String contextPath) throws Exception {
Server server = new Server(0);
return start(contextPath, server);
}
private int start(String contextPath, Server server) throws Exception {
server.setHandler(new AbstractHandler() {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/plain; charset=utf-8");
if (target.equals(contextPath + "/api/v2")) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
baseRequest.setHandled(true);
}
});
server.start();
servers.add(server);
return server.getURI().getPort();
}
private int startHttpsServer(Path directory, String contextPath) throws Exception {
Server server = new Server();
ServerConnector sslConnector = createSslConnector(server, directory, "changeit");
server.addConnector(sslConnector);
return start(contextPath, server);
}
private int startHttpRedirector(String contextPath, String targetScheme, int targetPort) throws Exception {
Server server = new Server(0);
server.setHandler(new AbstractHandler() {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/plain; charset=utf-8");
if (target.equals(contextPath + "/api/v2")) {
response.setHeader("Location", String.format("%s://127.0.0.1:%d%s", targetScheme, targetPort, target));
response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
baseRequest.setHandled(true);
}
});
server.start();
servers.add(server);
return server.getURI().getPort();
}
private int startInvalidRedirector(String contextPath) throws Exception {
Server server = new Server(0);
server.setHandler(new AbstractHandler() {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/plain; charset=utf-8");
if (target.equals(contextPath + "/api/v2")) {
response.setStatus(HttpServletResponse.SC_FOUND);
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
baseRequest.setHandled(true);
}
});
server.start();
servers.add(server);
return server.getURI().getPort();
}
private ServerConnector createSslConnector(Server server, Path directory, String password) throws Exception {
Path keystorePath = createSelfSignedKeyStore(directory, password);
KeyStore keyStore = createKeyStore(keystorePath, password);
SslContextFactory sslContextFactory = createSslContextFactory(keyStore, password);
ServerConnector sslConnector = new ServerConnector(
server,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(new HttpConfiguration())
);
sslConnector.setPort(0);
return sslConnector;
}
private SslContextFactory createSslContextFactory(KeyStore keyStore, String password) {
SslContextFactory sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStore(keyStore);
sslContextFactory.setKeyStorePassword(password);
return sslContextFactory;
}
private KeyStore createKeyStore(Path keystorePath, String password) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream stream = Files.newInputStream(keystorePath)) {
keyStore.load(stream, password.toCharArray());
}
return keyStore;
}
private Path createSelfSignedKeyStore(Path directory, String password) throws IOException, InterruptedException {
// no way to create a self-signed certificate from an api, so we use keytool for now
int result = new ProcessBuilder("keytool",
"-genkey",
"-keyalg", "RSA",
"-alias", "selfsigned",
"-keystore", "keystore",
"-storepass", password,
"-validity", "360",
"-keysize", "1024",
"-dname", "CN=localhost"
)
.directory(directory.toFile())
.start()
.waitFor();
if (result != 0) {
throw new IOException("failed to generate self signed certificate");
}
return directory.resolve("keystore");
}
}

View File

@@ -0,0 +1,103 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.server;
import com.google.common.io.Resources;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junitpioneer.jupiter.ClearSystemProperty;
import org.junitpioneer.jupiter.SetSystemProperty;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
@ClearSystemProperty(key = "basedir")
class ServerConfigurationTest {
@Test
void shouldReturnDefaultListener(@TempDir Path directory) throws IOException {
ServerConfiguration configuration = configure(directory, "default.xml");
assertThat(configuration.getListeners())
.hasSize(1)
.allSatisfy(listener -> {
assertThat(listener.getScheme()).isEqualTo("http");
assertThat(listener.getPort()).isEqualTo(8080);
assertThat(listener.getContextPath()).isEqualTo("/scm");
});
}
@Test
@SetSystemProperty(key = "jetty.port", value = "8081")
void shouldReturnCustomContextPathAndPort(@TempDir Path directory) throws IOException {
ServerConfiguration configuration = configure(directory, "ctxPath.xml");
assertThat(configuration.getListeners())
.hasSize(1)
.allSatisfy(listener -> {
assertThat(listener.getScheme()).isEqualTo("http");
assertThat(listener.getPort()).isEqualTo(8081);
assertThat(listener.getContextPath()).isEqualTo("/myscm");
});
}
@Test
void shouldReturnConfiguredSSListener(@TempDir Path directory) throws IOException {
ServerConfiguration configuration = configure(directory, "ssl.xml");
assertThat(configuration.getListeners())
.hasSize(2)
.anySatisfy(listener -> {
assertThat(listener.getScheme()).isEqualTo("http");
assertThat(listener.getPort()).isEqualTo(80);
assertThat(listener.getContextPath()).isEqualTo("/scm");
})
.anySatisfy(listener -> {
assertThat(listener.getScheme()).isEqualTo("https");
assertThat(listener.getPort()).isEqualTo(443);
assertThat(listener.getContextPath()).isEqualTo("/scm");
});
;
}
@SuppressWarnings("UnstableApiUsage")
private ServerConfiguration configure(Path directory, String configurationFilename) throws IOException {
URL resource = Resources.getResource("sonia/scm/server/" + configurationFilename);
Path path = directory.resolve("server-config.xml");
Files.write(path, Resources.toByteArray(resource));
Files.createDirectories(directory.resolve("var/webapp/docroot"));
Files.createFile(directory.resolve("var/webapp/scm-webapp.war"));
System.setProperty("basedir", directory.toString());
return new ServerConfiguration(path);
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
MIT License
Copyright (c) 2020-present Cloudogu GmbH and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure id="ScmServer" class="org.eclipse.jetty.server.Server">
<!--
This default configuration should match 90% of the use cases,
if you have to change something ensure you know what you are doing.
For further information on configuration scm-server have a look at:
https://scm-manager.org/docs/${version.major}.${version.minor}.x/en/administration/scm-server/
-->
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<!-- increase header size for mercurial -->
<Set name="requestHeaderSize">16384</Set>
<Set name="responseHeaderSize">16384</Set>
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
</Call>
</New>
<!--
Connectors
-->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server">
<Ref refid="ScmServer" />
</Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config">
<Ref refid="httpConfig" />
</Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host">
<SystemProperty name="jetty.host" default="0.0.0.0" />
</Set>
<Set name="port">
<SystemProperty name="jetty.port" default="8080" />
</Set>
</New>
</Arg>
</Call>
<New id="scm-webapp" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/myscm</Set>
<Set name="war">
<SystemProperty name="basedir" default="."/>/var/webapp/scm-webapp.war
</Set>
<!-- disable directory listings -->
<Call name="setInitParameter">
<Arg>org.eclipse.jetty.servlet.Default.dirAllowed</Arg>
<Arg>false</Arg>
</Call>
<Set name="tempDirectory">/var/cache/scm/work/webapp</Set>
</New>
<New id="docroot" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/</Set>
<Set name="baseResource">
<New class="org.eclipse.jetty.util.resource.ResourceCollection">
<Arg>
<Array type="java.lang.String">
<Item>
<SystemProperty name="basedir" default="."/>/var/webapp/docroot</Item>
</Array>
</Arg>
</New>
</Set>
<Set name="tempDirectory">/var/cache/scm/work/work/docroot</Set>
</New>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<Ref id="scm-webapp" />
</Item>
<Item>
<Ref id="docroot" />
</Item>
</Array>
</Set>
</New>
</Set>
</Configure>

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
MIT License
Copyright (c) 2020-present Cloudogu GmbH and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure id="ScmServer" class="org.eclipse.jetty.server.Server">
<!--
This default configuration should match 90% of the use cases,
if you have to change something ensure you know what you are doing.
For further information on configuration scm-server have a look at:
https://scm-manager.org/docs/${version.major}.${version.minor}.x/en/administration/scm-server/
-->
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<!-- increase header size for mercurial -->
<Set name="requestHeaderSize">16384</Set>
<Set name="responseHeaderSize">16384</Set>
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
</Call>
</New>
<!--
Connectors
-->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server">
<Ref refid="ScmServer" />
</Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config">
<Ref refid="httpConfig" />
</Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host">
<SystemProperty name="jetty.host" default="0.0.0.0" />
</Set>
<Set name="port">
<SystemProperty name="jetty.port" default="8080" />
</Set>
</New>
</Arg>
</Call>
<New id="scm-webapp" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/scm</Set>
<Set name="war">
<SystemProperty name="basedir" default="."/>/var/webapp/scm-webapp.war
</Set>
<!-- disable directory listings -->
<Call name="setInitParameter">
<Arg>org.eclipse.jetty.servlet.Default.dirAllowed</Arg>
<Arg>false</Arg>
</Call>
<Set name="tempDirectory">/var/cache/scm/work/webapp</Set>
</New>
<New id="docroot" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/</Set>
<Set name="baseResource">
<New class="org.eclipse.jetty.util.resource.ResourceCollection">
<Arg>
<Array type="java.lang.String">
<Item>
<SystemProperty name="basedir" default="."/>/var/webapp/docroot</Item>
</Array>
</Arg>
</New>
</Set>
<Set name="tempDirectory">/var/cache/scm/work/work/docroot</Set>
</New>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<Ref id="scm-webapp" />
</Item>
<Item>
<Ref id="docroot" />
</Item>
</Array>
</Set>
</New>
</Set>
</Configure>

View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
MIT License
Copyright (c) 2020-present Cloudogu GmbH and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure id="ScmServer" class="org.eclipse.jetty.server.Server">
<!--
This default configuration should match 90% of the use cases,
if you have to change something ensure you know what you are doing.
For further information on configuration scm-server have a look at:
https://scm-manager.org/docs/2.30.x/en/administration/scm-server/
-->
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<!-- increase header size for mercurial -->
<Set name="requestHeaderSize">16384</Set>
<Set name="responseHeaderSize">16384</Set>
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
</Call>
</New>
<!--
Connectors
-->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server">
<Ref refid="ScmServer" />
</Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config">
<Ref refid="httpConfig" />
</Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host">
<SystemProperty name="jetty.host" default="0.0.0.0" />
</Set>
<Set name="port">
<SystemProperty name="jetty.port" default="80" />
</Set>
</New>
</Arg>
</Call>
<New id="scm-webapp" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/scm</Set>
<Set name="war">
<SystemProperty name="basedir" default="."/>/var/webapp/scm-webapp.war
</Set>
<!-- disable directory listings -->
<Call name="setInitParameter">
<Arg>org.eclipse.jetty.servlet.Default.dirAllowed</Arg>
<Arg>false</Arg>
</Call>
<Set name="tempDirectory">/var/cache/scm/work/webapp</Set>
</New>
<New id="docroot" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/</Set>
<Set name="baseResource">
<New class="org.eclipse.jetty.util.resource.ResourceCollection">
<Arg>
<Array type="java.lang.String">
<Item>
<SystemProperty name="basedir" default="."/>/var/webapp/docroot</Item>
</Array>
</Arg>
</New>
</Set>
<Set name="tempDirectory">/var/cache/scm/work/work/docroot</Set>
</New>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<Ref id="scm-webapp" />
</Item>
<Item>
<Ref id="docroot" />
</Item>
</Array>
</Set>
</New>
</Set>
<!-- ssl configuration start -->
<New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory$Server">
<!--
path to your keystore, it can be a java keystore or in the pkcs12 format
-->
<Set name="KeyStorePath">
/scm/certs/certificates.p12
</Set>
<!--
use pkcs12 or jks for java keystore
-->
<Set name="KeyStoreType">PKCS12</Set>
<!--
the password of you keystore
-->
<Set name="KeyStorePassword">changeit</Set>
<!--
For a more up to date list of ciphers and protocols, have a look at the mozilla ssl configurator:
https://ssl-config.mozilla.org/#server=jetty&version=9.4.28&config=intermediate&guideline=5.4
-->
<!-- TLS 1.3 requires Java 11 or higher -->
<Set name="IncludeProtocols">
<Array type="String">
<Item>TLSv1.2</Item>
<Item>TLSv1.3</Item>
</Array>
</Set>
<Set name="IncludeCipherSuites">
<Array type="String">
<Item>TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384</Item>
<Item>TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</Item>
<Item>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</Item>
<Item>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</Item>
<Item>TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256</Item>
<Item>TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256</Item>
<Item>TLS_DHE_RSA_WITH_AES_256_GCM_SHA384</Item>
<Item>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256</Item>
</Array>
</Set>
<Set name="useCipherSuitesOrder">
<Property name="jetty.sslContext.useCipherSuitesOrder" default="false" />
</Set>
</New>
<New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<Arg>
<Ref refid="httpConfig"/>
</Arg>
<Call name="addCustomizer">
<Arg>
<New class="org.eclipse.jetty.server.SecureRequestCustomizer">
<Arg name="sniRequired" type="boolean"><Property name="jetty.ssl.sniRequired" default="false"/></Arg>
<Arg name="sniHostCheck" type="boolean"><Property name="jetty.ssl.sniHostCheck" default="true"/></Arg>
<Arg name="stsMaxAgeSeconds" type="int"><Property name="jetty.ssl.stsMaxAgeSeconds" default="-1"/></Arg>
<Arg name="stsIncludeSubdomains" type="boolean"><Property name="jetty.ssl.stsIncludeSubdomains" default="false"/></Arg>
</New>
</Arg>
</Call>
</New>
<Call name="addConnector">
<Arg>
<New id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server">
<Ref refid="ScmServer" />
</Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.SslConnectionFactory">
<Arg name="next">http/1.1</Arg>
<Arg name="sslContextFactory">
<Ref refid="sslContextFactory"/>
</Arg>
</New>
</Item>
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config">
<Ref refid="sslHttpConfig" />
</Arg>
</New>
</Item>
</Array>
</Arg>
<!--
Address to listen 0.0.0.0 means on every interface
-->
<Set name="host">
<SystemProperty name="jetty.host" default="0.0.0.0" />
</Set>
<!--
Port for the https connector
-->
<Set name="port">
<Property name="jetty.ssl.port" default="443" />
</Set>
</New>
</Arg>
</Call>
</Configure>