From 956df03cc15d11a4d234f7bde48612302ad084f8 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 29 Aug 2019 11:46:54 +0200 Subject: [PATCH] production --- docker/prod/standalone/selfsigned/.gitignore | 1 + .../selfsigned/application-prod.properties | 44 ++++++------ .../standalone/selfsigned/certs.Dockerfile | 14 ++-- .../standalone/selfsigned/docker-compose.yml | 8 +-- .../selfsigned/sebserver.Dockerfile | 11 ++- .../java/ch/ethz/seb/sebserver/SEBServer.java | 67 ++++++++++++++++++- .../ethz/seb/sebserver/WebSecurityConfig.java | 8 ++- .../gui/service/ResourceService.java | 7 +- .../config/application-dev-gui.properties | 1 + 9 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 docker/prod/standalone/selfsigned/.gitignore diff --git a/docker/prod/standalone/selfsigned/.gitignore b/docker/prod/standalone/selfsigned/.gitignore new file mode 100644 index 00000000..956d4725 --- /dev/null +++ b/docker/prod/standalone/selfsigned/.gitignore @@ -0,0 +1 @@ +/secrets diff --git a/docker/prod/standalone/selfsigned/application-prod.properties b/docker/prod/standalone/selfsigned/application-prod.properties index f9ed5f1c..19a0e3e0 100644 --- a/docker/prod/standalone/selfsigned/application-prod.properties +++ b/docker/prod/standalone/selfsigned/application-prod.properties @@ -1,41 +1,36 @@ spring.profiles.include=prod-ws,prod-gui - file.encoding=UTF-8 -logging.level.org.apache.tomcat.util.net.NioEndpoint=DEBUG -logging.level.ch=DEBUG - -sebserver.certs.password=[SET_PWD] -sebserver.mariadb.password=[SET_PWD] -sebserver.password=[SET_PWD] server.address=0.0.0.0 server.port=443 server.servlet.context-path=/ +########################################################## +### Security + security.require-ssl=true server.ssl.key-store-type=PKCS12 -server.ssl.key-store=file:/certs/seb-server-keystore.pkcs12 +server.ssl.key-store=C:/dev/workspaces/sebserver/seb-server/docker/prod/standalone/selfsigned/certs/seb-server-keystore.pkcs12 server.ssl.key-store-password=${sebserver.certs.password} -server.ssl.key-alias=sebserver server.ssl.key-password=${sebserver.certs.password} -server.ssl.trust-store=file:/certs/seb-server-truststore.pkcs12 +server.ssl.trust-store=C:/dev/workspaces/sebserver/seb-server/docker/prod/standalone/selfsigned/certs/seb-server-truststore.pkcs12 server.ssl.trust-store-password=${sebserver.certs.password} -server.ssl.enabled-protocols=SSLv3,TLSv1,TLSv1.1,TLSv1.2 +server.ssl.enabled-protocols=TLSv1,TLSv1.1,TLSv1.2 -javax.net.ssl.keyStore=/certs/seb-server-keystore.pkcs12 -javax.net.ssl.keyStorePassword=${sebserver.certs.password} -javax.net.ssl.trustStore=/certs/seb-server-truststore.pkcs12 -javax.net.ssl.trustStorePassword=${sebserver.certs.password} +########################################################## +### SEB Server Overall -sebserver.webservice.api.admin.clientSecret=${sebserver.password} -sebserver.webservice.internalSecret=${sebserver.password} +# Default logging level in the form "logging.level" + namespace=LEVEL +logging.level.ch=DEBUG +logging.file=log/sebserver.log + +# If webservice or gui runs on ssl and this flag is true, an integrated redirect from http to https is activated +# Disable this if a redirect is done by a pre-processing proxy +sebserver.ssl.redirect.enabled=true ########################################################## ### SEB Server Webservice configuration -# logging -logging.file=log/sebserver.log - # database server datastore.mariadb.server.address=seb-server-mariadb datastore.mariadb.server.port=3306 @@ -53,9 +48,11 @@ spring.datasource.hikari.maxLifetime=1800000 spring.datasource.password=${sebserver.mariadb.password} # webservice configuration +sebserver.webservice.api.admin.clientSecret=${sebserver.password} +sebserver.webservice.internalSecret=${sebserver.password} sebserver.webservice.distributed=false sebserver.webservice.http.scheme=https -sebserver.webservice.http.server.name=0.0.0.0 +sebserver.webservice.http.server.name=${server.address} sebserver.webservice.http.redirect.gui=/gui sebserver.webservice.api.admin.clientId=guiClient sebserver.webservice.api.admin.endpoint=/admin-api/v1 @@ -75,8 +72,6 @@ sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token management.endpoints.web.base-path=/actuator management.endpoints.web.exposure.include=logfile,loggers - - ########################################################## ### SEB Server GUI configuration server.servlet.session.cookie.http-only=true @@ -84,11 +79,12 @@ server.servlet.session.tracking-modes=cookie sebserver.gui.entrypoint=/gui sebserver.gui.webservice.protocol=https -sebserver.gui.webservice.address=0.0.0.0 +sebserver.gui.webservice.address=localhost sebserver.gui.webservice.port=443 sebserver.gui.webservice.apipath=/admin-api/v1 # defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page sebserver.gui.webservice.poll-interval=500 +sebserver.gui.webservice.mock-lms-enabled=true sebserver.gui.theme=css/sebserver.css diff --git a/docker/prod/standalone/selfsigned/certs.Dockerfile b/docker/prod/standalone/selfsigned/certs.Dockerfile index 243a6618..ff7631d0 100644 --- a/docker/prod/standalone/selfsigned/certs.Dockerfile +++ b/docker/prod/standalone/selfsigned/certs.Dockerfile @@ -2,26 +2,28 @@ FROM openjdk:11-jre-stretch RUN apt-get update && apt-get install -y openssl -ENV KEYSTORE_PWD= ENV OPENSSL_SUBJ="/C=CH/ST=Zuerich/L=Zuerich" ENV OPENSSL_CA="${OPENSSL_SUBJ}/CN=SEB_SEVER_CN" VOLUME /certs WORKDIR /certs +RUN export $(grep -v '^#' secrets | xargs) + CMD openssl genrsa -out ca-key.pem 2048 \ && openssl req -new -x509 -key ca-key.pem -nodes -days 3600 -subj "${OPENSSL_CA}" -out ca.pem \ - && openssl req -newkey rsa:2048 -days 3600 -nodes -config /certs/config/certs.cnf -keyout server-key.pem -out server-req.pem \ + && openssl req -newkey rsa:2048 -days 3600 -nodes -config certs.cnf -keyout server-key.pem -out server-req.pem \ && openssl rsa -in server-key.pem -out server-key.pem \ && openssl x509 -req -in server-req.pem -days 3600 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem \ - && openssl req -newkey rsa:2048 -days 3600 -nodes -config /certs/config/certs.cnf -keyout client-key.pem -out client-req.pem \ + && openssl req -newkey rsa:2048 -days 3600 -nodes -config certs.cnf -keyout client-key.pem -out client-req.pem \ && openssl rsa -in client-key.pem -out client-key.pem \ && openssl x509 -req -in client-req.pem -days 3600 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem \ && openssl verify -CAfile ca.pem server-cert.pem client-cert.pem \ && openssl x509 -in ca.pem -inform pem -out ca.der -outform der \ && openssl pkcs12 -export -out client-cert.pkcs12 -in client-cert.pem -inkey client-key.pem -passout pass:"${KEYSTORE_PWD}" \ - && keytool -importkeystore -destkeystore seb-server-keystore.pkcs12 -deststorepass "${KEYSTORE_PWD}" -srckeystore client-cert.pkcs12 -srcstoretype PKCS12 -srcstorepass "${KEYSTORE_PWD}" \ - && keytool -import -file ca.pem -keystore seb-server-truststore.pkcs12 -storepass "${KEYSTORE_PWD}" -srcstoretype PKCS12 -noprompt \ && keytool -genkeypair -alias sebserver -dname "CN=localhost, OU=ETHZ, O=ETHZ, L=Zuerich, S=Zuerich, C=CH" -keyalg RSA -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore seb-server-keystore.pkcs12 -storepass "${KEYSTORE_PWD}" -validity 3650 \ && keytool -export -alias sebserver -keystore seb-server-keystore.pkcs12 -rfc -file sebserver.cert -storetype PKCS12 -storepass "${KEYSTORE_PWD}" -noprompt \ - && keytool -importcert -trustcacerts -alias sebserver -file sebserver.cert -keystore seb-server-truststore.pkcs12 -storetype PKCS12 -storepass "${KEYSTORE_PWD}" -noprompt \ No newline at end of file + && keytool -importcert -trustcacerts -alias sebserver -file sebserver.cert -keystore seb-server-truststore.pkcs12 -storetype PKCS12 -storepass "${KEYSTORE_PWD}" -noprompt \ + && keytool -import -alias mariadb-ca -file ca.pem -keystore seb-server-truststore.pkcs12 -storepass "${KEYSTORE_PWD}" -srcstoretype PKCS12 -noprompt \ + && keytool -import -alias mariadb-client -file client-cert.pem -keystore seb-server-truststore.pkcs12 -storepass "${KEYSTORE_PWD}" -srcstoretype PKCS12 -noprompt \ + && keytool -import -alias mariadb-server -file server-cert.pem -keystore seb-server-keystore.pkcs12 -storepass "${KEYSTORE_PWD}" -srcstoretype PKCS12 -noprompt \ \ No newline at end of file diff --git a/docker/prod/standalone/selfsigned/docker-compose.yml b/docker/prod/standalone/selfsigned/docker-compose.yml index 83584cc5..94d911e9 100644 --- a/docker/prod/standalone/selfsigned/docker-compose.yml +++ b/docker/prod/standalone/selfsigned/docker-compose.yml @@ -7,11 +7,11 @@ services: container_name: gencerts volumes: - ./certs:/certs - - .:/certs/config + - ./certs.cnf:/certs/certs.cnf + - ./secrets:/certs/secrets environment: - SERVER_CN=seb-server-mariadb - CLIENT_CN=seb-server-mariadb - - KEYSTORE_PWD=[SET_PWD] mariadb: image: "mariadb/server:10.3" @@ -20,8 +20,8 @@ services: - .:/etc/mysql/conf.d - ./certs:/etc/mysql/certs - seb-server-mariadb-data:/var/lib/mysql - environment: - - MYSQL_ROOT_PASSWORD=[SET_PWD] + env_file: + - secrets ports: - 3306:3306 networks: diff --git a/docker/prod/standalone/selfsigned/sebserver.Dockerfile b/docker/prod/standalone/selfsigned/sebserver.Dockerfile index a8a56c38..18843153 100644 --- a/docker/prod/standalone/selfsigned/sebserver.Dockerfile +++ b/docker/prod/standalone/selfsigned/sebserver.Dockerfile @@ -25,6 +25,15 @@ ENV SEBSERVER_VERSION=${SEBSERVER_VERSION} WORKDIR /sebserver COPY --from=1 /sebserver/target/seb-server-"$SEBSERVER_VERSION".jar /sebserver -ENTRYPOINT exec java -Djavax.net.debug=SSL -jar seb-server-"${SEBSERVER_VERSION}".jar --spring.profiles.active=prod --spring.config.location=file:/config/,classpath:/config/ +RUN export $(grep -v '^#' secrets | xargs) + +ENTRYPOINT exec java \ + -Djavax.net.debug=SSL \ + -jar seb-server-"${SEBSERVER_VERSION}".jar \ + --spring.profiles.active=prod \ + --spring.config.location=file:/config/,classpath:/config/ \ + --sebserver.certs.password="${KEYSTORE_PWD}" \ + --sebserver.mariadb.password="${MYSQL_ROOT_PASSWORD}" \ + --sebserver.password="${SEBSERVER_PWD}" \ EXPOSE 443 \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java index e0d32da0..0601212f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java @@ -8,10 +8,24 @@ package ch.ethz.seb.sebserver; +import org.apache.catalina.Context; +import org.apache.catalina.connector.Connector; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.tomcat.util.descriptor.web.SecurityCollection; +import org.apache.tomcat.util.descriptor.web.SecurityConstraint; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.profile.ProdGuiProfile; +import ch.ethz.seb.sebserver.gbl.profile.ProdWebServiceProfile; /** SEB-Server (Safe Exam Browser Server) is a server component to maintain and support * Exams running with SEB (Safe Exam Browser). TODO add link(s) @@ -25,9 +39,7 @@ import org.springframework.cache.annotation.EnableCaching; * SEB-Server uses Spring's profiles to consequently separate sub-components of the webservice * and GUI and can be used to start the components on separate servers or within the same * server instance. Additional to the usual profiles like dev, prod, test there are combining - * profiles like dev-ws, dev-gui and prod-ws, prod-gui - * - * TODO documentation for presets to start all-in-one server or separated gui- and webservice- server */ + * profiles like dev-ws, dev-gui and prod-ws, prod-gui */ @SpringBootApplication(exclude = { UserDetailsServiceAutoConfiguration.class, }) @@ -39,4 +51,53 @@ public class SEBServer { SpringApplication.run(SEBServer.class, args); } + /* + * Add an additional redirect Connector on http port to redirect all http calls + * to https. + * + * NOTE: This works with TomcatServletWebServerFactory and embedded tomcat. + * If the webservice and/or gui is going to running on another server or + * redirect is handled by a proxy, this redirect can be deactivated within + * the "sebserver.ssl.redirect.enabled" property set to false + */ + @Bean + @ProdWebServiceProfile + @ProdGuiProfile + public ServletWebServerFactory servletContainer( + final Environment env, + final ApplicationContext applicationContext) { + + final String enabled = env.getProperty( + "sebserver.ssl.redirect.enabled", + Constants.FALSE_STRING); + + if (!BooleanUtils.toBoolean(enabled)) { + return new TomcatServletWebServerFactory(); + } + + final TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() { + @Override + protected void postProcessContext(final Context context) { + final SecurityConstraint securityConstraint = new SecurityConstraint(); + securityConstraint.setUserConstraint("CONFIDENTIAL"); + final SecurityCollection collection = new SecurityCollection(); + collection.addPattern("/*"); + securityConstraint.addCollection(collection); + context.addConstraint(securityConstraint); + } + }; + tomcat.addAdditionalTomcatConnectors(redirectConnector(env)); + return tomcat; + } + + private Connector redirectConnector(final Environment env) { + final String sslPort = env.getRequiredProperty("server.port"); + final Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + connector.setScheme("http"); + connector.setPort(80); + connector.setSecure(false); + connector.setRedirectPort(Integer.valueOf(sslPort)); + return connector; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java index 6728854d..f34dd2d5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java @@ -154,7 +154,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E log.info("Initialize with secure ClientHttpRequestFactory for production"); final String truststoreFilePath = env - .getProperty("javax.net.ssl.trustStore", ""); + .getProperty("server.ssl.trust-store", ""); if (StringUtils.isBlank(truststoreFilePath)) { throw new IllegalArgumentException("Missing trust-store file path"); @@ -163,7 +163,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E final File trustStoreFile = ResourceUtils.getFile("file:" + truststoreFilePath); final char[] password = env - .getProperty("javax.net.ssl.trustStorePassword", "") + .getProperty("server.ssl.trust-store-password", "") .toCharArray(); if (password.length < 3) { @@ -171,6 +171,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E throw new IllegalArgumentException("Missing or incorrect trust-store password"); } + // Set the specified trust-store also on javax.net.ssl level + System.setProperty("javax.net.ssl.trustStore", truststoreFilePath); + System.setProperty("javax.net.ssl.trustStorePassword", String.valueOf(password)); + final SSLContext sslContext = SSLContextBuilder .create() .loadTrustMaterial(trustStoreFile, password) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index 70ce26c5..757853bf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTimeZone; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -98,15 +99,18 @@ public class ResourceService { private final I18nSupport i18nSupport; private final RestService restService; private final CurrentUser currentUser; + private final boolean mock_lms_enabled; protected ResourceService( final I18nSupport i18nSupport, final RestService restService, - final CurrentUser currentUser) { + final CurrentUser currentUser, + @Value("${sebserver.gui.webservice.mock-lms-enabled:true}") final boolean mock_lms_enabled) { this.i18nSupport = i18nSupport; this.restService = restService; this.currentUser = currentUser; + this.mock_lms_enabled = mock_lms_enabled; } public I18nSupport getI18nSupport() { @@ -137,6 +141,7 @@ public class ResourceService { public List> lmsTypeResources() { return Arrays.asList(LmsType.values()) .stream() + .filter(lmsType -> lmsType != LmsType.MOCKUP || this.mock_lms_enabled) .map(lmsType -> new Tuple<>( lmsType.name(), this.i18nSupport.getText(LMSSETUP_TYPE_PREFIX + lmsType.name(), lmsType.name()))) diff --git a/src/main/resources/config/application-dev-gui.properties b/src/main/resources/config/application-dev-gui.properties index 3c3acb16..f13d2973 100644 --- a/src/main/resources/config/application-dev-gui.properties +++ b/src/main/resources/config/application-dev-gui.properties @@ -12,6 +12,7 @@ sebserver.gui.webservice.port=8080 sebserver.gui.webservice.apipath=/admin-api/v1 # defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page sebserver.gui.webservice.poll-interval=500 +sebserver.gui.webservice.mock-lms-enabled=true sebserver.gui.theme=css/sebserver.css