Projects STRLCPY wrongsecrets Commits edfe3abe
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
Showing first 98 files as there are too many
  • ■ ■ ■ ■
    .eslintrc.js
    1 1  module.exports = {
    2 2   env: {
     3 + jest: true,
     4 + 'cypress/globals': true,
    3 5   browser: true,
    4 6   commonjs: true,
    5 7   es2021: true
    skipped 5 lines
    11 13   ecmaVersion: 'latest'
    12 14   },
    13 15   rules: {
    14  - }
     16 + },
     17 + plugins: ['cypress']
    15 18  }
    16 19   
  • ■ ■ ■ ■ ■ ■
    .github/scripts/docker-create.sh
    skipped 392 lines
    393 393   else
    394 394   log_failure "The container test has failed, this means that when we built your changes and ran a basic sanity test on the homepage it failed. Please build the container locally and double check the container is running correctly."
    395 395   fi
     396 + echo "testing curl for webjar caching"
     397 + curl -I 'http://localhost:8080/webjars/bootstrap/5.2.3/css/bootstrap.min.css'
    396 398   echo "Testing complete"
    397 399   else
    398 400   return
    skipped 15 lines
  • ■ ■ ■ ■
    .github/workflows/dast-zap-test.yml
    skipped 19 lines
    20 20   java-version: "19"
    21 21   distribution: "temurin"
    22 22   - name: Clean install
    23  - run: ./mvnw clean install -DskipTests -Ddependency-check.skip -Dcyclonedx.skip=true
     23 + run: ./mvnw --no-transfer-progress clean install -DskipTests -Ddependency-check.skip -Dcyclonedx.skip=true -Dexec.skip
    24 24   - name: Start wrongsecrets
    25 25   run: nohup ./mvnw spring-boot:run -Dspring-boot.run.profiles=without-vault &
    26 26   - name: ZAP Scan
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    .github/workflows/java_swagger_doc.yml
    skipped 19 lines
    20 20   java-version: "19"
    21 21   distribution: "temurin"
    22 22   - name: Clean install
    23  - run: ./mvnw clean install -DskipTests -Ddependency-check.skip -Dcyclonedx.skip=true
     23 + run: ./mvnw --no-transfer-progress clean install -DskipTests -Ddependency-check.skip -Dcyclonedx.skip=true -Dexec.skip
    24 24   - name: Compile javadoc
    25  - run: ./mvnw compile javadoc:javadoc
     25 + run: ./mvnw --no-transfer-progress compile javadoc:javadoc
    26 26   - name: Start wrongsecrets
    27  - run: nohup ./mvnw spring-boot:run -Dspring-boot.run.profiles=without-vault &
     27 + run: nohup ./mvnw --no-transfer-progress spring-boot:run -Dspring-boot.run.profiles=without-vault &
    28 28   - name: Compile javadocs
    29  - run: ./mvnw compile javadoc:javadoc
     29 + run: ./mvnw --no-transfer-progress compile javadoc:javadoc
    30 30   - name: Generate swaggerdoc
    31  - run: ./mvnw springdoc-openapi:generate
     31 + run: ./mvnw --no-transfer-progress springdoc-openapi:generate
    32 32   - name: Upload swagger api doc
    33 33   uses: actions/upload-artifact@v3
    34 34   with:
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    .github/workflows/main.yml
    skipped 29 lines
    30 30   distribution: "temurin"
    31 31   - name: Test with Maven
    32 32   run: mvn --no-transfer-progress test
    33  - 
     33 + ui-test:
     34 + name: UI test with Cypress
     35 + runs-on: ubuntu-latest
     36 + steps:
     37 + - name: Checkout repo
     38 + uses: actions/checkout@v3
     39 + with:
     40 + fetch-depth: 0
     41 + - name: Install node
     42 + uses: actions/setup-node@v3
     43 + - name: Set up JDK 19
     44 + uses: actions/setup-java@v3
     45 + with:
     46 + java-version: "19"
     47 + distribution: "temurin"
     48 + - name: Test with Cypress
     49 + run: mvn verify -Dexec.id=xcypress-test -DskipTests -Ddependency-check.skip
     50 + - name: Uploading screenshots
     51 + uses: actions/upload-artifact@v3
     52 + if: failure()
     53 + with:
     54 + name: screenshots
     55 + path: cypress/screenshots
    34 56   lint:
    35 57   name: lint javacode
    36 58   runs-on: ubuntu-latest
    skipped 23 lines
  • ■ ■ ■ ■ ■
    .github/workflows/minikube-k8s-test.yml
    skipped 31 lines
    32 32   kubectl apply -f k8s/workspace-psa.yml
    33 33   kubectl apply -f k8s/secrets-config.yml
    34 34   kubectl apply -f k8s/secrets-secret.yml
     35 + kubectl apply -f k8s/challenge33.yml
    35 36   kubectl apply -f k8s/secret-challenge-deployment.yml
    36 37   while [[ $(kubectl get pods -l app=secret-challenge -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]]; do echo "waiting for secret-challenge" && sleep 2; done
    37 38   kubectl logs -l app=secret-challenge -f >> pod.log &
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    .gitignore
    skipped 72 lines
    73 73  node_modules
    74 74  .npm
    75 75   
     76 +# Cypress
     77 +cypress/videos
     78 +cypress/screenshots
     79 + 
  • ■ ■ ■ ■ ■ ■
    CONTRIBUTING.md
    skipped 134 lines
    135 135   
    136 136  ## How to get started with the project in IntelliJ IDEA
    137 137   
    138  -- ### Step 1: Fork the Project.
     138 +-
     139 + 
     140 +### Step 1: Fork the Project.
    139 141   
    140 142   Navigate to the landing page of the repository in your web browser and click on the **_Fork_** button on the repository’s home page.
    141 143   A forked copy of that Git repository will be added to your personal GitHub.
    142 144   
    143 145   ![](images/fork-project-1.png)
    144 146   
    145  -- ### Step 2: Clone the Project.
     147 +-
     148 + 
     149 +### Step 2: Clone the Project.
    146 150   
    147 151   A **clone** is a full copy of a repository, including all logging and versions of files.
    148 152   To **_clone_** the Project to your local desktop by clicking on the button as shown below.
    149 153   
    150 154   ![](images/clone-project-2.png)
    151 155   
    152  -- ### Step 3: Open the Project using IntelliJ IDEA
     156 +-
     157 + 
     158 +### Step 3: Open the Project using IntelliJ IDEA
    153 159   
    154 160   - **_Open_** the Cloned Project using IntelliJ IDEA by clicking on the button as shown below.
    155 161   
    skipped 3 lines
    159 165   
    160 166   ![](images/wait-3.2.png)
    161 167   
    162  -- ### Step 4: Setup.
    163 168   
    164  - - Open Settings by pressing **_Ctrl+Alt+S_**
    165  - ![](images/open-settings-4.1.png)
     169 +### Step 4: Setup.
    166 170   
    167  - - Follow the path **_IDE settings>Language & Frameworks > Lombok_** and then click on **_Lombok._**
    168  - ![](images/lombok-setup-4.2.png)
     171 +- Open Settings by pressing **_Ctrl+Alt+S_**
     172 + ![](images/open-settings-4.1.png)
    169 173   
    170  - - Make sure that the **_Lombok processing_** is enabled.
    171  - ![](images/lombok-processing-4.3.png)
     174 +- Follow the path **_IDE settings>Language & Frameworks > Lombok_** and then click on **_Lombok._**
     175 + ![](images/lombok-setup-4.2.png)
     176 + 
     177 +- Make sure that the **_Lombok processing_** is enabled.
     178 + ![](images/lombok-processing-4.3.png)
     179 + 
     180 +- Select **_Plugins > Marketplace_** and type 'google-java-format' and restart IntelliJ to install the plugin.
     181 + 
     182 +- Open Settings by pressing **_Ctrl+Alt+S_**
     183 + ![](images/open-settings-4.1.png)
     184 + 
     185 +- Select **_google-java-format Settings_** and click enable.
     186 + ![](images/open-settings-4.4.png)
    172 187   
    173 188  - ### Step 5: Reload the project
    174 189   
    skipped 9 lines
    184 199   
    185 200  **NOTE:** Indians and other Asia-Pacific countries users may have to use **VPN** if you enounter this exception `org.owasp.dependencycheck.utils.DownloadFailedException: TLS Connection Reset`.
    186 201   
    187  -- ### Step 6: Running the Project.
     202 +-
     203 + 
     204 +### Step 6: Running the Project.
    188 205   
    189 206   - Open the **_WrongSecretsApplication_** by following the path **_main>java>org.owasp.wrongsecrets>WrongSecretApplication_**.
    190 207   ![](images/open-application-6.1.png)
    skipped 22 lines
    213 230   
    214 231   ![](images/run-application-6.2.png)
    215 232   
    216  -- ### There you have it, **_WrongSecrets_** running successfully.
     233 +-
     234 + 
     235 +### There you have it, **_WrongSecrets_** running successfully.
    217 236   
    218 237   - Here is a _preview_ on how does it look after successfully running the Application.
    219 238   **Note:** Running the Application doesn't open any kind of **_GUI_**, it only initializes the **_local webserver_** that you can open via a **_browser._**
    skipped 7 lines
    227 246   
    228 247  ## How to add a challenge
    229 248   
    230  -- ### Step 1: Creating a new issue.
     249 +-
     250 + 
     251 +### Step 1: Creating a new issue.
    231 252   
    232 253   First make sure that you have an [Issue](https://github.com/OWASP/wrongsecrets/issues/new) reported for which a challenge is really wanted, And make sure the challenge is assigned to you, as others might be working on the challenge.
    233 254   
    234  -- ### Step 2: Adding the challenge.
    235  - Add the **new challenge** in this folder `wrongsecrets/src/main/java/org/owasp/wrongsecrets/challenges/`.
    236  - These are the things that you have to keep in mind.
    237  - - First and foremost make sure your challenge is coded in **Java**.
    238  - - Don't forget to add your challenge number in `@Order(28)` annotation, **_28_** in my case.
    239  - - Here is an example of a possible Challenge 28:
    240  - ```java
    241  - package org.owasp.wrongsecrets.challenges.docker;
    242  - import lombok.extern.slf4j.Slf4j;
    243  - import org.owasp.wrongsecrets.RuntimeEnvironment;
    244  - import org.owasp.wrongsecrets.ScoreCard;
    245  - import org.owasp.wrongsecrets.challenges.Challenge;
    246  - import org.owasp.wrongsecrets.challenges.ChallengeTechnology;
    247  - import org.owasp.wrongsecrets.challenges.Spoiler;
    248  - import org.springframework.core.annotation.Order;
    249  - import org.springframework.stereotype.Component;
    250  - import java.util.List;
    251  - /**
    252  - * Describe what your challenge does
    253  - */
    254  - @Slf4j
    255  - @Component
    256  - @Order(28) //make sure this number is the same as your challenge
    257  - public class Challenge28 extends Challenge {
    258  - private final String secret;
    259  - public Challenge28(ScoreCard scoreCard) {
    260  - super(scoreCard);
    261  - secret = "hello world";
    262  - }
    263  - //is this challenge usable in CTF mode?
    264  - @Override
    265  - public boolean canRunInCTFMode() {
    266  - return true;
    267  - }
    268  - //return the plain text secret here
    269  - @Override
    270  - public Spoiler spoiler() {
    271  - return new Spoiler(secret);
    272  - }
    273  - //here you validate if your answer matches the secret
    274  - @Override
    275  - public boolean answerCorrect(String answer) {
    276  - return secret.equals(answer);
    277  - }
    278  - //which runtime can you use to run the challenge on? (You can just use Docker here)
    279  - /**
    280  - * {@inheritDoc}
    281  - */
    282  - @Override
    283  - public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() {
    284  - return List.of(RuntimeEnvironment.Environment.DOCKER);
    285  - }
    286  - //set the difficulty: 1=low, 5=very hard
    287  - /**
    288  - * {@inheritDoc}
    289  - * Difficulty: 1.
    290  - */
    291  - @Override
    292  - public int difficulty() {
    293  - return 1;
    294  - }
    295  - //on which tech is this challenge? See ChallengeTechnology.Tech for categories
    296  - /**
    297  - * {@inheritDoc}
    298  - * Secrets based.
    299  - */
    300  - @Override
    301  - public String getTech() {
    302  - return ChallengeTechnology.Tech.SECRETS.id;
    303  - }
    304  - //if you use this in a shared environment and need to adapt it, then return true here.
    305  - @Override
    306  - public boolean isLimittedWhenOnlineHosted() {
    307  - return false;
     255 +-
     256 + 
     257 +### Step 2: Adding the challenge.
     258 + 
     259 +Add the **new challenge** in this folder `wrongsecrets/src/main/java/org/owasp/wrongsecrets/challenges/`.
     260 +These are the things that you have to keep in mind.
     261 +- First and foremost make sure your challenge is coded in **Java**.
     262 +- Don't forget to add your challenge number in `@Order(28)` annotation, **_28_** in my case.
     263 +- Here is an example of a possible Challenge 28:
     264 + 
     265 +```java
     266 +package org.owasp.wrongsecrets.challenges.docker;
     267 +import lombok.extern.slf4j.Slf4j;
     268 +import org.owasp.wrongsecrets.RuntimeEnvironment;
     269 +import org.owasp.wrongsecrets.ScoreCard;
     270 +import org.owasp.wrongsecrets.challenges.Challenge;
     271 +import org.owasp.wrongsecrets.challenges.ChallengeTechnology;
     272 +import org.owasp.wrongsecrets.challenges.Spoiler;
     273 +import org.springframework.core.annotation.Order;
     274 +import org.springframework.stereotype.Component;
     275 +import java.util.List;
     276 +/**
     277 +* Describe what your challenge does
     278 +*/
     279 +@Slf4j
     280 +@Component
     281 +@Order(28) //make sure this number is the same as your challenge
     282 +public class Challenge28 extends Challenge {
     283 +private final String secret;
     284 +public Challenge28(ScoreCard scoreCard) {
     285 +super(scoreCard);
     286 +secret = "hello world";
     287 +}
     288 +//is this challenge usable in CTF mode?
     289 +@Override
     290 +public boolean canRunInCTFMode() {
     291 +return true;
     292 +}
     293 +//return the plain text secret here
     294 +@Override
     295 +public Spoiler spoiler() {
     296 +return new Spoiler(secret);
     297 +}
     298 +//here you validate if your answer matches the secret
     299 +@Override
     300 +public boolean answerCorrect(String answer) {
     301 +return secret.equals(answer);
     302 +}
     303 +//which runtime can you use to run the challenge on? (You can just use Docker here)
     304 +/**
     305 +* {@inheritDoc}
     306 +*/
     307 +@Override
     308 +public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() {
     309 +return List.of(RuntimeEnvironment.Environment.DOCKER);
     310 +}
     311 +//set the difficulty: 1=low, 5=very hard
     312 +/**
     313 +* {@inheritDoc}
     314 +* Difficulty: 1.
     315 +*/
     316 +@Override
     317 +public int difficulty() {
     318 +return 1;
     319 +}
     320 +//on which tech is this challenge? See ChallengeTechnology.Tech for categories
     321 +/**
     322 +* {@inheritDoc}
     323 +* Secrets based.
     324 +*/
     325 +@Override
     326 +public String getTech() {
     327 +return ChallengeTechnology.Tech.SECRETS.id;
     328 +}
     329 +//if you use this in a shared environment and need to adapt it, then return true here.
     330 +@Override
     331 +public boolean isLimittedWhenOnlineHosted() {
     332 +return false;
     333 + 
    308 334   }
    309 335   }
    310  - ```
     336 +```
    311 337  - ### Step 3: Adding Test File.
    312 338   
    313 339   Add the **new TestFile** in this folder `wrongsecrets/src/test/java/org/owasp/wrongsecrets/challenges/`. TestFile is required to do **unit testing.**
    skipped 24 lines
    338 364   ```
    339 365  Please note that PRs for new challenges are only accepted when unit tests are added to prove that the challenge works. Normally tests should not immediately leak the actual secret, so leverage the `.spoil()` functionality of your test implementation for this.
    340 366   
    341  -- ### Step 4: Adding explanations, reasons and hints.
     367 +-
     368 + 
     369 +### Step 4: Adding explanations, reasons and hints.
    342 370   
    343 371   Add the explanation for your challenge along with the hints that will help in finding the secret in this folder `wrongsecrets/src/main/resources/explanations/`.
    344 372   Things to be noted.
    skipped 38 lines
  • ■ ■ ■ ■ ■ ■
    Dockerfile
    skipped 7 lines
    8 8  ENV APP_VERSION=$argBasedVersion
    9 9  ENV DOCKER_ENV_PASSWORD="This is it"
    10 10  ENV AZURE_KEY_VAULT_ENABLED=false
    11  -ENV springdoc_swagger-ui_enabled=false
    12  -ENV springdoc_api-docs_enabled=false
     11 +ENV SPRINGDOC_UI=false
     12 +ENV SPRINGDOC_DOC=false
    13 13   
    14 14  RUN echo "2vars"
    15 15  RUN echo "$ARG_BASED_PASSWORD"
    skipped 7 lines
    23 23  COPY --chown=wrongsecrets src/main/resources/executables/ /home/wrongsecrets/
    24 24  COPY --chown=wrongsecrets src/test/resources/alibabacreds.kdbx /var/tmp/helpers
    25 25  USER wrongsecrets
    26  -CMD java -jar -Dspring.profiles.active=$(echo ${SPRING_PROFILES_ACTIVE}) /application.jar
     26 +CMD java -jar -Dspring.profiles.active=$(echo ${SPRING_PROFILES_ACTIVE}) -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} -D /application.jar
    27 27   
  • ■ ■ ■ ■ ■ ■
    Dockerfile.web
    1  -FROM jeroenwillemsen/wrongsecrets:1.6.1-no-vault
    2  -ARG argBasedVersion="1.6.1-no-vault"
     1 +FROM jeroenwillemsen/wrongsecrets:1.6.4-no-vault
     2 +ARG argBasedVersion="1.6.4-no-vault"
    3 3  ARG CANARY_URLS="http://canarytokens.com/terms/about/s7cfbdakys13246ewd8ivuvku/post.jsp,http://canarytokens.com/terms/about/y0all60b627gzp19ahqh7rl6j/post.jsp"
    4 4  ARG CTF_ENABLED=false
    5 5  ARG HINTS_ENABLED=true
    skipped 23 lines
    29 29  ENV default_aws_value_challenge_11=$CHALLENGE_11_VALUE
    30 30  COPY .github/scripts/ /var/helpers
    31 31  COPY src/test/resources/alibabacreds.kdbx /var/helpers
    32  -CMD java -Xms128m -Xmx128m -Xss512k -jar -Dserver.port=$PORT -XX:MaxRAMPercentage=75 -XX:MinRAMPercentage=25 -Dspring.profiles.active=without-vault application.jar
     32 +CMD java -Xms128m -Xmx128m -Xss512k -jar -Dserver.port=$PORT -XX:MaxRAMPercentage=75 -XX:MinRAMPercentage=25 -Dspring.profiles.active=without-vault -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} application.jar
    33 33   
  • ■ ■ ■ ■ ■
    PULL_REQUEST_TEMPLATE.md
    skipped 35 lines
    36 36  - [ ] All the contributions made are solely the work of me and my co-authors
    37 37  - [ ] I tested the changes in this PR (if applicable)
    38 38  - [ ] I added unit tests to ensure my change works (when change in Java or on front-end code)
     39 +- [ ] I added UI tests to ensure my UI changes work (when change in the overall UI, not needed if just adding a challenge)
    39 40  - [ ] The PR passes pre-commit hooks and automated tests
    40 41   
  • ■ ■ ■ ■ ■
    README.md
    skipped 10 lines
    11 11   
    12 12  Welcome to the OWASP WrongSecrets game! The game is packed with real life examples of how to _not_ store secrets in your software. Each of these examples is captured in a challenge, which you need to solve using various tools and techniques. Solving these challenges will help you recognize common mistakes & can help you to reflect on your own secrets management strategy.
    13 13   
    14  -Can you solve all the 29 challenges?
     14 +Can you solve all the 32 challenges?
    15 15   
    16 16  Try some of them on [our Heroku demo environment](https://wrongsecrets.herokuapp.com/).
    17 17   
    skipped 49 lines
    67 67   
    68 68  ## Basic docker exercises
    69 69   
    70  -_Can be used for challenges 1-4, 8, 12-28_
     70 +_Can be used for challenges 1-4, 8, 12-32_
    71 71   
    72 72  For the basic docker exercises you currently require:
    73 73   
    skipped 30 lines
    104 104  - [localhost:8080/challenge/26](http://localhost:8080/challenge/26)
    105 105  - [localhost:8080/challenge/27](http://localhost:8080/challenge/27)
    106 106  - [localhost:8080/challenge/28](http://localhost:8080/challenge/28)
     107 +- [localhost:8080/challenge/29](http://localhost:8080/challenge/29)
     108 +- [localhost:8080/challenge/30](http://localhost:8080/challenge/30)
     109 +- [localhost:8080/challenge/31](http://localhost:8080/challenge/31)
     110 +- [localhost:8080/challenge/32](http://localhost:8080/challenge/32)
    107 111   
    108 112  Note that these challenges are still very basic, and so are their explanations. Feel free to file a PR to make them look
    109 113  better ;-).
    skipped 10 lines
    120 124   
    121 125  ## Basic K8s exercise
    122 126   
    123  -_Can be used for challenges 1-6, 8, 12-28_
     127 +_Can be used for challenges 1-6, 8, 12-32_
    124 128   
    125 129  ### Minikube based
    126 130   
    skipped 46 lines
    173 177   
    174 178  ## Vault exercises with minikube
    175 179   
    176  -_Can be used for challenges 1-8, 12-28_
     180 +_Can be used for challenges 1-8, 12-29_
    177 181  Make sure you have the following installed:
    178 182   
    179 183  - minikube with docker (or comment out line 8 and work at your own k8s setup),
    skipped 4 lines
    184 188  - vault [Install from here](https://www.vaultproject.io/downloads),
    185 189  - grep, Cat, and Sed
    186 190   
    187  -Run `./k8s-vault-minkube-start.sh`, when the script is done, then the challenges will wait for you at <http://localhost:8080> . This will allow you to run challenges 1-8, 12-22.
     191 +Run `./k8s-vault-minkube-start.sh`, when the script is done, then the challenges will wait for you at <http://localhost:8080> . This will allow you to run challenges 1-8, 12-32.
    188 192   
    189 193  When you stopped the `k8s-vault-minikube-start.sh` script and want to resume the port forward run: `k8s-vault-minikube-resume.sh`.
    190 194  This is because if you run the start script again it will replace the secret in the vault and not update the secret-challenge application with the new secret.
    191 195   
    192 196  ## Cloud Challenges
    193 197   
    194  -_Can be used for challenges 1-28_
     198 +_Can be used for challenges 1-32_
    195 199   
    196 200  **READ THIS**: Given that the exercises below contain IAM privilege escalation exercises,
    197 201  never run this on an account which is related to your production environment or can influence your account-over-arching
    skipped 24 lines
    222 226  7. Create a container and push it to your registry
    223 227  8. Override the K8s definition files for either [AWS](/aws/k8s/secret-challenge-vault-deployment.yml) or [GCP](/gcp/k8s/secret-challenge-vault-deployment.yml.tpl).
    224 228   
    225  -## Do you want to play without guidance?
     229 +## Do you want to play without guidance or spoils?
    226 230   
    227 231  Each challenge has a `Show hints` button and a `What's wrong?` button. These buttons help to simplify the challenges and give explanation to the reader. Though, the explanations can spoil the fun if you want to do this as a hacking exercise.
    228 232  Therefore, you can manipulate them by overriding the following settings in your env:
    229 233   
    230 234  - `hints_enabled=false` will turn off the `Show hints` button.
    231 235  - `reason_enabled=false` will turn of the `What's wrong?` explanation button.
     236 +- `spoiling_enabled=false` will turn off the `/spoil-x` endpoint (where `x` is the number of the challenge).
     237 + 
     238 +## Enabling Swaggerdocs and UI
     239 + 
     240 +You can enable Swagger documentation and the Swagger UI by overriding the `SPRINGDOC_UI` and `SPRINGDOC_DOC` when running the Docker container. See our [Okteto Deployment](https://github.com/OWASP/wrongsecrets/blob/master/okteto/k8s/secret-challenge-deployment.yml) for more details.
    232 241   
    233 242  ## Special thanks & Contributors
    234 243   
    skipped 10 lines
    245 254  - [Tibor Hercz @tiborhercz](https://github.com/tiborhercz)
    246 255  - [Chris Elbring Jr. @neatzsche](https://github.com/neatzsche)
    247 256  - [Puneeth Y @puneeth072003](https://github.com/puneeth072003)
     257 +- [Mike Woudenberg @mikewoudenberg](https://github.com/mikewoudenberg)
     258 +- [Divyanshu Dev @Novice-expert](https://github.com/Novice-expert)
    248 259  - [Filip Chyla @fchyla](https://github.com/fchyla)
    249 260  - [Dmitry Litosh @Dlitosh](https://github.com/Dlitosh)
    250 261  - [Josh Grossman @tghosth](https://github.com/tghosth)
     262 +- [Turjo Chowdhury @turjoc120](https://github.com/turjoc120)
    251 263  - [Spyros @northdpole](https://github.com/northdpole)
    252  -- [Mike Woudenberg @mikewoudenberg](https://github.com/mikewoudenberg)
    253 264  - [Ruben Kruiver @RubenAtBinx](https://github.com/RubenAtBinx)
     265 +- [Shlomo Zalman Heigh @szh](https://github.com/szh)
    254 266  - [Nicolas Humblot @nhumblot](https://github.com/nhumblot)
     267 +- [Madhu Akula @madhuakula](https://github.com/madhuakula)
    255 268  - [Finn @f3rn0s](https://github.com/f3rn0s)
    256 269  - [Alex Bender @alex-bender](https://github.com/alex-bender)
    257 270  - [Rick M @kingthorin](https://github.com/kingthorin)
    skipped 7 lines
    265 278  Special mentions for helping out:
    266 279   
    267 280  - [Madhu Akula @madhuakula](https://github.com/madhuakula)
    268  -- [Björn Kimminich @bkimminich](https://github.com/bkimminich)
     281 +- [BjÃrn Kimminich @bkimminich](https://github.com/bkimminich)
    269 282  - [Xiaolu Dai @saragluna](https://github.com/saragluna)
    270 283  - [Jonathan Giles @jonathanGiles](https://github.com/JonathanGiles)
    271 284   
    skipped 151 lines
    423 436   
    424 437  1. First make sure that you have an [Issue](https://github.com/OWASP/wrongsecrets/issues) reported for which a challenge is really wanted.
    425 438  2. Add the new challenge in the `org.owasp.wrongsecrets.challenges` folder. Make sure you add an explanation in `src/main/resources/explanations` and refer to it from your new Challenge class.
    426  -3. Add a unit and integration test to show that your challenge is working.
     439 +3. Add unit, integration and UI tests as appropriate to show that your challenge is working.
    427 440  4. Don't forget to add `@Order` annotation to your challenge ;-).
    428 441  5. Review the [CONTRIBUTING guide](CONTRIBUTING.md) for setting up your contributing environment and writing good commit messages.
    429 442   
    skipped 41 lines
    471 484  And then at [http://localhost:3000](http://localhost:3000).
    472 485   
    473 486  Note: be careful with trying to deploy the `jeroenwillemsen/wrongsecrets-desktop` container to Heroku ;-).
     487 + 
     488 +## Docker on macOS with M1 and Colima (Experimental!)
     489 + 
     490 +NOTE: Colima support is experimental.
     491 + 
     492 +Using [Colima](https://github.com/abiosoft/colima) (version 0.5.2 when written) you your macOS with Apple Silicon M1
     493 +to run Docker image `jeroenwillemsen/wrongsecrets` you try one of:
     494 + 
     495 +- switch off Colima
     496 +- change Docker context
     497 +- run Colima with 1 CPU
     498 + 
     499 +### Switch off Colima
     500 + 
     501 +```shell
     502 +colima stop
     503 +```
     504 +and run natively Docker image `jeroenwillemsen/wrongsecrets` on ARM.
     505 + 
     506 +### Change Docker context
     507 + 
     508 +Running docker image on Colima container runtimes on macOS Ventura with M1 CPU can run very slowly or can hang at some point.
     509 +Wrong Secrets provide `arm64` Docker image and switching to `desktop-linux` context will use the native `arm64` image.
     510 +To do that in the terminal run:
     511 + 
     512 +```shell
     513 +docker context ls
     514 +```
     515 + 
     516 +you should see context default `colima *`:
     517 + 
     518 +```
     519 +NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
     520 +colima * moby colima unix:///Users/YOUR_USER_NAME/.colima/default/docker.sock
     521 +default moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock https://127.0.0.1:6443 (default) swarm
     522 +desktop-linux moby unix:///Users/YOUR_USER_NAME/.docker/run/docker.sock
     523 +```
     524 + 
     525 +Now run one of the above Docker commands together with `--context` switch e.g.:
     526 + 
     527 +```bash
     528 +docker --context desktop-linux run -p 8080:8080 jeroenwillemsen/wrongsecrets:latest-no-vault
     529 +```
     530 + 
     531 +### Run Colima with 1 CPU
     532 + 
     533 +Colima is using QEMU behind and for QEMU on Apple Silicon M1 is recommended to use 1 CPU core:
     534 + 
     535 +```shell
     536 +colima start -m 8 -c 1 --arch x86_64
     537 +```
     538 + 
     539 +and run with AMD x64 emulation e.g.:
     540 + 
     541 +```bash
     542 +docker run -p 8080:8080 jeroenwillemsen/wrongsecrets:latest-no-vault
     543 +```
    474 544   
    475 545  ## Further reading on secrets management
    476 546   
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    aws/.terraform.lock.hcl
    skipped 1 lines
    2 2  # Manual edits may be lost in future updates.
    3 3   
    4 4  provider "registry.terraform.io/hashicorp/aws" {
    5  - version = "4.61.0"
    6  - constraints = ">= 3.72.0, >= 3.73.0, >= 4.0.0, >= 4.47.0, ~> 4.61.0"
     5 + version = "4.65.0"
     6 + constraints = ">= 3.72.0, >= 4.0.0, >= 4.35.0, >= 4.47.0, >= 4.57.0, ~> 4.65.0"
    7 7   hashes = [
    8  - "h1:qyBawxoNN6EpiiX5h5ZG5P2dHsBeA5Z67xESl2c1HRk=",
    9  - "zh:051e2588410b7448a5c4c30d668948dd6fdfa8037700bfc00fb228986ccbf3a5",
    10  - "zh:082fbcf9706b48d0880ba552a11c29527e228dadd6d83668d0789abda24e5922",
    11  - "zh:0e0e72f214fb24f4f9c601cab088a2d8e00ec3327c451bc753911951d773214a",
    12  - "zh:3af6d38ca733ca66cce15c6a5735ded7c18348ad26040ebd9a59778b2cd9cf6c",
    13  - "zh:404898bc2258bbb9527fa06c72cb927ca011fd9bc3f4b90931c0912652c3f9e9",
    14  - "zh:4f617653b0f17a7708bc896f029c4ab0b677a1a1c987bd77166acad1d82db469",
    15  - "zh:5dbe393355ac137aa3fd329e3d24871f27012d3ba93d714485b55820df240349",
    16  - "zh:6067c2127eb5c879227aca671f101de6dcba909d0d8d15d5711480351962a248",
     8 + "h1:npDM2DHnGDKlXJJGWdBpTVywKHa9clSgXzvin5phSM4=",
     9 + "zh:0461b8dfc14e94971bfd12783cbd5a5574b9fcfc3694b6afaa8836f90b61c1f9",
     10 + "zh:24a27e7b1f6eb33e9da6f2ffaaa6bc48e933a24224c6572d6e588994e5c7130b",
     11 + "zh:2ca189d04573414bef4876c17ccb2b76f6e721e0450f6ab3700d94d7c04bec64",
     12 + "zh:3fb0654a527677231dab2140e9a55df3b90dba478b3db50001e21a045437a47a",
     13 + "zh:4918173d9c7d2735908622c17efd01746a046f0a571690afa7dd0866f22045f7",
     14 + "zh:491d259b15166f751076d2bdc443928ca63f6c0a83b02ea75fff8b4224662207",
     15 + "zh:4ff8e178f0656f04f88558c295a1d246b1bdcf5ad81d8b3b9ccceaeca2eb7fa8",
     16 + "zh:5e4eaf2855a740124f4bbe34ac4bd22c7f320aa3e91d9cef64396ad0a1571544",
     17 + "zh:65762c60c4bac2e0d55ed8c2877e455e84465cb12f0c885363a1b561cd4f5f07",
     18 + "zh:7c5e4f85eb5f70e6da2d64701dd5551f2bc334dbb9add76bfc6a2bea6acf4483",
     19 + "zh:90d32b238113528319d7a5fade97bd8ac9a8b654482fc9056478a43d2e297886",
    17 20   "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
    18  - "zh:a939f94461f91aa3b7ec7096271e2714309bd917fe9a03e02f68afb556d65e0f",
    19  - "zh:b21227b9082e5fafe8b7c415dc6a99c0d82da05492457377a5fe7d4acaed80e2",
    20  - "zh:b8d9f09ed5fc8c654b768b7bee1237eaf1e2287c898249e740695055fb0fe072",
    21  - "zh:d360e1e185b148ff6b1d0ed4f7d574e08f2391697ab43df62085b04a1a5b1284",
    22  - "zh:da962da17ddda744911cb1e92b983fa3874d73a28f3ee72faa9ddb6680a63774",
    23  - "zh:e2f1c4f5ebeb4fd7ef690178168a4c529025b54a91bb7a087dcea48e0b82737a",
     21 + "zh:e6ed3299516a8fb2292af7e7e123d09817dfd8e039aaf35ad5a276f739668e88",
     22 + "zh:eb84fa96c63d836b3b4689835cb7c4487808dfd1ba7ddacf4d8c4c6ff65cdbef",
     23 + "zh:ff97d1498193c99c9c35afd9bfcdce011abf460ec041721727d6e542f7a3bedd",
    24 24   ]
    25 25  }
    26 26   
    skipped 18 lines
    45 45  }
    46 46   
    47 47  provider "registry.terraform.io/hashicorp/http" {
    48  - version = "3.2.1"
    49  - constraints = "~> 3.2.1"
     48 + version = "3.3.0"
     49 + constraints = "~> 3.3.0"
    50 50   hashes = [
    51  - "h1:Q2YQZzEhHQVlkQCQVpMzFVs0Gg+eXzISbOwaOYqpflc=",
    52  - "zh:088b3b3128034485e11dff8da16e857d316fbefeaaf5bef24cceda34c6980641",
    53  - "zh:09ed1f2462ea4590b112e048c4af556f0b6eafc7cf2c75bb2ac21cd87ca59377",
    54  - "zh:39c6b0b4d3f0f65e783c467d3f634e2394820b8aef907fcc24493f21dcf73ca3",
    55  - "zh:47aab45327daecd33158a36c1a36004180a518bf1620cdd5cfc5e1fe77d5a86f",
    56  - "zh:4d70a990aa48116ab6f194eef393082c21cf58bece933b63575c63c1d2b66818",
    57  - "zh:65470c43fda950c7e9ac89417303c470146de984201fff6ef84299ea29e02d30",
     51 + "h1:O2VLKCxxAgaFRPnhRuz/VOsP5HzQdQm9YAi848kvImg=",
     52 + "zh:27d101f4c089d1e367bbbbb3f260fc7d52f63559a4424c08633e566863c951b2",
     53 + "zh:37860671324229f52a7d82eea88a31fe24321297fd699d879de5b6cf6aae086c",
     54 + "zh:4680716579e361298e4331ce0c92e38011fc41ed56bd55302c23b696b3b8c469",
     55 + "zh:547cd2a407ca0d22307634d83ffc64cd4225f221baa09682b7a8c5a2429c34d8",
     56 + "zh:61965698af75aad7482f2f593b75f15e4a4f6f0117b643c69f3da61f40b1a9c7",
    58 57   "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
    59  - "zh:842b4dd63e438f5cd5fdfba1c09b8fdf268e8766e6690988ee24e8b25bfd9e8d",
    60  - "zh:a167a057f7e2d80c78d4b4057538588131fceb983d5c93b07675ad9eb1aa5790",
    61  - "zh:d0ba69b62b6db788cfe3cf8f7dc6e9a0eabe2927dc119d7fe3fe6573ee559e66",
    62  - "zh:e28d24c1d5ff24b1d1cc6f0074a1f41a6974f473f4ff7a37e55c7b6dca68308a",
    63  - "zh:fde8a50554960e5366fd0e1ca330a7c1d24ae6bbb2888137a5c83d83ce14fd18",
     58 + "zh:93f9e0f2244816cbb72197c733ada4214df691e4e6a84b8e340e43e43ab8a383",
     59 + "zh:969aad70624d033c257c365cf75001d29fa7341b48d673cd7317205395b4791b",
     60 + "zh:e9504018b1af992c041bda1e4a6f01db1f1cdb1a7df8055d1082049befbc4217",
     61 + "zh:fa7f6af94e75c6fe21782c622ed387ae08ee3ffeaa0176f08d0b06bb61bb50f4",
     62 + "zh:feda1d7cdae86bce829f82223f625b55c858a36d3aca1a762d7258798a25b476",
     63 + "zh:ff1f3d8c53930aad2fde32d6328df7e7e5b5de36dd7c0682d15518993ab199ef",
    64 64   ]
    65 65  }
    66 66   
    skipped 18 lines
    85 85  }
    86 86   
    87 87  provider "registry.terraform.io/hashicorp/random" {
    88  - version = "3.4.3"
    89  - constraints = "~> 3.0"
     88 + version = "3.5.1"
     89 + constraints = "~> 3.5.1"
    90 90   hashes = [
    91  - "h1:saZR+mhthL0OZl4SyHXZraxyaBNVMxiZzks78nWcZ2o=",
    92  - "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752",
    93  - "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b",
    94  - "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53",
     91 + "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=",
     92 + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64",
     93 + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d",
     94 + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831",
     95 + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3",
     96 + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f",
    95 97   "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
    96  - "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3",
    97  - "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5",
    98  - "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda",
    99  - "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6",
    100  - "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1",
    101  - "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d",
    102  - "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8",
    103  - "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93",
     98 + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b",
     99 + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2",
     100 + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865",
     101 + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03",
     102 + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602",
     103 + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014",
    104 104   ]
    105 105  }
    106 106   
    skipped 40 lines
  • ■ ■ ■ ■ ■ ■
    aws/README.md
    skipped 102 lines
    103 103  | Name | Version |
    104 104  |------|---------|
    105 105  | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.1 |
    106  -| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 4.61.0 |
    107  -| <a name="requirement_http"></a> [http](#requirement\_http) | ~> 3.2.1 |
    108  -| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.4.3 |
     106 +| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 4.65.0 |
     107 +| <a name="requirement_http"></a> [http](#requirement\_http) | ~> 3.3.0 |
     108 +| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.5.1 |
    109 109   
    110 110  ## Providers
    111 111   
    112 112  | Name | Version |
    113 113  |------|---------|
    114  -| <a name="provider_aws"></a> [aws](#provider\_aws) | 4.61.0 |
    115  -| <a name="provider_http"></a> [http](#provider\_http) | 3.2.1 |
    116  -| <a name="provider_random"></a> [random](#provider\_random) | 3.4.3 |
     114 +| <a name="provider_aws"></a> [aws](#provider\_aws) | 4.65.0 |
     115 +| <a name="provider_http"></a> [http](#provider\_http) | 3.3.0 |
     116 +| <a name="provider_random"></a> [random](#provider\_random) | 3.5.1 |
    117 117   
    118 118  ## Modules
    119 119   
    120 120  | Name | Source | Version |
    121 121  |------|--------|---------|
    122 122  | <a name="module_ebs_csi_irsa_role"></a> [ebs\_csi\_irsa\_role](#module\_ebs\_csi\_irsa\_role) | terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks | ~> 5.5 |
    123  -| <a name="module_eks"></a> [eks](#module\_eks) | terraform-aws-modules/eks/aws | 19.12.0 |
    124  -| <a name="module_vpc"></a> [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.19.0 |
     123 +| <a name="module_eks"></a> [eks](#module\_eks) | terraform-aws-modules/eks/aws | 19.13.1 |
     124 +| <a name="module_vpc"></a> [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0.1 |
    125 125   
    126 126  ## Resources
    127 127   
    skipped 43 lines
  • ■ ■ ■ ■ ■
    aws/k8s/secret-challenge-vault-deployment.yml
    skipped 40 lines
    41 41   volumeAttributes:
    42 42   secretProviderClass: "wrongsecrets-aws-secretsmanager"
    43 43   containers:
    44  - - image: jeroenwillemsen/wrongsecrets:1.6.1-k8s-vault
     44 + - image: jeroenwillemsen/wrongsecrets:1.6.4-k8s-vault
    45 45   imagePullPolicy: IfNotPresent
    46 46   name: secret-challenge
    47 47   securityContext:
    skipped 48 lines
    96 96   secretKeyRef:
    97 97   name: funnystuff
    98 98   key: funnier
     99 + - name: CHALLENGE33
     100 + valueFrom:
     101 + secretKeyRef:
     102 + name: challenge33
     103 + key: answer
    99 104   - name: SPRING_CLOUD_VAULT_URI
    100 105   value: "http://vault.vault.svc.cluster.local:8200"
    101 106   - name: JWT_PATH
    skipped 12 lines
  • ■ ■ ■ ■
    aws/k8s-vault-aws-resume.sh
    1 1  #!/bin/bash
    2 2   
    3  -kubectl port-forward vault-0 8200:8200 &
     3 +kubectl port-forward vault-0 -n vault 8200:8200 &
    4 4  kubectl port-forward \
    5 5   $(kubectl get pod -l app=secret-challenge -o jsonpath="{.items[0].metadata.name}") \
    6 6   8080:8080 \
    skipped 2 lines
  • ■ ■ ■ ■ ■
    aws/k8s-vault-aws-start.sh
    skipped 26 lines
    27 27   echo "secrets secret is already installed"
    28 28  else
    29 29   kubectl apply -f ../k8s/secrets-secret.yml
     30 + kubectl apply -f ../k8s/challenge33.yml
    30 31  fi
    31 32   
    32 33  kubectl get sa ebs-csi-controller-sa -n kube-system | grep '1' &>/dev/null
    skipped 40 lines
  • ■ ■ ■ ■ ■ ■
    aws/main.tf
    skipped 36 lines
    37 37   
    38 38  module "vpc" {
    39 39   source = "terraform-aws-modules/vpc/aws"
    40  - version = "~> 3.19.0"
     40 + version = "~> 4.0.1"
    41 41   
    42 42   name = "${var.cluster_name}-vpc"
    43 43   cidr = local.vpc_cidr
    skipped 18 lines
    62 62   
    63 63  module "eks" {
    64 64   source = "terraform-aws-modules/eks/aws"
    65  - version = "19.12.0"
     65 + version = "19.13.1"
    66 66   
    67 67   cluster_name = var.cluster_name
    68 68   cluster_version = var.cluster_version
    skipped 59 lines
  • ■ ■ ■ ■ ■ ■
    aws/versions.tf
    skipped 2 lines
    3 3   
    4 4   required_providers {
    5 5   aws = {
    6  - version = "~> 4.61.0"
     6 + version = "~> 4.65.0"
    7 7   }
    8 8   random = {
    9  - version = "~> 3.4.3"
     9 + version = "~> 3.5.1"
    10 10   }
    11 11   http = {
    12  - version = "~> 3.2.1"
     12 + version = "~> 3.3.0"
    13 13   }
    14 14   }
    15 15  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    azure/.terraform.lock.hcl
    skipped 1 lines
    2 2  # Manual edits may be lost in future updates.
    3 3   
    4 4  provider "registry.terraform.io/hashicorp/azurerm" {
    5  - version = "3.50.0"
    6  - constraints = "~> 3.50.0"
     5 + version = "3.54.0"
     6 + constraints = "~> 3.54.0"
    7 7   hashes = [
    8  - "h1:YtjvYF2CjSbljUs/DebAVkcRqH3xQYf0gzissfKAdCw=",
    9  - "zh:21186ea86e6377195e5267799cf637867a82bc44bdc98f1b3b988897750e5168",
    10  - "zh:2a7b377772249bb75565491c9b0e101ca5a8203e36695ee570abac85f793d8f2",
    11  - "zh:2d28479b0f80b2184013b00c6b2fc2ef714dcae65a8959d77f97a3b16b8ca541",
    12  - "zh:2f8cac9dd5895ef9a28d8c47db98c90b98a54f6ac887f537c8b0402f2ff03c30",
    13  - "zh:3ea8082e1738adb407c2eef67d182a10d7ed7c7ed6f09145ede910db2e7896b5",
    14  - "zh:66b93aa4a1b650c0d1b60601a0df270207e24ec0c571aeb3d1917fa7f85f4f47",
    15  - "zh:7f3c99a2b4dcc8e442c9e5e7ba0fef7e3e87205579d65802118e79cca206fa23",
    16  - "zh:c66f141dabfa1a88b98f4eb1673691339635285cc24993f7a57189a7efe0fc15",
    17  - "zh:d7128539adeb1bd6507d47114992ab2fbf69894a747f8ce2f90390da74f025e7",
    18  - "zh:de15ce973574d0745655f6f12a6dcca1eb5a8a08d7466dc76b90a61a77304f8e",
    19  - "zh:e2f80127aee8927934163d0c933a437daffc7721034ec38e787be3abff1ab845",
     8 + "h1:DVf2gGxq36IH2se0UDtvu1LgcWOn465XxttMR+TYSl0=",
     9 + "zh:0b5c5ffecc73c0f24f6c06c73883153affcb69a0a05376c4a34d71566be7c1e0",
     10 + "zh:0fb685212f914856c01b7182e2669b9b6d7a3cb77578e2d789baaab3e58fc63e",
     11 + "zh:3d938b865b36f4821a97e70dbbf8a22f498fb4e9848012c62e8ff2c1461d0760",
     12 + "zh:42d506add9bba2e96780c0b406f2d6020cf042ffae96faf96988a959766b3644",
     13 + "zh:433b105444462528f3b3e8619a2edbef6dbd5ce0c694f37ab86d91540c1e427a",
     14 + "zh:66c3cffd8b01413914703b1728d378e6d37d3b929e84c2a875ad125b828ce1d4",
     15 + "zh:7cb7187bfcca6f2d4e9d32544b2ca678b65c7c9f520b402d04301152be0c0aa3",
     16 + "zh:accf83e742d7edfe920b62523a190180257cd59180d0ec0435bf09b74a18e0ae",
     17 + "zh:c07b4d169931852007b2ba0ad6aaa24e10e9e3b17731080002ef927fb9912483",
     18 + "zh:e43d945e51228dfb0e1032590fb9409b2b89c40ec8a29cfd2efbd51c536a75a4",
     19 + "zh:ee248b8cf65fa9d3e8d3859cfcdc6940de5806bdc181b1a794085de162cceb7e",
    20 20   "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
    21 21   ]
    22 22  }
    23 23   
    24 24  provider "registry.terraform.io/hashicorp/http" {
    25  - version = "3.2.1"
    26  - constraints = "~> 3.2.1"
     25 + version = "3.3.0"
     26 + constraints = "~> 3.3.0"
    27 27   hashes = [
    28  - "h1:Q2YQZzEhHQVlkQCQVpMzFVs0Gg+eXzISbOwaOYqpflc=",
    29  - "zh:088b3b3128034485e11dff8da16e857d316fbefeaaf5bef24cceda34c6980641",
    30  - "zh:09ed1f2462ea4590b112e048c4af556f0b6eafc7cf2c75bb2ac21cd87ca59377",
    31  - "zh:39c6b0b4d3f0f65e783c467d3f634e2394820b8aef907fcc24493f21dcf73ca3",
    32  - "zh:47aab45327daecd33158a36c1a36004180a518bf1620cdd5cfc5e1fe77d5a86f",
    33  - "zh:4d70a990aa48116ab6f194eef393082c21cf58bece933b63575c63c1d2b66818",
    34  - "zh:65470c43fda950c7e9ac89417303c470146de984201fff6ef84299ea29e02d30",
     28 + "h1:O2VLKCxxAgaFRPnhRuz/VOsP5HzQdQm9YAi848kvImg=",
     29 + "zh:27d101f4c089d1e367bbbbb3f260fc7d52f63559a4424c08633e566863c951b2",
     30 + "zh:37860671324229f52a7d82eea88a31fe24321297fd699d879de5b6cf6aae086c",
     31 + "zh:4680716579e361298e4331ce0c92e38011fc41ed56bd55302c23b696b3b8c469",
     32 + "zh:547cd2a407ca0d22307634d83ffc64cd4225f221baa09682b7a8c5a2429c34d8",
     33 + "zh:61965698af75aad7482f2f593b75f15e4a4f6f0117b643c69f3da61f40b1a9c7",
    35 34   "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
    36  - "zh:842b4dd63e438f5cd5fdfba1c09b8fdf268e8766e6690988ee24e8b25bfd9e8d",
    37  - "zh:a167a057f7e2d80c78d4b4057538588131fceb983d5c93b07675ad9eb1aa5790",
    38  - "zh:d0ba69b62b6db788cfe3cf8f7dc6e9a0eabe2927dc119d7fe3fe6573ee559e66",
    39  - "zh:e28d24c1d5ff24b1d1cc6f0074a1f41a6974f473f4ff7a37e55c7b6dca68308a",
    40  - "zh:fde8a50554960e5366fd0e1ca330a7c1d24ae6bbb2888137a5c83d83ce14fd18",
     35 + "zh:93f9e0f2244816cbb72197c733ada4214df691e4e6a84b8e340e43e43ab8a383",
     36 + "zh:969aad70624d033c257c365cf75001d29fa7341b48d673cd7317205395b4791b",
     37 + "zh:e9504018b1af992c041bda1e4a6f01db1f1cdb1a7df8055d1082049befbc4217",
     38 + "zh:fa7f6af94e75c6fe21782c622ed387ae08ee3ffeaa0176f08d0b06bb61bb50f4",
     39 + "zh:feda1d7cdae86bce829f82223f625b55c858a36d3aca1a762d7258798a25b476",
     40 + "zh:ff1f3d8c53930aad2fde32d6328df7e7e5b5de36dd7c0682d15518993ab199ef",
    41 41   ]
    42 42  }
    43 43   
    44 44  provider "registry.terraform.io/hashicorp/random" {
    45  - version = "3.4.3"
    46  - constraints = "~> 3.4.3"
     45 + version = "3.5.1"
     46 + constraints = "~> 3.5.1"
    47 47   hashes = [
    48  - "h1:saZR+mhthL0OZl4SyHXZraxyaBNVMxiZzks78nWcZ2o=",
    49  - "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752",
    50  - "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b",
    51  - "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53",
     48 + "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=",
     49 + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64",
     50 + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d",
     51 + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831",
     52 + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3",
     53 + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f",
    52 54   "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
    53  - "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3",
    54  - "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5",
    55  - "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda",
    56  - "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6",
    57  - "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1",
    58  - "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d",
    59  - "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8",
    60  - "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93",
     55 + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b",
     56 + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2",
     57 + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865",
     58 + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03",
     59 + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602",
     60 + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014",
    61 61   ]
    62 62  }
    63 63   
  • ■ ■ ■ ■ ■ ■
    azure/README.md
    skipped 97 lines
    98 98  | Name | Version |
    99 99  |------|---------|
    100 100  | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.1 |
    101  -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.50.0 |
    102  -| <a name="requirement_http"></a> [http](#requirement\_http) | ~> 3.2.1 |
    103  -| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.4.3 |
     101 +| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.54.0 |
     102 +| <a name="requirement_http"></a> [http](#requirement\_http) | ~> 3.3.0 |
     103 +| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.5.1 |
    104 104   
    105 105  ## Providers
    106 106   
    107 107  | Name | Version |
    108 108  |------|---------|
    109  -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | 3.50.0 |
    110  -| <a name="provider_http"></a> [http](#provider\_http) | 3.2.1 |
    111  -| <a name="provider_random"></a> [random](#provider\_random) | 3.4.3 |
     109 +| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | 3.54.0 |
     110 +| <a name="provider_http"></a> [http](#provider\_http) | 3.3.0 |
     111 +| <a name="provider_random"></a> [random](#provider\_random) | 3.5.1 |
    112 112   
    113 113  ## Modules
    114 114   
    skipped 50 lines
  • ■ ■ ■ ■ ■
    azure/k8s/secret-challenge-vault-deployment.yml.tpl
    skipped 40 lines
    41 41   volumeAttributes:
    42 42   secretProviderClass: "azure-wrongsecrets-vault"
    43 43   containers:
    44  - - image: jeroenwillemsen/wrongsecrets:1.6.1-k8s-vault
     44 + - image: jeroenwillemsen/wrongsecrets:1.6.4-k8s-vault
    45 45   imagePullPolicy: IfNotPresent
    46 46   name: secret-challenge
    47 47   securityContext:
    skipped 58 lines
    106 106   secretKeyRef:
    107 107   name: funnystuff
    108 108   key: funnier
     109 + - name: CHALLENGE33
     110 + valueFrom:
     111 + secretKeyRef:
     112 + name: challenge33
     113 + key: answer
    109 114   - name: SPRING_CLOUD_VAULT_URI
    110 115   value: "http://vault.vault.svc.cluster.local:8200"
    111 116   - name: JWT_PATH
    skipped 12 lines
  • ■ ■ ■ ■
    azure/k8s-vault-azure-resume.sh
    1 1  #!/bin/bash
    2 2   
    3  -kubectl port-forward vault-0 8200:8200 &
     3 +kubectl port-forward vault-0 -n vault 8200:8200 &
    4 4  kubectl port-forward \
    5 5   $(kubectl get pod -l app=secret-challenge -o jsonpath="{.items[0].metadata.name}") \
    6 6   8080:8080 \
    skipped 2 lines
  • ■ ■ ■ ■ ■
    azure/k8s-vault-azure-start.sh
    skipped 48 lines
    49 49   echo "secrets secret is already installed"
    50 50  else
    51 51   kubectl apply -f ../k8s/secrets-secret.yml
     52 + kubectl apply -f ../k8s/challenge33.yml
    52 53  fi
    53 54   
    54 55  source ../scripts/install-consul.sh
    skipped 53 lines
  • ■ ■ ■ ■ ■ ■
    azure/versions.tf
    skipped 2 lines
    3 3   
    4 4   required_providers {
    5 5   random = {
    6  - version = "~> 3.4.3"
     6 + version = "~> 3.5.1"
    7 7   }
    8 8   azurerm = {
    9  - version = "~> 3.50.0"
     9 + version = "~> 3.54.0"
    10 10   }
    11 11   http = {
    12  - version = "~> 3.2.1"
     12 + version = "~> 3.3.0"
    13 13   }
    14 14   }
    15 15  }
    skipped 1 lines
  • ■ ■ ■ ■ ■
    config/checkstyle/checkstyle.xml
    skipped 241 lines
    242 242   value="Method name ''{0}'' must match pattern ''{1}''."/>
    243 243   </module>
    244 244   <module name="SingleLineJavadoc">
     245 + <property name="ignoredTags" value="@inheritDoc, @see"/>
    245 246   <property name="ignoreInlineTags" value="false"/>
    246 247   </module>
    247 248   <module name="EmptyCatchBlock">
    skipped 6 lines
  • ■ ■ ■ ■ ■ ■
    ctf-instructions.md
    skipped 23 lines
    24 24   
    25 25  Now users can directly use your Wrongsecrets setup together with the CTF-platform to play challenges without having to copy answers and flags twice!
    26 26   
    27  -Note: make sure that you do set `CTF_SERVER_ADDRESS` to point to the address where you are running your CTF-platform (E.g. CTFD/Facebook CTF) and that you set `challenge_acht_ctf_to_provide_to_host_value` to the flag you store in your CTF-platform.
     27 +Note: make sure that you do set `CTF_SERVER_ADDRESS` to point to the address where you are running your CTF-platform (E.g. CTFD/Facebook CTF) and that you set `challenge_acht_ctf_to_provide_to_host_value` and `challenge_thirty_ctf_to_provide_to_host_value` to the flag you store in your CTF-platform.
    28 28   
    29 29  ## Setting up CTFs
    30 30   
    skipped 8 lines
    39 39  Want to make it a little more exciting? Create your own custom Docker image for both the 'play-environment' and the 'CTF-scoring-environment', where you override certain values (e.g. the ARG, the docker ENV, etc.) with your preferred values, so that copying from any existing online solution no longer works!
    40 40  There are a few env-vars that you need to pay attention to when setting this up:
    41 41  - `CTF_SERVER_ADDRESS` in the 'play-environment' to be set to the URL of the 'CTF-scoring-environment' (e.g. your instance of wrongsecrets-ctf.herokuapp.com), and in the 2-domain approach that would be your CTF-platform. Note that in the domain where your users exchange answers for flags for your CTF-platform, you can set it to the URL where your CTF-platform lives.
    42  -- `challenge_acht_ctf_to_provide_to_host_value` needs to be set to a sufficiently long value at the 'play-environment' where your players interact with WrongSecrets to hack around. The value of this entry is returned to the players when they have found the randomly generated value in the logs. If you have the 2-domain approach: make sure that this value is actually the flag-entry for challenge 8 in your CTF-platform, if you have the normal setup, make sure that your 'CTF-scoring-environment' where people provide answers in exchange for flags has the same value stored under `challenge_acht_ctf_host_value`.
     42 +- `challenge_acht_ctf_to_provide_to_host_value` and `challenge_thirty_ctf_to_provide_to_host_value` need to be set to a sufficiently long value at the 'play-environment' where your players interact with WrongSecrets to hack around. The value of this entry is returned to the players when they have found the randomly generated value in the logs. If you have the 2-domain approach: make sure that this value is actually the flag-entry for challenge 8 in your CTF-platform, if you have the normal setup, make sure that your 'CTF-scoring-environment' where people provide answers in exchange for flags has the same value stored under `challenge_acht_ctf_host_value`.
    43 43  - `challenge_acht_ctf_host_value` needs to be set in your 'ctf scoring environment' where players exchange answers for CTF flags to the same value as `challenge_acht_ctf_to_provide_to_host_value` in the environment players play around. Note that this value is not required in a 2-domain approach.
    44 44   
    45 45  ### K8s based CTF
    skipped 24 lines
  • ■ ■ ■ ■ ■ ■
    cypress/README.md
     1 +# Cypress UI Tests
     2 + 
     3 +This project uses [Cypress](https://www.cypress.io/) to run UI tests for the project.
     4 + 
     5 +## How to run the tests
     6 + 
     7 +- Clone this repository and navigate to the project folder.
     8 +- Run `npm install` to install the dependencies.
     9 +- Run `npm run test:open` to launch the Cypress Test Runner.
     10 + 
     11 +## How to interact with elements
     12 + 
     13 +- To select an element in the UI, add a `data-cy` attribute to the HTML element and give it a unique value.
     14 +- To interact with the element in the test, use the `cy.dataCy()` command and pass the value of the `data-cy` attribute as an argument. For example:
     15 + 
     16 +```javascript
     17 +// HTML element
     18 +<h1 th:attr="data-cy='spoiler-title'">Spoiling secret</h1>
     19 + 
     20 +// Cypress test
     21 +cy.dataCy("spoiler-title").click();
     22 +```
     23 + 
     24 +## How the tests work
     25 +- The tests are located in the cypress/e2e folder.
     26 +- The tests loop through all the enabled challenges and check if they meet the expected criteria.
     27 + 
     28 +## When to create new tests
     29 +- A new UI test(s) only needs creating when the UI changes, not with each PR.
     30 +- If a new challenge is added or an existing challenge is modified no changes are needed.
     31 +- If a new UI element is added or an existing element is changed, update the data-cy attributes and the tests accordingly.
     32 + 
  • ■ ■ ■ ■ ■ ■
    cypress/e2e/challenges.cy.js
     1 +import ChallengesPage from '../pages/challengesPage'
     2 +const challengesPage = new ChallengesPage()
     3 + 
     4 +describe('Challenge Tests', () => {
     5 + beforeEach(() => {
     6 + cy.getEnabledChallenges()
     7 + cy.getDisabledChallenges()
     8 + })
     9 + 
     10 + it('Check all enabled challenges display correctly', () => {
     11 + cy.get('@enabledChallengeIds').then((enabledChallengeIds) => {
     12 + cy.wrap(enabledChallengeIds).each((challengeNum) => {
     13 + cy.visit('/')
     14 + challengesPage.selectChallenge(challengeNum)
     15 + challengesPage.assertEnabledChallengePage(challengeNum)
     16 + })
     17 + })
     18 + })
     19 + 
     20 + it('Check all disabled challenges display correctly', () => {
     21 + cy.get('@disabledChallengeIds').then((disabledChallengeIds) => {
     22 + cy.wrap(disabledChallengeIds).each((challengeNum) => {
     23 + cy.visit(`/challenge/${challengeNum}`)
     24 + challengesPage.assertDisabledChallengePage(challengeNum)
     25 + })
     26 + })
     27 + })
     28 + 
     29 + it('Check all hints display correctly', () => {
     30 + cy.get('@enabledChallengeIds').then((enabledChallengeIds) => {
     31 + cy.wrap(enabledChallengeIds).each((challengeNum) => {
     32 + cy.visit(`/challenge/${challengeNum}`)
     33 + cy.dataCy(ChallengesPage.SHOW_HINTS_BTN).click()
     34 + cy.dataCy(ChallengesPage.HINT_PARAGRAPH).should('be.visible')
     35 + })
     36 + })
     37 + })
     38 + 
     39 + it('Check whats wrong section display correctly', () => {
     40 + cy.get('@enabledChallengeIds').then((enabledChallengeIds) => {
     41 + cy.wrap(enabledChallengeIds).each((challengeNum) => {
     42 + cy.visit(`/challenge/${challengeNum}`)
     43 + cy.dataCy(ChallengesPage.WHATS_WRONG_BTN).click()
     44 + cy.dataCy(ChallengesPage.WHATS_WRONG_PARAGRAPH).should('be.visible')
     45 + })
     46 + })
     47 + })
     48 + 
     49 + it('Check reset button clears page', () => {
     50 + cy.get('@enabledChallengeIds').then((enabledChallengeIds) => {
     51 + cy.wrap(enabledChallengeIds).each((challengeNum) => {
     52 + cy.visit(`/challenge/${challengeNum}`)
     53 + cy.dataCy(ChallengesPage.WHATS_WRONG_BTN).click()
     54 + cy.dataCy(ChallengesPage.SHOW_HINTS_BTN).click()
     55 + cy.dataCy(ChallengesPage.RESET_BTN).click()
     56 + cy.dataCy(ChallengesPage.WHATS_WRONG_PARAGRAPH).should('not.be.visible')
     57 + cy.dataCy(ChallengesPage.HINT_PARAGRAPH).should('not.be.visible')
     58 + })
     59 + })
     60 + })
     61 + 
     62 + it('Clear button clears answer box', () => {
     63 + cy.get('@enabledChallengeIds').then((enabledChallengeIds) => {
     64 + cy.wrap(enabledChallengeIds).each((challengeNum) => {
     65 + cy.visit(`/challenge/${challengeNum}`)
     66 + cy.dataCy(ChallengesPage.ANSWER_TEXTBOX).type('Tst')
     67 + cy.dataCy(ChallengesPage.CLEAR_TEXTBOX_BTN).click()
     68 + cy.dataCy(ChallengesPage.ANSWER_TEXTBOX).should('be.empty')
     69 + })
     70 + })
     71 + })
     72 + 
     73 + it('Submitting wrong answer gives warning', () => {
     74 + cy.get('@enabledChallengeIds').then((enabledChallengeIds) => {
     75 + cy.wrap(enabledChallengeIds).each((challengeNum) => {
     76 + cy.visit(`/challenge/${challengeNum}`)
     77 + cy.dataCy(ChallengesPage.ANSWER_TEXTBOX).type('X')
     78 + cy.dataCy(ChallengesPage.SUBMIT_TEXTBOX_BTN).click()
     79 + cy.dataCy(ChallengesPage.INCORRECT_ALERT).should('contain', 'Your answer is incorrect, try harder ;-)')
     80 + })
     81 + })
     82 + })
     83 + 
     84 + it('Submitting right answer gives success notification and progress bar', () => {
     85 + cy.visit('/challenge/0')
     86 + cy.dataCy(ChallengesPage.ANSWER_TEXTBOX).type('The first answer')
     87 + cy.dataCy(ChallengesPage.SUBMIT_TEXTBOX_BTN).click()
     88 + cy.dataCy(ChallengesPage.SUCCESS_ALERT).should('contain', 'Your answer is correct!')
     89 + cy.dataCy(ChallengesPage.PROGRESS_BAR).should('be.visible').should('not.have.attr', 'aria-valuenow', '0')
     90 + })
     91 +})
     92 + 
  • ■ ■ ■ ■ ■ ■
    cypress/e2e/spoilers.cy.js
     1 +import SpoilersPage from '../pages/spoilersPage'
     2 + 
     3 +describe('Spoiler Tests', () => {
     4 + beforeEach(() => {
     5 + cy.getAllChallenges()
     6 + })
     7 + 
     8 + it('Check all spoiler pages display correctly (e.g. have a title and some data)', () => {
     9 + cy.get('@allChallengeIds').then((allChallengeIds) => {
     10 + cy.wrap(allChallengeIds).each((challengeNum) => {
     11 + cy.visit(`/spoil-${challengeNum}`)
     12 + cy.dataCy(SpoilersPage.SPOILER_TITLE).should('be.visible')
     13 + cy.dataCy(SpoilersPage.SPOILER_TITLE).should('not.be.empty')
     14 + cy.dataCy(SpoilersPage.SPOILER_ANSWER).should('be.visible')
     15 + cy.dataCy(SpoilersPage.SPOILER_ANSWER).should('not.be.empty')
     16 + })
     17 + })
     18 + })
     19 +})
     20 + 
  • ■ ■ ■ ■ ■ ■
    cypress/e2e/themeSwitch.cy.js
     1 +import ThemeSwitchPage from '../pages/themeSwitchPage'
     2 + 
     3 +describe('Theme Switching Tests', () => {
     4 + it('A user can switch the theme to dark on each page', () => {
     5 + cy.wrap(['', 'challenge/0', 'stats', 'about']).each((endpoint) => {
     6 + cy.visit(`/${endpoint}`)
     7 + cy.dataCy(ThemeSwitchPage.DARK_MODE_RADIO).click()
     8 + cy.get(ThemeSwitchPage.DARK_MODE).should('exist')
     9 + })
     10 + })
     11 + 
     12 + it('Dark mode persists on each page', () => {
     13 + cy.visit('/')
     14 + cy.dataCy(ThemeSwitchPage.DARK_MODE_RADIO).click()
     15 + cy.wrap(['', 'challenge/0', 'stats', 'about']).each((endpoint) => {
     16 + cy.visit(`/${endpoint}`)
     17 + cy.get(ThemeSwitchPage.DARK_MODE).should('exist')
     18 + })
     19 + })
     20 + 
     21 + it('A user can switch the theme to light on each page', () => {
     22 + cy.wrap(['', 'challenge/0', 'stats', 'about']).each((endpoint) => {
     23 + cy.visit(`/${endpoint}`)
     24 + cy.dataCy(ThemeSwitchPage.LIGHT_MODE_RADIO).click()
     25 + cy.get(ThemeSwitchPage.DARK_MODE).should('not.exist')
     26 + })
     27 + })
     28 + 
     29 + it('Light mode persists on each page', () => {
     30 + cy.visit('/')
     31 + cy.dataCy(ThemeSwitchPage.LIGHT_MODE_RADIO).click()
     32 + cy.wrap(['', 'challenge/0', 'stats', 'about']).each((endpoint) => {
     33 + cy.visit(`/${endpoint}`)
     34 + cy.get(ThemeSwitchPage.DARK_MODE).should('not.exist')
     35 + })
     36 + })
     37 + 
     38 + it('A user can switch theme to dark and back to light on each page', () => {
     39 + cy.wrap(['', 'challenge/0', 'stats', 'about']).each((endpoint) => {
     40 + cy.visit(`/${endpoint}`)
     41 + cy.dataCy(ThemeSwitchPage.DARK_MODE_RADIO).click()
     42 + cy.dataCy(ThemeSwitchPage.LIGHT_MODE_RADIO).click()
     43 + cy.get(ThemeSwitchPage.DARK_MODE).should('not.exist')
     44 + })
     45 + })
     46 +})
     47 + 
  • ■ ■ ■ ■ ■ ■
    cypress/pages/challengesPage.js
     1 +export default class ChallengesPage {
     2 + static CHALLENGE_TITLE = 'challenge-title'
     3 + static CHALLENGE_DESCRIPTION = 'challenge-description'
     4 + static SHOW_HINTS_BTN = 'show-hints-btn'
     5 + static HINT_PARAGRAPH = 'hint-paragraph'
     6 + static WHATS_WRONG_BTN = 'whats-wrong-btn'
     7 + static WHATS_WRONG_PARAGRAPH = 'whats-wrong-paragraph'
     8 + static RESET_BTN = 'reset-btn'
     9 + static ANSWER_TEXTBOX = 'answer-textbox'
     10 + static CLEAR_TEXTBOX_BTN = 'clear-textbox-btn'
     11 + static SUBMIT_TEXTBOX_BTN = 'submit-textbox-btn'
     12 + static INCORRECT_ALERT = 'incorrect-alert'
     13 + static SUCCESS_ALERT = 'success-alert'
     14 + static DISABLED_alert = 'disabled-alert'
     15 + static PROGRESS_BAR = 'progress-bar'
     16 + 
     17 + assertEnabledChallengePage (challengeNum) {
     18 + cy.url().should('contain', `challenge/${challengeNum}`)
     19 + cy.dataCy(ChallengesPage.CHALLENGE_TITLE).should('be.visible')
     20 + cy.dataCy(ChallengesPage.CHALLENGE_DESCRIPTION).should('be.visible')
     21 + cy.dataCy(ChallengesPage.SHOW_HINTS_BTN).should('be.visible')
     22 + cy.dataCy(ChallengesPage.WHATS_WRONG_BTN).should('be.visible')
     23 + cy.dataCy(ChallengesPage.RESET_BTN).should('be.visible')
     24 + cy.dataCy(ChallengesPage.SUBMIT_TEXTBOX_BTN).should('be.visible')
     25 + cy.dataCy(ChallengesPage.CLEAR_TEXTBOX_BTN).should('be.visible')
     26 + }
     27 + 
     28 + assertDisabledChallengePage (challengeNum) {
     29 + cy.url().should('contain', `challenge/${challengeNum}`)
     30 + cy.dataCy(ChallengesPage.CHALLENGE_TITLE).should('be.visible')
     31 + cy.dataCy(ChallengesPage.CHALLENGE_DESCRIPTION).should('be.visible')
     32 + cy.dataCy(ChallengesPage.SHOW_HINTS_BTN).should('be.visible')
     33 + cy.dataCy(ChallengesPage.WHATS_WRONG_BTN).should('be.visible')
     34 + cy.dataCy(ChallengesPage.RESET_BTN).should('be.visible')
     35 + cy.dataCy(ChallengesPage.SUBMIT_TEXTBOX_BTN).should('be.visible')
     36 + cy.dataCy(ChallengesPage.CLEAR_TEXTBOX_BTN).should('be.visible')
     37 + cy.dataCy(ChallengesPage.INCORRECT_ALERT).should('be.visible')
     38 + cy.dataCy(ChallengesPage.DISABLED_alert).should('be.visible')
     39 + }
     40 + 
     41 + selectChallenge (challengeNum) {
     42 + cy.get('.form-select').select('All')
     43 + cy.dataCy(`"challenge ${challengeNum}-link"`).click()
     44 + }
     45 +}
     46 + 
  • ■ ■ ■ ■ ■
    cypress/pages/spoilersPage.js
     1 +export default class SpoilersPage {
     2 + static SPOILER_TITLE = 'spoiler-title'
     3 + static SPOILER_ANSWER = 'spoiler-answer'
     4 +}
     5 + 
  • ■ ■ ■ ■ ■ ■
    cypress/pages/themeSwitchPage.js
     1 +export default class ChallengesPage {
     2 + static DARK_MODE_RADIO = 'dark-mode-radio'
     3 + static LIGHT_MODE_RADIO = 'light-mode-radio'
     4 + static DARK_MODE = '.dark-mode'
     5 +}
     6 + 
  • ■ ■ ■ ■ ■ ■
    cypress/support/commands.js
     1 +Cypress.Commands.add('dataCy', (value) => {
     2 + return cy.get(`[data-cy=${value}]`)
     3 +})
     4 + 
     5 +Cypress.Commands.add('getEnabledChallenges', () => {
     6 + cy.request('/api/challenges').then((response) => {
     7 + const numChallenges = response.body.data.length
     8 + const enabledChallengeIds = []
     9 + for (let i = 0; i < numChallenges; i++) {
     10 + if (response.body.data[i].disabledEnv != null) {
     11 + enabledChallengeIds.push(response.body.data[i].id - 1)
     12 + }
     13 + }
     14 + cy.wrap(enabledChallengeIds).as('enabledChallengeIds')
     15 + })
     16 +})
     17 + 
     18 +Cypress.Commands.add('getDisabledChallenges', () => {
     19 + cy.request('/api/challenges').then((response) => {
     20 + const numChallenges = response.body.data.length
     21 + const disabledChallengeIds = []
     22 + for (let i = 0; i < numChallenges; i++) {
     23 + if (response.body.data[i].disabledEnv === null) {
     24 + disabledChallengeIds.push(response.body.data[i].id - 1)
     25 + }
     26 + }
     27 + cy.wrap(disabledChallengeIds).as('disabledChallengeIds')
     28 + })
     29 +})
     30 + 
     31 +Cypress.Commands.add('getAllChallenges', () => {
     32 + cy.request('/api/challenges').then((response) => {
     33 + const numChallenges = response.body.data.length
     34 + const allChallengeIds = Array.from({ length: numChallenges }, (v, i) => i)
     35 + cy.wrap(allChallengeIds).as('allChallengeIds')
     36 + })
     37 +})
     38 + 
  • ■ ■ ■ ■ ■ ■
    cypress/support/e2e.js
     1 +// ***********************************************************
     2 +// This example support/e2e.js is processed and
     3 +// loaded automatically before your test files.
     4 +//
     5 +// This is a great place to put global configuration and
     6 +// behavior that modifies Cypress.
     7 +//
     8 +// You can change the location of this file or turn off
     9 +// automatically serving support files with the
     10 +// 'supportFile' configuration option.
     11 +//
     12 +// You can read more here:
     13 +// https://on.cypress.io/configuration
     14 +// ***********************************************************
     15 + 
     16 +// Import commands.js using ES2015 syntax:
     17 +import './commands'
     18 + 
     19 +// Alternatively you can use CommonJS syntax:
     20 +// require('./commands')
     21 + 
  • ■ ■ ■ ■ ■ ■
    cypress.config.js
     1 +const { defineConfig } = require('cypress')
     2 + 
     3 +module.exports = defineConfig({
     4 + video: false,
     5 + e2e: {
     6 + baseUrl: 'http://localhost:8080',
     7 + setupNodeEvents (on, config) {
     8 + // implement node event listeners here
     9 + }
     10 + }
     11 +})
     12 + 
  • ■ ■ ■ ■
    fly.toml
    skipped 8 lines
    9 9   dockerfile = "Dockerfile"
    10 10   
    11 11  [build.args]
    12  - argBasedVersion="1.6.1"
     12 + argBasedVersion="1.6.4"
    13 13   spring_profile="without-vault"
    14 14   springdoc_api-docs_enabled="false"
    15 15   springdoc_swagger-ui_enabled="false"
    skipped 35 lines
  • ■ ■ ■ ■ ■ ■
    gcp/.terraform.lock.hcl
    skipped 1 lines
    2 2  # Manual edits may be lost in future updates.
    3 3   
    4 4  provider "registry.terraform.io/hashicorp/google" {
    5  - version = "4.59.0"
    6  - constraints = "~> 4.59.0"
     5 + version = "4.63.1"
     6 + constraints = "~> 4.63.1"
    7 7   hashes = [
    8  - "h1:FnhQjRf+Tt0SagED79ryejOd0lqQ42zIbWD2Z9xCNVo=",
    9  - "zh:057042a29992ee5bddb8b0785ebba5c1455112602760bd88dca2ccab66de714c",
    10  - "zh:21a0e30a76a9e3e375a374ecd2e82d3240b32913f54017c7b8eca7165ffb2e27",
    11  - "zh:26cdc960455b335590c5473593d66eddbdb9c60709f416327092a9b4ba8c8b70",
    12  - "zh:2d8ffb7c150adb43d58fd0057b9a38e9e0435382bb870bf6fe3f13717828a34b",
    13  - "zh:4c1156babfaffcbb5e91b8a82710a4a33119be416aaee1b85fe5f45162ac37e2",
    14  - "zh:54de19d1d40fdfa2f9804b64355cac6e6de1bdcdcd193317dd2e24f923cd3007",
    15  - "zh:9f029f0478458d39cd7255abf8ce32d33111bc6f1ca822718e66344bea61522d",
    16  - "zh:ac5b8867769921f56e95c95332c6167b73c6b6275f158b762b01a0a8013b67a8",
    17  - "zh:d010b2b8b0d547fb712c2cc3e0f816c001783fcac072191a3e8ed5e22f826951",
    18  - "zh:d4e6b5f5aa78b16761c9b47534b631c2d1b6d6ec01f97a15db84dec20be3b8b2",
     8 + "h1:AJ0z+BKwPr0rEBmfRiIt3RhFKDlCZrlc/mVGPUdsJTw=",
     9 + "zh:0c8e024715dfe8bb4837059fc1a32369bf83f445129ebd3511591650eb9b3961",
     10 + "zh:3ca839141b59d670cc04a3f918fbbb3c6c95eeb8215bbb4214d1a2a57d1f6f7d",
     11 + "zh:3f68f83aaeecaf05f1066d0c7ca23ebc959a1ed10c57fd9f4d958b6b8d38fcc2",
     12 + "zh:3f84b372468f7768a7ca4775227afd105075670649474ba6524ea028175d5e0c",
     13 + "zh:60a016a6d4bd6a8f96ffdef5f9bd37863a8124056c39dbaf282c9713ceac06e8",
     14 + "zh:66e1fe61b78d7f35b5e1ed0d150dfa4997f32c877627c573b51735ab0c794d8e",
     15 + "zh:6d4b832f2147dae47da68d80a7d7cd66cb799205ed6b476ae490b2e2c3087d49",
     16 + "zh:bdf6555b6106ee5b597aa5e2ffed25d442f0e9ded1b531b0864c7d70d6b40c8b",
     17 + "zh:c2095125ce9f9627091fc673a3ca673c66caa288e38970ae585869c89cd5946d",
     18 + "zh:d43feedc9f6e0a49d208e4bac355ca0e843038c8f87cb8d3bb2355830d6e8dce",
    19 19   "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
    20  - "zh:fefbd0cd4cb94ecee0c6d99f079b6cbb56b86cd63609b72395c4f1fa7c2addcb",
     20 + "zh:f80bf9c3bef00ba4738d46c7e6170d1eb7f49e20a081acffa33a39035df86326",
    21 21   ]
    22 22  }
    23 23   
    24 24  provider "registry.terraform.io/hashicorp/google-beta" {
    25  - version = "4.59.0"
    26  - constraints = "~> 4.59.0"
     25 + version = "4.63.1"
     26 + constraints = "~> 4.63.1"
    27 27   hashes = [
    28  - "h1:RBM/zddsgsV/kpFSB5V0aN0r5VAT+bRowuIshGsDRvM=",
    29  - "zh:054abdeb3bb42d65719e66bfc8d601598e714480e85264560e9bb5459deb51e1",
    30  - "zh:24aeff4603a11ae5dcfde4eeb5033a8ecb3309530080a67e728860e98d88086f",
    31  - "zh:3999229a21f3ef40aaf903411c90b67f4aec1f739412a10357e4b6ac65a94e3f",
    32  - "zh:3ca10887496f4b4146054f7ed39676618255d0ea51d865e0408eed6a3e47e798",
    33  - "zh:6aade27f5203669e14f2a877a4c88cb71979e855b18535f1a52aefff7be74573",
    34  - "zh:a1467522aad1f4108da59bba01e9253413ae6f846fcd73fd2ac835117864b98e",
    35  - "zh:b146595c4951edc9ecad3cc52aba5dd16c2330d0e76cee75328cf1497f44e1f4",
    36  - "zh:b79f5266ab0cc05288e8bc7f2fae1b0400a8f6246fc81782ea5e62ff9d31eccc",
    37  - "zh:ca1448360baa6f9a2ea73089ad5d5dbea72bc13d2d1af1a48519134733a3091d",
    38  - "zh:d55fe5c8edf27483d124ab1a3a8a689e433497856b2ed7ad32e1ff914836f1c3",
    39  - "zh:e05347a2db0bcc95bb3564d487a24dfd51d5edb5ab63ee8d40b9e67453d0b2b4",
     28 + "h1:pqqFiQv23kiEbVk0OKRoT0+gxLZGsB28BUih+DZu/YM=",
     29 + "zh:03677a3781a5b4a49c64fc2cbf46912c8b1c13b95a36297e799df1b720871c87",
     30 + "zh:0a646edbb433bdda0dccc84af60b3e3460fe5d71341001ec8574cfeeef3948ac",
     31 + "zh:2cce8e374d4a4ca046b1abbbe5fe50090f731cf69783b3333bb3594e7e7ff340",
     32 + "zh:655a3e0805c125da33f60e0a0de5c0efaeab42a97e45a0c4bbcaaad1b38f6e09",
     33 + "zh:6d7470e4bf1ffc9915371a3aec01b6fd187c267eddd34378fda9f8793d7ae49a",
     34 + "zh:734a18e973a551b293e650806b77dfbca3c9a8c45aa087a1a34d6be5d4a5fe50",
     35 + "zh:97a6512247144c9ebe47bb189a831293c815bff0eb91d9a8b5c3240041ee794a",
     36 + "zh:a5f9a4290d0a0f6988c0139e64a9194ea96d711657669d5f52d18dd4738f2bce",
     37 + "zh:ace0af5e78bc5a829eefddbe32c360a5989128f8c999e30036961f692055ea61",
     38 + "zh:b01839915cd0da6522076e54656a1fe01eac72013f2567c3994b865714a973be",
     39 + "zh:bd68231046fe56b1cec25a51c484df6327bb7856638cf5e16dd2e686764ddc87",
    40 40   "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
    41 41   ]
    42 42  }
    43 43   
    44 44  provider "registry.terraform.io/hashicorp/http" {
    45  - version = "3.2.1"
    46  - constraints = "~> 3.2.1"
     45 + version = "3.3.0"
     46 + constraints = "~> 3.3.0"
    47 47   hashes = [
    48  - "h1:Q2YQZzEhHQVlkQCQVpMzFVs0Gg+eXzISbOwaOYqpflc=",
    49  - "zh:088b3b3128034485e11dff8da16e857d316fbefeaaf5bef24cceda34c6980641",
    50  - "zh:09ed1f2462ea4590b112e048c4af556f0b6eafc7cf2c75bb2ac21cd87ca59377",
    51  - "zh:39c6b0b4d3f0f65e783c467d3f634e2394820b8aef907fcc24493f21dcf73ca3",
    52  - "zh:47aab45327daecd33158a36c1a36004180a518bf1620cdd5cfc5e1fe77d5a86f",
    53  - "zh:4d70a990aa48116ab6f194eef393082c21cf58bece933b63575c63c1d2b66818",
    54  - "zh:65470c43fda950c7e9ac89417303c470146de984201fff6ef84299ea29e02d30",
     48 + "h1:O2VLKCxxAgaFRPnhRuz/VOsP5HzQdQm9YAi848kvImg=",
     49 + "zh:27d101f4c089d1e367bbbbb3f260fc7d52f63559a4424c08633e566863c951b2",
     50 + "zh:37860671324229f52a7d82eea88a31fe24321297fd699d879de5b6cf6aae086c",
     51 + "zh:4680716579e361298e4331ce0c92e38011fc41ed56bd55302c23b696b3b8c469",
     52 + "zh:547cd2a407ca0d22307634d83ffc64cd4225f221baa09682b7a8c5a2429c34d8",
     53 + "zh:61965698af75aad7482f2f593b75f15e4a4f6f0117b643c69f3da61f40b1a9c7",
    55 54   "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
    56  - "zh:842b4dd63e438f5cd5fdfba1c09b8fdf268e8766e6690988ee24e8b25bfd9e8d",
    57  - "zh:a167a057f7e2d80c78d4b4057538588131fceb983d5c93b07675ad9eb1aa5790",
    58  - "zh:d0ba69b62b6db788cfe3cf8f7dc6e9a0eabe2927dc119d7fe3fe6573ee559e66",
    59  - "zh:e28d24c1d5ff24b1d1cc6f0074a1f41a6974f473f4ff7a37e55c7b6dca68308a",
    60  - "zh:fde8a50554960e5366fd0e1ca330a7c1d24ae6bbb2888137a5c83d83ce14fd18",
     55 + "zh:93f9e0f2244816cbb72197c733ada4214df691e4e6a84b8e340e43e43ab8a383",
     56 + "zh:969aad70624d033c257c365cf75001d29fa7341b48d673cd7317205395b4791b",
     57 + "zh:e9504018b1af992c041bda1e4a6f01db1f1cdb1a7df8055d1082049befbc4217",
     58 + "zh:fa7f6af94e75c6fe21782c622ed387ae08ee3ffeaa0176f08d0b06bb61bb50f4",
     59 + "zh:feda1d7cdae86bce829f82223f625b55c858a36d3aca1a762d7258798a25b476",
     60 + "zh:ff1f3d8c53930aad2fde32d6328df7e7e5b5de36dd7c0682d15518993ab199ef",
    61 61   ]
    62 62  }
    63 63   
    64 64  provider "registry.terraform.io/hashicorp/random" {
    65  - version = "3.4.3"
    66  - constraints = "~> 3.4.3"
     65 + version = "3.5.1"
     66 + constraints = "~> 3.5.1"
    67 67   hashes = [
    68  - "h1:saZR+mhthL0OZl4SyHXZraxyaBNVMxiZzks78nWcZ2o=",
    69  - "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752",
    70  - "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b",
    71  - "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53",
     68 + "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=",
     69 + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64",
     70 + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d",
     71 + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831",
     72 + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3",
     73 + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f",
    72 74   "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
    73  - "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3",
    74  - "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5",
    75  - "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda",
    76  - "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6",
    77  - "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1",
    78  - "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d",
    79  - "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8",
    80  - "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93",
     75 + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b",
     76 + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2",
     77 + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865",
     78 + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03",
     79 + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602",
     80 + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014",
    81 81   ]
    82 82  }
    83 83   
  • ■ ■ ■ ■ ■ ■
    gcp/README.md
    skipped 95 lines
    96 96  | Name | Version |
    97 97  |------|---------|
    98 98  | <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.1 |
    99  -| <a name="requirement_google"></a> [google](#requirement\_google) | ~> 4.59.0 |
    100  -| <a name="requirement_google-beta"></a> [google-beta](#requirement\_google-beta) | ~> 4.59.0 |
    101  -| <a name="requirement_http"></a> [http](#requirement\_http) | ~> 3.2.1 |
    102  -| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.4.3 |
     99 +| <a name="requirement_google"></a> [google](#requirement\_google) | ~> 4.63.1 |
     100 +| <a name="requirement_google-beta"></a> [google-beta](#requirement\_google-beta) | ~> 4.63.1 |
     101 +| <a name="requirement_http"></a> [http](#requirement\_http) | ~> 3.3.0 |
     102 +| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.5.1 |
    103 103   
    104 104  ## Providers
    105 105   
    106 106  | Name | Version |
    107 107  |------|---------|
    108  -| <a name="provider_google"></a> [google](#provider\_google) | 4.59.0 |
    109  -| <a name="provider_google-beta"></a> [google-beta](#provider\_google-beta) | 4.59.0 |
    110  -| <a name="provider_http"></a> [http](#provider\_http) | 3.2.1 |
    111  -| <a name="provider_random"></a> [random](#provider\_random) | 3.4.3 |
     108 +| <a name="provider_google"></a> [google](#provider\_google) | 4.63.1 |
     109 +| <a name="provider_google-beta"></a> [google-beta](#provider\_google-beta) | 4.63.1 |
     110 +| <a name="provider_http"></a> [http](#provider\_http) | 3.3.0 |
     111 +| <a name="provider_random"></a> [random](#provider\_random) | 3.5.1 |
    112 112   
    113 113  ## Modules
    114 114   
    skipped 47 lines
  • ■ ■ ■ ■ ■
    gcp/k8s/secret-challenge-vault-deployment.yml.tpl
    skipped 38 lines
    39 39   volumeAttributes:
    40 40   secretProviderClass: "wrongsecrets-gcp-secretsmanager"
    41 41   containers:
    42  - - image: jeroenwillemsen/wrongsecrets:1.6.1-k8s-vault
     42 + - image: jeroenwillemsen/wrongsecrets:1.6.4-k8s-vault
    43 43   imagePullPolicy: IfNotPresent
    44 44   name: secret-challenge
    45 45   ports:
    skipped 47 lines
    93 93   configMapKeyRef:
    94 94   name: secrets-file
    95 95   key: funny.entry
     96 + - name: CHALLENGE33
     97 + valueFrom:
     98 + secretKeyRef:
     99 + name: challenge33
     100 + key: answer
    96 101   - name: SPECIAL_SPECIAL_K8S_SECRET
    97 102   valueFrom:
    98 103   secretKeyRef:
    skipped 17 lines
  • ■ ■ ■ ■
    gcp/k8s-vault-gcp-ingress-start.sh
    skipped 49 lines
    50 50  while [[ $isvaultrunning != *"vault-2"* ]]; do echo "waiting for Vaul2" && sleep 2 && isvaultrunning=$(kubectl get pods --field-selector=status.phase=Running); done
    51 51   
    52 52  echo "Setting up port forwarding"
    53  -kubectl port-forward vault-0 8200:8200 &
     53 +kubectl port-forward vault-0 -n vault 8200:8200 &
    54 54  echo "Unsealing Vault"
    55 55  kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json >cluster-keys.json
    56 56  cat cluster-keys.json | jq -r ".unseal_keys_b64[]"
    skipped 103 lines
  • ■ ■ ■ ■
    gcp/k8s-vault-gcp-resume.sh
    1 1  #!/bin/bash
    2 2   
    3  -kubectl port-forward vault-0 8200:8200 &
     3 +kubectl port-forward vault-0 -n vault 8200:8200 &
    4 4  kubectl port-forward \
    5 5   $(kubectl get pod -l app=secret-challenge -o jsonpath="{.items[0].metadata.name}") \
    6 6   8080:8080 \
    skipped 2 lines
  • ■ ■ ■ ■ ■
    gcp/k8s-vault-gcp-start.sh
    skipped 27 lines
    28 28   echo "secrets secret is already installed"
    29 29  else
    30 30   kubectl apply -f ../k8s/secrets-secret.yml
     31 + kubectl apply -f ../k8s/challenge33.yml
    31 32  fi
    32 33   
    33 34  source ../scripts/install-consul.sh
    skipped 44 lines
  • ■ ■ ■ ■ ■ ■
    gcp/versions.tf
    skipped 2 lines
    3 3   required_providers {
    4 4   google = {
    5 5   source = "hashicorp/google"
    6  - version = "~> 4.59.0"
     6 + version = "~> 4.63.1"
    7 7   }
    8 8   google-beta = {
    9 9   source = "hashicorp/google-beta"
    10  - version = "~> 4.59.0"
     10 + version = "~> 4.63.1"
    11 11   }
    12 12   random = {
    13  - version = "~> 3.4.3"
     13 + version = "~> 3.5.1"
    14 14   }
    15 15   http = {
    16  - version = "~> 3.2.1"
     16 + version = "~> 3.3.0"
    17 17   }
    18 18   }
    19 19  }
    skipped 1 lines
  • images/open-settings-4.4.png
  • ■ ■ ■ ■ ■ ■
    js/package-lock.json
    skipped 8 lines
    9 9   "version": "1.3.1",
    10 10   "license": "MIT",
    11 11   "dependencies": {
    12  - "minimatch": ">=8.0.3"
     12 + "minimatch": ">=9.0.0"
    13 13   },
    14 14   "devDependencies": {
    15 15   "javascript-obfuscator": "^4.0.2",
    16  - "minimatch": ">=8.0.3"
     16 + "minimatch": ">=9.0.0"
    17 17   }
    18 18   },
    19 19   "node_modules/@javascript-obfuscator/escodegen": {
    skipped 820 lines
    840 840   }
    841 841   },
    842 842   "node_modules/minimatch": {
    843  - "version": "8.0.3",
    844  - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.3.tgz",
    845  - "integrity": "sha512-tEEvU9TkZgnFDCtpnrEYnPsjT7iUx42aXfs4bzmQ5sMA09/6hZY0jeZcGkXyDagiBOvkUjNo8Viom+Me6+2x7g==",
     843 + "version": "9.0.0",
     844 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
     845 + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
    846 846   "dev": true,
    847 847   "dependencies": {
    848 848   "brace-expansion": "^2.0.1"
    skipped 969 lines
    1818 1818   }
    1819 1819   },
    1820 1820   "minimatch": {
    1821  - "version": "8.0.3",
    1822  - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.3.tgz",
    1823  - "integrity": "sha512-tEEvU9TkZgnFDCtpnrEYnPsjT7iUx42aXfs4bzmQ5sMA09/6hZY0jeZcGkXyDagiBOvkUjNo8Viom+Me6+2x7g==",
     1821 + "version": "9.0.0",
     1822 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
     1823 + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
    1824 1824   "dev": true,
    1825 1825   "requires": {
    1826 1826   "brace-expansion": "^2.0.1"
    skipped 274 lines
  • ■ ■ ■ ■ ■ ■
    js/package.json
    skipped 11 lines
    12 12   "author": "",
    13 13   "license": "MIT",
    14 14   "dependencies": {
    15  - "minimatch": ">=8.0.3"
     15 + "minimatch": ">=9.0.0"
    16 16   },
    17 17   "devDependencies": {
    18  - "minimatch": ">=8.0.3",
     18 + "minimatch": ">=9.0.0",
    19 19   "javascript-obfuscator": "^4.0.2"
    20 20   }
    21 21  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    jsconfig.json
     1 +{
     2 + "include": ["./node_modules/cypress", "cypress/**/*.js"]
     3 +}
     4 + 
  • ■ ■ ■ ■ ■ ■
    k8s/challenge33.yml
     1 +apiVersion: v1
     2 +data:
     3 + answer: VBUGh3wu/3I1naHj1Uf97Y0Lq8B5/92q1jwp3/aYSwHSJI8WqdZnYLj78hESlfPPKf1ZKPap4z2+r+G9NRwdFU/YBMTY3cNguMm5C6l2pTK9JhPFnUzerIwMrnhu9GjrqSFn/BtOvLnQa/mSgXDNJYUOU8gCHFs9JEeQv9hpWpyxlB2Nqu0MHrPNODY3ZohhkjWXaxbjCZi9SpmHydU06Z7LqWyF39G6V8CF6LBPkdUn3aJAV++F0Q9IcSM=
     4 +kind: Secret
     5 +metadata:
     6 + annotations:
     7 + kubectl.kubernetes.io/last-applied-configuration: |
     8 + {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"challenge33","namespace":"default"},"stringData":{"answer":"This was a standardValue as SecureSecret"},"type":"generic"}
     9 + creationTimestamp: "2023-05-14T20:58:31Z"
     10 + name: challenge33
     11 + namespace: default
     12 + resourceVersion: "1891"
     13 + uid: 02f40c7a-0319-4936-9d3f-f47b8c4eb642
     14 +type: generic
     15 + 
  • ■ ■ ■ ■ ■
    k8s/secret-challenge-deployment.yml
    skipped 27 lines
    28 28   runAsGroup: 2000
    29 29   fsGroup: 2000
    30 30   containers:
    31  - - image: jeroenwillemsen/wrongsecrets:1.6.1-no-vault
     31 + - image: jeroenwillemsen/wrongsecrets:1.6.4-no-vault
    32 32   imagePullPolicy: IfNotPresent
    33 33   name: secret-challenge
    34 34   ports:
    skipped 51 lines
    86 86   secretKeyRef:
    87 87   name: funnystuff
    88 88   key: funnier
     89 + - name: CHALLENGE33
     90 + valueFrom:
     91 + secretKeyRef:
     92 + name: challenge33
     93 + key: answer
    89 94   volumes:
    90 95   - name: 'ephemeral'
    91 96   emptyDir: { }
    skipped 5 lines
  • ■ ■ ■ ■ ■
    k8s/secret-challenge-vault-deployment.yml
    skipped 29 lines
    30 30   runAsNonRoot: true
    31 31   serviceAccountName: vault
    32 32   containers:
    33  - - image: jeroenwillemsen/wrongsecrets:1.6.1-k8s-vault
     33 + - image: jeroenwillemsen/wrongsecrets:1.6.4-k8s-vault
    34 34   imagePullPolicy: IfNotPresent
    35 35   name: secret-challenge
    36 36   securityContext:
    skipped 51 lines
    88 88   secretKeyRef:
    89 89   name: funnystuff
    90 90   key: funnier
     91 + - name: CHALLENGE33
     92 + valueFrom:
     93 + secretKeyRef:
     94 + name: challenge33
     95 + key: answer
    91 96   - name: SPRING_CLOUD_VAULT_URI
    92 97   value: "http://vault.vault.svc.cluster.local:8200"
    93 98   - name: JWT_PATH
    skipped 9 lines
  • ■ ■ ■ ■
    k8s-vault-minkube-resume.sh
    1 1  #!/bin/bash
    2 2   
    3  -kubectl port-forward vault-0 8200:8200 &
     3 +kubectl port-forward vault-0 -n vault 8200:8200 &
    4 4  kubectl port-forward \
    5 5   $(kubectl get pod -l app=secret-challenge -o jsonpath="{.items[0].metadata.name}") \
    6 6   8080:8080 \
    skipped 2 lines
  • ■ ■ ■ ■ ■
    k8s-vault-minkube-start.sh
    skipped 25 lines
    26 26   echo "secrets secret is already installed"
    27 27  else
    28 28   kubectl apply -f k8s/secrets-secret.yml
     29 + kubectl apply -f k8s/challenge33.yml
    29 30  fi
    30 31  helm list | grep 'consul' &> /dev/null
    31 32  if [ $? == 0 ]; then
    skipped 97 lines
  • ■ ■ ■ ■ ■ ■
    okteto/k8s/challenge33.yml
     1 +apiVersion: v1
     2 +data:
     3 + answer: VBUGh3wu/3I1naHj1Uf97Y0Lq8B5/92q1jwp3/aYSwHSJI8WqdZnYLj78hESlfPPKf1ZKPap4z2+r+G9NRwdFU/YBMTY3cNguMm5C6l2pTK9JhPFnUzerIwMrnhu9GjrqSFn/BtOvLnQa/mSgXDNJYUOU8gCHFs9JEeQv9hpWpyxlB2Nqu0MHrPNODY3ZohhkjWXaxbjCZi9SpmHydU06Z7LqWyF39G6V8CF6LBPkdUn3aJAV++F0Q9IcSM=
     4 +kind: Secret
     5 +metadata:
     6 + annotations:
     7 + kubectl.kubernetes.io/last-applied-configuration: |
     8 + {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"challenge33","namespace":"default"},"stringData":{"answer":"This was a standardValue as SecureSecret"},"type":"generic"}
     9 + creationTimestamp: "2023-05-14T20:58:31Z"
     10 + name: challenge33
     11 + namespace: default
     12 + resourceVersion: "1891"
     13 + uid: 02f40c7a-0319-4936-9d3f-f47b8c4eb642
     14 +type: generic
     15 + 
  • ■ ■ ■ ■ ■
    okteto/k8s/secret-challenge-ctf-deployment.yml
    skipped 27 lines
    28 28   runAsGroup: 2000
    29 29   fsGroup: 2000
    30 30   containers:
    31  - - image: jeroenwillemsen/wrongsecrets:1.6.1-no-vault
     31 + - image: jeroenwillemsen/wrongsecrets:1.6.4-no-vault
    32 32   name: secret-challenge-ctf
    33 33   imagePullPolicy: IfNotPresent
    34 34   securityContext:
    skipped 45 lines
    80 80   value: "false"
    81 81   - name: ctf_key
    82 82   value: TRwzkRJnHOTckssAeyJbysWgP!Qc2T
     83 + - name: CHALLENGE33
     84 + valueFrom:
     85 + secretKeyRef:
     86 + name: challenge33
     87 + key: answer
    83 88   - name: vaultpassword
    84 89   value: if_you_see_this_please_use_K8S_and_Vault
    85 90   - name: default_aws_value_challenge_9
    skipped 6 lines
    92 97   value: "https://canarytokens.org/history?token=cs07k832u9t1u4npowbvsw4mb&auth=7f75f2b2a4207c91fbc1ea59f7a495eb"
    93 98   - name: challenge15ciphertext
    94 99   value: "k9+HuPXEiFD6efujS5h1lOL1xgAC2OIgE2alg9JweUDy8k2SHUoG6I9FOhM1mgPKIUlyPWvROo+2T5p4qrAnuPYC/xAzVjGDUoN4eIXdXn+gwcYmL+Be8TodjXUt9U3g1/B9O2wyVZTT9Q839FaDHeBR4Og="
     100 + - name: springdoc_api-docs_enabled
     101 + value: "false"
    95 102   - name: challenge_acht_ctf_host_value
    96  - value: "not set"
     103 + value: "thisisfunnyisitnot?"
     104 + - name: challenge_thirty_ctf_to_provide_to_host_value
     105 + value: "thisisthekeyforchallengethirty"
    97 106   - name: K8S_ENV
    98 107   value: Okteto(k8s)
    99 108   - name: SPECIAL_K8S_SECRET
    skipped 17 lines
  • ■ ■ ■ ■ ■
    okteto/k8s/secret-challenge-deployment.yml
    skipped 27 lines
    28 28   runAsGroup: 2000
    29 29   fsGroup: 2000
    30 30   containers:
    31  - - image: jeroenwillemsen/wrongsecrets:1.6.1-no-vault
     31 + - image: jeroenwillemsen/wrongsecrets:1.6.4-no-vault
    32 32   name: secret-challenge
    33 33   imagePullPolicy: IfNotPresent
    34 34   securityContext:
    skipped 45 lines
    80 80   value: "k9+HuPXEiFD6efujS5h1lOL1xgAC2OIgE2alg9Jwe0qQlT+RGDJH/otpFgUzixTbCndwPW3HOqOCQYY844MgxM0N+RRbclS1bpJnYd7BT2aj8v4iA9xR8DwAjU0tt2n84PFKN4vNKjyNATETwPE1GQKBTIi1"
    81 81   - name: K8S_ENV
    82 82   value: Okteto(k8s)
     83 + - name: SPRINGDOC_UI
     84 + value: "true"
     85 + - name: SPRINGDOC_DOC
     86 + value: "true"
    83 87   - name: SPECIAL_K8S_SECRET
    84 88   valueFrom:
    85 89   configMapKeyRef:
    skipped 4 lines
    90 94   secretKeyRef:
    91 95   name: funnystuff
    92 96   key: funnier
     97 + - name: CHALLENGE33
     98 + valueFrom:
     99 + secretKeyRef:
     100 + name: challenge33
     101 + key: answer
    93 102   volumes:
    94 103   - name: "ephemeral"
    95 104   emptyDir: {}
    skipped 5 lines
  • package-lock.json
    Diff is too large to be displayed.
  • ■ ■ ■ ■ ■
    package.json
    1 1  {
     2 + "scripts": {
     3 + "test:open": "cypress open",
     4 + "test:ci": "cypress run --browser chrome"
     5 + },
    2 6   "devDependencies": {
    3  - "@commitlint/config-conventional": "^17.4.4",
    4  - "eslint": "^8.37.0",
     7 + "@commitlint/config-conventional": "^17.6.1",
     8 + "cypress": "^12.10.0",
     9 + "eslint": "^8.39.0",
    5 10   "eslint-config-standard": "^17.0.0",
     11 + "eslint-plugin-cypress": "^2.13.3",
    6 12   "eslint-plugin-import": "^2.27.5",
     13 + "eslint-plugin-jest": "^27.2.1",
    7 14   "eslint-plugin-n": "^15.7.0",
    8 15   "eslint-plugin-promise": "^6.1.1"
     16 + },
     17 + "dependencies": {
     18 + "test": "^3.3.0"
    9 19   }
    10 20  }
    11 21   
  • ■ ■ ■ ■ ■ ■
    pom.xml
    1 1  <?xml version="1.0" encoding="UTF-8"?>
    2  -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3  - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4  - <modelVersion>4.0.0</modelVersion>
    5  - <parent>
    6  - <groupId>org.springframework.boot</groupId>
    7  - <artifactId>spring-boot-starter-parent</artifactId>
    8  - <version>3.0.5</version>
    9  - <relativePath/> <!-- lookup parent from repository -->
    10  - </parent>
    11  - <groupId>org.owasp</groupId>
    12  - <artifactId>wrongsecrets</artifactId>
    13  - <version>1.6.1-SNAPSHOT</version>
    14  - <name>OWASP WrongSecrets</name>
    15  - <description>Examples with how to not use secrets</description>
    16  - <url>https://owasp.org/www-project-wrongsecrets/</url>
    17  - <scm>
    18  - <url>https://github.com/OWASP/wrongsecrets</url>
    19  - </scm>
    20  - <issueManagement>
    21  - <system>GitHub Issue Tracking</system>
    22  - <url>https://github.com/OWASP/wrongsecrets/issues</url>
    23  - </issueManagement>
    24  - <licenses>
    25  - <license>
    26  - <name>AGPLv3 License</name>
    27  - <url>https://opensource.org/license/agpl-v3/</url>
    28  - <distribution>repo</distribution>
    29  - </license>
    30  - </licenses>
    31  - <organization>
    32  - <name>The Open Web Application Security Project (OWASP)</name>
    33  - <url>https://owasp.org/</url>
    34  - </organization>
    35  - <!-- <distributionManagement>-->
    36  - <!-- <snapshotRepository>-->
    37  - <!-- <id>ossrh</id>-->
    38  - <!-- <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>-->
    39  - <!-- </snapshotRepository>-->
    40  - <!-- </distributionManagement>-->
     2 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     3 + <modelVersion>4.0.0</modelVersion>
    41 4   
    42  - <properties>
    43  - <java.version>19</java.version>
    44  - <maven.compiler.target>19</maven.compiler.target>
    45  - <spring.cloud-version>2022.0.2</spring.cloud-version>
    46  - <lombok.version>1.18.26</lombok.version>
    47  - <aws.sdk.version>2.20.39</aws.sdk.version>
    48  - <asciidoctorj.version>2.5.7</asciidoctorj.version>
    49  - <jruby.version>9.4.2.0</jruby.version>
    50  - <bootstrap.version>5.2.3</bootstrap.version>
    51  - <github.button.version>2.14.1</github.button.version>
    52  - <datatables.version>1.13.2</datatables.version>
    53  - <gcp.sdk.version>4.1.3</gcp.sdk.version>
    54  - <jquery.version>3.6.4</jquery.version>
    55  - <thymeleaf.layout.version>3.2.1</thymeleaf.layout.version>
    56  - <thymeleaf-extras-springsecurity6.version>3.1.1.RELEASE</thymeleaf-extras-springsecurity6.version>
    57  - <asciidoctor.maven.plugin.version>2.2.3</asciidoctor.maven.plugin.version>
    58  - <spring.security.version>6.0.2</spring.security.version>
    59  - <com.azure.spring.version>5.0.0</com.azure.spring.version>
    60  - <cyclonedx.core.version>7.3.2</cyclonedx.core.version>
    61  - <KeePassJava2.version>2.1.4</KeePassJava2.version>
    62  - <system-stubs-jupiter.version>2.0.2</system-stubs-jupiter.version>
    63  - <dependency-check-maven.version>8.1.2</dependency-check-maven.version>
    64  - </properties>
     5 + <parent>
     6 + <groupId>org.springframework.boot</groupId>
     7 + <artifactId>spring-boot-starter-parent</artifactId>
     8 + <version>3.0.6</version>
     9 + <relativePath></relativePath>
     10 + <!-- lookup parent from repository -->
     11 + </parent>
    65 12   
     13 + <groupId>org.owasp</groupId>
     14 + <artifactId>wrongsecrets</artifactId>
     15 + <version>1.6.1-SNAPSHOT</version>
     16 + 
     17 + <name>OWASP WrongSecrets</name>
     18 + <description>Examples with how to not use secrets</description>
     19 + <url>https://owasp.org/www-project-wrongsecrets/</url>
     20 + <organization>
     21 + <name>The Open Web Application Security Project (OWASP)</name>
     22 + <url>https://owasp.org/</url>
     23 + </organization>
     24 + <licenses>
     25 + <license>
     26 + <name>AGPLv3 License</name>
     27 + <url>https://opensource.org/license/agpl-v3/</url>
     28 + <distribution>repo</distribution>
     29 + </license>
     30 + </licenses>
     31 + 
     32 + <scm>
     33 + <url>https://github.com/OWASP/wrongsecrets</url>
     34 + </scm>
     35 + <issueManagement>
     36 + <system>GitHub Issue Tracking</system>
     37 + <url>https://github.com/OWASP/wrongsecrets/issues</url>
     38 + </issueManagement>
     39 + 
     40 + <!-- <distributionManagement>-->
     41 + <!-- <snapshotRepository>-->
     42 + <!-- <id>ossrh</id>-->
     43 + <!-- <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>-->
     44 + <!-- </snapshotRepository>-->
     45 + <!-- </distributionManagement>-->
     46 + <properties>
     47 + <KeePassJava2.version>2.1.4</KeePassJava2.version>
     48 + <asciidoctor.maven.plugin.version>2.2.3</asciidoctor.maven.plugin.version>
     49 + <asciidoctorj.version>2.5.8</asciidoctorj.version>
     50 + <aws.sdk.version>2.20.56</aws.sdk.version>
     51 + <bootstrap.version>5.2.3</bootstrap.version>
     52 + <com.azure.spring.version>5.1.0</com.azure.spring.version>
     53 + <cyclonedx.core.version>7.3.2</cyclonedx.core.version>
     54 + <datatables.version>1.13.2</datatables.version>
     55 + <dependency-check-maven.version>8.2.1</dependency-check-maven.version>
     56 + <gcp.sdk.version>4.2.0</gcp.sdk.version>
     57 + <github.button.version>2.14.1</github.button.version>
     58 + <java.version>19</java.version>
     59 + <jquery.version>3.6.4</jquery.version>
     60 + <jruby.version>9.4.2.0</jruby.version>
     61 + <lombok.version>1.18.26</lombok.version>
     62 + <maven.compiler.target>19</maven.compiler.target>
     63 + <spring.cloud-version>2022.0.2</spring.cloud-version>
     64 + <spring.security.version>6.0.2</spring.security.version>
     65 + <system-stubs-jupiter.version>2.0.2</system-stubs-jupiter.version>
     66 + <thymeleaf-extras-springsecurity6.version>3.1.1.RELEASE</thymeleaf-extras-springsecurity6.version>
     67 + <thymeleaf.layout.version>3.2.1</thymeleaf.layout.version>
     68 + </properties>
     69 + 
     70 + <dependencyManagement>
    66 71   <dependencies>
    67  - <dependency>
    68  - <groupId>org.projectlombok</groupId>
    69  - <artifactId>lombok</artifactId>
    70  - <version>${lombok.version}</version>
    71  - <scope>provided</scope>
    72  - </dependency>
    73  - <dependency>
    74  - <groupId>org.springframework.boot</groupId>
    75  - <artifactId>spring-boot-starter-thymeleaf</artifactId>
    76  - </dependency>
     72 + <dependency>
     73 + <groupId>org.springframework.cloud</groupId>
     74 + <artifactId>spring-cloud-dependencies</artifactId>
     75 + <version>${spring.cloud-version}</version>
     76 + <type>pom</type>
     77 + <scope>import</scope>
     78 + </dependency>
     79 + <dependency>
     80 + <groupId>com.google.cloud</groupId>
     81 + <artifactId>spring-cloud-gcp-dependencies</artifactId>
     82 + <version>${gcp.sdk.version}</version>
     83 + <type>pom</type>
     84 + <scope>import</scope>
     85 + </dependency>
     86 + <dependency>
     87 + <groupId>com.azure.spring</groupId>
     88 + <artifactId>spring-cloud-azure-dependencies</artifactId>
     89 + <version>${com.azure.spring.version}</version>
     90 + <type>pom</type>
     91 + <scope>import</scope>
     92 + </dependency>
     93 + </dependencies>
     94 + </dependencyManagement>
     95 + <dependencies>
     96 + <dependency>
     97 + <groupId>org.projectlombok</groupId>
     98 + <artifactId>lombok</artifactId>
     99 + <version>${lombok.version}</version>
     100 + <scope>provided</scope>
     101 + </dependency>
     102 + <dependency>
     103 + <groupId>org.springframework.boot</groupId>
     104 + <artifactId>spring-boot-starter-thymeleaf</artifactId>
     105 + </dependency>
    77 106   
    78  - <dependency>
    79  - <groupId>nz.net.ultraq.thymeleaf</groupId>
    80  - <artifactId>thymeleaf-layout-dialect</artifactId>
    81  - <version>${thymeleaf.layout.version}</version>
    82  - </dependency>
    83  - <dependency>
    84  - <groupId>org.thymeleaf.extras</groupId>
    85  - <artifactId>thymeleaf-extras-springsecurity6</artifactId>
    86  - <version>${thymeleaf-extras-springsecurity6.version}</version>
    87  - </dependency>
    88  - <dependency>
    89  - <groupId>org.springframework.boot</groupId>
    90  - <artifactId>spring-boot-starter</artifactId>
    91  - </dependency>
    92  - <dependency>
    93  - <groupId>org.springframework.boot</groupId>
    94  - <artifactId>spring-boot-starter-actuator</artifactId>
    95  - </dependency>
    96  - <dependency>
    97  - <groupId>org.springframework.security</groupId>
    98  - <artifactId>spring-security-config</artifactId>
    99  - <version>${spring-security.version}</version>
    100  - </dependency>
    101  - <dependency>
    102  - <groupId>org.springframework.security</groupId>
    103  - <artifactId>spring-security-web</artifactId>
    104  - <version>${spring-security.version}</version>
    105  - </dependency>
    106  - <dependency>
    107  - <groupId>org.springframework.security</groupId>
    108  - <artifactId>spring-security-test</artifactId>
    109  - <version>${spring-security.version}</version>
    110  - <scope>test</scope>
    111  - </dependency>
    112  - <dependency>
    113  - <groupId>org.springframework.boot</groupId>
    114  - <artifactId>spring-boot-starter-web</artifactId>
    115  - </dependency>
    116  - <dependency>
    117  - <groupId>org.springframework.cloud</groupId>
    118  - <artifactId>spring-cloud-starter-vault-config</artifactId>
    119  - </dependency>
    120  - <dependency>
    121  - <groupId>org.springframework.cloud</groupId>
    122  - <artifactId>spring-cloud-vault-config</artifactId>
    123  - </dependency>
    124  - <dependency>
    125  - <groupId>software.amazon.awssdk</groupId>
    126  - <artifactId>sts</artifactId>
    127  - <version>${aws.sdk.version}</version>
    128  - </dependency>
    129  - <dependency>
    130  - <groupId>software.amazon.awssdk</groupId>
    131  - <artifactId>ssm</artifactId>
    132  - <version>${aws.sdk.version}</version>
    133  - </dependency>
    134  - <dependency>
    135  - <groupId>org.linguafranca.pwdb</groupId>
    136  - <artifactId>KeePassJava2</artifactId>
    137  - <version>${KeePassJava2.version}</version>
    138  - </dependency>
    139  - <dependency>
    140  - <groupId>org.asciidoctor</groupId>
    141  - <artifactId>asciidoctorj-api</artifactId>
    142  - <version>${asciidoctorj.version}</version>
    143  - </dependency>
    144  - <dependency>
    145  - <groupId>org.asciidoctor</groupId>
    146  - <artifactId>asciidoctorj</artifactId>
    147  - <version>${asciidoctorj.version}</version>
    148  - </dependency>
    149  - <dependency>
    150  - <groupId>org.jruby</groupId>
    151  - <artifactId>jruby-complete</artifactId>
    152  - <version>${jruby.version}</version>
    153  - </dependency>
    154  - <dependency>
    155  - <groupId>org.webjars</groupId>
    156  - <artifactId>bootstrap</artifactId>
    157  - <version>${bootstrap.version}</version>
    158  - </dependency>
    159  - <dependency>
    160  - <groupId>org.webjars.npm</groupId>
    161  - <artifactId>github-buttons</artifactId>
    162  - <version>${github.button.version}</version>
    163  - </dependency>
    164  - <dependency>
    165  - <groupId>org.webjars</groupId>
    166  - <artifactId>datatables</artifactId>
    167  - <version>${datatables.version}</version>
    168  - </dependency>
    169  - <dependency>
    170  - <groupId>org.webjars</groupId>
    171  - <artifactId>jquery</artifactId>
    172  - <version>${jquery.version}</version>
    173  - </dependency>
    174  - <!-- <dependency>-->
    175  - <!-- <groupId>org.springframework.boot</groupId>-->
    176  - <!-- <artifactId>spring-boot-devtools</artifactId>-->
    177  - <!-- <optional>true</optional>-->
    178  - <!-- </dependency>-->
    179  - <dependency>
    180  - <groupId>com.google.cloud</groupId>
    181  - <artifactId>google-cloud-secretmanager</artifactId>
    182  - </dependency>
    183  - <dependency>
    184  - <groupId>org.springdoc</groupId>
    185  - <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    186  - <version>2.1.0</version>
    187  - </dependency>
    188  - <dependency>
    189  - <groupId>com.azure.spring</groupId>
    190  - <artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId>
    191  - </dependency>
     107 + <dependency>
     108 + <groupId>nz.net.ultraq.thymeleaf</groupId>
     109 + <artifactId>thymeleaf-layout-dialect</artifactId>
     110 + <version>${thymeleaf.layout.version}</version>
     111 + </dependency>
     112 + <dependency>
     113 + <groupId>org.thymeleaf.extras</groupId>
     114 + <artifactId>thymeleaf-extras-springsecurity6</artifactId>
     115 + <version>${thymeleaf-extras-springsecurity6.version}</version>
     116 + </dependency>
     117 + <dependency>
     118 + <groupId>org.springframework.boot</groupId>
     119 + <artifactId>spring-boot-starter</artifactId>
     120 + </dependency>
     121 + <dependency>
     122 + <groupId>org.springframework.boot</groupId>
     123 + <artifactId>spring-boot-starter-actuator</artifactId>
     124 + </dependency>
     125 + <dependency>
     126 + <groupId>org.springframework.security</groupId>
     127 + <artifactId>spring-security-config</artifactId>
     128 + <version>${spring-security.version}</version>
     129 + </dependency>
     130 + <dependency>
     131 + <groupId>org.springframework.security</groupId>
     132 + <artifactId>spring-security-web</artifactId>
     133 + <version>${spring-security.version}</version>
     134 + </dependency>
     135 + <dependency>
     136 + <groupId>org.springframework.security</groupId>
     137 + <artifactId>spring-security-test</artifactId>
     138 + <version>${spring-security.version}</version>
     139 + <scope>test</scope>
     140 + </dependency>
     141 + <dependency>
     142 + <groupId>org.springframework.boot</groupId>
     143 + <artifactId>spring-boot-starter-web</artifactId>
     144 + </dependency>
     145 + <dependency>
     146 + <groupId>org.springframework.cloud</groupId>
     147 + <artifactId>spring-cloud-starter-vault-config</artifactId>
     148 + </dependency>
     149 + <dependency>
     150 + <groupId>org.springframework.cloud</groupId>
     151 + <artifactId>spring-cloud-vault-config</artifactId>
     152 + </dependency>
     153 + <dependency>
     154 + <groupId>software.amazon.awssdk</groupId>
     155 + <artifactId>sts</artifactId>
     156 + <version>${aws.sdk.version}</version>
     157 + </dependency>
     158 + <dependency>
     159 + <groupId>software.amazon.awssdk</groupId>
     160 + <artifactId>ssm</artifactId>
     161 + <version>${aws.sdk.version}</version>
     162 + </dependency>
     163 + <dependency>
     164 + <groupId>org.linguafranca.pwdb</groupId>
     165 + <artifactId>KeePassJava2</artifactId>
     166 + <version>${KeePassJava2.version}</version>
     167 + </dependency>
     168 + <dependency>
     169 + <groupId>org.asciidoctor</groupId>
     170 + <artifactId>asciidoctorj-api</artifactId>
     171 + <version>${asciidoctorj.version}</version>
     172 + </dependency>
     173 + <dependency>
     174 + <groupId>org.asciidoctor</groupId>
     175 + <artifactId>asciidoctorj</artifactId>
     176 + <version>${asciidoctorj.version}</version>
     177 + </dependency>
     178 + <dependency>
     179 + <groupId>org.jruby</groupId>
     180 + <artifactId>jruby-complete</artifactId>
     181 + <version>${jruby.version}</version>
     182 + </dependency>
     183 + <dependency>
     184 + <groupId>org.webjars</groupId>
     185 + <artifactId>bootstrap</artifactId>
     186 + <version>${bootstrap.version}</version>
     187 + </dependency>
     188 + <dependency>
     189 + <groupId>org.webjars.npm</groupId>
     190 + <artifactId>github-buttons</artifactId>
     191 + <version>${github.button.version}</version>
     192 + </dependency>
     193 + <dependency>
     194 + <groupId>org.webjars</groupId>
     195 + <artifactId>datatables</artifactId>
     196 + <version>${datatables.version}</version>
     197 + </dependency>
     198 + <dependency>
     199 + <groupId>org.webjars</groupId>
     200 + <artifactId>jquery</artifactId>
     201 + <version>${jquery.version}</version>
     202 + </dependency>
     203 + <!-- <dependency>-->
     204 + <!-- <groupId>org.springframework.boot</groupId>-->
     205 + <!-- <artifactId>spring-boot-devtools</artifactId>-->
     206 + <!-- <optional>true</optional>-->
     207 + <!-- </dependency>-->
     208 + <dependency>
     209 + <groupId>com.google.cloud</groupId>
     210 + <artifactId>google-cloud-secretmanager</artifactId>
     211 + </dependency>
     212 + <dependency>
     213 + <groupId>org.springdoc</groupId>
     214 + <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
     215 + <version>2.1.0</version>
     216 + </dependency>
     217 + <dependency>
     218 + <groupId>com.azure.spring</groupId>
     219 + <artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId>
     220 + </dependency>
    192 221   
    193  - <dependency>
    194  - <groupId>org.springframework.boot</groupId>
    195  - <artifactId>spring-boot-starter-test</artifactId>
    196  - <scope>test</scope>
    197  - </dependency>
     222 + <dependency>
     223 + <groupId>org.springframework.boot</groupId>
     224 + <artifactId>spring-boot-starter-test</artifactId>
     225 + <scope>test</scope>
     226 + </dependency>
    198 227   
    199  - <dependency>
    200  - <groupId>uk.org.webcompere</groupId>
    201  - <artifactId>system-stubs-jupiter</artifactId>
    202  - <version>${system-stubs-jupiter.version}</version>
    203  - <scope>test</scope>
    204  - </dependency>
     228 + <dependency>
     229 + <groupId>uk.org.webcompere</groupId>
     230 + <artifactId>system-stubs-jupiter</artifactId>
     231 + <version>${system-stubs-jupiter.version}</version>
     232 + <scope>test</scope>
     233 + </dependency>
    205 234   
    206  - <dependency>
    207  - <groupId>org.cyclonedx</groupId>
    208  - <artifactId>cyclonedx-core-java</artifactId>
    209  - <version>${cyclonedx.core.version}</version>
    210  - </dependency>
     235 + <dependency>
     236 + <groupId>org.cyclonedx</groupId>
     237 + <artifactId>cyclonedx-core-java</artifactId>
     238 + <version>${cyclonedx.core.version}</version>
     239 + </dependency>
    211 240   
    212  - <dependency>
    213  - <groupId>org.owasp</groupId>
    214  - <artifactId>dependency-check-maven</artifactId>
    215  - <version>${dependency-check-maven.version}</version>
    216  - <type>maven-plugin</type>
    217  - </dependency>
    218  - <dependency>
    219  - <groupId>com.github.spotbugs</groupId>
    220  - <artifactId>spotbugs-annotations</artifactId>
    221  - <version>4.7.3</version>
    222  - </dependency>
    223  - <!-- <dependency>-->
    224  - <!-- <groupId>com.h2database</groupId>-->
    225  - <!-- <artifactId>h2</artifactId>-->
    226  - <!-- <version>2.1.214</version>-->
    227  - <!-- </dependency>-->
    228  - </dependencies>
     241 + <dependency>
     242 + <groupId>org.owasp</groupId>
     243 + <artifactId>dependency-check-maven</artifactId>
     244 + <version>${dependency-check-maven.version}</version>
     245 + <type>maven-plugin</type>
     246 + </dependency>
     247 + <dependency>
     248 + <groupId>com.github.spotbugs</groupId>
     249 + <artifactId>spotbugs-annotations</artifactId>
     250 + <version>4.7.3</version>
     251 + </dependency>
     252 + <!-- <dependency>-->
     253 + <!-- <groupId>com.h2database</groupId>-->
     254 + <!-- <artifactId>h2</artifactId>-->
     255 + <!-- <version>2.1.214</version>-->
     256 + <!-- </dependency>-->
     257 + </dependencies>
    229 258   
    230  - <dependencyManagement>
    231  - <dependencies>
     259 + <build>
     260 + <pluginManagement>
     261 + <plugins>
     262 + <plugin>
     263 + <groupId>org.apache.maven.plugins</groupId>
     264 + <artifactId>maven-checkstyle-plugin</artifactId>
     265 + <version>3.2.2</version>
     266 + <dependencies>
    232 267   <dependency>
    233  - <groupId>org.springframework.cloud</groupId>
    234  - <artifactId>spring-cloud-dependencies</artifactId>
    235  - <version>${spring.cloud-version}</version>
    236  - <type>pom</type>
    237  - <scope>import</scope>
     268 + <groupId>com.puppycrawl.tools</groupId>
     269 + <artifactId>checkstyle</artifactId>
     270 + <version>10.10.0</version>
    238 271   </dependency>
     272 + </dependencies>
     273 + </plugin>
     274 + <plugin>
     275 + <groupId>com.github.spotbugs</groupId>
     276 + <artifactId>spotbugs-maven-plugin</artifactId>
     277 + <version>4.7.3.4</version>
     278 + <configuration>
     279 + <omitVisitors>FindReturnRef</omitVisitors>
     280 + <plugins>
     281 + <plugin>
     282 + <groupId>com.h3xstream.findsecbugs</groupId>
     283 + <artifactId>findsecbugs-plugin</artifactId>
     284 + <version>1.12.0</version>
     285 + </plugin>
     286 + </plugins>
     287 + </configuration>
     288 + <dependencies>
     289 + <!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
    239 290   <dependency>
    240  - <groupId>com.google.cloud</groupId>
    241  - <artifactId>spring-cloud-gcp-dependencies</artifactId>
    242  - <version>${gcp.sdk.version}</version>
    243  - <type>pom</type>
    244  - <scope>import</scope>
     291 + <groupId>com.github.spotbugs</groupId>
     292 + <artifactId>spotbugs</artifactId>
     293 + <version>4.7.3</version>
    245 294   </dependency>
     295 + </dependencies>
     296 + </plugin>
     297 + </plugins>
     298 + </pluginManagement>
     299 + <plugins>
     300 + <plugin>
     301 + <groupId>org.springframework.boot</groupId>
     302 + <artifactId>spring-boot-maven-plugin</artifactId>
     303 + <configuration>
     304 + <excludeDevtools>true</excludeDevtools>
     305 + <!-- See http://docs.spring.io/spring-boot/docs/current/reference/html/howto-build.html#howto-extract-specific-libraries-when-an-executable-jar-runs -->
     306 + <requiresUnpack>
    246 307   <dependency>
    247  - <groupId>com.azure.spring</groupId>
    248  - <artifactId>spring-cloud-azure-dependencies</artifactId>
    249  - <version>${com.azure.spring.version}</version>
    250  - <type>pom</type>
    251  - <scope>import</scope>
     308 + <groupId>org.asciidoctor</groupId>
     309 + <artifactId>asciidoctorj</artifactId>
    252 310   </dependency>
    253  - </dependencies>
    254  - </dependencyManagement>
    255  - 
    256  - <!-- Required by exec-maven-plugin to run NPM *.CMD script on Windows -->
    257  - <profiles>
    258  - <profile>
    259  - <id>Windows</id>
    260  - <activation>
    261  - <os>
    262  - <family>Windows</family>
    263  - </os>
    264  - </activation>
    265  - <properties>
    266  - <script.extension>.cmd</script.extension>
    267  - </properties>
    268  - </profile>
    269  - <profile>
    270  - <id>unix</id>
    271  - <activation>
    272  - <os>
    273  - <family>unix</family>
    274  - </os>
    275  - </activation>
    276  - <properties>
    277  - <script.extension/>
    278  - </properties>
    279  - </profile>
    280  - </profiles>
    281  - 
    282  - <build>
    283  - <pluginManagement>
    284  - <plugins>
    285  - <plugin>
    286  - <groupId>org.apache.maven.plugins</groupId>
    287  - <artifactId>maven-checkstyle-plugin</artifactId>
    288  - <version>3.2.1</version>
    289  - <dependencies>
    290  - <dependency>
    291  - <groupId>com.puppycrawl.tools</groupId>
    292  - <artifactId>checkstyle</artifactId>
    293  - <version>10.9.3</version>
    294  - </dependency>
    295  - </dependencies>
    296  - </plugin>
    297  - <plugin>
    298  - <groupId>com.github.spotbugs</groupId>
    299  - <artifactId>spotbugs-maven-plugin</artifactId>
    300  - <version>4.7.3.3</version>
    301  - <configuration>
    302  - <omitVisitors>FindReturnRef</omitVisitors>
    303  - <plugins>
    304  - <plugin>
    305  - <groupId>com.h3xstream.findsecbugs</groupId>
    306  - <artifactId>findsecbugs-plugin</artifactId>
    307  - <version>1.12.0</version>
    308  - </plugin>
    309  - </plugins>
    310  - </configuration>
    311  - <dependencies>
    312  - <!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
    313  - <dependency>
    314  - <groupId>com.github.spotbugs</groupId>
    315  - <artifactId>spotbugs</artifactId>
    316  - <version>4.7.3</version>
    317  - </dependency>
    318  - </dependencies>
    319  - </plugin>
    320  - </plugins>
    321  - </pluginManagement>
    322  - <plugins>
    323  - <plugin>
    324  - <groupId>org.springframework.boot</groupId>
    325  - <artifactId>spring-boot-maven-plugin</artifactId>
    326  - <configuration>
    327  - <excludeDevtools>true</excludeDevtools>
    328  - <!-- See http://docs.spring.io/spring-boot/docs/current/reference/html/howto-build.html#howto-extract-specific-libraries-when-an-executable-jar-runs -->
    329  - <requiresUnpack>
    330  - <dependency>
    331  - <groupId>org.asciidoctor</groupId>
    332  - <artifactId>asciidoctorj</artifactId>
    333  - </dependency>
    334  - <dependency>
    335  - <groupId>org.jruby</groupId>
    336  - <artifactId>jruby-complete</artifactId>
    337  - </dependency>
    338  - </requiresUnpack>
    339  - <jvmArguments>
    340  - <!-- -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000-->
    341  - </jvmArguments>
    342  - </configuration>
    343  - </plugin>
    344  - <plugin>
    345  - <groupId>org.apache.maven.plugins</groupId>
    346  - <artifactId>maven-checkstyle-plugin</artifactId>
    347  - <version>3.2.1</version>
    348  - <configuration>
    349  - <consoleOutput>true</consoleOutput>
    350  - <failsOnError>true</failsOnError>
    351  - <configLocation>config/checkstyle/checkstyle.xml</configLocation>
    352  - <suppressionsLocation>config/checkstyle/suppressions.xml</suppressionsLocation>
    353  - <suppressionsFileExpression>checkstyle.suppressions.file</suppressionsFileExpression>
    354  - </configuration>
    355  - </plugin>
    356  - <plugin>
    357  - <!-- https://github.com/asciidoctor/asciidoctor-maven-examples/blob/main/asciidoc-to-html-example/pom.xml -->
    358  - <groupId>org.asciidoctor</groupId>
    359  - <artifactId>asciidoctor-maven-plugin</artifactId>
    360  - <version>${asciidoctor.maven.plugin.version}</version>
    361  - <configuration>
    362  - <baseDir>${project.basedir}</baseDir>
    363  - <sourceDirectory>src/main/resources/explanations</sourceDirectory>
    364  - <outputDirectory>${project.build.outputDirectory}/explanations</outputDirectory>
    365  - </configuration>
    366  - <executions>
    367  - <execution>
    368  - <id>asciidoc-to-html</id>
    369  - <phase>compile</phase>
    370  - <goals>
    371  - <goal>process-asciidoc</goal>
    372  - </goals>
    373  - <configuration>
    374  - <headerFooter>false</headerFooter>
    375  - <attributes>
    376  - <docType>null</docType>
    377  - </attributes>
    378  - </configuration>
    379  - </execution>
    380  - </executions>
    381  - </plugin>
    382  - <plugin>
    383  - <groupId>org.cyclonedx</groupId>
    384  - <artifactId>cyclonedx-maven-plugin</artifactId>
    385  - <version>2.7.6</version>
    386  - <executions>
    387  - <execution>
    388  - <phase>install</phase>
    389  - <goals>
    390  - <goal>makeAggregateBom</goal>
    391  - </goals>
    392  - </execution>
    393  - </executions>
    394  - <configuration>
    395  - <projectType>library</projectType>
    396  - <schemaVersion>1.3</schemaVersion>
    397  - <includeBomSerialNumber>true</includeBomSerialNumber>
    398  - <includeCompileScope>true</includeCompileScope>
    399  - <includeProvidedScope>true</includeProvidedScope>
    400  - <includeRuntimeScope>true</includeRuntimeScope>
    401  - <includeSystemScope>true</includeSystemScope>
    402  - <includeTestScope>false</includeTestScope>
    403  - <includeLicenseText>false</includeLicenseText>
    404  - <outputFormat>all</outputFormat>
    405  - <outputName>bom</outputName>
    406  - </configuration>
    407  - </plugin>
    408  - <plugin>
    409  - <groupId>org.owasp</groupId>
    410  - <artifactId>dependency-check-maven</artifactId>
    411  - <version>${dependency-check-maven.version}</version>
    412  - <executions>
    413  - <execution>
    414  - <goals>
    415  - <goal>check</goal>
    416  - </goals>
    417  - </execution>
    418  - </executions>
    419  - </plugin>
    420  - <!-- todo: #178 add the missing plugins and profiles from https://central.sonatype.org/publish/publish-maven/-->
    421  - <plugin>
    422  - <groupId>com.github.eirslett</groupId>
    423  - <artifactId>frontend-maven-plugin</artifactId>
    424  - <version>1.12.1</version>
    425  - <executions>
    426  - <execution>
    427  - <id>install node and npm</id>
    428  - <goals>
    429  - <goal>install-node-and-npm</goal>
    430  - </goals>
    431  - <phase>generate-resources</phase>
    432  - <configuration>
    433  - <nodeVersion>v16.13.2</nodeVersion> <!-- download node from https://nodejs.org/dist/ -->
    434  - <workingDirectory>js</workingDirectory>
    435  - </configuration>
    436  - </execution>
    437  - <execution>
    438  - <id>npm install</id>
    439  - <goals>
    440  - <goal>npm</goal>
    441  - </goals>
    442  - <phase>generate-resources</phase>
    443  - <configuration>
    444  - <arguments>install</arguments>
    445  - <workingDirectory>js</workingDirectory>
    446  - </configuration>
    447  - </execution>
    448  - </executions>
    449  - </plugin>
    450  - <plugin>
    451  - <groupId>org.springdoc</groupId>
    452  - <artifactId>springdoc-openapi-maven-plugin</artifactId>
    453  - <version>1.4</version>
    454  - </plugin>
    455  - <plugin>
    456  - <groupId>org.codehaus.mojo</groupId>
    457  - <artifactId>exec-maven-plugin</artifactId>
    458  - <version>3.1.0</version>
    459  - <executions>
    460  - <execution>
    461  - <phase>generate-resources</phase>
    462  - <goals>
    463  - <goal>exec</goal>
    464  - </goals>
    465  - </execution>
    466  - </executions>
    467  - <configuration>
    468  - 
    469  - <executable>./js/node_modules/.bin/javascript-obfuscator${script.extension}</executable>
    470  - <arguments>
    471  - <argument>./js/index.js</argument>
    472  - <argument>--output</argument>
    473  - <argument>./target/classes/static/js/index.js</argument>
    474  - </arguments>
    475  - </configuration>
    476  - </plugin>
    477  - <plugin>
    478  - <groupId>org.apache.maven.plugins</groupId>
    479  - <artifactId>maven-compiler-plugin</artifactId>
    480  - <configuration>
    481  - <source>19</source>
    482  - <target>19</target>
    483  - </configuration>
    484  - </plugin>
    485  - </plugins>
    486  - </build>
     311 + <dependency>
     312 + <groupId>org.jruby</groupId>
     313 + <artifactId>jruby-complete</artifactId>
     314 + </dependency>
     315 + </requiresUnpack>
     316 + <jvmArguments>
     317 + <!-- -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000-->
     318 + </jvmArguments>
     319 + </configuration>
     320 + <executions>
     321 + <execution>
     322 + <id>start-app</id>
     323 + <goals>
     324 + <goal>start</goal>
     325 + </goals>
     326 + <phase>pre-integration-test</phase>
     327 + <configuration>
     328 + <profiles>local,without-vault</profiles>
     329 + </configuration>
     330 + </execution>
     331 + <execution>
     332 + <id>stop-app</id>
     333 + <goals>
     334 + <goal>stop</goal>
     335 + </goals>
     336 + <phase>post-integration-test</phase>
     337 + </execution>
     338 + </executions>
     339 + </plugin>
     340 + <plugin>
     341 + <groupId>org.apache.maven.plugins</groupId>
     342 + <artifactId>maven-checkstyle-plugin</artifactId>
     343 + <version>3.2.2</version>
     344 + <configuration>
     345 + <consoleOutput>true</consoleOutput>
     346 + <failsOnError>true</failsOnError>
     347 + <configLocation>config/checkstyle/checkstyle.xml</configLocation>
     348 + <suppressionsLocation>config/checkstyle/suppressions.xml</suppressionsLocation>
     349 + <suppressionsFileExpression>checkstyle.suppressions.file</suppressionsFileExpression>
     350 + </configuration>
     351 + </plugin>
     352 + <plugin>
     353 + <groupId>org.asciidoctor</groupId>
     354 + <artifactId>asciidoctor-maven-plugin</artifactId>
     355 + <version>${asciidoctor.maven.plugin.version}</version>
     356 + <configuration>
     357 + <baseDir>${project.basedir}</baseDir>
     358 + <sourceDirectory>src/main/resources/explanations</sourceDirectory>
     359 + <outputDirectory>${project.build.outputDirectory}/explanations</outputDirectory>
     360 + <!-- https://github.com/asciidoctor/asciidoctor-maven-examples/blob/main/asciidoc-to-html-example/pom.xml -->
     361 + </configuration>
     362 + <executions>
     363 + <execution>
     364 + <id>asciidoc-to-html</id>
     365 + <goals>
     366 + <goal>process-asciidoc</goal>
     367 + </goals>
     368 + <phase>compile</phase>
     369 + <configuration>
     370 + <headerFooter>false</headerFooter>
     371 + <attributes>
     372 + <docType>null</docType>
     373 + </attributes>
     374 + </configuration>
     375 + </execution>
     376 + </executions>
     377 + </plugin>
     378 + <plugin>
     379 + <groupId>org.cyclonedx</groupId>
     380 + <artifactId>cyclonedx-maven-plugin</artifactId>
     381 + <version>2.7.8</version>
     382 + <configuration>
     383 + <projectType>library</projectType>
     384 + <schemaVersion>1.3</schemaVersion>
     385 + <includeBomSerialNumber>true</includeBomSerialNumber>
     386 + <includeCompileScope>true</includeCompileScope>
     387 + <includeProvidedScope>true</includeProvidedScope>
     388 + <includeRuntimeScope>true</includeRuntimeScope>
     389 + <includeSystemScope>true</includeSystemScope>
     390 + <includeTestScope>false</includeTestScope>
     391 + <includeLicenseText>false</includeLicenseText>
     392 + <outputFormat>all</outputFormat>
     393 + <outputName>bom</outputName>
     394 + </configuration>
     395 + <executions>
     396 + <execution>
     397 + <goals>
     398 + <goal>makeAggregateBom</goal>
     399 + </goals>
     400 + <phase>install</phase>
     401 + </execution>
     402 + </executions>
     403 + </plugin>
     404 + <plugin>
     405 + <groupId>org.owasp</groupId>
     406 + <artifactId>dependency-check-maven</artifactId>
     407 + <version>${dependency-check-maven.version}</version>
     408 + <executions>
     409 + <execution>
     410 + <goals>
     411 + <goal>check</goal>
     412 + </goals>
     413 + </execution>
     414 + </executions>
     415 + </plugin>
     416 + <!-- todo: #178 add the missing plugins and profiles from https://central.sonatype.org/publish/publish-maven/-->
     417 + <plugin>
     418 + <groupId>com.github.eirslett</groupId>
     419 + <artifactId>frontend-maven-plugin</artifactId>
     420 + <version>1.12.1</version>
     421 + <executions>
     422 + <execution>
     423 + <id>install node and npm</id>
     424 + <goals>
     425 + <goal>install-node-and-npm</goal>
     426 + </goals>
     427 + <phase>generate-resources</phase>
     428 + <configuration>
     429 + <nodeVersion>v16.13.2</nodeVersion>
     430 + <!-- download node from https://nodejs.org/dist/ -->
     431 + <workingDirectory>js</workingDirectory>
     432 + </configuration>
     433 + </execution>
     434 + <execution>
     435 + <id>npm install</id>
     436 + <goals>
     437 + <goal>npm</goal>
     438 + </goals>
     439 + <phase>generate-resources</phase>
     440 + <configuration>
     441 + <arguments>install</arguments>
     442 + <workingDirectory>js</workingDirectory>
     443 + </configuration>
     444 + </execution>
     445 + </executions>
     446 + </plugin>
     447 + <plugin>
     448 + <groupId>org.springdoc</groupId>
     449 + <artifactId>springdoc-openapi-maven-plugin</artifactId>
     450 + <version>1.4</version>
     451 + </plugin>
     452 + <plugin>
     453 + <groupId>org.codehaus.mojo</groupId>
     454 + <artifactId>exec-maven-plugin</artifactId>
     455 + <version>3.1.0</version>
     456 + <configuration>
    487 457   
     458 + <executable>./js/node_modules/.bin/javascript-obfuscator${script.extension}</executable>
     459 + <arguments>
     460 + <argument>./js/index.js</argument>
     461 + <argument>--output</argument>
     462 + <argument>./target/classes/static/js/index.js</argument>
     463 + </arguments>
     464 + </configuration>
     465 + <executions>
     466 + <execution>
     467 + <goals>
     468 + <goal>exec</goal>
     469 + </goals>
     470 + <phase>generate-resources</phase>
     471 + </execution>
     472 + <execution>
     473 + <id>npm-install</id>
     474 + <goals>
     475 + <goal>exec</goal>
     476 + </goals>
     477 + <phase>integration-test</phase>
     478 + <configuration>
     479 + <executable>npm</executable>
     480 + <arguments>
     481 + <argument>install</argument>
     482 + </arguments>
     483 + </configuration>
     484 + </execution>
     485 + <execution>
     486 + <id>xcypress-test</id>
     487 + <goals>
     488 + <goal>exec</goal>
     489 + </goals>
     490 + <phase>integration-test</phase>
     491 + <configuration>
     492 + <executable>npm</executable>
     493 + <arguments>
     494 + <argument>run</argument>
     495 + <argument>test:ci</argument>
     496 + </arguments>
     497 + </configuration>
     498 + </execution>
     499 + </executions>
     500 + </plugin>
     501 + <plugin>
     502 + <groupId>org.apache.maven.plugins</groupId>
     503 + <artifactId>maven-compiler-plugin</artifactId>
     504 + <configuration>
     505 + <source>19</source>
     506 + <target>19</target>
     507 + </configuration>
     508 + </plugin>
     509 + <plugin>
     510 + <groupId>org.codehaus.mojo</groupId>
     511 + <artifactId>tidy-maven-plugin</artifactId>
     512 + <version>1.2.0</version>
     513 + <executions>
     514 + <execution>
     515 + <id>validate</id>
     516 + <goals>
     517 + <goal>check</goal>
     518 + </goals>
     519 + <phase>validate</phase>
     520 + </execution>
     521 + </executions>
     522 + </plugin>
     523 + <plugin>
     524 + <groupId>com.diffplug.spotless</groupId>
     525 + <artifactId>spotless-maven-plugin</artifactId>
     526 + <version>2.36.0</version>
     527 + <configuration>
     528 + <formats>
     529 + <format>
     530 + <includes>
     531 + <include>.gitignore</include>
     532 + </includes>
     533 + <trimTrailingWhitespace></trimTrailingWhitespace>
     534 + <endWithNewline></endWithNewline>
     535 + <indent>
     536 + <tabs>true</tabs>
     537 + <spacesPerTab>4</spacesPerTab>
     538 + </indent>
     539 + </format>
     540 + </formats>
     541 + <java>
     542 + <includes>
     543 + <include>src/main/java/**/*.java</include>
     544 + <include>src/test/java/**/*.java</include>
     545 + </includes>
     546 + <removeUnusedImports></removeUnusedImports>
     547 + <googleJavaFormat>
     548 + <style>GOOGLE</style>
     549 + <reflowLongStrings>true</reflowLongStrings>
     550 + </googleJavaFormat>
     551 + </java>
     552 + <pom>
     553 + <sortPom>
     554 + <encoding>UTF-8</encoding>
     555 + <lineSeparator>${line.separator}</lineSeparator>
     556 + <expandEmptyElements>true</expandEmptyElements>
     557 + <spaceBeforeCloseEmptyElement>false</spaceBeforeCloseEmptyElement>
     558 + <keepBlankLines>true</keepBlankLines>
     559 + <nrOfIndentSpace>2</nrOfIndentSpace>
     560 + <indentBlankLines>false</indentBlankLines>
     561 + <indentSchemaLocation>false</indentSchemaLocation>
     562 + <predefinedSortOrder>recommended_2008_06</predefinedSortOrder>
     563 + <sortProperties>true</sortProperties>
     564 + <sortModules>true</sortModules>
     565 + <sortExecutions>true</sortExecutions>
     566 + </sortPom>
     567 + </pom>
     568 + </configuration>
     569 + <executions>
     570 + <execution>
     571 + <goals>
     572 + <goal>check</goal>
     573 + </goals>
     574 + </execution>
     575 + </executions>
     576 + </plugin>
     577 + </plugins>
     578 + </build>
    488 579   
     580 + <!-- Required by exec-maven-plugin to run NPM *.CMD script on Windows -->
     581 + <profiles>
     582 + <profile>
     583 + <id>Windows</id>
     584 + <activation>
     585 + <os>
     586 + <family>Windows</family>
     587 + </os>
     588 + </activation>
     589 + <properties>
     590 + <script.extension>.cmd</script.extension>
     591 + </properties>
     592 + </profile>
     593 + <profile>
     594 + <id>unix</id>
     595 + <activation>
     596 + <os>
     597 + <family>unix</family>
     598 + </os>
     599 + </activation>
     600 + <properties>
     601 + <script.extension></script.extension>
     602 + </properties>
     603 + </profile>
     604 + </profiles>
    489 605  </project>
    490 606   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/AboutController.java
    skipped 1 lines
    2 2   
    3 3  import io.swagger.v3.oas.annotations.Operation;
    4 4  import org.springframework.stereotype.Controller;
    5  -import org.springframework.ui.Model;
    6 5  import org.springframework.web.bind.annotation.GetMapping;
    7 6   
    8  -/**
    9  - * About controler hosting /about endpoint.
    10  - */
     7 +/** About controler hosting /about endpoint. */
    11 8  @Controller
    12 9  public class AboutController {
    13 10   
    14  - 
    15  - 
    16  - @GetMapping("/about")
    17  - @Operation(description = "Endpoint to get dynamic data on about")
    18  - public String getStats(Model model) {
    19  - return "about";
    20  - }
     11 + @GetMapping("/about")
     12 + @Operation(description = "Endpoint to get dynamic data on about")
     13 + public String getStats() {
     14 + return "about";
     15 + }
    21 16  }
    22 17   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/ActuatorSecurityConfiguration.java
    1  -package org.owasp.wrongsecrets;
    2  - 
    3  -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    4  -import org.springframework.context.annotation.Bean;
    5  -import org.springframework.context.annotation.Configuration;
    6  -import org.springframework.core.annotation.Order;
    7  -import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    8  -import org.springframework.security.web.SecurityFilterChain;
    9  - 
    10  -/**
    11  - * Configuration used to disable CSRF on the actuator health endpoint otherwise it does not work for health-checks.
    12  - */
    13  -@Configuration
    14  -public class ActuatorSecurityConfiguration {
    15  - 
    16  - @SuppressFBWarnings(value = "SPRING_CSRF_PROTECTION_DISABLED",justification = "There is no need for the health endpoint to have CSRF as it is only used by K8s")
    17  - @Bean
    18  - @Order(2)
    19  - public SecurityFilterChain configureActuatorSecurity(HttpSecurity http) throws Exception {
    20  - http.securityMatcher(r ->
    21  - r.getRequestURL().toString().contains("/actuator/health"))
    22  - .csrf().disable();
    23  - return http.build();
    24  - }
    25  -}
    26  - 
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/AllControllerAdvice.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
    3 3  import jakarta.servlet.http.HttpServletRequest;
     4 +import java.util.List;
    4 5  import org.owasp.wrongsecrets.challenges.Challenge;
    5 6  import org.owasp.wrongsecrets.challenges.ChallengeUI;
    6 7  import org.springframework.beans.factory.annotation.Value;
    skipped 2 lines
    9 10  import org.springframework.web.bind.annotation.ControllerAdvice;
    10 11  import org.springframework.web.bind.annotation.ModelAttribute;
    11 12   
    12  -import java.util.List;
    13  - 
    14 13  /**
    15  - * Make sure shared model properties are always set for each controller. So for example `challenges` should be present
    16  - * in the model instead of adding it in all endpoint we can use this advice to let Spring do this for us.
     14 + * Make sure shared model properties are always set for each controller. So for example `challenges`
     15 + * should be present in the model instead of adding it in all endpoint we can use this advice to let
     16 + * Spring do this for us.
    17 17   */
    18 18  @ControllerAdvice
    19 19  public class AllControllerAdvice {
    20 20   
    21  - private final List<ChallengeUI> challenges;
    22  - private final String version;
    23  - private final RuntimeEnvironment runtimeEnvironment;
     21 + private final List<ChallengeUI> challenges;
     22 + private final String version;
     23 + private final RuntimeEnvironment runtimeEnvironment;
    24 24   
    25  - public AllControllerAdvice(List<Challenge> challenges, @Value("${APP_VERSION}") String version, RuntimeEnvironment runtimeEnvironment) {
    26  - this.challenges = ChallengeUI.toUI(challenges, runtimeEnvironment);
    27  - this.version = version;
    28  - this.runtimeEnvironment = runtimeEnvironment;
    29  - }
     25 + public AllControllerAdvice(
     26 + List<Challenge> challenges,
     27 + @Value("${APP_VERSION}") String version,
     28 + RuntimeEnvironment runtimeEnvironment) {
     29 + this.challenges = ChallengeUI.toUI(challenges, runtimeEnvironment);
     30 + this.version = version;
     31 + this.runtimeEnvironment = runtimeEnvironment;
     32 + }
    30 33   
    31  - @ModelAttribute
    32  - public void addChallenges(Model model) {
    33  - model.addAttribute("challenges", challenges);
    34  - }
     34 + @ModelAttribute
     35 + public void addChallenges(Model model) {
     36 + model.addAttribute("challenges", challenges);
     37 + }
    35 38   
    36  - @ModelAttribute
    37  - public void addVersion(Model model) {
    38  - model.addAttribute("version", version);
    39  - }
     39 + @ModelAttribute
     40 + public void addVersion(Model model) {
     41 + model.addAttribute("version", version);
     42 + }
    40 43   
    41  - @ModelAttribute
    42  - public void addRequest(Model model, HttpServletRequest request) {
    43  - model.addAttribute("requestURI", request.getRequestURI());
    44  - }
     44 + @ModelAttribute
     45 + public void addRequest(Model model, HttpServletRequest request) {
     46 + model.addAttribute("requestURI", request.getRequestURI());
     47 + }
    45 48   
    46  - @ModelAttribute
    47  - public void addRuntimeEnvironment(Model model) {
    48  - model.addAttribute("environment", runtimeEnvironment.getRuntimeEnvironment().name());
    49  - model.addAttribute("ctf_enabled", runtimeEnvironment.runtimeInCTFMode());
    50  - }
     49 + @ModelAttribute
     50 + public void addRuntimeEnvironment(Model model) {
     51 + model.addAttribute("environment", runtimeEnvironment.getRuntimeEnvironment().name());
     52 + model.addAttribute("ctf_enabled", runtimeEnvironment.runtimeInCTFMode());
     53 + }
    51 54   
    52  - @Bean
    53  - public List<ChallengeUI> uiChallenges() {
    54  - return challenges;
    55  - }
     55 + @Bean
     56 + public List<ChallengeUI> uiChallenges() {
     57 + return challenges;
     58 + }
    56 59  }
    57 60   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/FailtoStartupException.java
    skipped 1 lines
    2 2   
    3 3  import org.springframework.boot.ExitCodeGenerator;
    4 4   
    5  -/**
    6  - * Used to give a clear non-0 exit code when the Application cannot start.
    7  - */
     5 +/** Used to give a clear non-0 exit code when the Application cannot start. */
    8 6  public class FailtoStartupException extends RuntimeException implements ExitCodeGenerator {
    9 7   
    10  - public FailtoStartupException(String message) {
    11  - super(message);
    12  - }
     8 + public FailtoStartupException(String message) {
     9 + super(message);
     10 + }
    13 11   
    14  - @Override
    15  - public int getExitCode() {
    16  - return 1;
    17  - }
     12 + @Override
     13 + public int getExitCode() {
     14 + return 1;
     15 + }
    18 16  }
    19 17   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/HerokuWebSecurityConfig.java
    1  -package org.owasp.wrongsecrets;
    2  - 
    3  -import org.springframework.beans.factory.ObjectProvider;
    4  -import org.springframework.context.annotation.Bean;
    5  -import org.springframework.context.annotation.Configuration;
    6  -import org.springframework.core.annotation.Order;
    7  -import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    8  -import org.springframework.security.web.PortMapper;
    9  -import org.springframework.security.web.SecurityFilterChain;
    10  - 
    11  -/**
    12  - * Used to implement https redirect for our Heroku-hosted workload.
    13  - */
    14  -@Configuration
    15  -public class HerokuWebSecurityConfig {
    16  - 
    17  - @Bean
    18  - @Order(1)
    19  - public SecurityFilterChain configureHerokuWebSecurity(HttpSecurity http, ObjectProvider<PortMapper> portMapper) throws Exception {
    20  - http.requiresChannel()
    21  - .requestMatchers(r ->
    22  - r.getRequestURL().toString().contains("heroku")
    23  - && (r.getHeader("x-forwarded-proto") != null || r.getHeader("X-Forwarded-Proto") != null)
    24  - )
    25  - .requiresSecure();
    26  - return http.build();
    27  - }
    28  -}
    29  - 
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/InMemoryScoreCard.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
    3  -import org.owasp.wrongsecrets.challenges.Challenge;
    4  - 
    5 3  import java.util.HashSet;
    6 4  import java.util.Set;
     5 +import org.owasp.wrongsecrets.challenges.Challenge;
    7 6   
    8  -/**
    9  - * In-memory implementation of the ScoreCard (E.g. no persistence).
    10  - */
     7 +/** In-memory implementation of the ScoreCard (E.g. no persistence). */
    11 8  public class InMemoryScoreCard implements ScoreCard {
    12 9   
    13  - private final int maxNumberOfChallenges;
    14  - private final Set<Challenge> solvedChallenges = new HashSet<>();
     10 + private final int maxNumberOfChallenges;
     11 + private final Set<Challenge> solvedChallenges = new HashSet<>();
    15 12   
    16  - public InMemoryScoreCard(int numberOfChallenge) {
    17  - maxNumberOfChallenges = numberOfChallenge;
    18  - }
     13 + public InMemoryScoreCard(int numberOfChallenge) {
     14 + maxNumberOfChallenges = numberOfChallenge;
     15 + }
    19 16   
    20  - @Override
    21  - public void completeChallenge(Challenge challenge) {
    22  - solvedChallenges.add(challenge);
    23  - }
     17 + @Override
     18 + public void completeChallenge(Challenge challenge) {
     19 + solvedChallenges.add(challenge);
     20 + }
    24 21   
    25  - @Override
    26  - public boolean getChallengeCompleted(Challenge challenge) {
    27  - return solvedChallenges.contains(challenge);
    28  - }
     22 + @Override
     23 + public boolean getChallengeCompleted(Challenge challenge) {
     24 + return solvedChallenges.contains(challenge);
     25 + }
    29 26   
    30  - @Override
    31  - public float getProgress() {
    32  - return ((float) 100 / maxNumberOfChallenges) * solvedChallenges.size();
    33  - }
     27 + @Override
     28 + public float getProgress() {
     29 + return ((float) 100 / maxNumberOfChallenges) * solvedChallenges.size();
     30 + }
    34 31   
    35  - @Override
    36  - public int getTotalReceivedPoints() {
    37  - return solvedChallenges.stream().map(challenge -> challenge.difficulty() * (100 + (challenge.difficulty() - 1) * 25)).reduce(0, Integer::sum);
    38  - }
     32 + @Override
     33 + public int getTotalReceivedPoints() {
     34 + return solvedChallenges.stream()
     35 + .map(challenge -> challenge.difficulty() * (100 + (challenge.difficulty() - 1) * 25))
     36 + .reduce(0, Integer::sum);
     37 + }
    39 38   
    40  - @Override
    41  - public void reset(Challenge challenge) {
    42  - solvedChallenges.remove(challenge);
    43  - }
     39 + @Override
     40 + public void reset(Challenge challenge) {
     41 + solvedChallenges.remove(challenge);
     42 + }
    44 43  }
    45 44   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/IndexController.java
    skipped 4 lines
    5 5  import org.springframework.stereotype.Controller;
    6 6  import org.springframework.web.bind.annotation.GetMapping;
    7 7   
    8  -/**
    9  - * Controller used to return the dynamic data for the welcome screen.
    10  - */
     8 +/** Controller used to return the dynamic data for the welcome screen. */
    11 9  @Controller
    12 10  @Slf4j
    13 11  public class IndexController {
    14 12   
    15  - @GetMapping("/")
    16  - @Operation(description = "Returns all dynamic data for the welcome screen")
    17  - public String index() {
    18  - return "welcome";
    19  - }
     13 + @GetMapping("/")
     14 + @Operation(description = "Returns all dynamic data for the welcome screen")
     15 + public String index() {
     16 + return "welcome";
     17 + }
    20 18  }
    21 19   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/MvcConfiguration.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
     3 +import java.util.Set;
    3 4  import lombok.extern.slf4j.Slf4j;
    4 5  import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
    5 6  import org.owasp.wrongsecrets.asciidoc.AsciiDocGenerator;
    skipped 13 lines
    19 20  import org.thymeleaf.templateresolver.FileTemplateResolver;
    20 21  import org.thymeleaf.templateresolver.ITemplateResolver;
    21 22   
    22  -import java.util.Set;
    23  - 
    24  -/**
    25  - * Used to generate and return all the html in thymeleaf and convert asciidoc to html.
    26  - */
     23 +/** Used to generate and return all the html in thymeleaf and convert asciidoc to html. */
    27 24  @Configuration
    28 25  @Slf4j
    29 26  public class MvcConfiguration implements WebMvcConfigurer {
    30 27   
    31  - private static final String UTF8 = "UTF-8";
     28 + private static final String UTF8 = "UTF-8";
    32 29   
    33  - @Bean
    34  - public ITemplateResolver springThymeleafTemplateResolver(ApplicationContext applicationContext) {
    35  - SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
    36  - resolver.setPrefix("classpath:/templates/");
    37  - resolver.setSuffix(".html");
    38  - resolver.setTemplateMode(TemplateMode.HTML);
    39  - resolver.setOrder(2);
    40  - resolver.setCacheable(false);
    41  - resolver.setCharacterEncoding(UTF8);
    42  - resolver.setApplicationContext(applicationContext);
    43  - return resolver;
    44  - }
     30 + @Bean
     31 + public ITemplateResolver springThymeleafTemplateResolver(ApplicationContext applicationContext) {
     32 + SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
     33 + resolver.setPrefix("classpath:/templates/");
     34 + resolver.setSuffix(".html");
     35 + resolver.setTemplateMode(TemplateMode.HTML);
     36 + resolver.setOrder(2);
     37 + resolver.setCacheable(false);
     38 + resolver.setCharacterEncoding(UTF8);
     39 + resolver.setApplicationContext(applicationContext);
     40 + return resolver;
     41 + }
    45 42   
    46  - @Bean
    47  - public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {
    48  - ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
    49  - viewResolver.setTemplateEngine(templateEngine);
    50  - viewResolver.setOrder(1);
    51  - viewResolver.setViewNames(new String[]{".html", ".xhtml"});
    52  - return viewResolver;
    53  - }
     43 + @Bean
     44 + public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {
     45 + ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
     46 + viewResolver.setTemplateEngine(templateEngine);
     47 + viewResolver.setOrder(1);
     48 + viewResolver.setViewNames(new String[] {".html", ".xhtml"});
     49 + return viewResolver;
     50 + }
    54 51   
    55  - @Bean
    56  - public TemplateGenerator generator(@Value("${asciidoctor.enabled}") boolean asciiDoctorEnabled) {
    57  - if (asciiDoctorEnabled) {
    58  - return new AsciiDocGenerator();
    59  - }
    60  - return new PreCompiledGenerator();
     52 + @Bean
     53 + public TemplateGenerator generator(@Value("${asciidoctor.enabled}") boolean asciiDoctorEnabled) {
     54 + if (asciiDoctorEnabled) {
     55 + return new AsciiDocGenerator();
    61 56   }
     57 + return new PreCompiledGenerator();
     58 + }
    62 59   
    63  - @Bean
    64  - public AsciiDoctorTemplateResolver asciiDoctorTemplateResolver(TemplateGenerator generator) {
    65  - AsciiDoctorTemplateResolver resolver = new AsciiDoctorTemplateResolver(generator);
    66  - resolver.setCacheable(false);
    67  - resolver.setOrder(1);
    68  - resolver.setCharacterEncoding(UTF8);
    69  - return resolver;
    70  - }
     60 + @Bean
     61 + public AsciiDoctorTemplateResolver asciiDoctorTemplateResolver(TemplateGenerator generator) {
     62 + AsciiDoctorTemplateResolver resolver = new AsciiDoctorTemplateResolver(generator);
     63 + resolver.setCacheable(false);
     64 + resolver.setOrder(1);
     65 + resolver.setCharacterEncoding(UTF8);
     66 + return resolver;
     67 + }
    71 68   
    72  - @Bean
    73  - public SpringTemplateEngine thymeleafTemplateEngine(ITemplateResolver springThymeleafTemplateResolver,
    74  - FileTemplateResolver asciiDoctorTemplateResolver) {
    75  - SpringTemplateEngine engine = new SpringTemplateEngine();
    76  - engine.setEnableSpringELCompiler(true);
    77  - engine.addDialect(new LayoutDialect());
    78  - engine.addDialect(new SpringSecurityDialect());
    79  - engine.setTemplateResolvers(
    80  - Set.of(asciiDoctorTemplateResolver, springThymeleafTemplateResolver));
    81  - return engine;
    82  - }
     69 + @Bean
     70 + public SpringTemplateEngine thymeleafTemplateEngine(
     71 + ITemplateResolver springThymeleafTemplateResolver,
     72 + FileTemplateResolver asciiDoctorTemplateResolver) {
     73 + SpringTemplateEngine engine = new SpringTemplateEngine();
     74 + engine.setEnableSpringELCompiler(true);
     75 + engine.addDialect(new LayoutDialect());
     76 + engine.addDialect(new SpringSecurityDialect());
     77 + engine.setTemplateResolvers(
     78 + Set.of(asciiDoctorTemplateResolver, springThymeleafTemplateResolver));
     79 + return engine;
     80 + }
    83 81  }
    84 82   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/RuntimeEnvironment.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
    3  -import lombok.Getter;
    4  -import org.owasp.wrongsecrets.challenges.Challenge;
    5  -import org.springframework.beans.factory.annotation.Autowired;
    6  -import org.springframework.beans.factory.annotation.Value;
    7  -import org.springframework.stereotype.Component;
     3 +import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.*;
    8 4   
    9 5  import java.util.Arrays;
    10 6  import java.util.Collections;
    11 7  import java.util.List;
    12 8  import java.util.Map;
    13  - 
    14  -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.*;
     9 +import lombok.Getter;
     10 +import org.owasp.wrongsecrets.challenges.Challenge;
     11 +import org.springframework.beans.factory.annotation.Autowired;
     12 +import org.springframework.beans.factory.annotation.Value;
     13 +import org.springframework.stereotype.Component;
    15 14   
    16 15  /**
    17  - * Class establishing whether a challenge can run or not depending on the given RuntimeEnvironment and whether components are configured and the CTFmode is enabled or not.
     16 + * Class establishing whether a challenge can run or not depending on the given RuntimeEnvironment
     17 + * and whether components are configured and the CTFmode is enabled or not.
    18 18   */
    19 19  @Component
    20 20  public class RuntimeEnvironment {
    21 21   
    22  - @Value("${ctf_enabled}")
    23  - private boolean ctfModeEnabled;
     22 + @Value("${ctf_enabled}")
     23 + private boolean ctfModeEnabled;
    24 24   
    25  - @Value("${SPECIAL_K8S_SECRET}")
    26  - private String challenge5Value; //used to determine if k8s/vault challenges are overriden;
     25 + @Value("${SPECIAL_K8S_SECRET}")
     26 + private String challenge5Value; // used to determine if k8s/vault challenges are overriden;
    27 27   
    28  - @Value("${vaultpassword}")
    29  - private String challenge7Value;
     28 + @Value("${vaultpassword}")
     29 + private String challenge7Value;
    30 30   
    31  - @Value("${default_aws_value_challenge_9}")
    32  - private String defaultChallenge9Value; //used to determine if the csloud challenge values are overriden
     31 + @Value("${default_aws_value_challenge_9}")
     32 + private String
     33 + defaultChallenge9Value; // used to determine if the csloud challenge values are overriden
    33 34   
    34  - private static final Map<Environment, List<Environment>> envToOverlappingEnvs = Map.of(FLY_DOCKER, List.of(DOCKER, FLY_DOCKER), HEROKU_DOCKER, List.of(DOCKER, HEROKU_DOCKER), DOCKER, List.of(DOCKER, HEROKU_DOCKER, FLY_DOCKER), GCP, List.of(DOCKER, K8S, VAULT), AWS, List.of(DOCKER, K8S, VAULT), AZURE, List.of(DOCKER, K8S, VAULT), VAULT, List.of(DOCKER, K8S), K8S, List.of(DOCKER), OKTETO_K8S, List.of(K8S, DOCKER, OKTETO_K8S));
     35 + private static final Map<Environment, List<Environment>> envToOverlappingEnvs =
     36 + Map.of(
     37 + FLY_DOCKER,
     38 + List.of(DOCKER, FLY_DOCKER),
     39 + HEROKU_DOCKER,
     40 + List.of(DOCKER, HEROKU_DOCKER),
     41 + DOCKER,
     42 + List.of(DOCKER, HEROKU_DOCKER, FLY_DOCKER),
     43 + GCP,
     44 + List.of(DOCKER, K8S, VAULT),
     45 + AWS,
     46 + List.of(DOCKER, K8S, VAULT),
     47 + AZURE,
     48 + List.of(DOCKER, K8S, VAULT),
     49 + VAULT,
     50 + List.of(DOCKER, K8S),
     51 + K8S,
     52 + List.of(DOCKER),
     53 + OKTETO_K8S,
     54 + List.of(K8S, DOCKER, OKTETO_K8S));
    35 55   
    36  - /**
    37  - * Enum with possible environments supported by the app.
    38  - */
    39  - public enum Environment {
    40  - DOCKER("Docker"), HEROKU_DOCKER("Heroku(Docker)"), FLY_DOCKER("Fly(Docker)"), GCP("gcp"), AWS("aws"), AZURE("azure"), VAULT("k8s-with-vault"), K8S("k8s"), OKTETO_K8S("Okteto(k8s)");
     56 + /** Enum with possible environments supported by the app. */
     57 + public enum Environment {
     58 + DOCKER("Docker"),
     59 + HEROKU_DOCKER("Heroku(Docker)"),
     60 + FLY_DOCKER("Fly(Docker)"),
     61 + GCP("gcp"),
     62 + AWS("aws"),
     63 + AZURE("azure"),
     64 + VAULT("k8s-with-vault"),
     65 + K8S("k8s"),
     66 + OKTETO_K8S("Okteto(k8s)");
    41 67   
    42  - private final String id;
     68 + private final String id;
    43 69   
    44  - Environment(String id) {
    45  - this.id = id;
    46  - }
     70 + Environment(String id) {
     71 + this.id = id;
     72 + }
    47 73   
    48  - static Environment fromId(String id) {
    49  - return Arrays.stream(Environment.values()).filter(e -> e.id.equalsIgnoreCase(id)).findAny().get();
    50  - }
     74 + static Environment fromId(String id) {
     75 + return Arrays.stream(Environment.values())
     76 + .filter(e -> e.id.equalsIgnoreCase(id))
     77 + .findAny()
     78 + .get();
    51 79   }
     80 + }
    52 81   
    53  - @Getter
    54  - private final Environment runtimeEnvironment;
     82 + @Getter private final Environment runtimeEnvironment;
    55 83   
    56  - @Autowired
    57  - public RuntimeEnvironment(@Value("${K8S_ENV}") String currentRuntimeEnvironment) {
    58  - this.runtimeEnvironment = Environment.fromId(currentRuntimeEnvironment);
    59  - }
     84 + @Autowired
     85 + public RuntimeEnvironment(@Value("${K8S_ENV}") String currentRuntimeEnvironment) {
     86 + this.runtimeEnvironment = Environment.fromId(currentRuntimeEnvironment);
     87 + }
    60 88   
    61  - public RuntimeEnvironment(Environment runtimeEnvironment) {
    62  - this.runtimeEnvironment = runtimeEnvironment;
    63  - }
     89 + public RuntimeEnvironment(Environment runtimeEnvironment) {
     90 + this.runtimeEnvironment = runtimeEnvironment;
     91 + }
    64 92   
    65  - public boolean canRun(Challenge challenge) {
    66  - if (isCloudUnlockedInCTFMode()) {
    67  - return true;
    68  - }
    69  - if (isVaultUnlockedInCTFMode() && isK8sUnlockedInCTFMode()) {
    70  - return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
    71  - || challenge.supportedRuntimeEnvironments().contains(DOCKER)
    72  - || challenge.supportedRuntimeEnvironments().contains(K8S)
    73  - || challenge.supportedRuntimeEnvironments().contains(VAULT);
    74  - }
    75  - if (isK8sUnlockedInCTFMode()) {
    76  - return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
    77  - || challenge.supportedRuntimeEnvironments().contains(DOCKER)
    78  - || challenge.supportedRuntimeEnvironments().contains(K8S);
    79  - }
    80  - return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
    81  - || !Collections.disjoint(envToOverlappingEnvs.get(runtimeEnvironment), challenge.supportedRuntimeEnvironments());
     93 + public boolean canRun(Challenge challenge) {
     94 + if (isCloudUnlockedInCTFMode()) {
     95 + return true;
    82 96   }
    83  - 
    84  - public boolean runtimeInCTFMode() {
    85  - return ctfModeEnabled;
     97 + if (isVaultUnlockedInCTFMode() && isK8sUnlockedInCTFMode()) {
     98 + return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
     99 + || challenge.supportedRuntimeEnvironments().contains(DOCKER)
     100 + || challenge.supportedRuntimeEnvironments().contains(K8S)
     101 + || challenge.supportedRuntimeEnvironments().contains(VAULT);
    86 102   }
     103 + if (isK8sUnlockedInCTFMode()) {
     104 + return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
     105 + || challenge.supportedRuntimeEnvironments().contains(DOCKER)
     106 + || challenge.supportedRuntimeEnvironments().contains(K8S);
     107 + }
     108 + return challenge.supportedRuntimeEnvironments().contains(runtimeEnvironment)
     109 + || !Collections.disjoint(
     110 + envToOverlappingEnvs.get(runtimeEnvironment), challenge.supportedRuntimeEnvironments());
     111 + }
    87 112   
    88  - private boolean isK8sUnlockedInCTFMode() {
    89  - String defaultValueChallenge5 = "if_you_see_this_please_use_k8s";
    90  - return ctfModeEnabled && !challenge5Value.equals(defaultValueChallenge5);
    91  - }
     113 + public boolean runtimeInCTFMode() {
     114 + return ctfModeEnabled;
     115 + }
     116 + 
     117 + private boolean isK8sUnlockedInCTFMode() {
     118 + String defaultValueChallenge5 = "if_you_see_this_please_use_k8s";
     119 + return ctfModeEnabled && !challenge5Value.equals(defaultValueChallenge5);
     120 + }
    92 121   
    93  - private boolean isVaultUnlockedInCTFMode() {
    94  - String defaultVaultAnswer = "ACTUAL_ANSWER_CHALLENGE7";
    95  - String secondDefaultVaultAnswer = "if_you_see_this_please_use_K8S_and_Vault";
    96  - return ctfModeEnabled && !challenge7Value.equals(defaultVaultAnswer) && !challenge7Value.equals(secondDefaultVaultAnswer);
    97  - }
     122 + private boolean isVaultUnlockedInCTFMode() {
     123 + String defaultVaultAnswer = "ACTUAL_ANSWER_CHALLENGE7";
     124 + String secondDefaultVaultAnswer = "if_you_see_this_please_use_K8S_and_Vault";
     125 + return ctfModeEnabled
     126 + && !challenge7Value.equals(defaultVaultAnswer)
     127 + && !challenge7Value.equals(secondDefaultVaultAnswer);
     128 + }
    98 129   
    99  - private boolean isCloudUnlockedInCTFMode() {
    100  - String defaultValueAWSValue = "if_you_see_this_please_use_AWS_Setup";
    101  - return ctfModeEnabled && !defaultChallenge9Value.equals(defaultValueAWSValue);
    102  - }
     130 + private boolean isCloudUnlockedInCTFMode() {
     131 + String defaultValueAWSValue = "if_you_see_this_please_use_AWS_Setup";
     132 + return ctfModeEnabled && !defaultChallenge9Value.equals(defaultValueAWSValue);
     133 + }
    103 134  }
    104 135   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/ScoreCard.java
    skipped 1 lines
    2 2   
    3 3  import org.owasp.wrongsecrets.challenges.Challenge;
    4 4   
    5  -/**
    6  - * Interface of a scorecard where a player's progress is stored into.
    7  - */
     5 +/** Interface of a scorecard where a player's progress is stored into. */
    8 6  public interface ScoreCard {
    9 7   
    10  - /**
    11  - * Marks a challenge as completed.
    12  - * @param challenge Challenge object which is completed
    13  - */
    14  - void completeChallenge(Challenge challenge);
     8 + /**
     9 + * Marks a challenge as completed.
     10 + *
     11 + * @param challenge Challenge object which is completed
     12 + */
     13 + void completeChallenge(Challenge challenge);
    15 14   
    16  - /**
    17  - * Checks if the given challenge is marked as completed in the scorecard.
    18  - * @param challenge Challenge object tested for completion
    19  - * @return true if challenge solved correctly
    20  - */
    21  - boolean getChallengeCompleted(Challenge challenge);
     15 + /**
     16 + * Checks if the given challenge is marked as completed in the scorecard.
     17 + *
     18 + * @param challenge Challenge object tested for completion
     19 + * @return true if challenge solved correctly
     20 + */
     21 + boolean getChallengeCompleted(Challenge challenge);
    22 22   
    23  - /**
    24  - * Gives a 0-100 implementation completeness score.
    25  - * @return float with completeness percentage
    26  - */
    27  - float getProgress();
     23 + /**
     24 + * Gives a 0-100 implementation completeness score.
     25 + *
     26 + * @return float with completeness percentage
     27 + */
     28 + float getProgress();
    28 29   
    29  - /**
    30  - * Gives total number of received points.
    31  - * @return int with points
    32  - */
    33  - int getTotalReceivedPoints();
     30 + /**
     31 + * Gives total number of received points.
     32 + *
     33 + * @return int with points
     34 + */
     35 + int getTotalReceivedPoints();
    34 36   
    35  - /**
    36  - * Resets the status of a given challenge its entry in the score-card.
    37  - * @param challenge Challenge of which the status should be reset.
    38  - */
    39  - void reset(Challenge challenge);
     37 + /**
     38 + * Resets the status of a given challenge its entry in the score-card.
     39 + *
     40 + * @param challenge Challenge of which the status should be reset.
     41 + */
     42 + void reset(Challenge challenge);
    40 43  }
    41 44   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/SecretsErrorController.java
    skipped 3 lines
    4 4  import org.springframework.boot.web.servlet.error.ErrorController;
    5 5  import org.springframework.stereotype.Controller;
    6 6  import org.springframework.web.bind.annotation.GetMapping;
    7  -import org.springframework.web.bind.annotation.RequestMapping;
    8 7   
    9  -/**
    10  - * Controller used to generate content for the error page.
    11  - */
     8 +/** Controller used to generate content for the error page. */
    12 9  @Controller
    13 10  public class SecretsErrorController implements ErrorController {
    14 11   
    15  - @GetMapping("/error")
    16  - @Operation(summary = "Returns data for the error page")
    17  - public String handleError() {
    18  - return "error";
    19  - }
     12 + @GetMapping("/error")
     13 + @Operation(summary = "Returns data for the error page")
     14 + public String handleError() {
     15 + return "error";
     16 + }
    20 17  }
    21 18   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/SecurityHeaderAddingFilter.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
    3  -import org.springframework.stereotype.Component;
    4  - 
    5 3  import jakarta.servlet.*;
    6 4  import jakarta.servlet.http.HttpServletResponse;
    7 5  import java.io.IOException;
     6 +import org.springframework.stereotype.Component;
    8 7   
    9  -/**
    10  - * Filter used to provide basic security headers in all cases.
    11  - */
     8 +/** Filter used to provide basic security headers in all cases. */
    12 9  @Component
    13 10  public class SecurityHeaderAddingFilter implements Filter {
    14 11   
    15  - @Override
    16  - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    17  - HttpServletResponse res = (HttpServletResponse) response;
    18  - res.addHeader("Server", "WrongSecrets - Star us!");
    19  - res.addHeader("X-Frame-Options", "SAMEORIGIN");
    20  - res.addHeader("X-Content-Type-Options", "nosniff");
    21  - res.addHeader("Content-Security-Policy", "default-src * 'self'; script-src * 'self' 'unsafe-inline'; style-src * 'self' 'unsafe-inline'; img-src data:");
    22  - chain.doFilter(request, res);
    23  - }
     12 + @Override
     13 + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
     14 + throws IOException, ServletException {
     15 + HttpServletResponse res = (HttpServletResponse) response;
     16 + res.addHeader("Server", "WrongSecrets - Star us!");
     17 + res.addHeader("X-Frame-Options", "SAMEORIGIN");
     18 + res.addHeader("X-Content-Type-Options", "nosniff");
     19 + res.addHeader(
     20 + "Content-Security-Policy",
     21 + "default-src * 'self'; script-src * 'self' 'unsafe-inline'; style-src * 'self'"
     22 + + " 'unsafe-inline'; img-src data:");
     23 + chain.doFilter(request, res);
     24 + }
    24 25  }
    25 26   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/SessionConfiguration.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
    3  -import lombok.extern.slf4j.Slf4j;
    4  -import org.springframework.context.annotation.Bean;
    5  -import org.springframework.context.annotation.Configuration;
    6  - 
    7 3  import jakarta.servlet.http.HttpSessionEvent;
    8 4  import jakarta.servlet.http.HttpSessionListener;
    9 5  import java.util.concurrent.atomic.AtomicInteger;
     6 +import lombok.extern.slf4j.Slf4j;
     7 +import org.springframework.context.annotation.Bean;
     8 +import org.springframework.context.annotation.Configuration;
    10 9   
    11 10  /**
    12  - * HTTPSessionListener with decorator: adds logging on new sessions started so we can keep track somewhat.
     11 + * HTTPSessionListener with decorator: adds logging on new sessions started so we can keep track
     12 + * somewhat.
    13 13   */
    14 14  @Configuration
    15 15  @Slf4j
    16 16  public class SessionConfiguration {
    17 17   
    18  - private static final AtomicInteger numberOfSessions = new AtomicInteger(0);
     18 + private static final AtomicInteger numberOfSessions = new AtomicInteger(0);
    19 19   
    20  - @Bean
    21  - public HttpSessionListener httpSessionListener() {
    22  - return new HttpSessionListener() {
    23  - @Override
    24  - public void sessionCreated(HttpSessionEvent hse) {
    25  - log.info("Session created, currently there are {} sessions active", numberOfSessions.incrementAndGet());
    26  - }
     20 + @Bean
     21 + public HttpSessionListener httpSessionListener() {
     22 + return new HttpSessionListener() {
     23 + @Override
     24 + public void sessionCreated(HttpSessionEvent hse) {
     25 + log.info(
     26 + "Session created, currently there are {} sessions active",
     27 + numberOfSessions.incrementAndGet());
     28 + }
    27 29   
    28  - @Override
    29  - public void sessionDestroyed(HttpSessionEvent hse) {
    30  - log.info("Session destroyed, currently there are {} sessions active", numberOfSessions.decrementAndGet());
    31  - }
    32  - };
    33  - }
     30 + @Override
     31 + public void sessionDestroyed(HttpSessionEvent hse) {
     32 + log.info(
     33 + "Session destroyed, currently there are {} sessions active",
     34 + numberOfSessions.decrementAndGet());
     35 + }
     36 + };
     37 + }
    34 38   
    35  - public AtomicInteger getCounter() {
    36  - return numberOfSessions;
    37  - }
     39 + public AtomicInteger getCounter() {
     40 + return numberOfSessions;
     41 + }
    38 42  }
    39 43   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/StartupListener.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
     3 +import java.util.Arrays;
     4 +import java.util.stream.Collectors;
    3 5  import lombok.experimental.UtilityClass;
    4 6  import lombok.extern.slf4j.Slf4j;
    5 7  import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
    6 8  import org.springframework.context.ApplicationEvent;
    7 9  import org.springframework.context.ApplicationListener;
    8 10   
    9  -import java.util.Arrays;
    10  -import java.util.stream.Collectors;
    11  - 
    12  -/**
    13  - * Helps handling application startup and breaks nicely if K8S_ENV is wrong.
    14  - */
     11 +/** Helps application startup and breaks nicely if K8S_ENV is wrong. */
    15 12  @Slf4j
    16 13  public class StartupListener implements ApplicationListener<ApplicationEvent> {
    17 14   
    18  - @Override
    19  - public void onApplicationEvent(final ApplicationEvent event) {
    20  - if (event instanceof ApplicationEnvironmentPreparedEvent envEvent) {
    21  - if (!StartupHelper.passedCorrectEnv(envEvent.getEnvironment().getProperty("K8S_ENV"))) {
    22  - log.error("K8S_ENV does not contain one of the expected values: {}.", StartupHelper.envsToReadableString());
    23  - throw new FailtoStartupException("K8S_ENV does not contain one of the expected values");
    24  - }
    25  - }
     15 + @Override
     16 + public void onApplicationEvent(final ApplicationEvent event) {
     17 + if (event instanceof ApplicationEnvironmentPreparedEvent envEvent) {
     18 + if (!StartupHelper.passedCorrectEnv(envEvent.getEnvironment().getProperty("K8S_ENV"))) {
     19 + log.error(
     20 + "K8S_ENV does not contain one of the expected values: {}.",
     21 + StartupHelper.envsToReadableString());
     22 + throw new FailtoStartupException("K8S_ENV does not contain one of the expected values");
     23 + }
    26 24   }
    27  - 
    28  - @UtilityClass
    29  - private class StartupHelper {
     25 + }
    30 26   
    31  - private boolean passedCorrectEnv(String k8sEnv) {
    32  - try {
    33  - RuntimeEnvironment.Environment.fromId(k8sEnv);
    34  - return true;
    35  - } catch (Exception e) {
    36  - return false;
    37  - }
    38  - }
     27 + @UtilityClass
     28 + private class StartupHelper {
    39 29   
    40  - private String envsToReadableString() {
    41  - return Arrays.stream(RuntimeEnvironment.Environment.values())
    42  - .map(Enum::toString)
    43  - .collect(Collectors.joining(", "));
    44  - }
     30 + private boolean passedCorrectEnv(String k8sEnv) {
     31 + try {
     32 + RuntimeEnvironment.Environment.fromId(k8sEnv);
     33 + return true;
     34 + } catch (Exception e) {
     35 + return false;
     36 + }
    45 37   }
    46 38   
     39 + private String envsToReadableString() {
     40 + return Arrays.stream(RuntimeEnvironment.Environment.values())
     41 + .map(Enum::toString)
     42 + .collect(Collectors.joining(", "));
     43 + }
     44 + }
    47 45  }
    48 46   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/StatsController.java
    skipped 7 lines
    8 8  import org.springframework.ui.Model;
    9 9  import org.springframework.web.bind.annotation.GetMapping;
    10 10   
    11  -/**
    12  - * Controller that is used to render data in the stats page.
    13  - */
     11 +/** Controller that is used to render data in the stats page. */
    14 12  @Controller
    15 13  public class StatsController {
    16 14   
    17  - @Autowired
    18  - private CanaryCounter canaryCounter;
    19  - @Autowired
    20  - private SessionConfiguration sessionConfiguration;
     15 + @Autowired private CanaryCounter canaryCounter;
     16 + @Autowired private SessionConfiguration sessionConfiguration;
    21 17   
    22  - @Value("${hints_enabled}")
    23  - private boolean hintsEnabled;
    24  - @Value("${reason_enabled}")
    25  - private boolean reasonEnabled;
    26  - @Value("${ctf_enabled}")
    27  - private boolean ctfModeEnabled;
     18 + @Value("${hints_enabled}")
     19 + private boolean hintsEnabled;
    28 20   
    29  - @Value("${spoiling_enabled}")
    30  - private boolean spoilingEnabled;
     21 + @Value("${reason_enabled}")
     22 + private boolean reasonEnabled;
    31 23   
    32  - @Value("${springdoc.swagger-ui.enabled}")
    33  - private boolean swaggerUIEnabled;
     24 + @Value("${ctf_enabled}")
     25 + private boolean ctfModeEnabled;
    34 26   
    35  - @Value("${springdoc.api-docs.enabled}")
    36  - private boolean springdocenabled;
     27 + @Value("${spoiling_enabled}")
     28 + private boolean spoilingEnabled;
    37 29   
     30 + @Value("${springdoc.swagger-ui.enabled}")
     31 + private boolean swaggerUIEnabled;
    38 32   
    39  - @Value("${canarytokenURLs}")
    40  - private String[] canaryTokenURLs;
     33 + @Value("${springdoc.api-docs.enabled}")
     34 + private boolean springdocenabled;
    41 35   
    42  - @Value("${springdoc.swagger-ui.path}")
    43  - private String swaggerURI;
     36 + @Value("${canarytokenURLs}")
     37 + private String[] canaryTokenURLs;
    44 38   
     39 + @Value("${springdoc.swagger-ui.path}")
     40 + private String swaggerURI;
     41 + 
     42 + @GetMapping("/stats")
     43 + @Operation(description = "Returns all dynamic data for the stats screen")
     44 + public String getStats(Model model) {
     45 + model.addAttribute("canaryCounter", canaryCounter.getTotalCount());
     46 + model.addAttribute("sessioncounter", sessionConfiguration.getCounter());
     47 + model.addAttribute("lastCanaryToken", canaryCounter.getLastToken());
     48 + model.addAttribute("canarytokenURLs", canaryTokenURLs);
     49 + model.addAttribute("hintsEnabled", hintsEnabled);
     50 + model.addAttribute("reasonEnabled", reasonEnabled);
     51 + model.addAttribute("ctfModeEnabled", ctfModeEnabled);
     52 + model.addAttribute("spoilingEnabled", spoilsEnabled());
     53 + model.addAttribute("swaggerUIEnabled", swaggerUIEnabled);
     54 + model.addAttribute("springdocenabled", springdocenabled);
     55 + model.addAttribute("swaggerURI", swaggerURI);
     56 + return "stats";
     57 + }
    45 58   
    46  - @GetMapping("/stats")
    47  - @Operation(description = "Returns all dynamic data for the stats screen")
    48  - public String getStats(Model model) {
    49  - model.addAttribute("canaryCounter", canaryCounter.getTotalCount());
    50  - model.addAttribute("sessioncounter", sessionConfiguration.getCounter());
    51  - model.addAttribute("lastCanaryToken", canaryCounter.getLastToken());
    52  - model.addAttribute("canarytokenURLs", canaryTokenURLs);
    53  - model.addAttribute("hintsEnabled", hintsEnabled);
    54  - model.addAttribute("reasonEnabled", reasonEnabled);
    55  - model.addAttribute("ctfModeEnabled", ctfModeEnabled);
    56  - model.addAttribute("spoilingEnabled", spoilingEnabled);
    57  - model.addAttribute("swaggerUIEnabled", swaggerUIEnabled);
    58  - model.addAttribute("springdocenabled", springdocenabled);
    59  - model.addAttribute("swaggerURI", swaggerURI);
    60  - return "stats";
    61  - }
     59 + private boolean spoilsEnabled() {
     60 + return spoilingEnabled && !ctfModeEnabled;
     61 + }
    62 62  }
    63 63   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/WrongSecretsApplication.java
    1 1  package org.owasp.wrongsecrets;
    2 2   
    3  -import org.owasp.wrongsecrets.challenges.ChallengesController;
     3 +import java.util.List;
     4 +import lombok.extern.slf4j.Slf4j;
     5 +import org.owasp.wrongsecrets.challenges.Challenge;
    4 6  import org.owasp.wrongsecrets.challenges.kubernetes.Vaultpassword;
    5  -import org.owasp.wrongsecrets.oauth.TokenController;
    6  -import org.springdoc.core.utils.SpringDocUtils;
    7 7  import org.springframework.boot.SpringApplication;
    8 8  import org.springframework.boot.autoconfigure.SpringBootApplication;
    9 9  import org.springframework.boot.context.properties.EnableConfigurationProperties;
    skipped 3 lines
    13 13   
    14 14  @SpringBootApplication
    15 15  @EnableConfigurationProperties(Vaultpassword.class)
     16 +@Slf4j
    16 17  public class WrongSecretsApplication {
    17 18   
    18  - public static void main(String[] args) {
    19  - SpringApplication.run(WrongSecretsApplication.class, args);
    20  - }
     19 + public static void main(String[] args) {
     20 + SpringApplication.run(WrongSecretsApplication.class, args);
     21 + }
    21 22   
    22  - @Bean
    23  - @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
    24  - public InMemoryScoreCard scoreCard() {
    25  - return new InMemoryScoreCard(28);
    26  - }
    27  - 
    28  - static {
    29  - SpringDocUtils.getConfig().addRestControllers(AboutController.class, ChallengesController.class, IndexController.class, StatsController.class, TokenController.class);
    30  - }
    31  - 
     23 + @Bean
     24 + @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
     25 + public InMemoryScoreCard scoreCard(List<Challenge> challenges) {
     26 + log.info("Initializing scorecard with {} challenges", challenges.size());
     27 + return new InMemoryScoreCard(challenges.size());
     28 + }
    32 29  }
    33 30   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/asciidoc/AsciiDocGenerator.java
    1 1  package org.owasp.wrongsecrets.asciidoc;
    2 2   
    3  -import org.asciidoctor.Asciidoctor;
    4  -import org.asciidoctor.Options;
    5  -import org.springframework.core.io.ClassPathResource;
     3 +import static org.asciidoctor.Asciidoctor.Factory.create;
    6 4   
    7 5  import java.io.IOException;
    8 6  import java.io.InputStreamReader;
    9 7  import java.io.StringWriter;
    10 8  import java.nio.charset.StandardCharsets;
    11  - 
    12  -import static org.asciidoctor.Asciidoctor.Factory.create;
     9 +import org.asciidoctor.Asciidoctor;
     10 +import org.asciidoctor.Options;
     11 +import org.springframework.core.io.ClassPathResource;
    13 12   
    14 13  /**
    15  - * Used for generating HTML out of asciidoc. Used for all Challenges' challenge texts, tips, and explanations.
     14 + * Used for generating HTML out of asciidoc. Used for all Challenges' challenge texts, tips, and
     15 + * explanations.
    16 16   */
    17 17  public class AsciiDocGenerator implements TemplateGenerator {
    18 18   
    19  - private static final Asciidoctor asciidoctor = create();
     19 + private static final Asciidoctor asciidoctor = create();
    20 20   
    21  - @Override
    22  - public String generate(String name) throws IOException {
    23  - var templateFile = name + ".adoc";
    24  - try (var is = new ClassPathResource(templateFile).getInputStream()) {
    25  - var writer = new StringWriter();
    26  - asciidoctor.convert(new InputStreamReader(is, StandardCharsets.UTF_8), writer, Options.builder().build());
    27  - return writer.toString();
    28  - }
     21 + @Override
     22 + public String generate(String name) throws IOException {
     23 + var templateFile = name + ".adoc";
     24 + try (var is = new ClassPathResource(templateFile).getInputStream()) {
     25 + var writer = new StringWriter();
     26 + asciidoctor.convert(
     27 + new InputStreamReader(is, StandardCharsets.UTF_8), writer, Options.builder().build());
     28 + return writer.toString();
    29 29   }
     30 + }
    30 31  }
    31 32   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/asciidoc/AsciiDoctorTemplateResolver.java
    1 1  package org.owasp.wrongsecrets.asciidoc;
    2 2   
     3 +import java.io.IOException;
     4 +import java.util.Map;
     5 +import java.util.Set;
    3 6  import lombok.extern.slf4j.Slf4j;
    4 7  import org.thymeleaf.IEngineConfiguration;
    5 8  import org.thymeleaf.templateresolver.FileTemplateResolver;
    6 9  import org.thymeleaf.templateresource.ITemplateResource;
    7 10  import org.thymeleaf.templateresource.StringTemplateResource;
    8  - 
    9  -import java.io.IOException;
    10  -import java.util.Map;
    11  -import java.util.Set;
    12 11   
    13 12  /**
    14 13   * Thymeleaf resolver for AsciiDoc used in the lesson, can be used as follows inside a lesson file.
    15  - * See <a href="https://github.com/OWASP/wrongsecrets#automatic-reload-during-development">The repo readme</a> for more details on how to use it
     14 + * See <a href="https://github.com/OWASP/wrongsecrets#automatic-reload-during-development">The repo
     15 + * readme</a> for more details on how to use it
    16 16   */
    17 17  @Slf4j
    18 18  public class AsciiDoctorTemplateResolver extends FileTemplateResolver {
    19 19   
    20  - private static final String PREFIX = "doc:";
    21  - private final TemplateGenerator generator;
     20 + private static final String PREFIX = "doc:";
     21 + private final TemplateGenerator generator;
    22 22   
    23  - public AsciiDoctorTemplateResolver(TemplateGenerator generator) {
    24  - this.generator = generator;
    25  - setResolvablePatterns(Set.of(PREFIX + "*"));
    26  - }
     23 + public AsciiDoctorTemplateResolver(TemplateGenerator generator) {
     24 + this.generator = generator;
     25 + setResolvablePatterns(Set.of(PREFIX + "*"));
     26 + }
    27 27   
    28  - @Override
    29  - protected ITemplateResource computeTemplateResource(IEngineConfiguration configuration, String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String, Object> templateResolutionAttributes) {
    30  - var templateName = resourceName.substring(PREFIX.length());
    31  - try {
    32  - return new StringTemplateResource(generator.generate(computeResourceName(templateName)));
    33  - } catch (IOException e) {
    34  - return new StringTemplateResource("");
    35  - }
     28 + @Override
     29 + protected ITemplateResource computeTemplateResource(
     30 + IEngineConfiguration configuration,
     31 + String ownerTemplate,
     32 + String template,
     33 + String resourceName,
     34 + String characterEncoding,
     35 + Map<String, Object> templateResolutionAttributes) {
     36 + var templateName = resourceName.substring(PREFIX.length());
     37 + try {
     38 + return new StringTemplateResource(generator.generate(computeResourceName(templateName)));
     39 + } catch (IOException e) {
     40 + return new StringTemplateResource("");
    36 41   }
     42 + }
    37 43   
    38  - private String computeResourceName(String resourceName) {
    39  - return String.format("explanations/%s", resourceName.replace(".adoc", ""));
    40  - }
     44 + private String computeResourceName(String resourceName) {
     45 + return String.format("explanations/%s", resourceName.replace(".adoc", ""));
     46 + }
    41 47  }
    42 48   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/asciidoc/PreCompiledGenerator.java
    1 1  package org.owasp.wrongsecrets.asciidoc;
    2 2   
     3 +import java.io.IOException;
     4 +import java.nio.charset.StandardCharsets;
    3 5  import lombok.extern.slf4j.Slf4j;
    4 6  import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
    5 7  import org.springframework.core.io.ClassPathResource;
    6 8  import org.springframework.util.FileCopyUtils;
    7 9   
    8  -import java.io.IOException;
    9  -import java.nio.charset.StandardCharsets;
    10  - 
    11 10  /**
    12  - * Default enabled template rendering class which uses the HTML files
    13  - * Note that Ascidoc files need to be converted to HTML,
    14  - * use `mvn package` or `mvn install` to make sure they are generated.
     11 + * Default enabled template rendering class which uses the HTML files Note that Ascidoc files need
     12 + * to be converted to HTML, use `mvn package` or `mvn install` to make sure they are generated.
    15 13   */
    16 14  @Slf4j
    17 15  public class PreCompiledGenerator implements TemplateGenerator {
    18 16   
    19  - @Override
    20  - public String generate(String name) throws IOException {
    21  - var templateFile = name + ".html";
     17 + @Override
     18 + public String generate(String name) throws IOException {
     19 + var templateFile = name + ".html";
    22 20   
    23  - try (var bos = new ByteArrayOutputStream()) {
    24  - FileCopyUtils.copy(new ClassPathResource(templateFile).getInputStream(), bos);
    25  - return new String(bos.toByteArray(), StandardCharsets.UTF_8);
    26  - }
     21 + try (var bos = new ByteArrayOutputStream()) {
     22 + FileCopyUtils.copy(new ClassPathResource(templateFile).getInputStream(), bos);
     23 + return new String(bos.toByteArray(), StandardCharsets.UTF_8);
    27 24   }
     25 + }
    28 26  }
    29 27   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/asciidoc/TemplateGenerator.java
    skipped 1 lines
    2 2   
    3 3  import java.io.IOException;
    4 4   
    5  -/**
    6  - * Template generator used for Asciidoc to HTML conversion.
    7  - */
     5 +/** Template generator used for Asciidoc to HTML conversion. */
    8 6  public interface TemplateGenerator {
    9 7   
    10  - String generate(String name) throws IOException;
     8 + String generate(String name) throws IOException;
    11 9  }
    12 10   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/canaries/AdditionalCanaryData.java
    skipped 6 lines
    7 7  /**
    8 8   * Canarytokens used to communicate with <a href="http://canarytokens.com/">canarytokens.com</a>.
    9 9   * canarytokens.com will send a CanaryToken with this AdditionalCanaryData.
     10 + *
    10 11   * @see org.owasp.wrongsecrets.canaries.CanaryToken
    11 12   */
    12 13  @RequiredArgsConstructor
    13 14  @Getter
    14 15  public class AdditionalCanaryData {
    15 16   
    16  - @JsonProperty("src_ip")
    17  - private final String srcIp;
    18  - private final String useragent;
    19  - private final String referer;
    20  - private final String location;
     17 + @JsonProperty("src_ip")
     18 + private final String srcIp;
    21 19   
     20 + private final String useragent;
     21 + private final String referer;
     22 + private final String location;
    22 23  }
    23 24  /*
    24 25  {"manage_url": "http://canarytokens.org/manage?token=y0all60b627gzp19ahqh7rl6j&auth=09193ea6b8def3e27a1a41f98d4265d7",
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/canaries/CanariesController.java
    skipped 14 lines
    15 15  import org.springframework.web.bind.annotation.RequestBody;
    16 16  import org.springframework.web.bind.annotation.RestController;
    17 17   
    18  -/**
    19  - * Restcontroller used to accept calls from canarytokens.com
    20  - */
     18 +/** Restcontroller used to accept calls from canarytokens.com */
    21 19  @Slf4j
    22 20  @RestController
    23 21  public class CanariesController {
    24 22   
    25  - @Autowired
    26  - CanaryCounter canaryCounter;
     23 + @Autowired CanaryCounter canaryCounter;
    27 24   
    28  - @PostMapping(path = "/canaries/tokencallback", consumes = MediaType.APPLICATION_JSON_VALUE)
    29  - @Operation(summary = "Callback method for canarytokens.com",
    30  - requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Required token",
    31  - content = @Content(schema = @Schema(implementation = CanaryToken.class)), required = true)
    32  - )
    33  - public ResponseEntity<String> processCanaryToken(@RequestBody @Valid CanaryToken canaryToken) {
    34  - try {
    35  - String canarytokenContents = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(canaryToken);
    36  - log.info("Canarytoken callback called with following token: {}", canarytokenContents);
    37  - canaryCounter.upCallBackCounter();
    38  - canaryCounter.setLastCanaryToken(canarytokenContents);
    39  - } catch (JsonProcessingException e) {
    40  - log.warn("Exception with processing canarytoken: {}", e.getMessage());
    41  - }
    42  - log.info("Canarytoken called, with manage_url {}", canaryToken.getManageUrl());
    43  - log.info("Total number of canary callback calls: {}", canaryCounter.getTotalCount());
    44  - return new ResponseEntity<>("all good", HttpStatus.ACCEPTED);
     25 + @PostMapping(path = "/canaries/tokencallback", consumes = MediaType.APPLICATION_JSON_VALUE)
     26 + @Operation(
     27 + summary = "Callback method for canarytokens.com",
     28 + requestBody =
     29 + @io.swagger.v3.oas.annotations.parameters.RequestBody(
     30 + description = "Required token",
     31 + content = @Content(schema = @Schema(implementation = CanaryToken.class)),
     32 + required = true))
     33 + public ResponseEntity<String> processCanaryToken(@RequestBody @Valid CanaryToken canaryToken) {
     34 + try {
     35 + String canarytokenContents =
     36 + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(canaryToken);
     37 + log.info("Canarytoken callback called with following token: {}", canarytokenContents);
     38 + canaryCounter.upCallBackCounter();
     39 + canaryCounter.setLastCanaryToken(canarytokenContents);
     40 + } catch (JsonProcessingException e) {
     41 + log.warn("Exception with processing canarytoken: {}", e.getMessage());
    45 42   }
     43 + log.info("Canarytoken called, with manage_url {}", canaryToken.getManageUrl());
     44 + log.info("Total number of canary callback calls: {}", canaryCounter.getTotalCount());
     45 + return new ResponseEntity<>("all good", HttpStatus.ACCEPTED);
     46 + }
    46 47   
    47  - @PostMapping(path = "/canaries/tokencallbackdebug", consumes = MediaType.APPLICATION_JSON_VALUE)
    48  - @Operation(summary = "Callback method for canarytokens.com using unstructed data",
    49  - requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Required data",
    50  - content = @Content(schema = @Schema(implementation = String.class)), required = true))
    51  - public ResponseEntity<String> processCanaryTokendebug(@RequestBody String canarytokenContents) {
    52  - canaryCounter.upCallBackCounter();
    53  - canaryCounter.setLastCanaryToken(canarytokenContents);
    54  - return new ResponseEntity<>("all good", HttpStatus.ACCEPTED);
    55  - }
     48 + @PostMapping(path = "/canaries/tokencallbackdebug", consumes = MediaType.APPLICATION_JSON_VALUE)
     49 + @Operation(
     50 + summary = "Callback method for canarytokens.com using unstructed data",
     51 + requestBody =
     52 + @io.swagger.v3.oas.annotations.parameters.RequestBody(
     53 + description = "Required data",
     54 + content = @Content(schema = @Schema(implementation = String.class)),
     55 + required = true))
     56 + public ResponseEntity<String> processCanaryTokendebug(@RequestBody String canarytokenContents) {
     57 + canaryCounter.upCallBackCounter();
     58 + canaryCounter.setLastCanaryToken(canarytokenContents);
     59 + return new ResponseEntity<>("all good", HttpStatus.ACCEPTED);
     60 + }
    56 61  }
    57 62   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/canaries/CanaryCounter.java
    skipped 1 lines
    2 2   
    3 3  /**
    4 4   * Used for counting the number of canary-token based callbacks.
     5 + *
    5 6   * @see org.owasp.wrongsecrets.StatsController for the tracking controller
    6 7   * @see org.owasp.wrongsecrets.canaries.CanariesController for the calling controler
    7 8   */
    8 9  public interface CanaryCounter {
    9 10   
    10  - /**
    11  - * incement the counter of callbacks with 1.
    12  - */
    13  - void upCallBackCounter();
     11 + /** incement the counter of callbacks with 1. */
     12 + void upCallBackCounter();
    14 13   
    15  - /**
    16  - * Gets the total number of canary token callback calls.
    17  - * @return int ≥ 0
    18  - */
    19  - int getTotalCount();
     14 + /**
     15 + * Gets the total number of canary token callback calls.
     16 + *
     17 + * @return int ≥ 0
     18 + */
     19 + int getTotalCount();
    20 20   
    21  - /**
    22  - * Sets the content of the last token its contents as a Strings.
    23  - * @param tokenContent unprocessed contents of the callback (E.g. full data of the canarytoken)
    24  - */
    25  - void setLastCanaryToken(String tokenContent);
    26  - 
    27  - /**
    28  - * Returns the last token given during the callback invocation.
    29  - * @return unprocessed callback token contents
    30  - */
    31  - String getLastToken();
     21 + /**
     22 + * Sets the content of the last token its contents as a Strings.
     23 + *
     24 + * @param tokenContent unprocessed contents of the callback (E.g. full data of the canarytoken)
     25 + */
     26 + void setLastCanaryToken(String tokenContent);
    32 27   
     28 + /**
     29 + * Returns the last token given during the callback invocation.
     30 + *
     31 + * @return unprocessed callback token contents
     32 + */
     33 + String getLastToken();
    33 34  }
    34 35   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/canaries/CanaryCounterImpl.java
    1 1  package org.owasp.wrongsecrets.canaries;
    2 2   
     3 +import java.util.concurrent.atomic.AtomicInteger;
    3 4  import org.springframework.stereotype.Service;
    4 5   
    5  -import java.util.concurrent.atomic.AtomicInteger;
    6  - 
    7  -/**
    8  - * Implementation of CanaryCounter using an Atomic integer for actual implementation.
    9  - */
     6 +/** Implementation of CanaryCounter using an Atomic integer for actual implementation. */
    10 7  @Service
    11 8  public class CanaryCounterImpl implements CanaryCounter {
    12 9   
    13  - private static final AtomicInteger numberofCanaryCalls = new AtomicInteger(0);
    14  - 
    15  - private String lastToken;
     10 + private static final AtomicInteger numberofCanaryCalls = new AtomicInteger(0);
    16 11   
     12 + private String lastToken;
    17 13   
    18  - @Override
    19  - public void upCallBackCounter() {
    20  - numberofCanaryCalls.incrementAndGet();
    21  - }
     14 + @Override
     15 + public void upCallBackCounter() {
     16 + numberofCanaryCalls.incrementAndGet();
     17 + }
    22 18   
    23  - @Override
    24  - public int getTotalCount() {
    25  - return numberofCanaryCalls.get();
    26  - }
     19 + @Override
     20 + public int getTotalCount() {
     21 + return numberofCanaryCalls.get();
     22 + }
    27 23   
    28  - @Override
    29  - public void setLastCanaryToken(String tokenContent) {
    30  - lastToken = tokenContent;
    31  - }
     24 + @Override
     25 + public void setLastCanaryToken(String tokenContent) {
     26 + lastToken = tokenContent;
     27 + }
    32 28   
    33  - @Override
    34  - public String getLastToken() {
    35  - return lastToken;
    36  - }
     29 + @Override
     30 + public String getLastToken() {
     31 + return lastToken;
     32 + }
    37 33  }
    38 34   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/canaries/CanaryToken.java
    skipped 4 lines
    5 5  import lombok.RequiredArgsConstructor;
    6 6   
    7 7  /**
    8  - * Actual canaryToken as received by <a href="http://canarytokens.com/">canarytokens.com</a>
    9  - * Example content:
    10  - * json:
    11  - * {
    12  - * "manage_url": "http://canarytokens.org/manage?token....",
    13  - * "memo": "debugtoken",
    14  - * "additional_data":
    15  - * {
    16  - * "src_ip": "83.128.90.255",
    17  - * "useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36",
    18  - * "referer": null,
    19  - * "location": null
    20  - * },
    21  - * "channel": "HTTP",
    22  - * "time": "2022-03-07 06:14:36 (UTC)"
    23  - * }
     8 + * Actual canaryToken as received by <a href="http://canarytokens.com/">canarytokens.com</a> Example
     9 + * content: json: { "manage_url": "http://canarytokens.org/manage?token....", "memo": "debugtoken",
     10 + * "additional_data": { "src_ip": "83.128.90.255", "useragent": "Mozilla/5.0 (Windows NT 10.0;
     11 + * Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36",
     12 + * "referer": null, "location": null }, "channel": "HTTP", "time": "2022-03-07 06:14:36 (UTC)" }
     13 + *
    24 14   * @see org.owasp.wrongsecrets.canaries.AdditionalCanaryData
    25 15   */
    26 16  @RequiredArgsConstructor
    27 17  @Getter
    28 18  public class CanaryToken {
    29  - @JsonProperty("manage_url")
    30  - private final String manageUrl;
    31  - private final String memo;
    32  - private final String channel;
    33  - private final String time;
    34  - @JsonProperty("additional_data")
    35  - private final AdditionalCanaryData additionalData;
     19 + @JsonProperty("manage_url")
     20 + private final String manageUrl;
     21 + 
     22 + private final String memo;
     23 + private final String channel;
     24 + private final String time;
     25 + 
     26 + @JsonProperty("additional_data")
     27 + private final AdditionalCanaryData additionalData;
    36 28  }
    37 29   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/canaries/TokenCallbackSecurityConfiguration.java
    skipped 7 lines
    8 8  import org.springframework.security.web.SecurityFilterChain;
    9 9   
    10 10  /**
    11  - * Security configuration for the CanariesController: so that a CSRF token is not required for canarytokens.com
     11 + * Security configuration for the CanariesController: so that a CSRF token is not required for
     12 + * canarytokens.com
    12 13   */
    13 14  @Configuration
    14 15  public class TokenCallbackSecurityConfiguration {
    15 16   
    16  - @SuppressFBWarnings(value = "SPRING_CSRF_PROTECTION_DISABLED",justification = "There is no need for the token & canaries endpoints to have CSRF as it is only used for callbacks by canarytokens.org")
    17  - @Bean
    18  - @Order(0)
    19  - public SecurityFilterChain configureTokenCallbackSecurity(HttpSecurity http) throws Exception {
    20  - http.securityMatcher(r ->
    21  - r.getRequestURL().toString().contains("canaries") || r.getRequestURL().toString().contains("token"))
    22  - .csrf().disable();
    23  - return http.build();
    24  - }
     17 + @SuppressFBWarnings(
     18 + value = "SPRING_CSRF_PROTECTION_DISABLED",
     19 + justification =
     20 + "There is no need for the token & canaries endpoints to have CSRF as it is only used for"
     21 + + " callbacks by canarytokens.org")
     22 + @Bean
     23 + @Order(0)
     24 + public SecurityFilterChain configureTokenCallbackSecurity(HttpSecurity http) throws Exception {
     25 + http.securityMatcher(
     26 + r ->
     27 + r.getRequestURL().toString().contains("canaries")
     28 + || r.getRequestURL().toString().contains("token"))
     29 + .csrf()
     30 + .disable();
     31 + return http.build();
     32 + }
    25 33  }
    26 34   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java
    1 1  package org.owasp.wrongsecrets.challenges;
    2 2   
     3 +import java.util.List;
    3 4  import lombok.Getter;
    4 5  import lombok.RequiredArgsConstructor;
    5 6  import org.owasp.wrongsecrets.RuntimeEnvironment.Environment;
    6 7  import org.owasp.wrongsecrets.ScoreCard;
    7  - 
    8  -import java.util.List;
    9 8   
    10 9  /**
    11 10   * General Abstract Challenge class containing all the necessary members for a challenge.
     11 + *
    12 12   * @see org.owasp.wrongsecrets.ScoreCard for tracking
    13 13   */
    14 14  @RequiredArgsConstructor
    15 15  @Getter
    16 16  public abstract class Challenge {
    17 17   
    18  - private final ScoreCard scoreCard;
     18 + private final ScoreCard scoreCard;
    19 19   
    20  - /**
    21  - * Returns a Spoiler object containing the secret for the challenge.
    22  - * @return Spoiler with anser
    23  - */
    24  - public abstract Spoiler spoiler();
     20 + /**
     21 + * Returns a Spoiler object containing the secret for the challenge.
     22 + *
     23 + * @return Spoiler with anser
     24 + */
     25 + public abstract Spoiler spoiler();
    25 26   
    26  - /**
    27  - * method that needs to be overwritten by the Challenge implementation class to do the actual evaluation of the answer.
    28  - * @param answer String provided by the user
    29  - * @return true if answer is Correct
    30  - */
    31  - protected abstract boolean answerCorrect(String answer);
     27 + /**
     28 + * method that needs to be overwritten by the Challenge implementation class to do the actual
     29 + * evaluation of the answer.
     30 + *
     31 + * @param answer String provided by the user
     32 + * @return true if answer is Correct
     33 + */
     34 + protected abstract boolean answerCorrect(String answer);
    32 35   
    33  - /**
    34  - * Gives the supported runtime envs in which the class can run.
    35  - * @return a list of Environment objects representing supported envs for the class
    36  - */
    37  - public abstract List<Environment> supportedRuntimeEnvironments();
     36 + /**
     37 + * Gives the supported runtime envs in which the class can run.
     38 + *
     39 + * @return a list of Environment objects representing supported envs for the class
     40 + */
     41 + public abstract List<Environment> supportedRuntimeEnvironments();
    38 42   
    39  - /**
    40  - * returns the difficulty (1-5).
    41  - * @return int with difficulty
    42  - */
    43  - public abstract int difficulty();
     43 + /**
     44 + * returns the difficulty level.
     45 + *
     46 + * @return int with difficulty
     47 + */
     48 + public abstract int difficulty();
    44 49   
    45  - /**
    46  - * returns the technology used.
    47  - * @see ChallengeTechnology.Tech
    48  - * @return a string from Tech.id
    49  - */
    50  - public abstract String getTech();
     50 + /**
     51 + * returns the technology used.
     52 + *
     53 + * @see ChallengeTechnology.Tech
     54 + * @return a string from Tech.id
     55 + */
     56 + public abstract String getTech();
    51 57   
    52  - /**
    53  - * boolean indicating a challenge needs to be run differently with a different explanation/steps when running on a shared platform.
    54  - * @return boolean with true if a different explanation is required when running on a shared platform
    55  - */
    56  - public abstract boolean isLimittedWhenOnlineHosted();
     58 + /**
     59 + * boolean indicating a challenge needs to be run differently with a different explanation/steps
     60 + * when running on a shared platform.
     61 + *
     62 + * @return boolean with true if a different explanation is required when running on a shared
     63 + * platform
     64 + */
     65 + public abstract boolean isLimitedWhenOnlineHosted();
    57 66   
    58  - /**
    59  - * boolean indicating if the challenge can be enabled when running in CTF mode.
    60  - * Note: All challenges should be able to run in non-CTF mode.
    61  - * @return true if the challenge can be run in CTF mode.
    62  - */
    63  - public abstract boolean canRunInCTFMode();
     67 + /**
     68 + * boolean indicating if the challenge can be enabled when running in CTF mode. Note: All
     69 + * challenges should be able to run in non-CTF mode.
     70 + *
     71 + * @return true if the challenge can be run in CTF mode.
     72 + */
     73 + public abstract boolean canRunInCTFMode();
    64 74   
    65  - /**
    66  - * Solving method which, if the correct answer is provided, will mark the challenge as solved in the scorecard.
    67  - * @param answer String provided by the user to validate.
    68  - * @return true if answer was correct.
    69  - */
    70  - public boolean solved(String answer) {
    71  - var correctAnswer = answerCorrect(answer);
    72  - if (correctAnswer) {
    73  - scoreCard.completeChallenge(this);
    74  - }
    75  - return correctAnswer;
     75 + /**
     76 + * Solving method which, if the correct answer is provided, will mark the challenge as solved in
     77 + * the scorecard.
     78 + *
     79 + * @param answer String provided by the user to validate.
     80 + * @return true if answer was correct.
     81 + */
     82 + public boolean solved(String answer) {
     83 + var correctAnswer = answerCorrect(answer);
     84 + if (correctAnswer) {
     85 + scoreCard.completeChallenge(this);
    76 86   }
     87 + return correctAnswer;
     88 + }
    77 89   
    78  - /**
    79  - * Returns the name of the explanation file for adoc rendering.
    80  - * @return String with name of file for explanation
    81  - */
    82  - public String getExplanation() {
    83  - return this.getClass().getSimpleName().toLowerCase();
    84  - }
     90 + /**
     91 + * Returns the name of the explanation file for adoc rendering.
     92 + *
     93 + * @return String with name of file for explanation
     94 + */
     95 + public String getExplanation() {
     96 + return this.getClass().getSimpleName().toLowerCase();
     97 + }
    85 98   
    86  - /**
    87  - * Returns the name of the hints file for adoc rendering.
    88  - * @return String with name of file for hints
    89  - */
    90  - public String getHint() {
    91  - return this.getClass().getSimpleName().toLowerCase() + "_hint";
    92  - }
     99 + /**
     100 + * Returns the name of the hints file for adoc rendering.
     101 + *
     102 + * @return String with name of file for hints
     103 + */
     104 + public String getHint() {
     105 + return this.getClass().getSimpleName().toLowerCase() + "_hint";
     106 + }
    93 107   
    94  - /**
    95  - * Returns the name of the reason file for adoc rendering.
    96  - * @return String with name of file for reason
    97  - */
    98  - public String getReason() {
    99  - return this.getClass().getSimpleName().toLowerCase() + "_reason";
    100  - }
     108 + /**
     109 + * Returns the name of the reason file for adoc rendering.
     110 + *
     111 + * @return String with name of file for reason
     112 + */
     113 + public String getReason() {
     114 + return this.getClass().getSimpleName().toLowerCase() + "_reason";
     115 + }
    101 116  }
    102 117   
  • ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/ChallengeForm.java
    skipped 1 lines
    2 2   
    3 3  /**
    4 4   * Used to communicate with the front-end.
     5 + *
    5 6   * @param solution as provided throught the form.
    6 7   */
    7  -public record ChallengeForm(String solution) {
    8  - 
    9  -}
     8 +public record ChallengeForm(String solution) {}
    10 9   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/ChallengeRestController.java
     1 +package org.owasp.wrongsecrets.challenges;
     2 + 
     3 +import io.swagger.v3.oas.annotations.Hidden;
     4 +import org.owasp.wrongsecrets.challenges.docker.Challenge30;
     5 +import org.springframework.stereotype.Controller;
     6 +import org.springframework.web.bind.annotation.GetMapping;
     7 +import org.springframework.web.bind.annotation.RestController;
     8 + 
     9 +/**
     10 + * This is a controller used in challenge 30 to retrieve data that can be put into localstorage by
     11 + * the frontend.
     12 + */
     13 +@Controller
     14 +@RestController
     15 +public class ChallengeRestController {
     16 + private final Challenge30 challenge30;
     17 + 
     18 + public ChallengeRestController(Challenge30 challenge30) {
     19 + this.challenge30 = challenge30;
     20 + }
     21 + 
     22 + @GetMapping("/hidden")
     23 + @Hidden
     24 + public String getChallengeSecret() {
     25 + return challenge30.spoiler().solution();
     26 + }
     27 +}
     28 + 
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/ChallengeTechnology.java
    skipped 1 lines
    2 2   
    3 3  import java.util.Arrays;
    4 4   
    5  -/**
    6  - * provides the technology used within a challenge.
    7  - */
     5 +/** provides the technology used within a challenge. */
    8 6  public class ChallengeTechnology {
    9 7   
    10  - /**
    11  - * enum from which you can choose the tech to have consistent naming.
    12  - */
    13  - public enum Tech {
    14  - 
    15  - GIT("Git"), DOCKER("Docker"), CONFIGMAPS("Configmaps"), SECRETS("Secrets"), VAULT("Vault"), LOGGING("Logging"), TERRAFORM("Terraform"), CSI("CSI-Driver"), CICD("CI/CD"), PASSWORD_MANAGER("Password Manager"), CRYPTOGRAPHY("Cryptography"), BINARY("Binary"), FRONTEND("Front-end"), IAM("IAM privilege escalation"), WEB3("Web3"), DOCUMENTATION("Documentation");
    16  - public final String id;
     8 + /** enum from which you can choose the tech to have consistent naming. */
     9 + public enum Tech {
     10 + GIT("Git"),
     11 + DOCKER("Docker"),
     12 + CONFIGMAPS("Configmaps"),
     13 + SECRETS("Secrets"),
     14 + VAULT("Vault"),
     15 + LOGGING("Logging"),
     16 + TERRAFORM("Terraform"),
     17 + CSI("CSI-Driver"),
     18 + CICD("CI/CD"),
     19 + PASSWORD_MANAGER("Password Manager"),
     20 + CRYPTOGRAPHY("Cryptography"),
     21 + BINARY("Binary"),
     22 + FRONTEND("Front-end"),
     23 + IAM("IAM privilege escalation"),
     24 + WEB3("Web3"),
     25 + DOCUMENTATION("Documentation"),
     26 + AI("AI");
     27 + public final String id;
    17 28   
    18  - Tech(String id) {
    19  - this.id = id;
    20  - }
     29 + Tech(String id) {
     30 + this.id = id;
     31 + }
    21 32   
    22  - static ChallengeTechnology.Tech fromId(String id) {
    23  - return Arrays.stream(ChallengeTechnology.Tech.values()).filter(e -> e.id.equalsIgnoreCase(id)).findAny().get();
    24  - }
     33 + static ChallengeTechnology.Tech fromId(String id) {
     34 + return Arrays.stream(ChallengeTechnology.Tech.values())
     35 + .filter(e -> e.id.equalsIgnoreCase(id))
     36 + .findAny()
     37 + .get();
    25 38   }
     39 + }
    26 40  }
    27 41   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/ChallengeUI.java
    1 1  package org.owasp.wrongsecrets.challenges;
    2 2   
    3  -import lombok.Getter;
    4  -import org.owasp.wrongsecrets.RuntimeEnvironment;
    5  - 
    6 3  import java.util.Comparator;
    7 4  import java.util.List;
    8 5  import java.util.regex.Pattern;
    9 6  import java.util.stream.Collectors;
     7 +import lombok.Getter;
     8 +import org.owasp.wrongsecrets.RuntimeEnvironment;
    10 9   
    11  -/**
    12  - * Wrapper class to move logic from Thymeleaf to keep logic in code instead of the html file.
    13  - */
     10 +/** Wrapper class to move logic from Thymeleaf to keep logic in code instead of the html file. */
    14 11  @Getter
    15 12  public class ChallengeUI {
    16 13   
    17  - private static final Pattern challengePattern = Pattern.compile("(\\D+)(\\d+)");
    18  - 
    19  - private final Challenge challenge;
    20  - private final int challengeNumber;
    21  - private final RuntimeEnvironment runtimeEnvironment;
     14 + /** Wrapper class to express the difficulty level into a UI representation. */
     15 + private record DifficultyUI(int difficulty) {
    22 16   
    23  - public ChallengeUI(Challenge challenge, int challengeNumber, RuntimeEnvironment runtimeEnvironment) {
    24  - this.challenge = challenge;
    25  - this.challengeNumber = challengeNumber;
    26  - this.runtimeEnvironment = runtimeEnvironment;
     17 + public String minimal() {
     18 + return "☆".repeat(difficulty);
    27 19   }
    28 20   
    29  - /**
    30  - * Converts the name of the class into the challenge name.
    31  - * @return String with name of the challenge.
    32  - */
    33  - public String getName() {
    34  - var matchers = challengePattern.matcher(challenge.getClass().getSimpleName());
    35  - if (matchers.matches()) {
    36  - return matchers.group(1) + " " + matchers.group(2);
    37  - }
    38  - return "Unknown";
     21 + public String scale() {
     22 + int numberOfDifficultyLevels = Difficulty.totalOfDifficultyLevels();
     23 + String fullScale = "★".repeat(difficulty) + "☆".repeat(numberOfDifficultyLevels);
     24 + return fullScale.substring(0, numberOfDifficultyLevels);
    39 25   }
     26 + }
    40 27   
    41  - /**
    42  - * gives back the number of the challenge.
    43  - * @return int with challenge number.
    44  - */
    45  - public Integer getLink() {
    46  - return challengeNumber;
    47  - }
     28 + private static final Pattern challengePattern = Pattern.compile("(\\D+)(\\d+)");
    48 29   
    49  - /**
    50  - * Returns the tech used for a challenge.
    51  - * @return string with tech.
    52  - */
    53  - public String getTech() {
    54  - return challenge.getTech();
    55  - }
     30 + private final Challenge challenge;
     31 + private final int challengeNumber;
     32 + private final RuntimeEnvironment runtimeEnvironment;
    56 33   
    57  - /**
    58  - * Returns the number of the next challenge (e.g current+1).
    59  - * @return int with next challenge number.
    60  - */
    61  - public Integer next() {
    62  - return challengeNumber + 1;
    63  - }
     34 + public ChallengeUI(
     35 + Challenge challenge, int challengeNumber, RuntimeEnvironment runtimeEnvironment) {
     36 + this.challenge = challenge;
     37 + this.challengeNumber = challengeNumber;
     38 + this.runtimeEnvironment = runtimeEnvironment;
     39 + }
    64 40   
    65  - /**
    66  - * Returns the number of the previous challenge (e.g current-1).
    67  - * @return int with previous challenge number.
    68  - */
    69  - public Integer previous() {
    70  - return challengeNumber - 1;
     41 + /**
     42 + * Converts the name of the class into the challenge name.
     43 + *
     44 + * @return String with name of the challenge.
     45 + */
     46 + public String getName() {
     47 + var matchers = challengePattern.matcher(challenge.getClass().getSimpleName());
     48 + if (matchers.matches()) {
     49 + return matchers.group(1) + " " + matchers.group(2);
    71 50   }
     51 + return "Unknown";
     52 + }
    72 53   
    73  - /**
    74  - * Returns filename of the explanation of the challenge.
    75  - * @return String with filename.
    76  - */
    77  - public String getExplanation() {
    78  - return challenge.getExplanation();
     54 + /**
     55 + * gives back the number of the challenge.
     56 + *
     57 + * @return int with challenge number.
     58 + */
     59 + public Integer getLink() {
     60 + return challengeNumber;
     61 + }
     62 + 
     63 + /**
     64 + * Returns the tech used for a challenge.
     65 + *
     66 + * @return string with tech.
     67 + */
     68 + public String getTech() {
     69 + return challenge.getTech();
     70 + }
     71 + 
     72 + /**
     73 + * Returns the number of the next challenge (e.g current+1).
     74 + *
     75 + * @return int with next challenge number.
     76 + */
     77 + public Integer next() {
     78 + return challengeNumber + 1;
     79 + }
     80 + 
     81 + /**
     82 + * Returns the number of the previous challenge (e.g current-1).
     83 + *
     84 + * @return int with previous challenge number.
     85 + */
     86 + public Integer previous() {
     87 + return challengeNumber - 1;
     88 + }
     89 + 
     90 + /**
     91 + * Returns filename of the explanation of the challenge.
     92 + *
     93 + * @return String with filename.
     94 + */
     95 + public String getExplanation() {
     96 + return challenge.getExplanation();
     97 + }
     98 + 
     99 + /**
     100 + * Returns filename of the hints for the challenge.
     101 + *
     102 + * @return String with filename.
     103 + */
     104 + public String getHint() {
     105 + List<RuntimeEnvironment.Environment> limitedOnlineEnvs =
     106 + List.of(
     107 + RuntimeEnvironment.Environment.HEROKU_DOCKER,
     108 + RuntimeEnvironment.Environment.FLY_DOCKER,
     109 + RuntimeEnvironment.Environment.OKTETO_K8S);
     110 + if (limitedOnlineEnvs.contains(runtimeEnvironment.getRuntimeEnvironment())
     111 + && challenge.isLimitedWhenOnlineHosted()) {
     112 + return challenge.getHint() + "_limited";
    79 113   }
     114 + return challenge.getHint();
     115 + }
    80 116   
    81  - /**
    82  - * Returns filename of the hints for the challenge.
    83  - * @return String with filename.
    84  - */
    85  - public String getHint() {
    86  - List<RuntimeEnvironment.Environment> limitedOnlineEnvs = List.of(RuntimeEnvironment.Environment.HEROKU_DOCKER, RuntimeEnvironment.Environment.FLY_DOCKER, RuntimeEnvironment.Environment.OKTETO_K8S);
    87  - if (limitedOnlineEnvs.contains(runtimeEnvironment.getRuntimeEnvironment()) && challenge.isLimittedWhenOnlineHosted()) {
    88  - return challenge.getHint() + "_limitted";
    89  - }
    90  - return challenge.getHint();
    91  - }
     117 + /**
     118 + * Returns filename of the reasons of the challenge.
     119 + *
     120 + * @return String with filename.
     121 + */
     122 + public String getReason() {
     123 + return challenge.getReason();
     124 + }
    92 125   
    93  - /**
    94  - * Returns filename of the reasons of the challenge.
    95  - * @return String with filename.
    96  - */
    97  - public String getReason() {
    98  - return challenge.getReason();
    99  - }
     126 + /**
     127 + * String providing the minimal required env. Used in homescreen.
     128 + *
     129 + * @return String with required env.
     130 + */
     131 + public String requiredEnv() {
     132 + return challenge.supportedRuntimeEnvironments().stream()
     133 + .map(Enum::name)
     134 + .limit(1)
     135 + .collect(Collectors.joining());
     136 + }
    100 137   
    101  - /**
    102  - * String providing the minimal required env. Used in homescreen.
    103  - * @return String with required env.
    104  - */
    105  - public String requiredEnv() {
    106  - return challenge.supportedRuntimeEnvironments().stream()
    107  - .map(Enum::name)
    108  - .limit(1)
    109  - .collect(Collectors.joining());
    110  - }
     138 + /**
     139 + * Returns the difficulty level in stars on a full scale, for example for level NORMAL it will
     140 + * return "★★☆☆☆".
     141 + *
     142 + * @return stars
     143 + */
     144 + public String getStarsOnScale() {
     145 + return new DifficultyUI(challenge.difficulty()).scale();
     146 + }
    111 147   
    112  - /**
    113  - * returns integer with difficulty of the challenge.
    114  - * @return int
    115  - */
    116  - public int difficulty() {
    117  - return challenge.difficulty();
    118  - }
     148 + /**
     149 + * Returns the difficulty level in stars, for example for level NORMAL it will return "☆☆".
     150 + *
     151 + * @return stars
     152 + */
     153 + public String getStars() {
     154 + return new DifficultyUI(challenge.difficulty()).minimal();
     155 + }
    119 156   
    120  - /**
    121  - * checks whether challenge is enabled based on used runtimemode and CTF enablement.
    122  - * @return boolean true if the challenge can run.
    123  - */
    124  - public boolean isChallengeEnabled() {
    125  - if (runtimeEnvironment.runtimeInCTFMode()) {
    126  - return runtimeEnvironment.canRun(challenge) && challenge.canRunInCTFMode();
    127  - }
    128  - return runtimeEnvironment.canRun(challenge);
     157 + /**
     158 + * checks whether challenge is enabled based on used runtimemode and CTF enablement.
     159 + *
     160 + * @return boolean true if the challenge can run.
     161 + */
     162 + public boolean isChallengeEnabled() {
     163 + if (runtimeEnvironment.runtimeInCTFMode()) {
     164 + return runtimeEnvironment.canRun(challenge) && challenge.canRunInCTFMode();
    129 165   }
     166 + return runtimeEnvironment.canRun(challenge);
     167 + }
    130 168   
    131  - /**
    132  - * returns the list of challengeUIs based on the status sof the runtime.
    133  - * @param challenges actual challenges to be used in app.
    134  - * @param environment the runtime env we are running on as an app.
    135  - * @return list of ChallengeUIs.
    136  - */
    137  - public static List<ChallengeUI> toUI(List<Challenge> challenges, RuntimeEnvironment environment) {
    138  - return challenges.stream()
    139  - .sorted(Comparator.comparingInt(challenge -> Integer.parseInt(challenge.getClass().getSimpleName().replace("Challenge", ""))))
    140  - .map(challenge -> new ChallengeUI(challenge, challenges.indexOf(challenge), environment))
    141  - .toList();
    142  - }
     169 + /**
     170 + * returns the list of challengeUIs based on the status sof the runtime.
     171 + *
     172 + * @param challenges actual challenges to be used in app.
     173 + * @param environment the runtime env we are running on as an app.
     174 + * @return list of ChallengeUIs.
     175 + */
     176 + public static List<ChallengeUI> toUI(List<Challenge> challenges, RuntimeEnvironment environment) {
     177 + return challenges.stream()
     178 + .sorted(
     179 + Comparator.comparingInt(
     180 + challenge ->
     181 + Integer.parseInt(
     182 + challenge.getClass().getSimpleName().replace("Challenge", ""))))
     183 + .map(challenge -> new ChallengeUI(challenge, challenges.indexOf(challenge), environment))
     184 + .toList();
     185 + }
    143 186  }
    144 187   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/ChallengesAPIController.java
    skipped 2 lines
    3 3  import com.nimbusds.jose.shaded.json.JSONArray;
    4 4  import com.nimbusds.jose.shaded.json.JSONObject;
    5 5  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    6  - 
     6 +import io.swagger.v3.oas.annotations.Operation;
    7 7  import java.io.BufferedReader;
    8 8  import java.io.IOException;
    9 9  import java.io.InputStreamReader;
    10 10  import java.nio.charset.StandardCharsets;
    11 11  import java.util.ArrayList;
    12 12  import java.util.List;
    13  - 
    14  -import io.swagger.v3.oas.annotations.Operation;
    15 13  import lombok.extern.slf4j.Slf4j;
    16 14  import org.asciidoctor.Asciidoctor;
    17 15  import org.asciidoctor.Options;
    skipped 12 lines
    30 28  @RestController
    31 29  public class ChallengesAPIController {
    32 30   
    33  - private final ScoreCard scoreCard;
    34  - private final List<ChallengeUI> challenges;
     31 + private final ScoreCard scoreCard;
     32 + private final List<ChallengeUI> challenges;
    35 33   
    36  - private final List<String> descriptions;
     34 + private final List<String> descriptions;
    37 35   
    38  - private final List<String> hints;
     36 + private final List<String> hints;
    39 37   
    40  - private final TemplateGenerator templateGenerator;
     38 + private final TemplateGenerator templateGenerator;
    41 39   
    42  - private final RuntimeEnvironment runtimeEnvironment;
    43  - 
    44  - public ChallengesAPIController(ScoreCard scoreCard, List<ChallengeUI> challenges, RuntimeEnvironment runtimeEnvironment, TemplateGenerator templateGenerator) {
    45  - this.scoreCard = scoreCard;
    46  - this.challenges = challenges;
    47  - this.descriptions = new ArrayList<>();
    48  - this.hints = new ArrayList<>();
    49  - this.runtimeEnvironment = runtimeEnvironment;
    50  - this.templateGenerator = templateGenerator;
    51  - }
     40 + private final RuntimeEnvironment runtimeEnvironment;
    52 41   
     42 + public ChallengesAPIController(
     43 + ScoreCard scoreCard,
     44 + List<ChallengeUI> challenges,
     45 + RuntimeEnvironment runtimeEnvironment,
     46 + TemplateGenerator templateGenerator) {
     47 + this.scoreCard = scoreCard;
     48 + this.challenges = challenges;
     49 + this.descriptions = new ArrayList<>();
     50 + this.hints = new ArrayList<>();
     51 + this.runtimeEnvironment = runtimeEnvironment;
     52 + this.templateGenerator = templateGenerator;
     53 + }
    53 54   
    54  - @GetMapping(value = {"/api/Challenges", "/api/challenges"}, produces = MediaType.APPLICATION_JSON_VALUE)
    55  - @Operation(summary = "Gives all challenges back in a jsonArray, to be used with the Juiceshop CTF cli")
    56  - public String getChallenges() {
    57  - if (descriptions.size() == 0) {
    58  - initiaLizeHintsAndDescriptions();
    59  - }
    60  - JSONObject json = new JSONObject();
    61  - JSONArray jsonArray = new JSONArray();
    62  - for (int i = 0; i < challenges.size(); i++) {
    63  - JSONObject jsonChallenge = new JSONObject();
    64  - jsonChallenge.put("id", i + 1);
    65  - jsonChallenge.put("name", challenges.get(i).getName());
    66  - jsonChallenge.put("key", challenges.get(i).getExplanation());
    67  - jsonChallenge.put("category", getCategory(challenges.get(i)) + " - " + challenges.get(i).getTech());
    68  - jsonChallenge.put("description", descriptions.get(i));
    69  - jsonChallenge.put("hint", hints.get(i));
    70  - jsonChallenge.put("solved", scoreCard.getChallengeCompleted(challenges.get(i).getChallenge()));
    71  - jsonChallenge.put("disabledEnv", getDisabledEnv(challenges.get(i)));
    72  - jsonChallenge.put("difficulty", challenges.get(i).getChallenge().difficulty());
    73  - jsonArray.add(jsonChallenge);
    74  - }
    75  - json.put("status", "success");
    76  - json.put("data", jsonArray);
    77  - String result = json.toJSONString();
    78  - log.info("returning {}", result);
    79  - return result;
     55 + @GetMapping(
     56 + value = {"/api/Challenges", "/api/challenges"},
     57 + produces = MediaType.APPLICATION_JSON_VALUE)
     58 + @Operation(
     59 + summary = "Gives all challenges back in a jsonArray, to be used with the Juiceshop CTF cli")
     60 + public String getChallenges() {
     61 + if (descriptions.size() == 0) {
     62 + initializeHintsAndDescriptions();
    80 63   }
    81  - 
    82  - private String getCategory(ChallengeUI challengeUI) {
    83  - return switch (challengeUI.getChallenge().supportedRuntimeEnvironments().get(0)) {
    84  - case DOCKER, HEROKU_DOCKER, FLY_DOCKER -> "Docker";
    85  - case GCP, AWS, AZURE -> "Cloud";
    86  - case VAULT -> "Vault";
    87  - case K8S, OKTETO_K8S -> "Kubernetes";
    88  - };
     64 + JSONObject json = new JSONObject();
     65 + JSONArray jsonArray = new JSONArray();
     66 + for (int i = 0; i < challenges.size(); i++) {
     67 + JSONObject jsonChallenge = new JSONObject();
     68 + jsonChallenge.put("id", i + 1);
     69 + jsonChallenge.put("name", challenges.get(i).getName());
     70 + jsonChallenge.put("key", challenges.get(i).getExplanation());
     71 + jsonChallenge.put(
     72 + "category", getCategory(challenges.get(i)) + " - " + challenges.get(i).getTech());
     73 + jsonChallenge.put("description", descriptions.get(i));
     74 + jsonChallenge.put("hint", hints.get(i));
     75 + jsonChallenge.put(
     76 + "solved", scoreCard.getChallengeCompleted(challenges.get(i).getChallenge()));
     77 + jsonChallenge.put("disabledEnv", getDisabledEnv(challenges.get(i)));
     78 + jsonChallenge.put("difficulty", challenges.get(i).getChallenge().difficulty());
     79 + jsonArray.add(jsonChallenge);
    89 80   }
     81 + json.put("status", "success");
     82 + json.put("data", jsonArray);
     83 + String result = json.toJSONString();
     84 + log.info("returning {}", result);
     85 + return result;
     86 + }
    90 87   
    91  - private void initiaLizeHintsAndDescriptions() {
    92  - log.info("Initialize hints and descriptions");
    93  - challenges.forEach(challengeUI -> { //note requires mvn install to generate the html files!
    94  - try {
    95  - String hint = templateGenerator.generate("explanations/" + challengeUI.getExplanation() + "_hint");
    96  - hints.add(hint);
    97  - String description = templateGenerator.generate("explanations/" + challengeUI.getExplanation());
    98  - descriptions.add(description);
    99  - } catch (IOException e) {
    100  - String rawHint = extractResource("classpath:explanations/" + challengeUI.getExplanation() + "_hint.adoc");
    101  - try (Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
    102  - String hint = asciidoctor.convert(rawHint, Options.builder().build());
    103  - hints.add(hint);
    104  - }
    105  - String rawDescription = extractResource("classpath:explanations/" + challengeUI.getExplanation() + ".adoc");
    106  - try (Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
    107  - String description = asciidoctor.convert(rawDescription, Options.builder().build());
    108  - descriptions.add(description);
     88 + private String getCategory(ChallengeUI challengeUI) {
     89 + return switch (challengeUI.getChallenge().supportedRuntimeEnvironments().get(0)) {
     90 + case DOCKER, HEROKU_DOCKER, FLY_DOCKER -> "Docker";
     91 + case GCP, AWS, AZURE -> "Cloud";
     92 + case VAULT -> "Vault";
     93 + case K8S, OKTETO_K8S -> "Kubernetes";
     94 + };
     95 + }
    109 96   
    110  - }
    111  - throw new RuntimeException(e);
     97 + private void initializeHintsAndDescriptions() {
     98 + log.info("Initialize hints and descriptions");
     99 + challenges.forEach(
     100 + challengeUI -> { // note requires mvn install to generate the html files!
     101 + try {
     102 + String hint =
     103 + templateGenerator.generate(
     104 + "explanations/" + challengeUI.getExplanation() + "_hint");
     105 + hints.add(hint);
     106 + String description =
     107 + templateGenerator.generate("explanations/" + challengeUI.getExplanation());
     108 + descriptions.add(description);
     109 + } catch (IOException e) {
     110 + String rawHint =
     111 + extractResource(
     112 + "classpath:explanations/" + challengeUI.getExplanation() + "_hint.adoc");
     113 + try (Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
     114 + String hint = asciidoctor.convert(rawHint, Options.builder().build());
     115 + hints.add(hint);
    112 116   }
    113  - 
     117 + String rawDescription =
     118 + extractResource("classpath:explanations/" + challengeUI.getExplanation() + ".adoc");
     119 + try (Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
     120 + String description = asciidoctor.convert(rawDescription, Options.builder().build());
     121 + descriptions.add(description);
     122 + }
     123 + throw new RuntimeException(e);
     124 + }
    114 125   });
    115  - }
     126 + }
    116 127   
    117  - @SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD",
    118  - justification = "Read from specific classpath")
    119  - private String extractResource(String resourceName) {
    120  - try {
    121  - var resource = ResourceUtils.getURL(resourceName);
    122  - final StringBuilder resourceStringbuilder = new StringBuilder();
    123  - try (BufferedReader bufferedReader = new BufferedReader(
    124  - new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
    125  - bufferedReader.lines().forEach(resourceStringbuilder::append);
    126  - return resourceStringbuilder.toString();
    127  - }
     128 + @SuppressFBWarnings(
     129 + value = "URLCONNECTION_SSRF_FD",
     130 + justification = "Read from specific classpath")
     131 + private String extractResource(String resourceName) {
     132 + try {
     133 + var resource = ResourceUtils.getURL(resourceName);
     134 + final StringBuilder resourceStringbuilder = new StringBuilder();
     135 + try (BufferedReader bufferedReader =
     136 + new BufferedReader(
     137 + new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
     138 + bufferedReader.lines().forEach(resourceStringbuilder::append);
     139 + return resourceStringbuilder.toString();
     140 + }
    128 141   
    129  - } catch (IOException e) {
    130  - throw new RuntimeException(e);
    131  - }
     142 + } catch (IOException e) {
     143 + throw new RuntimeException(e);
    132 144   }
     145 + }
    133 146   
    134  - private String getDisabledEnv(ChallengeUI challenge) {
    135  - if (runtimeEnvironment.canRun(challenge.getChallenge())) {
    136  - return runtimeEnvironment.getRuntimeEnvironment().name();
    137  - }
    138  - return null;
     147 + private String getDisabledEnv(ChallengeUI challenge) {
     148 + if (runtimeEnvironment.canRun(challenge.getChallenge())) {
     149 + return runtimeEnvironment.getRuntimeEnvironment().name();
    139 150   }
    140  - 
     151 + return null;
     152 + }
    141 153  }
    142 154   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/ChallengesController.java
    skipped 2 lines
    3 3  import com.google.common.base.Strings;
    4 4  import io.swagger.v3.oas.annotations.Hidden;
    5 5  import io.swagger.v3.oas.annotations.Operation;
     6 +import java.nio.charset.StandardCharsets;
     7 +import java.security.InvalidKeyException;
     8 +import java.security.NoSuchAlgorithmException;
     9 +import java.util.List;
     10 +import java.util.stream.Collectors;
     11 +import javax.crypto.Mac;
     12 +import javax.crypto.spec.SecretKeySpec;
    6 13  import org.owasp.wrongsecrets.RuntimeEnvironment;
    7 14  import org.owasp.wrongsecrets.ScoreCard;
    8 15  import org.owasp.wrongsecrets.challenges.docker.Challenge0;
     16 +import org.owasp.wrongsecrets.challenges.docker.Challenge30;
    9 17  import org.owasp.wrongsecrets.challenges.docker.Challenge8;
    10 18  import org.springframework.beans.factory.annotation.Value;
    11 19  import org.springframework.http.HttpStatus;
    skipped 6 lines
    18 26  import org.springframework.web.bind.annotation.PostMapping;
    19 27  import org.springframework.web.server.ResponseStatusException;
    20 28   
    21  -import javax.crypto.Mac;
    22  -import javax.crypto.spec.SecretKeySpec;
    23  -import java.nio.charset.StandardCharsets;
    24  -import java.security.InvalidKeyException;
    25  -import java.security.NoSuchAlgorithmException;
    26  -import java.util.List;
    27  -import java.util.stream.Collectors;
    28  - 
    29  -/**
    30  - * Controller used to host the Challenges UI.
    31  - */
     29 +/** Controller used to host the Challenges UI. */
    32 30  @Controller
    33 31  public class ChallengesController {
    34 32   
    35  - private final ScoreCard scoreCard;
    36  - private final List<ChallengeUI> challenges;
    37  - private final RuntimeEnvironment runtimeEnvironment;
     33 + private final ScoreCard scoreCard;
     34 + private final List<ChallengeUI> challenges;
     35 + private final RuntimeEnvironment runtimeEnvironment;
    38 36   
    39  - @Value("${hints_enabled}")
    40  - private boolean hintsEnabled;
    41  - @Value("${reason_enabled}")
    42  - private boolean reasonEnabled;
     37 + @Value("${hints_enabled}")
     38 + private boolean hintsEnabled;
    43 39   
    44  - @Value("${ctf_enabled}")
    45  - private boolean ctfModeEnabled;
     40 + @Value("${reason_enabled}")
     41 + private boolean reasonEnabled;
    46 42   
    47  - private boolean spoilingEnabled;
     43 + @Value("${ctf_enabled}")
     44 + private boolean ctfModeEnabled;
    48 45   
    49  - @Value("${ctf_key}")
    50  - private String ctfKey;
     46 + private boolean spoilingEnabled;
    51 47   
    52  - @Value("${challenge_acht_ctf_to_provide_to_host_value}")
    53  - private String keyToProvideToHost;
     48 + @Value("${ctf_key}")
     49 + private String ctfKey;
    54 50   
    55  - @Value("${CTF_SERVER_ADDRESS}")
    56  - private String ctfServerAddress;
     51 + @Value("${challenge_acht_ctf_to_provide_to_host_value}")
     52 + private String keyToProvideToHost;
    57 53   
     54 + @Value("${challenge_thirty_ctf_to_provide_to_host_value}")
     55 + private String keyToProvideToHostForChallenge30;
    58 56   
    59  - public ChallengesController(ScoreCard scoreCard, List<ChallengeUI> challenges, RuntimeEnvironment runtimeEnvironment, @Value("${spoiling_enabled}") boolean spoilingEnabled) {
    60  - this.scoreCard = scoreCard;
    61  - this.challenges = challenges;
    62  - this.runtimeEnvironment = runtimeEnvironment;
    63  - this.spoilingEnabled = spoilingEnabled;
    64  - }
     57 + @Value("${CTF_SERVER_ADDRESS}")
     58 + private String ctfServerAddress;
    65 59   
    66  - private boolean checkId(int id) {
    67  - // If the id is either negative or larger than the amount of challenges, return false.
    68  - if (id < 0 || id >= challenges.size()) {
    69  - return false;
    70  - }
    71  - return true;
    72  - }
     60 + public ChallengesController(
     61 + ScoreCard scoreCard,
     62 + List<ChallengeUI> challenges,
     63 + RuntimeEnvironment runtimeEnvironment,
     64 + @Value("${spoiling_enabled}") boolean spoilingEnabled) {
     65 + this.scoreCard = scoreCard;
     66 + this.challenges = challenges;
     67 + this.runtimeEnvironment = runtimeEnvironment;
     68 + this.spoilingEnabled = spoilingEnabled;
     69 + }
    73 70   
    74  - @GetMapping
    75  - @Operation(description = "Returns the given expalantion text for a challenge")
    76  - public String explanation(@PathVariable Integer id) {
    77  - return challenges.get(id).getExplanation();
     71 + private void checkValidChallengeNumber(int id) {
     72 + // If the id is either negative or larger than the amount of challenges, return false.
     73 + if (id < 0 || id >= challenges.size()) {
     74 + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "challenge not found");
    78 75   }
     76 + }
    79 77   
    80  - /**
    81  - * return a spoil of the secret
    82  - * Please note that there is no way to enable this in ctfMode: spoils can never be returned during a CTF
    83  - * By default, in normal operations, spoils are enabled, unless `spoilingEnabled` is set to false.
    84  - *
    85  - * @param model exchanged with the FE
    86  - * @param id id of the challenge
    87  - * @return either a notification or a spoil
    88  - */
    89  - @GetMapping("/spoil-{id}")
    90  - @Hidden
    91  - public String spoiler(Model model, @PathVariable Integer id) {
    92  - if (ctfModeEnabled) {
    93  - model.addAttribute("spoiler", new Spoiler("Spoils are disabled in CTF mode"));
    94  - } else if (!spoilingEnabled) {
    95  - model.addAttribute("spoiler", new Spoiler("Spoils are disabled in the configuration"));
    96  - } else {
    97  - var challenge = challenges.get(id).getChallenge();
    98  - model.addAttribute("spoiler", challenge.spoiler());
    99  - }
    100  - return "spoil";
     78 + /**
     79 + * return a spoil of the secret Please note that there is no way to enable this in ctfMode: spoils
     80 + * can never be returned during a CTF By default, in normal operations, spoils are enabled, unless
     81 + * `spoilingEnabled` is set to false.
     82 + *
     83 + * @param model exchanged with the FE
     84 + * @param id id of the challenge
     85 + * @return either a notification or a spoil
     86 + */
     87 + @GetMapping("/spoil-{id}")
     88 + @Hidden
     89 + public String spoiler(Model model, @PathVariable Integer id) {
     90 + if (ctfModeEnabled) {
     91 + model.addAttribute("spoiler", new Spoiler("Spoils are disabled in CTF mode"));
     92 + } else if (!spoilingEnabled) {
     93 + model.addAttribute("spoiler", new Spoiler("Spoils are disabled in the configuration"));
     94 + } else {
     95 + var challenge = challenges.get(id).getChallenge();
     96 + model.addAttribute("spoiler", challenge.spoiler());
    101 97   }
     98 + return "spoil";
     99 + }
    102 100   
    103  - @GetMapping("/challenge/{id}")
    104  - @Operation(description = "Returns the data for a given challenge's form interaction")
    105  - public String challenge(Model model, @PathVariable Integer id) {
    106  - if (!checkId(id)) {
    107  - throw new ResponseStatusException(
    108  - HttpStatus.NOT_FOUND, "challenge not found"
    109  - );
    110  - }
    111  - var challenge = challenges.get(id);
     101 + @GetMapping("/challenge/{id}")
     102 + @Operation(description = "Returns the data for a given challenge's form interaction")
     103 + public String challenge(Model model, @PathVariable Integer id) {
     104 + checkValidChallengeNumber(id);
     105 + var challenge = challenges.get(id);
    112 106   
    113  - model.addAttribute("challengeForm", new ChallengeForm(""));
    114  - model.addAttribute("challenge", challenge);
     107 + model.addAttribute("challengeForm", new ChallengeForm(""));
     108 + model.addAttribute("challenge", challenge);
    115 109   
    116  - model.addAttribute("answerCorrect", null);
    117  - model.addAttribute("answerIncorrect", null);
    118  - model.addAttribute("solution", null);
    119  - if (!challenge.isChallengeEnabled()) {
    120  - model.addAttribute("answerIncorrect", "This challenge has been disabled.");
    121  - }
    122  - if (ctfModeEnabled && challenge.getChallenge() instanceof Challenge0) {
    123  - if (!Strings.isNullOrEmpty(ctfServerAddress) && !ctfServerAddress.equals("not_set")) {
    124  - model.addAttribute("answerCorrect", "You are playing in CTF Mode where you need to give your answer once more to " + ctfServerAddress + " if it is correct. We have to do this as you can otherwise reverse engineer our challenge flag generation process after completing the first 8 challenges");
    125  - } else {
    126  - model.addAttribute("answerCorrect", "You are playing in CTF Mode, please submit the flag you receive after solving this challenge to your CTFD/Facebook CTF instance");
    127  - }
    128  - }
    129  - enrichWithHintsAndReasons(model);
    130  - includeScoringStatus(model, challenge.getChallenge());
    131  - addWarning(challenge.getChallenge(), model);
    132  - fireEnding(model);
    133  - return "challenge";
     110 + model.addAttribute("answerCorrect", null);
     111 + model.addAttribute("answerIncorrect", null);
     112 + model.addAttribute("solution", null);
     113 + if (!challenge.isChallengeEnabled()) {
     114 + model.addAttribute("answerIncorrect", "This challenge has been disabled.");
     115 + }
     116 + if (ctfModeEnabled && challenge.getChallenge() instanceof Challenge0) {
     117 + if (!Strings.isNullOrEmpty(ctfServerAddress) && !ctfServerAddress.equals("not_set")) {
     118 + model.addAttribute(
     119 + "answerCorrect",
     120 + "You are playing in CTF Mode where you need to give your answer once more to "
     121 + + ctfServerAddress
     122 + + " if it is correct. We have to do this as you can otherwise reverse engineer our"
     123 + + " challenge flag generation process after completing the first 8 challenges");
     124 + } else {
     125 + model.addAttribute(
     126 + "answerCorrect",
     127 + "You are playing in CTF Mode, please submit the flag you receive after solving this"
     128 + + " challenge to your CTFD/Facebook CTF instance");
     129 + }
    134 130   }
     131 + enrichWithHintsAndReasons(model);
     132 + includeScoringStatus(model, challenge.getChallenge());
     133 + addWarning(challenge.getChallenge(), model);
     134 + fireEnding(model);
     135 + return "challenge";
     136 + }
    135 137   
    136  - @PostMapping(value = "/challenge/{id}", params = "action=reset")
    137  - @Operation(description = "Resets the state of a given challenge")
    138  - public String reset(@ModelAttribute ChallengeForm challengeForm, @PathVariable Integer id, Model model) {
    139  - if (!checkId(id)) {
    140  - throw new ResponseStatusException(
    141  - HttpStatus.NOT_FOUND, "challenge not found"
    142  - );
    143  - }
    144  - var challenge = challenges.get(id);
    145  - scoreCard.reset(challenge.getChallenge());
     138 + @PostMapping(value = "/challenge/{id}", params = "action=reset")
     139 + @Operation(description = "Resets the state of a given challenge")
     140 + public String reset(
     141 + @ModelAttribute ChallengeForm challengeForm, @PathVariable Integer id, Model model) {
     142 + checkValidChallengeNumber(id);
     143 + var challenge = challenges.get(id);
     144 + scoreCard.reset(challenge.getChallenge());
    146 145   
    147  - model.addAttribute("challenge", challenge);
    148  - includeScoringStatus(model, challenge.getChallenge());
    149  - addWarning(challenge.getChallenge(), model);
    150  - enrichWithHintsAndReasons(model);
    151  - return "challenge";
    152  - }
     146 + model.addAttribute("challenge", challenge);
     147 + includeScoringStatus(model, challenge.getChallenge());
     148 + addWarning(challenge.getChallenge(), model);
     149 + enrichWithHintsAndReasons(model);
     150 + return "challenge";
     151 + }
    153 152   
    154  - @PostMapping(value = "/challenge/{id}", params = "action=submit")
    155  - @Operation(description = "Post your answer to the challenge for a given challenge ID")
    156  - public String postController(@ModelAttribute ChallengeForm challengeForm, Model model, @PathVariable Integer id) {
    157  - if (!checkId(id)) {
    158  - throw new ResponseStatusException(
    159  - HttpStatus.NOT_FOUND, "challenge not found"
    160  - );
    161  - }
    162  - var challenge = challenges.get(id);
     153 + @PostMapping(value = "/challenge/{id}", params = "action=submit")
     154 + @Operation(description = "Post your answer to the challenge for a given challenge ID")
     155 + public String postController(
     156 + @ModelAttribute ChallengeForm challengeForm, Model model, @PathVariable Integer id) {
     157 + checkValidChallengeNumber(id);
     158 + var challenge = challenges.get(id);
    163 159   
    164  - if (!challenge.isChallengeEnabled()) {
    165  - model.addAttribute("answerIncorrect", "This challenge has been disabled.");
    166  - } else {
    167  - if (challenge.getChallenge().solved(challengeForm.solution())) {
    168  - if (ctfModeEnabled) {
    169  - if (!Strings.isNullOrEmpty(ctfServerAddress) && !ctfServerAddress.equals("not_set")) {
    170  - if (challenge.getChallenge() instanceof Challenge8) {
    171  - if (!Strings.isNullOrEmpty(keyToProvideToHost) && !keyToProvideToHost.equals("not_set")) { //this means that it was overriden with a code that needs to be returned to the ctf key exchange host.
    172  - model.addAttribute("answerCorrect", "Your answer is correct! " + "fill in the following answer in the CTF instance at " + ctfServerAddress + "for which you get your code: " + keyToProvideToHost);
    173  - }
    174  - } else {
    175  - model.addAttribute("answerCorrect", "Your answer is correct! " + "fill in the same answer in the ctf-instance of the app: " + ctfServerAddress);
    176  - }
    177  - } else {
    178  - String code = generateCode(challenge);
    179  - model.addAttribute("answerCorrect", "Your answer is correct! " + "fill in the following code in CTF scoring: " + code);
    180  - }
    181  - } else {
    182  - model.addAttribute("answerCorrect", "Your answer is correct!");
    183  - }
     160 + if (!challenge.isChallengeEnabled()) {
     161 + model.addAttribute("answerIncorrect", "This challenge has been disabled.");
     162 + } else {
     163 + if (challenge.getChallenge().solved(challengeForm.solution())) {
     164 + if (ctfModeEnabled) {
     165 + if (!Strings.isNullOrEmpty(ctfServerAddress) && !ctfServerAddress.equals("not_set")) {
     166 + if (challenge.getChallenge() instanceof Challenge8) {
     167 + if (!Strings.isNullOrEmpty(keyToProvideToHost)
     168 + && !keyToProvideToHost.equals(
     169 + "not_set")) { // this means that it was overriden with a code that needs to be
     170 + // returned to the ctf key exchange host.
     171 + model.addAttribute(
     172 + "answerCorrect",
     173 + "Your answer is correct! "
     174 + + "fill in the following answer in the CTF instance at "
     175 + + ctfServerAddress
     176 + + "for which you get your code: "
     177 + + keyToProvideToHost);
     178 + }
     179 + } else if (challenge.getChallenge() instanceof Challenge30) {
     180 + if (!Strings.isNullOrEmpty(keyToProvideToHostForChallenge30)
     181 + && !keyToProvideToHostForChallenge30.equals(
     182 + "not_set")) { // this means that it was overriden with a code that needs to be
     183 + // returned to the ctf key exchange host.
     184 + model.addAttribute(
     185 + "answerCorrect",
     186 + "Your answer is correct! "
     187 + + "fill in the following answer in the CTF instance at "
     188 + + ctfServerAddress
     189 + + "for which you get your code: "
     190 + + keyToProvideToHostForChallenge30);
     191 + }
    184 192   } else {
    185  - model.addAttribute("answerIncorrect", "Your answer is incorrect, try harder ;-)");
     193 + model.addAttribute(
     194 + "answerCorrect",
     195 + "Your answer is correct! "
     196 + + "fill in the same answer in the ctf-instance of the app: "
     197 + + ctfServerAddress);
    186 198   }
     199 + } else {
     200 + String code = generateCode(challenge);
     201 + model.addAttribute(
     202 + "answerCorrect",
     203 + "Your answer is correct! " + "fill in the following code in CTF scoring: " + code);
     204 + }
     205 + } else {
     206 + model.addAttribute("answerCorrect", "Your answer is correct!");
    187 207   }
     208 + } else {
     209 + model.addAttribute("answerIncorrect", "Your answer is incorrect, try harder ;-)");
     210 + }
     211 + }
    188 212   
    189  - model.addAttribute("challenge", challenge);
     213 + model.addAttribute("challenge", challenge);
    190 214   
    191  - includeScoringStatus(model, challenge.getChallenge());
     215 + includeScoringStatus(model, challenge.getChallenge());
    192 216   
    193  - enrichWithHintsAndReasons(model);
     217 + enrichWithHintsAndReasons(model);
    194 218   
    195  - fireEnding(model);
    196  - return "challenge";
    197  - }
     219 + fireEnding(model);
     220 + return "challenge";
     221 + }
    198 222   
    199  - private String generateCode(ChallengeUI challenge) {
    200  - SecretKeySpec secretKeySpec = new SecretKeySpec(ctfKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
    201  - try {
    202  - Mac mac = Mac.getInstance("HmacSHA1");
    203  - mac.init(secretKeySpec);
    204  - byte[] result = mac.doFinal(challenge.getName().getBytes(StandardCharsets.UTF_8));
    205  - return new String(Hex.encode(result));
    206  - } catch (NoSuchAlgorithmException | InvalidKeyException e) {
    207  - throw new RuntimeException(e);
    208  - }
     223 + private String generateCode(ChallengeUI challenge) {
     224 + SecretKeySpec secretKeySpec =
     225 + new SecretKeySpec(ctfKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
     226 + try {
     227 + Mac mac = Mac.getInstance("HmacSHA1");
     228 + mac.init(secretKeySpec);
     229 + byte[] result = mac.doFinal(challenge.getName().getBytes(StandardCharsets.UTF_8));
     230 + return new String(Hex.encode(result));
     231 + } catch (NoSuchAlgorithmException | InvalidKeyException e) {
     232 + throw new RuntimeException(e);
    209 233   }
     234 + }
    210 235   
    211  - private void includeScoringStatus(Model model, Challenge challenge) {
    212  - model.addAttribute("totalPoints", scoreCard.getTotalReceivedPoints());
    213  - model.addAttribute("progress", "" + scoreCard.getProgress());
     236 + private void includeScoringStatus(Model model, Challenge challenge) {
     237 + model.addAttribute("totalPoints", scoreCard.getTotalReceivedPoints());
     238 + model.addAttribute("progress", "" + scoreCard.getProgress());
    214 239   
    215  - if (scoreCard.getChallengeCompleted(challenge)) {
    216  - model.addAttribute("challengeCompletedAlready", "This exercise is already completed");
    217  - }
     240 + if (scoreCard.getChallengeCompleted(challenge)) {
     241 + model.addAttribute("challengeCompletedAlready", "This exercise is already completed");
    218 242   }
     243 + }
    219 244   
    220  - private void addWarning(Challenge challenge, Model model) {
    221  - if (!runtimeEnvironment.canRun(challenge)) {
    222  - var warning = challenge.supportedRuntimeEnvironments().stream()
    223  - .map(Enum::name)
    224  - .limit(1)
    225  - .collect(Collectors.joining());
    226  - model.addAttribute("missingEnvWarning", warning);
    227  - }
     245 + private void addWarning(Challenge challenge, Model model) {
     246 + if (!runtimeEnvironment.canRun(challenge)) {
     247 + var warning =
     248 + challenge.supportedRuntimeEnvironments().stream()
     249 + .map(Enum::name)
     250 + .limit(1)
     251 + .collect(Collectors.joining());
     252 + model.addAttribute("missingEnvWarning", warning);
    228 253   }
     254 + }
    229 255   
    230  - private void enrichWithHintsAndReasons(Model model) {
    231  - model.addAttribute("hintsEnabled", hintsEnabled);
    232  - model.addAttribute("reasonEnabled", reasonEnabled);
    233  - }
     256 + private void enrichWithHintsAndReasons(Model model) {
     257 + model.addAttribute("hintsEnabled", hintsEnabled);
     258 + model.addAttribute("reasonEnabled", reasonEnabled);
     259 + }
    234 260   
    235  - private void fireEnding(Model model) {
    236  - var notCompleted = challenges.stream()
     261 + private void fireEnding(Model model) {
     262 + var notCompleted =
     263 + challenges.stream()
    237 264   .filter(ChallengeUI::isChallengeEnabled)
    238 265   .map(ChallengeUI::getChallenge)
    239 266   .filter(this::challengeNotCompleted)
    240 267   .count();
    241  - if (notCompleted == 0) {
    242  - model.addAttribute("allCompleted", "party");
    243  - }
     268 + if (notCompleted == 0) {
     269 + model.addAttribute("allCompleted", "party");
    244 270   }
     271 + }
    245 272   
    246  - private boolean challengeNotCompleted(Challenge challenge) {
    247  - return !scoreCard.getChallengeCompleted(challenge);
    248  - }
     273 + private boolean challengeNotCompleted(Challenge challenge) {
     274 + return !scoreCard.getChallengeCompleted(challenge);
     275 + }
    249 276  }
    250 277   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/Difficulty.java
     1 +package org.owasp.wrongsecrets.challenges;
     2 + 
     3 +/** Representation of the difficulty levels. */
     4 +public class Difficulty {
     5 + 
     6 + public static final int EASY = 1;
     7 + public static final int NORMAL = 2;
     8 + public static final int HARD = 3;
     9 + public static final int EXPERT = 4;
     10 + public static final int MASTER = 5;
     11 + 
     12 + private static final int[] allLevels = new int[] {EASY, NORMAL, HARD, EXPERT, MASTER};
     13 + 
     14 + public static int totalOfDifficultyLevels() {
     15 + return allLevels.length;
     16 + }
     17 +}
     18 + 
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/Spoiler.java
    1 1  package org.owasp.wrongsecrets.challenges;
    2 2   
    3 3  /**
    4  - * Class used to return spoilers with the actual answer.
    5  - * Spoilers are used for quick testing and for finding the answer if a tutorial fails.
    6  - * Please note that spoiling is disabled in CTF mode, and there is the setting
     4 + * Class used to return spoilers with the actual answer. Spoilers are used for quick testing and for
     5 + * finding the answer if a tutorial fails. Please note that spoiling is disabled in CTF mode, and
     6 + * there is the setting
     7 + *
    7 8   * @param solution the actual solution
    8 9   */
    9  -public record Spoiler(String solution) {
    10  -}
     10 +public record Spoiler(String solution) {}
    11 11   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java
    1 1  package org.owasp.wrongsecrets.challenges.cloud;
    2 2   
     3 +import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AWS;
     4 +import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AZURE;
     5 +import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.GCP;
    3 6   
    4 7  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     8 +import java.nio.file.Files;
     9 +import java.nio.file.Paths;
     10 +import java.util.List;
    5 11  import lombok.extern.slf4j.Slf4j;
    6 12  import org.owasp.wrongsecrets.RuntimeEnvironment;
    7 13  import org.owasp.wrongsecrets.ScoreCard;
    8 14  import org.owasp.wrongsecrets.challenges.ChallengeTechnology;
     15 +import org.owasp.wrongsecrets.challenges.Difficulty;
    9 16  import org.owasp.wrongsecrets.challenges.Spoiler;
    10 17  import org.springframework.beans.factory.annotation.Value;
    11 18  import org.springframework.core.annotation.Order;
    12 19  import org.springframework.stereotype.Component;
    13 20   
    14  -import java.nio.file.Files;
    15  -import java.nio.file.Paths;
    16  -import java.util.List;
    17  - 
    18  -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AWS;
    19  -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.GCP;
    20  -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AZURE;
    21  - 
    22  -/**
    23  - * Cloud challenge that leverages the CSI secrets driver of the cloud you are running in.
    24  - */
     21 +/** Cloud challenge that leverages the CSI secrets driver of the cloud you are running in. */
    25 22  @Component
    26 23  @Slf4j
    27 24  @Order(10)
    28 25  public class Challenge10 extends CloudChallenge {
    29 26   
    30  - private final String awsDefaultValue;
    31  - private final String challengeAnswer;
     27 + private final String awsDefaultValue;
     28 + private final String challengeAnswer;
    32 29   
    33  - public Challenge10(ScoreCard scoreCard,
    34  - @Value("${secretmountpath}") String filePath,
    35  - @Value("${default_aws_value_challenge_10}") String awsDefaultValue,
    36  - @Value("${FILENAME_CHALLENGE10}") String fileName,
    37  - RuntimeEnvironment runtimeEnvironment) {
    38  - super(scoreCard, runtimeEnvironment);
    39  - this.awsDefaultValue = awsDefaultValue;
    40  - this.challengeAnswer = getCloudChallenge9and10Value(filePath, fileName);
    41  - }
     30 + public Challenge10(
     31 + ScoreCard scoreCard,
     32 + @Value("${secretmountpath}") String filePath,
     33 + @Value("${default_aws_value_challenge_10}") String awsDefaultValue,
     34 + @Value("${FILENAME_CHALLENGE10}") String fileName,
     35 + RuntimeEnvironment runtimeEnvironment) {
     36 + super(scoreCard, runtimeEnvironment);
     37 + this.awsDefaultValue = awsDefaultValue;
     38 + this.challengeAnswer = getCloudChallenge9and10Value(filePath, fileName);
     39 + }
    42 40   
    43  - /**
    44  - * {@inheritDoc}
    45  - */
    46  - @Override
    47  - public Spoiler spoiler() {
    48  - return new Spoiler(challengeAnswer);
    49  - }
     41 + /** {@inheritDoc} */
     42 + @Override
     43 + public Spoiler spoiler() {
     44 + return new Spoiler(challengeAnswer);
     45 + }
    50 46   
    51  - /**
    52  - * {@inheritDoc}
    53  - */
    54  - @Override
    55  - public boolean answerCorrect(String answer) {
    56  - return challengeAnswer.equals(answer);
    57  - }
     47 + /** {@inheritDoc} */
     48 + @Override
     49 + public boolean answerCorrect(String answer) {
     50 + return challengeAnswer.equals(answer);
     51 + }
    58 52   
    59  - @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "The location of the file is based on an Env Var")
    60  - private String getCloudChallenge9and10Value(String filePath, String fileName) {
    61  - try {
    62  - return Files.readString(Paths.get(filePath, fileName));
    63  - } catch (Exception e) {
    64  - log.warn("Exception during reading file ({}/{}}), defaulting to default without AWS", filePath, fileName);
    65  - return awsDefaultValue;
    66  - }
     53 + @SuppressFBWarnings(
     54 + value = "PATH_TRAVERSAL_IN",
     55 + justification = "The location of the file is based on an Env Var")
     56 + private String getCloudChallenge9and10Value(String filePath, String fileName) {
     57 + try {
     58 + return Files.readString(Paths.get(filePath, fileName));
     59 + } catch (Exception e) {
     60 + log.warn(
     61 + "Exception during reading file ({}/{}}), defaulting to default without AWS",
     62 + filePath,
     63 + fileName);
     64 + return awsDefaultValue;
    67 65   }
     66 + }
    68 67   
    69  - /**
    70  - * {@inheritDoc}
    71  - */
    72  - public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() {
    73  - return List.of(GCP, AWS, AZURE);
    74  - }
     68 + /** {@inheritDoc} */
     69 + public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() {
     70 + return List.of(GCP, AWS, AZURE);
     71 + }
    75 72   
    76  - /**
    77  - * {@inheritDoc}
    78  - * Difficulty: 4
    79  - */
    80  - @Override
    81  - public int difficulty() {
    82  - return 4;
    83  - }
     73 + /** {@inheritDoc} */
     74 + @Override
     75 + public int difficulty() {
     76 + return Difficulty.EXPERT;
     77 + }
    84 78   
    85  - /**
    86  - * {@inheritDoc}
    87  - * Uses CSI Driver
    88  - */
    89  - @Override
    90  - public String getTech() {
    91  - return ChallengeTechnology.Tech.CSI.id;
    92  - }
     79 + /** {@inheritDoc} Uses CSI Driver */
     80 + @Override
     81 + public String getTech() {
     82 + return ChallengeTechnology.Tech.CSI.id;
     83 + }
    93 84   
    94  - @Override
    95  - public boolean canRunInCTFMode() {
    96  - return true;
    97  - }
     85 + @Override
     86 + public boolean canRunInCTFMode() {
     87 + return true;
     88 + }
    98 89  }
    99 90   
  • ■ ■ ■ ■ ■ ■
    src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java
    1 1  package org.owasp.wrongsecrets.challenges.cloud;
    2 2   
     3 +import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AWS;
     4 +import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.AZURE;
     5 +import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.GCP;
    3 6   
    4 7  import com.google.api.gax.rpc.ApiException;
    5 8  import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse;
    skipped 1 lines
    7 10  import com.google.cloud.secretmanager.v1.SecretVersionName;
    8 11  import com.google.common.base.Strings;
    9 12  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     13 +import java.io.IOException;
     14 +import java.nio.file.Files;
     15 +import java.nio.file.Paths;
     16 +import java.util.List;
    10 17  import lombok.extern.slf4j.Slf4j;
    11 18  import org.owasp.wrongsecrets.RuntimeEnvironment;
    12 19  import org.owasp.wrongsecrets.ScoreCard;
    13 20  import org.owasp.wrongsecrets.challenges.ChallengeTechnology;
     21 +import org.owasp.wrongsecrets.challenges.Difficulty;
    14 22  import org.owasp.wrongsecrets.challenges.Spoiler;
    15 23  import org.springframework.beans.factory.annotation.Value;
    16 24  import org.springframework.core.annotation.Order;
    skipped 8 lines
    25 33  import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest;
    26 34  import software.amazon.awssdk.services.sts.model.StsException;
    27 35   
    28  -import java.io.IOException;
    29  -import java.nio.file.Files;
    30  -import java.nio.file.Paths;
    31  -import java.util.List;
    32  - 
    33  -import static org.owasp.wrongsecrets.RuntimeEnvironment.Environment.*;
    34  - 
    35  -/**
    36  - * Cloud challenge which uses IAM privilelge escalation (differentiating per cloud).
    37  - */
     36 +/** Cloud challenge which uses IAM privilelge escalation (differentiating per cloud). */
    38 37  @Component
    39 38  @Slf4j
    40 39  @Order(11)
    41 40  public class Challenge11 extends CloudChallenge {
    42 41   
    43  - private final String awsRoleArn;
    44  - private final String awsRegion;
    45  - private final String tokenFileLocation;
    46  - private final String awsDefaultValue;
    47  - private final String gcpDefaultValue;
    48  - private final String azureDefaultValue;
    49  - private final String challengeAnswer;
    50  - private final String projectId;
    51  - private final String azureVaultUri;
    52  - private final String azureWrongSecret3;
     42 + private final String awsRoleArn;
     43 + private final String awsRegion;
     44 + private final String tokenFileLocation;
     45 + private final String awsDefaultValue;
     46 + private final String gcpDefaultValue;
     47 + private final String azureDefaultValue;
     48 + private final String challengeAnswer;
     49 + private final String projectId;
     50 + private final String azureVaultUri;
     51 + private final String azureWrongSecret3;
    53 52   
    54  - private final String ctfValue;
     53 + private final String ctfValue;
    55 54   
    56  - private final boolean ctfEnabled;
     55 + private final boolean ctfEnabled;
    57 56   
    58  - public Challenge11(ScoreCard scoreCard,
    59  - @Value("${AWS_ROLE_ARN}") String awsRoleArn,
    60  - @Value("${AWS_WEB_IDENTITY_TOKEN_FILE}") String tokenFileLocation,
    61  - @Value("${AWS_REGION}") String awsRegion,
    62  - @Value("${default_gcp_value}") String gcpDefaultValue,
    63  - @Value("${default_aws_value}") String awsDefaultValue,
    64  - @Value("${default_azure_value}") String azureDefaultValue,
    65  - @Value("${spring.cloud.azure.keyvault.secret.property-sources[0].endpoint}") String azureVaultUri,
    66  - @Value("${wrongsecret-3}") String azureWrongSecret3, // Exclusively auto-wired for Azure
    67  - @Value("${GOOGLE_CLOUD_PROJECT}") String projectId,
    68  - @Value("${default_aws_value_challenge_11}") String ctfValue,
    69  - @Value("${ctf_enabled}") boolean ctfEnabled,
    70  - RuntimeEnvironment runtimeEnvironment) {
    71  - super(scoreCard, runtimeEnvironment);
    72  - this.awsRoleArn = awsRoleArn;
    73  - this.tokenFileLocation = tokenFileLocation;
    74  - this.awsRegion = awsRegion;
    75  - this.awsDefaultValue = awsDefaultValue;
    76  - this.gcpDefaultValue = gcpDefaultValue;
    77  - this.azureDefaultValue = azureDefaultValue;
    78  - this.projectId = projectId;
    79  - this.azureVaultUri = azureVaultUri;
    80  - this.azureWrongSecret3 = azureWrongSecret3;
    81  - this.ctfValue = ctfValue;
    82  - this.ctfEnabled = ctfEnabled;
    83  - this.challengeAnswer = getChallenge11Value(runtimeEnvironment);
    84  - }
    85  - 
    86  - /**
    87  - * {@inheritDoc}
    88  - */
    89  - @Override
    90  - public Spoiler spoiler() {
    91  - return new Spoiler(challengeAnswer);
    92  - }
     57 + public Challenge11(
     58 + ScoreCard scoreCard,
     59 + @Value("${AWS_ROLE_ARN}") String awsRoleArn,
     60 + @Value("${AWS_WEB_IDENTITY_TOKEN_FILE}") String tokenFileLocation,
     61 + @Value("${AWS_REGION}") String awsRegion,
     62 + @Value("${default_gcp_value}") String gcpDefaultValue,
     63 + @Value("${default_aws_value}") String awsDefaultValue,
     64 + @Value("${default_azure_value}") String azureDefaultValue,
     65 + @Value("${spring.cloud.azure.keyvault.secret.property-sources[0].endpoint}")
     66 + String azureVaultUri,
     67 + @Value("${wrongsecret-3}") String azureWrongSecret3, // Exclusively auto-wired for Azure
     68 + @Value("${GOOGLE_CLOUD_PROJECT}") String projectId,
     69 + @Value("${default_aws_value_challenge_11}") String ctfValue,
     70 + @Value("${ctf_enabled}") boolean ctfEnabled,
     71 + RuntimeEnvironment runtimeEnvironment) {
     72 + super(scoreCard, runtimeEnvironment);
     73 + this.awsRoleArn = awsRoleArn;
     74 + this.tokenFileLocation = tokenFileLocation;
     75 + this.awsRegion = awsRegion;
     76 + this.awsDefaultValue = awsDefaultValue;
     77 + this.gcpDefaultValue = gcpDefaultValue;
     78 + this.azureDefaultValue = azureDefaultValue;
     79 + this.projectId = projectId;
     80 + this.azureVaultUri = azureVaultUri;
     81 + this.azureWrongSecret3 = azureWrongSecret3;
     82 + this.ctfValue = ctfValue;
     83 + this.ctfEnabled = ctfEnabled;
     84 + this.challengeAnswer = getChallenge11Value(runtimeEnvironment);
     85 + }
    93 86   
    94  - /**
    95  - * {@inheritDoc}
    96  - */
    97  - @Override
    98  - public boolean answerCorrect(String answer) {
    99  - return challengeAnswer.equals(answer);
    100  - }
     87 + /** {@inheritDoc} */
     88 + @Override
     89 + public Spoiler spoiler() {
     90 + return new Spoiler(challengeAnswer);
     91 + }
    101 92   
    102  - /**
    103  - * {@inheritDoc}
    104  - */
    105  - public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() {
    106  - return List.of(AWS, GCP, AZURE);
    107  - }
     93 + /** {@inheritDoc} */
     94 + @Override
     95 + public boolean answerCorrect(String answer) {
     96 + return challengeAnswer.equals(answer);
     97 + }
    108 98   
    109  - /**
    110  - * {@inheritDoc}
    111  - * Difficulty: 4
    112  - */
    113  - @Override
    114  - public int difficulty() {
    115  - return 4;
    116  - }
     99 + /** {@inheritDoc} */
     100 + public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() {
     101 + return List.of(AWS, GCP, AZURE);
     102 + }
    117 103   
    118  - /**
    119  - * {@inheritDoc}
    120  - * Uses IAM Privilege escalation
    121  - */
    122  - @Override
    123  - public String getTech() {
    124  - return ChallengeTechnology.Tech.IAM.id;
    125  - }
     104 + /** {@inheritDoc} */
     105 + @Override
     106 + public int difficulty() {
     107 + return Difficulty.EXPERT;
     108 + }
    126 109   
    127  - private String getChallenge11Value(RuntimeEnvironment runtimeEnvironment) {
    128  - if (!ctfEnabled) {
    129  - if (runtimeEnvironment != null && runtimeEnvironment.getRuntimeEnvironment() != null) {
    130  - return switch (runtimeEnvironment.getRuntimeEnvironment()) {
    131  - case AWS -> getAWSChallenge11Value();
    132  - case GCP -> getGCPChallenge11Value();
    133  - case AZURE -> getAzureChallenge11Value();
    134  - default -> "please_use_supported_cloud_env";
    135  - };
    136  - }
    137  - } else if (!Strings.isNullOrEmpty(ctfValue) && !Strings.isNullOrEmpty(awsDefaultValue)
    138  - && !ctfValue.equals(awsDefaultValue)) {
    139  - return ctfValue;
    140  - }
     110 + /** {@inheritDoc} Uses IAM Privilege escalation */
     111 + @Override
     112 + public String getTech() {
     113 + return ChallengeTechnology.Tech.IAM.id;
     114 + }
    141 115   
    142  - log.info("CTF enabled, skipping challenge11");
    143  - return "please_use_supported_cloud_env";
     116 + private String getChallenge11Value(RuntimeEnvironment runtimeEnvironment) {
     117 + if (!ctfEnabled) {
     118 + if (runtimeEnvironment != null && runtimeEnvironment.getRuntimeEnvironment() != null) {
     119 + return switch (runtimeEnvironment.getRuntimeEnvironment()) {
     120 + case AWS -> getAWSChallenge11Value();
     121 + case GCP -> getGCPChallenge11Value();
     122 + case AZURE -> getAzureChallenge11Value();
     123 + default -> "please_use_supported_cloud_env";
     124 + };
     125 + }
     126 + } else if (!Strings.isNullOrEmpty(ctfValue)
     127 + && !Strings.isNullOrEmpty(awsDefaultValue)
     128 + && !ctfValue.equals(awsDefaultValue)) {
     129 + return ctfValue;
    144 130   }
    145 131   
    146  - @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "The location of the tokenFileLocation is based on an Env Var")
    147  - private String getAWSChallenge11Value() {
    148  - log.info("pre-checking AWS data");
    149  - if (!"if_you_see_this_please_use_AWS_Setup".equals(awsRoleArn)) {
    150  - log.info("Getting credentials from AWS");
    151  - try { //based on https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/sts/src/main/java/com/example/sts
     132 + log.info("CTF enabled, skipping challenge11");
     133 + return "please_use_supported_cloud_env";
     134 + }
     135 + 
     136 + @SuppressFBWarnings(
     137 + value = "PATH_TRAVERSAL_IN",
     138 + justification = "The location of the tokenFileLocation is based on an Env Var")
     139 + private String getAWSChallenge11Value() {
     140 + log.info("pre-checking AWS data");
     141 + if (!"if_you_see_this_please_use_AWS_Setup".equals(awsRoleArn)) {
     142 + log.info("Getting credentials from AWS");
     143 + try { // based on
     144 + // https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/sts/src/main/java/com/example/sts
    152 145   
    153  - String webIDentityToken = Files.readString(Paths.get(tokenFileLocation));
    154  - StsClient stsClient = StsClient.builder()
    155  - .region(Region.of(awsRegion))
    156  - .build();
    157  - AssumeRoleWithWebIdentityRequest webIdentityRequest = AssumeRoleWithWebIdentityRequest.builder()
    158  - .roleArn(awsRoleArn)
    159  - .roleSessionName("WrongsecretsApp")
    160  - .webIdentityToken(webIDentityToken)
    161  - .build();
    162  - stsClient.assumeRoleWithWebIdentity(webIdentityRequest); //returns a AssumeRoleWithWebIdentityResponse which you can debug with //log.debug("The token value is " + tokenResponse.credentials().sessionToken());
    163  - SsmClient ssmClient = SsmClient.builder()
    164  - .region(Region.of(awsRegion))
    165  - .credentialsProvider(StsAssumeRoleWithWebIdentityCredentialsProvider.builder()
     146 + String webIDentityToken = Files.readString(Paths.get(tokenFileLocation));
     147 + StsClient stsClient = StsClient.builder().region(Region.of(awsRegion)).build();
     148 + AssumeRoleWithWebIdentityRequest webIdentityRequest =
     149 + AssumeRoleWithWebIdentityRequest.builder()
     150 + .roleArn(awsRoleArn)
     151 + .roleSessionName("WrongsecretsApp")
     152 + .webIdentityToken(webIDentityToken)
     153 + .build();
     154 + stsClient.assumeRoleWithWebIdentity(
     155 + webIdentityRequest); // returns a AssumeRoleWithWebIdentityResponse which you can debug
     156 + // with //log.debug("The token value is " +
     157 + // tokenResponse.credentials().sessionToken());
     158 + SsmClient ssmClient =
     159 + SsmClient.builder()
     160 + .region(Region.of(awsRegion))
     161 + .credentialsProvider(
     162 + StsAssumeRoleWithWebIdentityCredentialsProvider.builder()
    166 163   .stsClient(stsClient)
    167 164   .refreshRequest(webIdentityRequest)
    168 165   .build())
    169  - .build();
    170  - GetParameterRequest parameterRequest = GetParameterRequest.builder()
    171  - .name("wrongsecretvalue")
    172  - .withDecryption(true)
    173  - .build();
    174  - GetParameterResponse parameterResponse = ssmClient.getParameter(parameterRequest);
    175  - //log.debug("The parameter value is " + parameterResponse.parameter().value());
    176  - ssmClient.close();
    177  - return parameterResponse.parameter().value();
    178  - } catch (StsException e) {
    179  - log.error("Exception with getting credentials", e);
    180  - } catch (SsmException e) {
    181  - log.error("Exception with getting parameter", e);
    182  - } catch (IOException e) {
    183  - log.error("Could not get the web identity token, due to ", e);
    184  - }
    185  - } else {
    186  - log.info("Skipping credentials from AWS");
    187  - }
    188  - return awsDefaultValue;
     166 + .build();
     167 + GetParameterRequest parameterRequest =
     168 + GetParameterRequest.builder().name("wrongsecretvalue").withDecryption(true).build();
     169 + GetParameterResponse parameterResponse = ssmClient.getParameter(parameterRequest);
     170 + // log.debug("The parameter value is " + parameterResponse.parameter().value());
     171 + ssmClient.close();
     172 + return parameterResponse.parameter().value();
     173 + } catch (StsException e) {
     174 + log.error("Exception with getting credentials", e);
     175 + } catch (SsmException e) {
     176 + log.error("Exception with getting parameter", e);
     177 + } catch (IOException e) {
     178 + log.error("Could not get the web identity token, due to ", e);
     179 + }
     180 + } else {
     181 + log.info("Skipping credentials from AWS");
    189 182   }
     183 + return awsDefaultValue;
     184 + }
    190 185   
    191  - private String getGCPChallenge11Value() {
    192  - log.info("pre-checking GCP data");
    193  - if (isGCP()) {
    194  - log.info("Getting credentials from GCP");
    195  - // Based on https://cloud.google.com/secret-manager/docs/reference/libraries
    196  - try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) {
    197  - log.info("Fetching secret form Google Secret Manager...");
    198  - SecretVersionName secretVersionName = SecretVersionName.of(projectId, "wrongsecret-3", "latest");
    199  - AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);
    200  - return response.getPayload().getData().toStringUtf8();
    201  - } catch (ApiException e) {
    202  - log.error("Exception getting secret: ", e);
    203  - } catch (IOException e) {
    204  - log.error("Could not get the web identity token, due to ", e);
    205  - }
    206  - } else {
    207  - log.info("Skipping credentials from GCP");
    208  - }
    209  - return gcpDefaultValue;
     186 + private String getGCPChallenge11Value() {
     187 + log.info("pre-checking GCP data");
     188 + if (isGCP()) {
     189 + log.info("Getting credentials from GCP");
     190 + // Based on https://cloud.google.com/secret-manager/docs/reference/libraries
     191 + try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) {
     192 + log.info("Fetching secret form Google Secret Manager...");
     193 + SecretVersionName secretVersionName =
     194 + SecretVersionName.of(projectId, "wrongsecret-3", "latest");
     195 + AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);
     196 + return response.getPayload().getData().toStringUtf8();
     197 + } catch (ApiException e) {
     198 + log.error("Exception getting secret: ", e);
     199 + } catch (IOException e) {
     200 + log.error("Could not get the web identity token, due to ", e);
     201 + }
     202 + } else {
     203 + log.info("Skipping credentials from GCP");
    210 204   }
     205 + return gcpDefaultValue;
     206 + }
    211 207   
    212  - private String getAzureChallenge11Value() {
    213  - log.info("pre-checking Azure data");
    214  - if (isAzure()) {
    215  - log.info(String.format("Using Azure Key Vault URI: %s", azureVaultUri));
    216  - return azureWrongSecret3;
    217  - }
    218  - log.error("Fetching secret from Azure did not work, returning default");
    219  - return azureDefaultValue;
     208 + private String getAzureChallenge11Value() {
     209 + log.info("pre-checking Azure data");
     210 + if (isAzure()) {
     211 + log.info(String.format("Using Azure Key Vault URI: %s", azureVaultUri));
     212 + return azureWrongSecret3;
    220 213   }
     214 + log.error("Fetching secret from Azure did not work, returning default");
     215 + return azureDefaultValue;
     216 + }
    221 217   
    222  - @Override
    223  - public boolean canRunInCTFMode() {
    224  - return false;
    225  - }
     218 + @Override
     219 + public boolean canRunInCTFMode() {
     220 + return false;
     221 + }
    226 222  }
    227 223   
Please wait...
Page is in error, reload to recover