mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-05-07 17:17:02 +02:00
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:
2
gradle/changelog/docker_healthcheck.yaml
Normal file
2
gradle/changelog/docker_healthcheck.yaml
Normal 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))
|
||||
@@ -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" ]
|
||||
|
||||
@@ -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" ]
|
||||
|
||||
@@ -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}"]
|
||||
|
||||
@@ -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>
|
||||
|
||||
6
scm-packaging/docker/src/main/fs/opt/scm-server/bin/healthcheck
Executable file
6
scm-packaging/docker/src/main/fs/opt/scm-server/bin/healthcheck
Executable 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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
153
scm-server/src/main/java/sonia/scm/server/HealthCheck.java
Normal file
153
scm-server/src/main/java/sonia/scm/server/HealthCheck.java
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
50
scm-server/src/main/java/sonia/scm/server/Listener.java
Normal file
50
scm-server/src/main/java/sonia/scm/server/Listener.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 --------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
311
scm-server/src/test/java/sonia/scm/server/HealthCheckTest.java
Normal file
311
scm-server/src/test/java/sonia/scm/server/HealthCheckTest.java
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
120
scm-server/src/test/resources/sonia/scm/server/ctxPath.xml
Normal file
120
scm-server/src/test/resources/sonia/scm/server/ctxPath.xml
Normal 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>
|
||||
120
scm-server/src/test/resources/sonia/scm/server/default.xml
Normal file
120
scm-server/src/test/resources/sonia/scm/server/default.xml
Normal 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>
|
||||
227
scm-server/src/test/resources/sonia/scm/server/ssl.xml
Normal file
227
scm-server/src/test/resources/sonia/scm/server/ssl.xml
Normal 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>
|
||||
Reference in New Issue
Block a user