diff --git a/.factorypath b/.factorypath index e08d3a32a3..8b59ae4de5 100644 --- a/.factorypath +++ b/.factorypath @@ -1,5 +1,5 @@ - + @@ -25,7 +25,6 @@ - @@ -40,7 +39,6 @@ - @@ -103,7 +101,6 @@ - @@ -111,4 +108,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index e913def77b..c57e626c06 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,9 @@ aws/.terraform.tfstate.lock.info # Templated gcp/k8s/secret-volume.yml +gcp/k8s/secret-challenge-vault-deployment.yml # Challenge 12 ;-) -.github/scripts/yourkey.txt \ No newline at end of file +.github/scripts/yourkey.txt + + diff --git a/Dockerfile.web b/Dockerfile.web index 4961806710..ab96c4501c 100644 --- a/Dockerfile.web +++ b/Dockerfile.web @@ -1,6 +1,6 @@ -FROM jeroenwillemsen/addo-example:1.0.4-no-vault +FROM jeroenwillemsen/wrongsecrets:java-rebuild3-no-vault -ARG argBasedVersion="1.0.4" +ARG argBasedVersion="1.0.4b" ENV APP_VERSION=$argBasedVersion ENV K8S_ENV=Heroku(Docker) CMD java -jar -Dserver.port=$PORT -Dspring.profiles.active=without-vault application.jar \ No newline at end of file diff --git a/README.md b/README.md index 6fafc4c24b..5ecf38615f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ For the basic docker exercises you currently require: You can install it by doing: ```bash -docker run -p 8080:8080 jeroenwillemsen/addo-example:1.0.4-no-vault +docker run -p 8080:8080 jeroenwillemsen/wrongsecrets:1.0.4-no-vault ``` Now you can try to find the secrets by means of solving the challenge offered at: @@ -186,3 +186,13 @@ To make changes made load faster we added `spring-dev-tools` to the Maven projec - Under Advanced settings -> Allow auto-make to start even if developed application is currently running. You can also manually invoke: Build -> Recompile the file you just changed, this will also force a reload of the application. + +### How to add a Challenge + +Follow the steps below on adding a challenge: + +1. First make sure that you have an [Issue](https://github.com/commjoen/wrongsecrets/issues) reported for which a challenge is really wanteds. +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. +3. Add a unit and integration test to show that your challenge is working. + +If you want to move existing cloud challenges to antoerh cloud: extend Challenge classes in the `org.owasp.wrongsecrets.challenges.cloud` package and make sure you add the required Terraform in a folder with the separate cloud identified. Collaborate with the others at the project to get your container running so you can test at the cloud account. \ No newline at end of file diff --git a/aws/k8s/secret-challenge-vault-deployment.yml b/aws/k8s/secret-challenge-vault-deployment.yml index c562f0a0d5..0f940bbfc2 100644 --- a/aws/k8s/secret-challenge-vault-deployment.yml +++ b/aws/k8s/secret-challenge-vault-deployment.yml @@ -33,7 +33,7 @@ spec: volumeAttributes: secretProviderClass: "wrongsecrets-aws-secretsmanager" containers: - - image: jeroenwillemsen/addo-example:1.0.4-k8s-vault + - image: jeroenwillemsen/wrongsecrets:1.0.4-k8s-vault imagePullPolicy: IfNotPresent ports: - containerPort: 8080 diff --git a/gcp/k8s-vault-gcp-start.sh b/gcp/k8s-vault-gcp-start.sh index 4e3914e6c5..4c3c351258 100755 --- a/gcp/k8s-vault-gcp-start.sh +++ b/gcp/k8s-vault-gcp-start.sh @@ -4,8 +4,7 @@ # set -o nounset function checkCommandsAvailable() { - for var in "$@" - do + for var in "$@"; do if ! [ -x "$(command -v "$var")" ]; then echo "🔥 ${var} is not installed." >&2 exit 1 @@ -15,9 +14,9 @@ function checkCommandsAvailable() { done } -checkCommandsAvailable helm minikube jq vault sed grep docker grep cat gcloud +checkCommandsAvailable helm minikube jq vault sed grep docker grep cat gcloud envsubst -echo "This is a script to bootstrap the configuration. You need to have installed: helm, kubectl, jq, vault, grep, cat, sed, and google cloud cli, and is only tested on mac, Debian and Ubuntu" +echo "This is a script to bootstrap the configuration. You need to have installed: helm, kubectl, jq, vault, grep, cat, sed, envsubst, and google cloud cli, and is only tested on mac, Debian and Ubuntu" echo "This script is based on the steps defined in https://learn.hashicorp.com/tutorials/vault/kubernetes-minikube. Vault is awesome!" export GCP_PROJECT=$(gcloud config list --format 'value(core.project)' 2>/dev/null) @@ -148,6 +147,8 @@ kubectl annotate serviceaccount \ --namespace default default \ "iam.gke.io/gcp-service-account=wrongsecrets-workload-sa@${GCP_PROJECT}.iam.gserviceaccount.com" +envsubst <./k8s/secret-challenge-vault-deployment.yml.tpl >./k8s/secret-challenge-vault-deployment.yml + kubectl apply -f./k8s/secret-challenge-vault-deployment.yml 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 #kubectl expose deployment secret-challenge --type=LoadBalancer --port=8080 diff --git a/gcp/k8s/secret-challenge-vault-deployment.yml b/gcp/k8s/secret-challenge-vault-deployment.yml.tpl similarity index 93% rename from gcp/k8s/secret-challenge-vault-deployment.yml rename to gcp/k8s/secret-challenge-vault-deployment.yml.tpl index f152db230d..90ce867d23 100644 --- a/gcp/k8s/secret-challenge-vault-deployment.yml +++ b/gcp/k8s/secret-challenge-vault-deployment.yml.tpl @@ -33,7 +33,7 @@ spec: volumeAttributes: secretProviderClass: "wrongsecrets-gcp-secretsmanager" containers: - - image: jeroenwillemsen/addo-example:1.0.4-k8s-vault + - image: jeroenwillemsen/wrongsecrets:1.0.4-k8s-vault imagePullPolicy: IfNotPresent ports: - containerPort: 8080 @@ -43,6 +43,8 @@ spec: terminationMessagePath: /dev/termination-log terminationMessagePolicy: File env: + - name: GCP_PROJECT_ID + value: ${GCP_PROJECT} - name: K8S_ENV value: gcp - name: SPECIAL_K8S_SECRET diff --git a/gcp/secrets.tf b/gcp/secrets.tf index 157e217927..4954eb59fd 100644 --- a/gcp/secrets.tf +++ b/gcp/secrets.tf @@ -22,7 +22,7 @@ resource "random_password" "password" { override_special = "_%@" } -resource "google_secret_manager_secret_version" "secret-version-basic" { +resource "google_secret_manager_secret_version" "secret_version_basic" { secret = google_secret_manager_secret.wrongsecret_1.id secret_data = random_password.password.result diff --git a/k8s/secret-challenge-deployment.yml b/k8s/secret-challenge-deployment.yml index 9d4c2c3cd8..ca87a1d4b1 100644 --- a/k8s/secret-challenge-deployment.yml +++ b/k8s/secret-challenge-deployment.yml @@ -25,7 +25,7 @@ spec: name: secret-challenge spec: containers: - - image: jeroenwillemsen/addo-example:1.0.4-no-vault + - image: jeroenwillemsen/wrongsecrets:1.0.4-no-vault imagePullPolicy: IfNotPresent ports: - containerPort: 8080 diff --git a/k8s/secret-challenge-vault-deployment.yml b/k8s/secret-challenge-vault-deployment.yml index d3a0ed1ebd..0bd60906ef 100644 --- a/k8s/secret-challenge-vault-deployment.yml +++ b/k8s/secret-challenge-vault-deployment.yml @@ -26,7 +26,7 @@ spec: spec: serviceAccountName: vault containers: - - image: jeroenwillemsen/addo-example:1.0.4-k8s-vault + - image: jeroenwillemsen/wrongsecrets:1.0.4-k8s-vault imagePullPolicy: IfNotPresent ports: - containerPort: 8080 diff --git a/pom.xml b/pom.xml index 1ad2a54060..26441b7e6a 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ 9.3.0.0 5.1.2 2.14.1 + 24.0.0 @@ -105,6 +106,10 @@ spring-boot-devtools true + + com.google.cloud + google-cloud-secretmanager + @@ -116,6 +121,13 @@ pom import + + com.google.cloud + libraries-bom + ${gcp.sdk.version} + pom + import + diff --git a/src/main/java/org/owasp/wrongsecrets/ChallengesController.java b/src/main/java/org/owasp/wrongsecrets/ChallengesController.java index 995cf859e0..af2fb74780 100644 --- a/src/main/java/org/owasp/wrongsecrets/ChallengesController.java +++ b/src/main/java/org/owasp/wrongsecrets/ChallengesController.java @@ -19,11 +19,16 @@ public class ChallengesController { private final String version; private final ScoreCard scoreCard; private final List challenges; + private final String k8sEnvironment; - public ChallengesController(@Value("${APP_VERSION}") String version, ScoreCard scoreCard, List challenges) { + public ChallengesController(@Value("${APP_VERSION}") String version, ScoreCard scoreCard, List challenges, @Value("${K8S_ENV}") String k8sEnvironment) { this.version = version; this.scoreCard = scoreCard; this.challenges = challenges; + /** + * note: this is required as "environment" in our model, as the templates require it to show the right cloud explanation + */ + this.k8sEnvironment = k8sEnvironment; } /** @@ -58,18 +63,30 @@ public String challenge(Model model, @PathVariable String id) { model.addAttribute("answerIncorrect", null); model.addAttribute("solution", null); model.addAttribute("challengeNumber", challengeNumber(challenge)); - + addPreviousAndNextChallenge(model, challenge); + model.addAttribute("explanationfile", challenge.getExplanationFileIdentifier()); + model.addAttribute("environment", k8sEnvironment); includeScoringStatus(model, challenge); addWarning(challenge, model); return "challenge"; } + private void addPreviousAndNextChallenge(Model model, Challenge challenge) { + if (challengeNumber(challenge) > 1) { + model.addAttribute("previouschallenge", challengeNumber(challenge) - 1); + } + if (challengeNumber(challenge) < challenges.size()) { + model.addAttribute("nextchallenge", challengeNumber(challenge) + 1); + } + } + @PostMapping("/challenge/{id}") public String postController(@ModelAttribute ChallengeForm challengeForm, Model model, @PathVariable String id) { var challenge = findChallenge(id); model.addAttribute("challengeNumber", challengeNumber(challenge)); - + model.addAttribute("explanationfile", challenge.getExplanationFileIdentifier()); + model.addAttribute("environment", k8sEnvironment); if (challenge.solved(challengeForm.solution())) { model.addAttribute("answerCorrect", "Your answer is correct!"); } else { @@ -77,6 +94,7 @@ public String postController(@ModelAttribute ChallengeForm challengeForm, Model } includeScoringStatus(model, challenge); addWarning(challenge, model); + addPreviousAndNextChallenge(model, challenge); return "challenge"; } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java b/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java index 603375b867..9693079a6b 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/Challenge.java @@ -13,6 +13,10 @@ public abstract class Challenge { public abstract Spoiler spoiler(); + public String getExplanationFileIdentifier() { + return this.getClass().getAnnotation(ChallengeNumber.class).value(); + } + public boolean solved(String answer) { var correctAnswer = answerCorrect(answer); if (correctAnswer) { diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/IndexController.java b/src/main/java/org/owasp/wrongsecrets/challenges/IndexController.java index c35e7a4257..4a40131042 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/IndexController.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/IndexController.java @@ -23,8 +23,16 @@ public class IndexController { @GetMapping("/") public String index(Model model) { + linkDisableInstruction(model); model.addAttribute("version", version); model.addAttribute("environment", k8sEnvironment); + return "index"; + } + + /** + * Added the cloud, vault, and k8s variables in the model in order ot highlight the challenges which are functional and which are not. + */ + private void linkDisableInstruction(Model model) { if ("gcp".equals(k8sEnvironment) || "aws".equals(k8sEnvironment)) { model.addAttribute("cloud", "enabled"); } @@ -34,6 +42,5 @@ public String index(Model model) { if (k8sEnvironment.contains("k8s") || "gcp".equals(k8sEnvironment) || "aws".equals(k8sEnvironment)) { model.addAttribute("k8s", "enabled"); } - return "index"; } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java index 0916c9494b..d3632bfb80 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge10.java @@ -37,6 +37,14 @@ public Spoiler spoiler() { return new Spoiler(challengeAnswer); } + @Override + public String getExplanationFileIdentifier() { + if ("gcp".equals(k8sEnvironment)) { + return "10-gcp"; + } + return "10"; + } + @Override public boolean answerCorrect(String answer) { return challengeAnswer.equals(answer); diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java index 83f10fb087..806898de8c 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge11.java @@ -1,6 +1,7 @@ package org.owasp.wrongsecrets.challenges.cloud; +import com.google.api.gax.rpc.ApiException; import lombok.extern.slf4j.Slf4j; import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; @@ -20,6 +21,11 @@ import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityResponse; import software.amazon.awssdk.services.sts.model.StsException; +import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretVersionName; + + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; @@ -33,22 +39,28 @@ public class Challenge11 extends Challenge { private final String awsRegion; private final String tokenFileLocation; private final String awsDefaultValue; + private final String gcpDefaultValue; private final String challengeAnswer; private final String k8sEnvironment; + private final String projectId; public Challenge11(ScoreCard scoreCard, @Value("${AWS_ROLE_ARN}") String awsRoleArn, @Value("${AWS_WEB_IDENTITY_TOKEN_FILE}") String tokenFileLocation, @Value("${AWS_REGION}") String awsRegion, + @Value("${default_gcp_value}") String gcpDefaultValue, @Value("${default_aws_value}") String awsDefaultValue, + @Value("${GCP_PROJECT_ID}") String projectId, @Value("${K8S_ENV}") String k8sEnvironment) { super(scoreCard, ChallengeEnvironment.CLOUD); this.awsRoleArn = awsRoleArn; this.tokenFileLocation = tokenFileLocation; this.awsRegion = awsRegion; this.awsDefaultValue = awsDefaultValue; - this.challengeAnswer = getAWSChallenge11Value(); + this.gcpDefaultValue = gcpDefaultValue; this.k8sEnvironment = k8sEnvironment; + this.projectId = projectId; + this.challengeAnswer = k8sEnvironment.equals("aws") ? getAWSChallenge11Value() : getGCPChallenge11Value(); } @Override @@ -56,6 +68,14 @@ public Spoiler spoiler() { return new Spoiler(challengeAnswer); } + @Override + public String getExplanationFileIdentifier() { + if ("gcp".equals(k8sEnvironment)) { + return "11-gcp"; + } + return "11"; + } + @Override public boolean answerCorrect(String answer) { return challengeAnswer.equals(answer); @@ -67,7 +87,7 @@ public boolean environmentSupported() { } private String getAWSChallenge11Value() { - log.info("Getting credentials"); + log.info("Getting credentials from AWS"); if (!"if_you_see_this_please_use_AWS_Setup".equals(awsRoleArn)) { try { //based on https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/sts/src/main/java/com/example/sts @@ -108,4 +128,23 @@ private String getAWSChallenge11Value() { } return awsDefaultValue; } + + private String getGCPChallenge11Value() { + log.info("Getting credentials from GCP"); + if ("gcp".equals(k8sEnvironment)) { + // Based on https://cloud.google.com/secret-manager/docs/reference/libraries + try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) { + log.info("Fetching secret form Google Secret Manager..."); + SecretVersionName secretVersionName = SecretVersionName.of(projectId, "wrongsecret-3", "latest"); + AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName); + String payload = response.getPayload().getData().toStringUtf8(); + return payload; + } catch (ApiException e) { + log.error("Exception getting secret", e); + } catch (IOException e) { + log.error("Could not get the web identity token, due to ", e); + } + } + return gcpDefaultValue; + } } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java index 1bcdb6cb51..102c16984d 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/cloud/Challenge9.java @@ -37,6 +37,14 @@ public Spoiler spoiler() { return new Spoiler(challengeAnswer); } + @Override + public String getExplanationFileIdentifier() { + if ("gcp".equals(k8sEnvironment)) { + return "9-gcp"; + } + return "9"; + } + @Override public boolean answerCorrect(String answer) { return challengeAnswer.equals(answer); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8c322a6173..32d9c2d404 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,10 +6,12 @@ ARG_BASED_PASSWORD=if_you_see_this_please_use_docker_instead DOCKER_ENV_PASSWORD=if_you_see_this_please_use_docker_instead vaultpassword=if_you_see_this_please_use_K8S_and_Vault default_aws_value=if_you_see_this_please_use_AWS_Setup +default_gcp_value=if_you_see_this_please_use_GCP_Setup AWS_ROLE_ARN=if_you_see_this_please_use_AWS_Setup AWS_WEB_IDENTITY_TOKEN_FILE=if_you_see_this_please_use_AWS_Setup secretmountpath=/mnt/secrets-store AWS_REGION=if_you_see_this_please_use_AWS_Setup +GCP_PROJECT_ID=if_you_see_this_please_use_GCP_Setup K8S_ENV=Docker APP_VERSION=@project.version@ logging.level.root=INFO @@ -41,13 +43,3 @@ spring.cloud.vault.token=00000000-0000-0000-0000-000000000000 spring.config.activate.on-profile=without-vault wrongsecretvalue=wrongsecret spring.cloud.vault.enabled=false - - - - - - - - - - diff --git a/src/main/resources/templates/challenge.html b/src/main/resources/templates/challenge.html index 8a72376915..70dbda378c 100644 --- a/src/main/resources/templates/challenge.html +++ b/src/main/resources/templates/challenge.html @@ -54,13 +54,8 @@ - - - - - + + There are 11 challenges (/challenge/1-11), can you solve them all? - Go back to the main page. + + + Previous + + + Go the main page + + + Next + + + Challenge 4 (requires Docker)"))) + .andExpect(MockMvcResultMatchers.content().string(CoreMatchers.containsString("Challenge 5 (requires"))); + } + + +} diff --git a/src/test/java/org/owasp/wrongsecrets/SecretLeakageControllerTest.java b/src/test/java/org/owasp/wrongsecrets/SecretLeakageControllerTest.java index 854a253641..35541895ed 100644 --- a/src/test/java/org/owasp/wrongsecrets/SecretLeakageControllerTest.java +++ b/src/test/java/org/owasp/wrongsecrets/SecretLeakageControllerTest.java @@ -4,19 +4,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.owasp.wrongsecrets.challenges.cloud.Challenge10; -import org.owasp.wrongsecrets.challenges.cloud.Challenge9; import org.owasp.wrongsecrets.challenges.docker.Constants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.support.TestPropertySourceUtils; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -24,10 +19,6 @@ import org.springframework.vault.core.VaultTemplate; import org.springframework.web.context.WebApplicationContext; -import java.io.File; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; - import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 9e731c204f..5394029366 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -8,9 +8,11 @@ DOCKER_ENV_PASSWORD= if_you_see_this_please_use_docker_instead vaultpassword = if_you_see_this_please_use_K8S_and_Vault vaultpassword.password = if_you_see_this_please_use_K8S_and_Vault default_aws_value = if_you_see_this_please_use_AWS_Setup +default_gcp_value = if_you_see_this_please_use_GCP_Setup AWS_REGION=if_you_see_this_please_use_AWS_Setup AWS_ROLE_ARN= if_you_see_this_please_use_AWS_Setup AWS_WEB_IDENTITY_TOKEN_FILE= if_you_see_this_please_use_AWS_Setup +GCP_PROJECT_ID= if_you_see_this_please_use_GCP_Setup secretmountpath = ${java.io.tmpdir} wrongsecretvalue = wrongsecret APP_VERSION=0.0.0