Skip to content

Commit bfcf5b0

Browse files
authored
Merge pull request #2408 from OWASP/copilot/update-spring-boot-version
Upgrade Spring Boot from 3.5.x to 4.0.3
2 parents 8d18ca1 + b514c35 commit bfcf5b0

32 files changed

+246
-81
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ RUN mkdir -p /var/run/secrets/kubernetes.io/serviceaccount && \
5858
chmod 600 /var/run/secrets/kubernetes.io/serviceaccount/token
5959

6060
# Create a dynamic archive
61-
RUN java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar application.jar
61+
RUN java --add-modules=jdk.unsupported -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar application.jar
6262

6363
# Clean up the mocked token
6464
RUN rm -rf /var/run/secrets/kubernetes.io
@@ -70,4 +70,4 @@ RUN rm -rf /var/run/secrets/kubernetes.io
7070
RUN adduser -u 2000 -D wrongsecrets
7171
USER wrongsecrets
7272

73-
CMD java -jar -XX:SharedArchiveFile=application.jsa -Dspring.profiles.active=$(echo ${SPRING_PROFILES_ACTIVE}) -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} -D application.jar
73+
CMD java --add-modules=jdk.unsupported -jar -XX:SharedArchiveFile=application.jsa -Dspring.profiles.active=$(echo ${SPRING_PROFILES_ACTIVE}) -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} -D application.jar

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ This repository contains **intentionally vulnerable code and configuration files
9696

9797
### 👨‍💻 Development & Contribution
9898
- [Notes on development](#notes-on-development)
99+
- [Spring Boot 4 adoption checklist](docs/SPRING_BOOT_4_ADOPTION_CHECKLIST.md)
99100
- [Dependency management](#dependency-management)
100101
- [Get the project started in IntelliJ IDEA](#get-the-project-started-in-intellij-idea)
101102
- [Automatic reload during development](#automatic-reload-during-development)

docs/DEVELOPMENT_PATTERNS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This document outlines common code patterns, conventions, and best practices used throughout the WrongSecrets project.
44

5+
## Related documentation
6+
7+
- [Spring Boot 4 Adoption Checklist](SPRING_BOOT_4_ADOPTION_CHECKLIST.md)
8+
59
## Challenge Structure Patterns
610

711
### Challenge Interface vs FixedAnswerChallenge
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Spring Boot 4 Adoption Checklist (WrongSecrets)
2+
3+
This checklist is tailored to the current `wrongsecrets` codebase (Spring Boot `4.0.3`, Java `25`).
4+
5+
## How to use this document
6+
7+
- Keep this as a living checklist in PRs.
8+
- Mark items complete when merged.
9+
- Prefer small, focused migrations (one concern per PR).
10+
11+
## Current baseline (already in place)
12+
13+
- [x] Spring Boot `4.0.3` is configured in `pom.xml`.
14+
- [x] Spring Cloud line is aligned (`2025.1.1`).
15+
- [x] `@ConfigurationProperties` is already used in multiple places.
16+
- [x] Mockito inline-mock-maker warning addressed by passing Mockito as Java agent in Surefire.
17+
18+
---
19+
20+
## Priority 0 — Safety and consistency (start here)
21+
22+
### 1) Standardize HTTP error responses with `ProblemDetail`
23+
24+
- [ ] Add a global `@RestControllerAdvice` for API endpoints that returns `ProblemDetail`.
25+
- [ ] Keep MVC HTML error handling as-is for Thymeleaf pages; only modernize JSON API errors.
26+
- [ ] Add tests that assert RFC 9457-style payload fields (`type`, `title`, `status`, `detail`, `instance`).
27+
28+
**Why now:** Reduces custom exception payload drift and improves API consistency.
29+
30+
### 2) Replace new `RestTemplate` usage with `RestClient`
31+
32+
- [ ] Stop introducing any new `RestTemplate` usage.
33+
- [ ] Migrate existing bean in `WrongSecretsApplication` from `RestTemplate` to `RestClient.Builder`.
34+
- [ ] Migrate call sites incrementally (start with `SlackNotificationService`).
35+
- [ ] Add timeout and retry policy explicitly for outbound calls.
36+
37+
**Current state:** `RestTemplate` bean and usage exist and can be migrated safely in phases.
38+
39+
### 3) Add/verify deprecation gate in CI
40+
41+
- [ ] Run compile with deprecation warnings enabled in CI (`-Xlint:deprecation`).
42+
- [ ] Fail build on newly introduced deprecations (can be soft-fail initially).
43+
- [ ] Track remaining suppressions/deprecations as explicit TODOs.
44+
45+
**Why now:** Boot 4/Spring 7 deprecations will accumulate quickly otherwise.
46+
47+
---
48+
49+
## Priority 1 — Observability and operability
50+
51+
### 4) Enable tracing + log correlation end-to-end
52+
53+
- [ ] Ensure tracing is enabled in all non-local profiles.
54+
- [ ] Ensure logs include trace/span correlation IDs.
55+
- [ ] Add dashboard/alerts for key challenge-flow operations.
56+
57+
### 5) Harden Actuator for production profiles
58+
59+
- [ ] Verify readiness/liveness probes are exposed and used by deployment manifests.
60+
- [ ] Restrict sensitive actuator endpoints by profile.
61+
- [ ] Add health contributors for external dependencies used in runtime profiles.
62+
63+
### 6) Structured logging profile
64+
65+
- [ ] Use JSON logs for cloud/container profiles.
66+
- [ ] Keep developer-friendly text logs for local profile.
67+
- [ ] Document expected log fields for incident response.
68+
69+
---
70+
71+
## Priority 2 — Runtime and performance
72+
73+
### 7) Evaluate virtual threads for I/O-heavy flows
74+
75+
- [ ] Add profile-based toggle (`spring.threads.virtual.enabled=true`) for evaluation.
76+
- [ ] Run load comparison (latency, throughput, memory) before default-enabling.
77+
- [ ] Keep a rollback toggle in case of third-party incompatibilities.
78+
79+
### 8) Validate graceful shutdown behavior
80+
81+
- [ ] Verify request drain behavior on shutdown in containerized environments.
82+
- [ ] Confirm no challenge state corruption occurs during rolling updates.
83+
84+
### 9) AOT/native readiness checks
85+
86+
- [ ] Add optional CI job for AOT/native compatibility (not necessarily release artifact yet).
87+
- [ ] Record blockers (reflection/dynamic proxies/resources) in this document.
88+
89+
---
90+
91+
## Priority 3 — Security and configuration posture
92+
93+
### 10) Expand typed config, reduce scattered `@Value`
94+
95+
- [ ] Introduce/extend `@ConfigurationProperties` classes for grouped settings.
96+
- [ ] Limit direct `@Value` usage to simple one-off values.
97+
- [ ] Validate config with bean validation annotations.
98+
99+
### 11) TLS/SSL bundles standardization
100+
101+
- [ ] Use SSL bundle config for outbound TLS trust/key material where applicable.
102+
- [ ] Remove ad-hoc SSL setup code if present.
103+
104+
### 12) Secret handling consistency by profile
105+
106+
- [ ] Document expected secret source per profile (`docker`, `k8s`, `aws`, `gcp`, `azure`).
107+
- [ ] Ensure no fallback path accidentally logs sensitive values.
108+
109+
---
110+
111+
## Priority 4 — Testing modernization
112+
113+
### 13) Keep Mockito java-agent setup stable
114+
115+
- [x] Surefire passes Mockito as `-javaagent`.
116+
- [ ] Mirror same setup in Failsafe if/when integration tests use inline mocking.
117+
118+
### 14) Strengthen integration testing with Testcontainers service connection patterns
119+
120+
- [ ] Prefer service-connection style wiring for test dependencies.
121+
- [ ] Reduce custom bootstrapping code in integration tests where possible.
122+
123+
### 15) Add contract tests for outbound HTTP clients
124+
125+
- [ ] Add tests for success, timeout, retry, and non-2xx mapping behavior.
126+
- [ ] Ensure migrated `RestClient` paths are fully covered.
127+
128+
---
129+
130+
## Concrete first 5 PRs
131+
132+
1. **PR 1:** Add API `ProblemDetail` advice + tests.
133+
2. **PR 2:** Introduce `RestClient` bean and migrate `SlackNotificationService`.
134+
3. **PR 3:** Add deprecation checks to CI and document policy.
135+
4. **PR 4:** Add tracing/log-correlation defaults for non-local profiles.
136+
5. **PR 5:** Virtual thread evaluation profile + benchmark notes.
137+
138+
---
139+
140+
## Definition of done for Boot 4 adoption
141+
142+
- [ ] No new `RestTemplate` code introduced.
143+
- [ ] API errors are standardized on `ProblemDetail`.
144+
- [ ] Deprecation warnings are tracked and controlled in CI.
145+
- [ ] Observability baseline (metrics, traces, log correlation) is active in non-local profiles.
146+
- [ ] Migration choices and rollout decisions are documented in `docs/`.

pom.xml

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.springframework.boot</groupId>
77
<artifactId>spring-boot-starter-parent</artifactId>
8-
<version>3.5.11</version>
8+
<version>4.0.3</version>
99
<!-- lookup parent from repository -->
1010
</parent>
1111

@@ -47,11 +47,11 @@
4747
<archunit.version>1.4.1</archunit.version>
4848
<asciidoctor.maven.plugin.version>3.2.0</asciidoctor.maven.plugin.version>
4949
<asciidoctorj.version>3.0.1</asciidoctorj.version>
50-
<aws.sdk.version>2.40.9</aws.sdk.version>
50+
<aws.sdk.version>2.42.4</aws.sdk.version>
5151
<bootstrap.version>5.3.8</bootstrap.version>
5252
<checkstyle-maven.version>3.6.0</checkstyle-maven.version>
5353
<checkstyle.version>13.2.0</checkstyle.version>
54-
<com.azure.spring.version>6.0.0</com.azure.spring.version>
54+
<com.azure.spring.version>7.0.0</com.azure.spring.version>
5555
<cyclonedx-maven.version>2.9.1</cyclonedx-maven.version>
5656
<cyclonedx.core.version>12.1.0</cyclonedx.core.version>
5757
<datatables.version>2.3.7</datatables.version>
@@ -60,10 +60,12 @@
6060
<findsecbugs.version>1.14.0</findsecbugs.version>
6161
<frontend-maven-plugin.version>1.15.4</frontend-maven-plugin.version>
6262
<gatling-maven-plugin.version>4.21.0</gatling-maven-plugin.version>
63-
<gatling.version>3.14.9</gatling.version>
64-
<gcp.sdk.version>7.4.5</gcp.sdk.version>
63+
<gatling.version>3.15.0</gatling.version>
6564
<github.button.version>2.14.1</github.button.version>
66-
<io.netty.version>4.1.123.Final</io.netty.version>
65+
<google.cloud.libraries-bom.version>26.76.0</google.cloud.libraries-bom.version>
66+
<!-- Pin Groovy to 4.x for thymeleaf-layout-dialect compatibility (Spring Boot 4.0 manages 5.x) -->
67+
<groovy.version>4.0.25</groovy.version>
68+
<io.netty.version>4.1.130.Final</io.netty.version>
6769
<java.version>25</java.version>
6870
<jquery.version>3.7.1</jquery.version>
6971
<jruby.version>10.0.3.0</jruby.version>
@@ -77,12 +79,12 @@
7779
<spotbugs-maven.version>4.9.8.2</spotbugs-maven.version>
7880
<spotbugs.version>4.9.8</spotbugs.version>
7981
<spotless-maven-plugin.version>3.2.1</spotless-maven-plugin.version>
80-
<spring-boot.version>3.5.11</spring-boot.version>
81-
<spring-vault.version>3.2.0</spring-vault.version>
82-
<spring.cloud-version>2025.0.0</spring.cloud-version>
83-
<spring.security.version>6.5.8</spring.security.version>
82+
<spring-boot.version>4.0.3</spring-boot.version>
83+
<spring-vault.version>4.0.1</spring-vault.version>
84+
<spring.cloud-version>2025.1.1</spring.cloud-version>
85+
<spring.security.version>7.0.3</spring.security.version>
8486
<springdoc-maven.version>1.5</springdoc-maven.version>
85-
<springdoc.version>2.8.15</springdoc.version>
87+
<springdoc.version>3.0.1</springdoc.version>
8688
<system-stubs-jupiter.version>2.1.8</system-stubs-jupiter.version>
8789
<testcontainers-cypress.version>1.10.0</testcontainers-cypress.version>
8890
<testcontainers.version>1.21.4</testcontainers.version>
@@ -108,8 +110,8 @@
108110

109111
<dependency>
110112
<groupId>com.google.cloud</groupId>
111-
<artifactId>spring-cloud-gcp-dependencies</artifactId>
112-
<version>${gcp.sdk.version}</version>
113+
<artifactId>libraries-bom</artifactId>
114+
<version>${google.cloud.libraries-bom.version}</version>
113115
<type>pom</type>
114116
<scope>import</scope>
115117
</dependency>
@@ -297,6 +299,12 @@
297299
<scope>test</scope>
298300
</dependency>
299301

302+
<dependency>
303+
<groupId>org.springframework.boot</groupId>
304+
<artifactId>spring-boot-starter-webmvc-test</artifactId>
305+
<scope>test</scope>
306+
</dependency>
307+
300308
<dependency>
301309
<groupId>uk.org.webcompere</groupId>
302310
<artifactId>system-stubs-jupiter</artifactId>
@@ -658,12 +666,24 @@
658666
<artifactId>gatling-maven-plugin</artifactId>
659667
<version>${gatling-maven-plugin.version}</version>
660668
</plugin>
669+
<plugin>
670+
<groupId>org.apache.maven.plugins</groupId>
671+
<artifactId>maven-dependency-plugin</artifactId>
672+
<version>3.8.1</version>
673+
<executions>
674+
<execution>
675+
<goals>
676+
<goal>properties</goal>
677+
</goals>
678+
</execution>
679+
</executions>
680+
</plugin>
661681
<plugin>
662682
<groupId>org.apache.maven.plugins</groupId>
663683
<artifactId>maven-surefire-plugin</artifactId>
664684
<version>${maven-surefire-plugin.version}</version>
665685
<configuration>
666-
<argLine>-Dspring.profiles.active=test,maven-test</argLine>
686+
<argLine>-Dspring.profiles.active=test,maven-test -javaagent:${org.mockito:mockito-core:jar}</argLine>
667687
</configuration>
668688
</plugin>
669689
</plugins>

src/main/java/org/owasp/wrongsecrets/SecretsErrorController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.owasp.wrongsecrets;
22

33
import io.swagger.v3.oas.annotations.Operation;
4-
import org.springframework.boot.web.servlet.error.ErrorController;
4+
import org.springframework.boot.webmvc.error.ErrorController;
55
import org.springframework.stereotype.Controller;
66
import org.springframework.web.bind.annotation.GetMapping;
77

src/main/java/org/owasp/wrongsecrets/challenges/ChallengesCtfController.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package org.owasp.wrongsecrets.challenges;
22

3+
import com.fasterxml.jackson.databind.node.ArrayNode;
4+
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
35
import io.swagger.v3.oas.annotations.Operation;
46
import java.util.List;
57
import lombok.extern.slf4j.Slf4j;
6-
import net.minidev.json.JSONArray;
7-
import net.minidev.json.JSONObject;
88
import org.owasp.wrongsecrets.Challenges;
99
import org.owasp.wrongsecrets.RuntimeEnvironment;
1010
import org.owasp.wrongsecrets.ScoreCard;
@@ -45,11 +45,11 @@ public ChallengesCtfController(
4545
summary = "Gives all challenges back in a jsonArray, to be used with the Juiceshop CTF cli")
4646
public String getChallenges() {
4747
List<ChallengeDefinition> definitions = challenges.getDefinitions().challenges();
48-
JSONObject json = new JSONObject();
49-
JSONArray jsonArray = new JSONArray();
48+
var json = JsonNodeFactory.instance.objectNode();
49+
ArrayNode jsonArray = JsonNodeFactory.instance.arrayNode();
5050
for (int i = 0; i < definitions.size(); i++) {
5151
ChallengeDefinition definition = definitions.get(i);
52-
JSONObject jsonChallenge = new JSONObject();
52+
var jsonChallenge = JsonNodeFactory.instance.objectNode();
5353
jsonChallenge.put("id", i + 1);
5454
jsonChallenge.put("name", definition.name().name());
5555
jsonChallenge.put("key", definition.name().shortName());
@@ -68,13 +68,18 @@ public String getChallenges() {
6868
.map(s -> s.hint().contents().get())
6969
.orElse(disabledChallenge));
7070
jsonChallenge.put("solved", scoreCard.getChallengeCompleted(definition));
71-
jsonChallenge.put("disabledEnv", getDisabledEnv(definition));
71+
String disabledEnv = getDisabledEnv(definition);
72+
if (disabledEnv != null) {
73+
jsonChallenge.put("disabledEnv", disabledEnv);
74+
} else {
75+
jsonChallenge.putNull("disabledEnv");
76+
}
7277
jsonChallenge.put("difficulty", getDificulty(definition.difficulty()));
7378
jsonArray.add(jsonChallenge);
7479
}
7580
json.put("status", "success");
76-
json.put("data", jsonArray);
77-
String result = json.toJSONString();
81+
json.set("data", jsonArray);
82+
String result = json.toString();
7883
log.trace("returning {}", result);
7984
return result;
8085
}

src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge49.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public boolean answerCorrect(String answer) {
4646
return false;
4747
}
4848
} catch (Exception e) {
49-
log.warn("given answer is not an integer", e);
49+
log.warn("given answer is not an integer. Exception: {}", e.getMessage());
5050
return false;
5151
}
5252

src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge52.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ private String getActualSecret() {
3333
try {
3434
return Files.readString(Paths.get(dockerMountsecret, "secret.txt"), StandardCharsets.UTF_8);
3535
} catch (Exception e) {
36-
log.warn("Exception during file reading, defaulting to default without cloud environment", e);
36+
log.warn(
37+
"Exception during file reading, defaulting to default without cloud environment."
38+
+ " Exception message: {}",
39+
e.getMessage());
3740
return Challenges.ErrorResponses.OUTSIDE_DOCKER;
3841
}
3942
}

src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge59.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static java.nio.charset.StandardCharsets.UTF_8;
44

55
import java.util.Base64;
6+
import lombok.extern.slf4j.Slf4j;
67
import org.owasp.wrongsecrets.challenges.FixedAnswerChallenge;
78
import org.springframework.beans.factory.annotation.Value;
89
import org.springframework.stereotype.Component;
@@ -12,6 +13,7 @@
1213
* variables. Shows how an ex-employee could misuse the webhook if it's not rotated when they leave.
1314
*/
1415
@Component
16+
@Slf4j
1517
public class Challenge59 extends FixedAnswerChallenge {
1618

1719
private final String obfuscatedSlackWebhookUrl;
@@ -37,6 +39,7 @@ private String deobfuscateSlackWebhookUrl(String obfuscatedUrl) {
3739
byte[] secondDecode = Base64.getDecoder().decode(firstDecode);
3840
return new String(secondDecode, UTF_8);
3941
} catch (Exception e) {
42+
log.warn("Webhook URL not properly set for Slack in {}", this);
4043
// Return a default value if the environment variable is not properly set
4144
return "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX";
4245
}

0 commit comments

Comments
 (0)