Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
563e16c
Use Lombok to Access Redonly Property
Weltraumschaf Dec 30, 2024
d3e00b4
Improves GRammar of Exception Messages
Weltraumschaf Dec 30, 2024
decfdd3
Move Property to top of Class to the Others
Weltraumschaf Dec 30, 2024
1600c8c
Make PersistenceProviderConfig Final to Prevent Extension
Weltraumschaf Dec 30, 2024
511c19b
Make Time Zone Final Becuase Never Changed
Weltraumschaf Dec 30, 2024
7deb89b
Move Cosntructor Before Methods As Expected
Weltraumschaf Dec 30, 2024
67b144b
Generate toString Method for Better Debugging
Weltraumschaf Dec 30, 2024
1dde6ac
Use Generated Nullcheck to simplify Code
Weltraumschaf Dec 30, 2024
294f303
Group Properties Together in Class
Weltraumschaf Dec 30, 2024
87ee122
Make Inline Comment A Real JavaDoc to be Visible in Intellisense
Weltraumschaf Dec 30, 2024
a0974d5
Docuemnt Unchecked Exception Condition
Weltraumschaf Dec 30, 2024
6e1eaf0
Improve and Test Wrong Argument Length Check
Weltraumschaf Dec 30, 2024
dfbbfad
Document Constructors of Exception
Weltraumschaf Jan 7, 2025
ee60cfa
Show Helpfull Errormessage When K8s Env Vars Not Set
Weltraumschaf Jan 7, 2025
cb28f63
Implement Help Message for Easier Usage of Final Binary
Weltraumschaf Jan 7, 2025
c09cc78
Verify Number of Passed In Arguments With Proper Error Message
Weltraumschaf Jan 7, 2025
c2e7e80
Tests For Help Flag
Weltraumschaf Jan 7, 2025
d7419e7
Log Warning When Running In Dev Mode To PRevent It In Production
Weltraumschaf Jan 7, 2025
1467ec2
Close File Reader By Using Try-With-Resource
Weltraumschaf Jan 7, 2025
fbb20fa
Add Shorthand Script to Execute Jar File
Weltraumschaf Jan 7, 2025
3730f86
Improve Error Message If Kubernetes Config Can't Be Read In Dev Mode
Weltraumschaf Jan 7, 2025
2365ba7
Handle Erros Parsing Kubernetes Config
Weltraumschaf Jan 7, 2025
6f4a291
Show Env Vars Also In Help Message
Weltraumschaf Jan 7, 2025
c31d600
Add Missing License Headers
Weltraumschaf Jan 7, 2025
0be20ca
Add Missing SPDX Headers
Weltraumschaf Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions hooks/persistence-defectdojo/hook/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

# SPDX-FileCopyrightText: the secureCodeBox authors
#
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

java -jar ./build/libs/defectdojo-persistenceprovider-1.0.0-SNAPSHOT.jar "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
// SPDX-License-Identifier: Apache-2.0
package io.securecodebox.persistence;

import io.securecodebox.persistence.config.EnvConfig;
import io.securecodebox.persistence.config.PersistenceProviderConfig;
import io.securecodebox.persistence.defectdojo.config.Config;
import io.securecodebox.persistence.defectdojo.model.Finding;
import io.securecodebox.persistence.defectdojo.service.EndpointService;
import io.securecodebox.persistence.defectdojo.service.FindingService;
import io.securecodebox.persistence.exceptions.DefectDojoPersistenceException;
import io.securecodebox.persistence.mapping.DefectDojoFindingToSecureCodeBoxMapper;
import io.securecodebox.persistence.models.Scan;
import io.securecodebox.persistence.service.KubernetesService;
Expand All @@ -16,24 +18,79 @@
import io.securecodebox.persistence.strategies.VersionedEngagementsStrategy;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class DefectDojoPersistenceProvider {
private static final String JAR_FILE = "defectdojo-persistenceprovider-1.0.0-SNAPSHOT.jar";
private static final String USAGE = "Usage: java -jar " + JAR_FILE + " <RAW_RESULT_DOWNLOAD_URL> <FINDING_DOWNLOAD_URL> [<RAW_RESULT_UPLOAD_URL> <FINDING_UPLOAD_URL>] [-h|--help]";
private static final String HELP = """
This hook imports secureCodeBox findings into DefectDojo.

This provider supports two modes:

1. Read-only Mode: Only imports the findings oneway from secureCodeBox into DefectDojo.
2. syncFindingBack Mode: Replace the finding in secureCodeBox with the finding modified by DefectDojo.

This provider uses positional arguments. The first and second argument is required (Read-only Mode).
The third and fourth arguments are optional (syncFindingBack Mode).

Required arguments

1st argument (RAW_RESULT_DOWNLOAD_URL): HTTP URL where the raw finding file (various formats depending on scanner) is available.
2nd argument (FINDING_DOWNLOAD_URL): HTTP URL where the secureCodeBox finding file (JSON) is available.

Optional arguments:

3rd argument (RAW_RESULT_UPLOAD_URL): HTTP URL where to store modified finding file (various formats depending on scanner).
4th argument (FINDING_UPLOAD_URL): HTTP URL where to store modified secureCodeBox finding file (JSON).
-h|--help Show this help.

The hook also looks for various environment variables:

<ENV-VARS>

See the documentation for more details: https://www.securecodebox.io/docs/hooks/defectdojo
""";
private static final String HELP_HINT = "Use option -h or --help to get more details about the arguments.";
private static final int EXIT_CODE_OK = 0;
private static final int EXIT_CODE_ERROR = 1;
private final S3Service s3Service = new S3Service();
private final KubernetesService kubernetesService = new KubernetesService();

public static void main(String[] args) {
try {
new DefectDojoPersistenceProvider().execute(args);
} catch (Exception e) {
log.error(e.getMessage(), e);
System.exit(1);
}
try {
new DefectDojoPersistenceProvider().execute(args);
System.exit(EXIT_CODE_OK);
} catch (final DefectDojoPersistenceException e) {
// We do not log stack traces on own errors because the message itself must be helpful enough to fix it!
log.error(e.getMessage());
log.error(USAGE);
log.error(HELP_HINT);
System.exit(EXIT_CODE_ERROR);
} catch (final Exception e) {
// Also log the stack trace as context for unforeseen errors.
log.error(e.getMessage(), e);
log.error(USAGE);
log.error(HELP_HINT);
System.exit(EXIT_CODE_ERROR);
}
}

private void execute(String[] args) throws Exception {
log.info("Starting DefectDojo persistence provider");

if (shouldShowHelp(args)) {
showHelp();
return; // Someone showing the help does not expect that anything more is done.
}

if (!wrongNumberOfArguments(args)) {
throw new DefectDojoPersistenceException("Wrong number of arguments!");
}

kubernetesService.init();

var scan = new Scan(kubernetesService.getScanFromKubernetes());
Expand Down Expand Up @@ -74,4 +131,28 @@ private void overwriteFindingWithDefectDojoFinding(Config config, List<Finding>
kubernetesService.updateScanInKubernetes(findings);
}

boolean shouldShowHelp(String[] args) {
return Arrays.stream(args).anyMatch(arg -> arg.equals("-h") || arg.equals("--help"));
}

private void showHelp() {
System.out.println(USAGE);
System.out.println();
final var envVars = Arrays.stream(EnvConfig.EnvVarNames.values())
.map(name -> " " + name.getLiteral() + ": " + name.getDescription())
.collect(Collectors.joining("\n"));
System.out.println(HELP.replace("<ENV-VARS>", envVars));
}

boolean wrongNumberOfArguments(String[] args) {
if (args.length == 2) {
return true;
}

if (args.length == 4) {
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,42 +119,28 @@ private String retrieveEnvVar(EnvVarNames name) {
* Enumerates all environment variable names used in this hook
*/
@Getter
enum EnvVarNames {
public enum EnvVarNames {
/**
* Enable development mode.
*
* @deprecated use {@link #IS_DEV} instead
*/
@Deprecated
IS_DEV_LEGACY("IS_DEV"),
IS_DEV_LEGACY("IS_DEV", "(deprecated) Enable development mode."),
IS_DEV("DEFECTDOJO_IS_DEV", "Enable development mode."),
SCAN_NAME("SCAN_NAME", "(provided) secureCodeBox wide environment variable populated with name of the scan custom resource."),
NAMESPACE("NAMESPACE", "(provided) secureCodeBox wide environment variable populated with the Kubernetes namespace the scan is running in."),
LOW_PRIVILEGED_MODE("DEFECTDOJO_LOW_PRIVILEGED_MODE", "Whether low privilege mode is enabled."),
/**
* Enable development mode.
*/
IS_DEV("DEFECTDOJO_IS_DEV"),
/**
* secureCodeBox wide environment variable populated with name of the scan custom resource
*/
SCAN_NAME("SCAN_NAME"),
/**
* secureCodeBox wide environment variable populated with the Kubernetes namespace the scan is running in
*/
NAMESPACE("NAMESPACE"),
/**
* Whether low privilege mode is enabled
*/
LOW_PRIVILEGED_MODE("DEFECTDOJO_LOW_PRIVILEGED_MODE"),
/**
* Seconds to wait until re-fetching findings from DefectDojo
*
* @deprecated see {@link EnvConfig#refetchWaitSeconds()}
*/
@Deprecated
REFETCH_WAIT_SECONDS("DEFECTDOJO_REFETCH_WAIT_SECONDS");
REFETCH_WAIT_SECONDS("DEFECTDOJO_REFETCH_WAIT_SECONDS", "(deprecated) Seconds to wait until re-fetching findings from DefectDojo.");

private final String literal;
private final String description;

EnvVarNames(String literal) {
EnvVarNames(String literal, String description) {
this.literal = literal;
this.description = description;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

import io.securecodebox.persistence.exceptions.DefectDojoPersistenceException;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.ZoneId;
import java.util.List;
Expand All @@ -18,51 +18,94 @@
* the Hook is run in ReadOnly or ReadAndWrite mode based on the number of args.
*/
@Slf4j
public class PersistenceProviderConfig {
private final EnvConfig env = new EnvConfig();

@ToString
public final class PersistenceProviderConfig {
private static final int RAW_RESULT_DOWNLOAD_ARG_POSITION = 0;
private static final int FINDING_DOWNLOAD_ARG_POSITION = 1;

private static final int RAW_RESULT_UPLOAD_ARG_POSITION = 2;
private static final int FINDING_UPLOAD_ARG_POSITION = 3;
public static final int NUMBER_OF_ARGS_READONLY = 2;
public static final int NUMBER_OF_ARGS_READWRITE = 4;

// DefectDojo does in contrast to secureCodeBox not pay attention to time zones
// to guarantee consistent results when converting back and forth a time zone
// has to be assumed for DefectDojo. It defaults to the Time Zone of the system clock
private final EnvConfig env = new EnvConfig();
/**
* Assumed time zone of DefectDojo
* <p>
* DefectDojo does in contrast to secureCodeBox not pay attention to time zones
* to guarantee consistent results when converting back and forth a time zone
* has to be assumed for DefectDojo. It defaults to the Time Zone of the system clock.
* </p>
*/
@Getter
final ZoneId defectDojoTimezoneId = ZoneId.systemDefault();
@Getter
ZoneId defectDojoTimezoneId = ZoneId.systemDefault();
final boolean readOnly;

// Download Urls
/**
* URL where to download the raw result file
*/
@Getter
final String rawResultDownloadUrl;
/**
* URL where to download the parsed finding file
*/
@Getter
final String findingDownloadUrl;

// Upload Urls
/**
* URL where to upload the raw result file, maybe {@code null}
*/
final String rawResultUploadUrl;
/**
* URL where to upload the parsed finding file, maybe {@code null}
*/
final String findingUploadUrl;

/**
* Provider configuration
*
* @param args not {@code null}, hook args passed via command line flags
*/
public PersistenceProviderConfig(@NonNull final String[] args) {
if (args.length == NUMBER_OF_ARGS_READONLY) {
this.readOnly = true;
this.rawResultDownloadUrl = args[RAW_RESULT_DOWNLOAD_ARG_POSITION];
this.findingDownloadUrl = args[FINDING_DOWNLOAD_ARG_POSITION];
// Not set for ReadOnly hooks
this.rawResultUploadUrl = null;
this.findingUploadUrl = null;
} else if (args.length == NUMBER_OF_ARGS_READWRITE) {
this.readOnly = false;
this.rawResultDownloadUrl = args[RAW_RESULT_DOWNLOAD_ARG_POSITION];
this.findingDownloadUrl = args[FINDING_DOWNLOAD_ARG_POSITION];
this.rawResultUploadUrl = args[RAW_RESULT_UPLOAD_ARG_POSITION];
this.findingUploadUrl = args[FINDING_UPLOAD_ARG_POSITION];
} else {
final var msg = "Unexpected number of arguments given %d! Expected are either %d or %d arguments in array!";
throw new DefectDojoPersistenceException(
String.format(msg, args.length, NUMBER_OF_ARGS_READONLY, NUMBER_OF_ARGS_READWRITE));
}
}

/**
* Throws {@link DefectDojoPersistenceException} if {@link #isReadOnly()} is {@code true}
*/
public String getRawResultUploadUrl() {
if (isReadOnly()) {
throw new DefectDojoPersistenceException("Cannot Access RawResult Upload URL as the hook is run is ReadOnly mode.");
throw new DefectDojoPersistenceException("Cannot access the RawResult Upload URL because the hook is executed in ReadOnly mode!");
}
return rawResultUploadUrl;
}

/**
* Throws {@link DefectDojoPersistenceException} if {@link #isReadOnly()} is {@code true}
*/
public String getFindingUploadUrl() {
if (isReadOnly()) {
throw new DefectDojoPersistenceException("Cannot Access Finding Upload URL as the hook is run is ReadOnly mode.");
throw new DefectDojoPersistenceException("Cannot access the Finding Upload URL because the hook is executed in ReadOnly mode!");
}
return findingUploadUrl;
}

final boolean readOnly;

public boolean isReadOnly() {
return readOnly;
}

public boolean isReadAndWrite() {
return !readOnly;
}
Expand All @@ -71,28 +114,4 @@ public boolean isInLowPrivilegedMode() {
return env.lowPrivilegedMode();
}

public PersistenceProviderConfig(String[] args) {
// Parse Hook Args passed via command line flags
if (args == null) {
throw new DefectDojoPersistenceException("Received `null` as command line flags. Expected exactly four (RawResult & Finding Up/Download Urls)");
} else if (args.length == 2) {
this.readOnly = true;

this.rawResultDownloadUrl = args[RAW_RESULT_DOWNLOAD_ARG_POSITION];
this.findingDownloadUrl = args[FINDING_DOWNLOAD_ARG_POSITION];
// Not set for ReadOnly hooks
this.rawResultUploadUrl = null;
this.findingUploadUrl = null;
} else if (args.length == 4) {
this.readOnly = false;

this.rawResultDownloadUrl = args[RAW_RESULT_DOWNLOAD_ARG_POSITION];
this.findingDownloadUrl = args[FINDING_DOWNLOAD_ARG_POSITION];
this.rawResultUploadUrl = args[RAW_RESULT_UPLOAD_ARG_POSITION];
this.findingUploadUrl = args[FINDING_UPLOAD_ARG_POSITION];
} else {
log.error("Received unexpected command line arguments: {}", List.of(args));
throw new DefectDojoPersistenceException("DefectDojo Hook received a unexpected number of command line flags. Expected exactly two (for ReadOnly Mode) or four (for ReadAndWrite mode)");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@
* The base error type of this hook
*/
public class DefectDojoPersistenceException extends RuntimeException {
/**
* Creates an exception with a message
*
* @param message must not be {@code null} ar empty. Should be formatted to be directly printed to STDERR.
*/
public DefectDojoPersistenceException(String message) {
super(message);
this(message, null);
}

/**
* Dedicated constructor
*
* @param message see {@link #DefectDojoPersistenceException(String}
* @param cause may be {@code null} if context where the exception occurred is unnecessary.
*/
public DefectDojoPersistenceException(String message, Throwable cause) {
super(message, cause);
}
Expand Down
Loading
Loading