diff --git a/docker/start.sh b/docker/start.sh index de84b044a..6f6b27ee7 100644 --- a/docker/start.sh +++ b/docker/start.sh @@ -16,8 +16,6 @@ java \ --add-opens java.base/java.io=ALL-UNNAMED \ -jar webgoat.jar --webgoat.build.version="$1" --server.address=0.0.0.0 > webgoat.log & -sleep 30 - echo "Starting WebWolf..." java -Duser.home=/home/webgoat -Dfile.encoding=UTF-8 -jar webwolf.jar --webgoat.build.version=$1 --server.address=0.0.0.0 > webwolf.log & diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java b/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java index e2c5be022..4766bc5aa 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java @@ -58,7 +58,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = http .authorizeRequests() - .antMatchers("/css/**", "/images/**", "/js/**", "fonts/**", "/plugins/**", "/registration", "/register.mvc").permitAll() + .antMatchers("/css/**", "/images/**", "/js/**", "fonts/**", "/plugins/**", "/registration", "/register.mvc", "/actuator/**").permitAll() .anyRequest().authenticated(); security.and() .formLogin() diff --git a/webgoat-container/src/main/resources/application-webgoat.properties b/webgoat-container/src/main/resources/application-webgoat.properties index e2918e640..3339d1ced 100644 --- a/webgoat-container/src/main/resources/application-webgoat.properties +++ b/webgoat-container/src/main/resources/application-webgoat.properties @@ -55,4 +55,8 @@ exclude.categories=${EXCLUDE_CATEGORIES:none,none} #exclude based on the enum of the Category exclude.lessons=${EXCLUDE_LESSONS:none,none} -#exclude based on the class name of a lesson e.g.: LessonTemplate \ No newline at end of file +#exclude based on the class name of a lesson e.g.: LessonTemplate + +management.health.db.enabled=true +management.endpoint.health.show-details=always +management.endpoints.web.exposure.include=health,configprops diff --git a/webwolf/pom.xml b/webwolf/pom.xml index fcfe4f92f..71ca07a4f 100644 --- a/webwolf/pom.xml +++ b/webwolf/pom.xml @@ -67,6 +67,10 @@ org.springframework.boot spring-boot-starter-actuator + + org.springframework.retry + spring-retry + org.thymeleaf.extras thymeleaf-extras-springsecurity5 diff --git a/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java b/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java index d798d2ef0..1b045d45c 100644 --- a/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java +++ b/webwolf/src/main/java/org/owasp/webwolf/WebWolf.java @@ -22,18 +22,14 @@ package org.owasp.webwolf; +import java.io.IOException; +import java.net.Socket; + import org.owasp.webwolf.requests.WebWolfTraceRepository; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.trace.http.HttpTraceRepository; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.jdbc.datasource.DriverManagerDataSource; - -import java.io.IOException; -import java.net.Socket; - -import javax.sql.DataSource; @SpringBootApplication public class WebWolf { @@ -45,26 +41,20 @@ public class WebWolf { public static void main(String[] args) { System.setProperty("spring.config.name", "application-webwolf"); - + String webwolfPort = System.getenv("WEBWOLF_PORT"); - String databasePort = System.getenv("WEBGOAT_HSQLPORT"); String webGoatHost = null==System.getenv("WEBGOAT_HOST")?"127.0.0.1":System.getenv("WEBGOAT_HOST"); String webWolfHost = null==System.getenv("WEBWOLF_HOST")?"127.0.0.1":System.getenv("WEBWOLF_HOST"); String fileEncoding = System.getProperty("file.encoding"); int wolfPort = webwolfPort == null?9090:Integer.parseInt(webwolfPort); - int dbPort = databasePort == null?9001:Integer.parseInt(databasePort); if (null==fileEncoding || !fileEncoding.equals("UTF-8")) { System.out.println("It seems the application is startd on a OS with non default UTF-8 encoding:"+fileEncoding); System.out.println("Please add: -Dfile.encoding=UTF-8"); System.exit(-1); } - - if (!isAlreadyRunning(webGoatHost, dbPort)) { - System.out.println("It seems that the required database is not running. Please start WebGoat with the integrated or standalone database first."); - System.exit(-1); - } + if (isAlreadyRunning(webGoatHost, wolfPort)) { System.out.println("Port "+webWolfHost+":"+wolfPort+" is in use. Use environment value WEBWOLF_PORT to set a different value."); System.exit(-1); @@ -72,13 +62,6 @@ public class WebWolf { SpringApplication.run(WebWolf.class, args); } - @Bean - public DataSource dataSource(@Value("${spring.datasource.url}") String url, @Value("${spring.datasource.driver-class-name}") String driverClassName) { - DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource(url); - driverManagerDataSource.setDriverClassName(driverClassName); - return driverManagerDataSource; - } - private static boolean isAlreadyRunning(String host, int port) { try (var ignored = new Socket(host, port)) { return true; diff --git a/webwolf/src/main/java/org/owasp/webwolf/db/ActuatorDsJsonParser.java b/webwolf/src/main/java/org/owasp/webwolf/db/ActuatorDsJsonParser.java new file mode 100644 index 000000000..56353d344 --- /dev/null +++ b/webwolf/src/main/java/org/owasp/webwolf/db/ActuatorDsJsonParser.java @@ -0,0 +1,58 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webwolf.db; + +import com.fasterxml.jackson.databind.JsonNode; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * + * @author Angel Olle Blazquez + * + */ + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ActuatorDsJsonParser { + + protected static final String getDsPropertyFromConfigProps(JsonNode node, String propertyName) { + return node + .get("application") + .get("beans") + .get("spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties") + .get("properties") + .get(propertyName) + .asText(); + } + + protected static final String getDsHealthStatus(JsonNode node) { + return node + .get("components") + .get("db") + .get("components") + .get("dataSource") + .get("status") + .asText(); + } +} diff --git a/webwolf/src/main/java/org/owasp/webwolf/db/DataSourceProperties.java b/webwolf/src/main/java/org/owasp/webwolf/db/DataSourceProperties.java new file mode 100644 index 000000000..7f493fd7d --- /dev/null +++ b/webwolf/src/main/java/org/owasp/webwolf/db/DataSourceProperties.java @@ -0,0 +1,52 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webwolf.db; + +import static org.owasp.webwolf.db.ActuatorDsJsonParser.getDsPropertyFromConfigProps; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +import lombok.Data; + +/** + * + * @author Angel Olle Blazquez + * + */ + +@Data +public class DataSourceProperties implements Serializable { + private static final long serialVersionUID = -5897408528235134090L; + private String url; + private String driverClassName; + + @JsonProperty("contexts") + protected void props(JsonNode node) { + url = getDsPropertyFromConfigProps(node, "url"); + driverClassName = getDsPropertyFromConfigProps(node, "driverClassName"); + } + +} diff --git a/webwolf/src/main/java/org/owasp/webwolf/db/DataSourceResolver.java b/webwolf/src/main/java/org/owasp/webwolf/db/DataSourceResolver.java new file mode 100644 index 000000000..407b66ed5 --- /dev/null +++ b/webwolf/src/main/java/org/owasp/webwolf/db/DataSourceResolver.java @@ -0,0 +1,111 @@ +/* + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/ + * + * Copyright (c) 2002 - 2021 Bruce Mayhew + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Getting Source ============== + * + * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects. + */ + +package org.owasp.webwolf.db; + +import static org.owasp.webwolf.db.ActuatorDsJsonParser.getDsHealthStatus; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.DependsOn; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.EnableRetry; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.databind.JsonNode; + +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author Angel Olle Blazquez + * + */ + +@Slf4j +@Component +@EnableRetry +public class DataSourceResolver { + + @Value("${webgoat.actuator.base.url}") + private String baseUrl; + + @Value("${webgoat.actuator.health.db.path:/health}") + private String dbHealthPath; + + @Value("${webgoat.actuator.configprops.path:/configprops}") + private String configPropsPath; + + @Autowired + ApplicationContext ctx; + + @Bean + @DependsOn("dsConfigDiscovery") + public DataSource dataSource(DataSourceProperties dataSourceProperties) { + DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource(dataSourceProperties.getUrl()); + driverManagerDataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); + return driverManagerDataSource; + } + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } + + @Bean + @Retryable(maxAttemptsExpression = "${webwolf.datasource-discovery.retry.max-attempts:5}", + value = Exception.class, + backoff = @Backoff( + multiplierExpression = "${webwolf.datasource-discovery.retry.backoff.multiplier:1.5}", + maxDelayExpression = "${webwolf.datasource-discovery.retry.backoff.max-delay:30000}", + delayExpression = "${webwolf.datasource-discovery.retry.backoff.delay:5000}")) + public DataSourceProperties dsConfigDiscovery(RestTemplate restTemplate) { + healthCheck(restTemplate); + return restTemplate.getForObject(baseUrl + configPropsPath, DataSourceProperties.class); + } + + public void healthCheck(RestTemplate restTemplate) { + log.info("Checking database availability."); + JsonNode json = restTemplate.getForObject(baseUrl + dbHealthPath, JsonNode.class); + String status = getDsHealthStatus(json); + if (!status.equals("UP")) { + throw new ResourceUnavailableException(); + } + } + + @Recover + public DataSourceProperties exitOnResourceUnavailable(Exception e, RestTemplate restTemplate) { + log.error("It seems that the required database is not running. Please start WebGoat with the integrated or standalone database first."); + System.exit(SpringApplication.exit(ctx, () -> 1)); + return null; + } +} diff --git a/webwolf/src/main/java/org/owasp/webwolf/db/ResourceUnavailableException.java b/webwolf/src/main/java/org/owasp/webwolf/db/ResourceUnavailableException.java new file mode 100644 index 000000000..50f62c913 --- /dev/null +++ b/webwolf/src/main/java/org/owasp/webwolf/db/ResourceUnavailableException.java @@ -0,0 +1,22 @@ +package org.owasp.webwolf.db; + +public class ResourceUnavailableException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ResourceUnavailableException() { + super(); + } + + public ResourceUnavailableException(Throwable cause) { + super(cause); + } + + public ResourceUnavailableException(String message) { + super(message); + } + + public ResourceUnavailableException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/webwolf/src/main/resources/application-webwolf.properties b/webwolf/src/main/resources/application-webwolf.properties index 755be1dc8..2c5c464a7 100644 --- a/webwolf/src/main/resources/application-webwolf.properties +++ b/webwolf/src/main/resources/application-webwolf.properties @@ -7,8 +7,6 @@ server.address=${WEBWOLF_HOST:127.0.0.1} server.servlet.session.cookie.name=WEBWOLFSESSION server.servlet.session.timeout=6000 -spring.datasource.url=jdbc:hsqldb:hsql://${WEBGOAT_HOST:127.0.0.1}:${WEBGOAT_HSQLPORT:9001}/webgoat -spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver spring.jpa.properties.hibernate.default_schema=CONTAINER spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.HSQLDialect spring.jpa.hibernate.ddl-auto=update @@ -32,9 +30,11 @@ multipart.max-file-size=1Mb multipart.max-request-size=1Mb webgoat.build.version=@project.version@ +webgoat.actuator.base.url=http://${WEBGOAT_HOST:127.0.0.1}:${WEBGOAT_PORT:8080}/WebGoat/actuator webgoat.server.directory=${user.home}/.webgoat-${webgoat.build.version}/ webwolf.fileserver.location=${java.io.tmpdir}/webwolf-fileserver + spring.jackson.serialization.indent_output=true spring.jackson.serialization.write-dates-as-timestamps=false