Committed-by: Eduard Heimbuch<eduard.heimbuch@cloudogu.com>
Pushed-by: Rene Pfeuffer<rene.pfeuffer@cloudogu.com>
Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
Pushed-by: Eduard Heimbuch<eduard.heimbuch@cloudogu.com>
Committed-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2023-11-29 18:14:03 +01:00
committed by René Pfeuffer
parent d760f46d9d
commit cf4d9cf20f
1217 changed files with 7258 additions and 5987 deletions

View File

@@ -24,11 +24,12 @@
package sonia.scm.server;
import jakarta.servlet.http.HttpServletResponse;
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;

View File

@@ -21,56 +21,34 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.server;
//~--- non-JDK imports --------------------------------------------------------
package sonia.scm.server;
import org.eclipse.jetty.server.Server;
/**
*
* @author Sebastian Sdorra
*/
public class ScmServer extends Thread
{
/** Field description */
public class ScmServer extends Thread {
static final int GRACEFUL_TIMEOUT = 2000;
private boolean initialized = false;
private final Server server;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
public ScmServer()
{
ServerConfiguration config = new ServerConfiguration();
public ScmServer() {
server = new org.eclipse.jetty.server.Server();
config.configure(server);
ServerConfiguration config = new ServerConfiguration();
config.configureServer(server);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
@Override
public void run()
{
try
{
if (!initialized)
{
public void run() {
try {
if (!initialized) {
init();
}
server.join();
}
catch (InterruptedException ex)
{
throw new ScmServerException("could not start scm-server", ex);
} catch (InterruptedException ex) {
System.err.println("server interrupted");
ex.printStackTrace();
Thread.currentThread().interrupt();
}
}
@@ -79,43 +57,23 @@ public class ScmServer extends Thread
*
* @see <a href="http://goo.gl/Zfy0Ev">http://goo.gl/Zfy0Ev</a>
*/
public void stopServer()
{
try
{
public void stopServer() {
try {
server.setStopTimeout(GRACEFUL_TIMEOUT);
server.setStopAtShutdown(true);
server.stop();
initialized = false;
}
catch (Exception ex)
{
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
/**
* Method description
*
*/
void init()
{
try
{
void init() {
try {
server.start();
initialized = true;
}
catch (Exception ex)
{
} catch (Exception ex) {
throw new ScmServerException("could not initialize server", ex);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private boolean initialized = false;
/** Field description */
private Server server = new Server();
}

View File

@@ -0,0 +1,66 @@
/*
* 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.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ServerConfigParser {
private static final String CONFIGURATION_FILE = "/config.yml";
ServerConfigParser() {
}
ServerConfigYaml parse() {
return parse(this.getClass().getResource(CONFIGURATION_FILE));
}
ServerConfigYaml parse(URL configFile) {
if (configFile == null) {
//TODO add link
throw new ServerConfigurationException("""
Could not find config.yml.
If you have upgraded from an older SCM-Manager version, you have to migrate your server-config.xml
to the new format using the official instructions:
<link>
""");
}
try (InputStream is = configFile.openStream()) {
Representer representer = new Representer(new DumperOptions());
representer.getPropertyUtils().setSkipMissingProperties(true);
return new Yaml(representer).loadAs(is, ServerConfigYaml.class);
} catch (IOException e) {
throw new ServerConfigurationException("Could not parse config.yml", e);
}
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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 ServerConfigYaml {
private static final String SCM_SERVER_PREFIX = "SCM_";
// ### Server
private String addressBinding = "0.0.0.0";
private int port = 8080;
private String contextPath = "/scm";
private int httpHeaderSize = 16384;
// The default temp dir path depends on the platform
private String tempDir = "work/scm";
// Resolves the client ip instead of the reverse proxy ip if the X-Forwarded-For header is present
private boolean forwardHeadersEnabled = false;
// ### SSL-related config
// Only configure SSL if the key store path is set
private SSLConfig https = new SSLConfig();
public static class SSLConfig {
private String keyStorePath = "";
private String keyStorePassword = "";
private String keyStoreType = "PKCS12";
// If the ssl port is set, the http port will automatically redirect to this
private int sslPort = 8443;
private boolean redirectHttpToHttps = false;
public String getKeyStorePath() {
return getEnvWithDefault("HTTPS_KEY_STORE_PATH", keyStorePath);
}
public String getKeyStorePassword() {
return getEnvWithDefault("HTTPS_KEY_STORE_PASSWORD", keyStorePassword);
}
public String getKeyStoreType() {
return getEnvWithDefault("HTTPS_KEY_STORE_TYPE", keyStoreType);
}
public int getSslPort() {
return getEnvWithDefault("HTTPS_SSL_PORT", sslPort);
}
public boolean isRedirectHttpToHttps() {
return getEnvWithDefault("HTTPS_REDIRECT_HTTP_TO_HTTPS", redirectHttpToHttps);
}
public void setKeyStorePath(String keyStorePath) {
this.keyStorePath = keyStorePath;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public void setKeyStoreType(String keyStoreType) {
this.keyStoreType = keyStoreType;
}
public void setSslPort(int sslPort) {
this.sslPort = sslPort;
}
public void setRedirectHttpToHttps(boolean redirectHttpToHttps) {
this.redirectHttpToHttps = redirectHttpToHttps;
}
}
public String getAddressBinding() {
return getEnvWithDefault("ADDRESS_BINDING", addressBinding);
}
public void setAddressBinding(String addressBinding) {
this.addressBinding = addressBinding;
}
public int getPort() {
return getEnvWithDefault("PORT", port);
}
public void setPort(int port) {
this.port = port;
}
public String getContextPath() {
return getEnvWithDefault("CONTEXT_PATH", contextPath);
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public int getHttpHeaderSize() {
return getEnvWithDefault("HTTP_HEADER_SIZE", httpHeaderSize);
}
public void setHttpHeaderSize(int httpHeaderSize) {
this.httpHeaderSize = httpHeaderSize;
}
public SSLConfig getHttps() {
return https;
}
public void setHttps(SSLConfig https) {
this.https = https;
}
public String getTempDir() {
return getEnvWithDefault("TEMP_DIR", tempDir);
}
public void setTempDir(String tempDir) {
this.tempDir = tempDir;
}
public boolean isForwardHeadersEnabled() {
return getEnvWithDefault("FORWARD_REMOTE_ADDRESS", forwardHeadersEnabled);
}
public void setForwardHeadersEnabled(boolean forwardHeadersEnabled) {
this.forwardHeadersEnabled = forwardHeadersEnabled;
}
static int getEnvWithDefault(String envKey, int configValue) {
String value = getEnv(envKey);
return value != null ? Integer.parseInt(value) : configValue;
}
static String getEnvWithDefault(String envKey, String configValue) {
String value = getEnv(envKey);
return value != null ? value : configValue;
}
static boolean getEnvWithDefault(String envKey, boolean configValue) {
String value = getEnv(envKey);
return value != null ? Boolean.getBoolean(value) : configValue;
}
private static String getEnv(String envKey) {
return System.getenv(SCM_SERVER_PREFIX + envKey);
}
}

View File

@@ -25,54 +25,155 @@
package sonia.scm.server;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
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.nio.file.Paths;
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;
private final ServerConfigYaml configYaml;
private Path testTempDir;
public ServerConfiguration() {
this(CONFIGURATION);
this.configYaml = new ServerConfigParser().parse();
}
public ServerConfiguration(String configurationUrl) {
this.jettyConfiguration = read(configurationUrl);
// Visible for testing
public ServerConfiguration(URL configFile, Path tempDir) {
this.configYaml = new ServerConfigParser().parse(configFile);
this.testTempDir = tempDir;
}
public ServerConfiguration(Path configurationPath) {
this.jettyConfiguration = parse(Resource.newResource(configurationPath));
}
public void configure(Server server) {
public void configureServer(Server server) {
try {
jettyConfiguration.configure(server);
configureHttp(server);
configureHandler(server);
if (configYaml.getHttps().getKeyStorePath() != null && !configYaml.getHttps().getKeyStorePath().isEmpty()) {
configureSSL(server);
}
} catch (Exception ex) {
throw new ScmServerException("error during server configuration", ex);
}
}
private void configureSSL(Server server) {
SslContextFactory.Server sslServer = new SslContextFactory.Server();
ServerConfigYaml.SSLConfig https = configYaml.getHttps();
sslServer.setKeyStorePath(https.getKeyStorePath());
sslServer.setKeyStorePassword(https.getKeyStorePassword());
sslServer.setKeyStoreType(https.getKeyStoreType());
sslServer.setIncludeProtocols("TLSv1.2", "TLSv1.3");
sslServer.setIncludeCipherSuites(
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256");
sslServer.setUseCipherSuitesOrder(false);
HttpConfiguration sslHttpConfig = new HttpConfiguration(createCustomHttpConfig());
sslHttpConfig.addCustomizer(new SecureRequestCustomizer(
false,
true,
-1,
false
)
);
ServerConnector sslConnector = new ServerConnector(server, (
new SslConnectionFactory(sslServer, "http/1.1")),
new HttpConnectionFactory(sslHttpConfig));
sslConnector.setHost(configYaml.getAddressBinding());
sslConnector.setPort(https.getSslPort());
server.addConnector(sslConnector);
}
private void configureHttp(Server server) {
HttpConfiguration httpConfig = createCustomHttpConfig();
redirectHttpToHttps(httpConfig);
ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
System.out.println("Set http address binding to " + configYaml.getAddressBinding());
connector.setHost(configYaml.getAddressBinding());
System.out.println("Set http port to " + configYaml.getPort());
connector.setPort(configYaml.getPort());
server.addConnector(connector);
}
private void redirectHttpToHttps(HttpConfiguration httpConfig) {
ServerConfigYaml.SSLConfig https = configYaml.getHttps();
if (configYaml.getHttps().isRedirectHttpToHttps()) {
httpConfig.setSecurePort(https.getSslPort());
httpConfig.setSecureScheme("https");
}
}
private void configureHandler(Server server) {
HandlerCollection handlerCollection = new HandlerCollection();
handlerCollection.setHandlers(new Handler[]{createWebAppContext(), createDocRoot()});
server.setHandler(handlerCollection);
}
private WebAppContext createWebAppContext() {
WebAppContext webApp = new WebAppContext();
webApp.setContextPath(configYaml.getContextPath());
// disable directory listings
webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
String baseDir = resolveBaseDir();
webApp.setWar(baseDir + "/var/webapp/scm-webapp.war");
String tempDir = configYaml.getTempDir();
webApp.setTempDirectory(tempDir.startsWith("/") ? Paths.get(tempDir, "webapp").toFile() : Paths.get(baseDir, tempDir).toFile());
return webApp;
}
private WebAppContext createDocRoot() {
WebAppContext docRoot = new WebAppContext();
docRoot.setContextPath("/");
String baseDir = resolveBaseDir();
docRoot.setBaseResource(new ResourceCollection(new String[]{baseDir + "/var/webapp/docroot"}));
String tempDir = configYaml.getTempDir();
docRoot.setTempDirectory(tempDir.startsWith("/") ? Paths.get(tempDir, "work/docroot").toFile() : Paths.get(baseDir, tempDir).toFile());
return docRoot;
}
private HttpConfiguration createCustomHttpConfig() {
HttpConfiguration httpConfig = new HttpConfiguration();
System.out.println("Set http request header size to " + configYaml.getHttpHeaderSize());
httpConfig.setRequestHeaderSize(configYaml.getHttpHeaderSize());
System.out.println("Set http response header size to " + configYaml.getHttpHeaderSize());
httpConfig.setResponseHeaderSize(configYaml.getHttpHeaderSize());
httpConfig.setSendServerVersion(false);
System.out.println("Set forward request customizer: " + configYaml.isForwardHeadersEnabled());
if (configYaml.isForwardHeadersEnabled()) {
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
}
return httpConfig;
}
public List<Listener> getListeners() {
List<Listener> listeners = new ArrayList<>();
Server server = new Server();
configure(server);
configureServer(server);
String contextPath = findContextPath(server.getHandlers());
if (contextPath == null) {
@@ -94,6 +195,11 @@ public final class ServerConfiguration {
return listeners;
}
private String resolveBaseDir() {
return testTempDir != null ? testTempDir.toString() : System.getProperty("baseDir", ".");
}
private String findContextPath(Handler[] handlers) {
for (Handler handler : handlers) {
if (handler instanceof WebAppContext) {
@@ -107,23 +213,4 @@ public final class ServerConfiguration {
}
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,36 @@
/*
* 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 ServerConfigurationException extends RuntimeException {
public ServerConfigurationException(String message) {
super(message);
}
public ServerConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}