From 9a0def304722bb6c4e9a59f9d5b4e74c0f32e68e Mon Sep 17 00:00:00 2001 From: Jeremy Bonghwan Choi Date: Mon, 12 Feb 2024 18:15:26 +1000 Subject: [PATCH] export generic scanner results in SARIF to Defect Dojo (#167) * inline can include redirection shell charaters. updated the generic template, explicitly specifing container.type is none * export generic scanner result to DefectDojo * test case updated now the generic_cli is a 'string' type --- config/config-template-generic-scan.yaml | 81 +++++++++++++------ helm/README.md | 3 +- scanners/generic/generic.py | 38 ++++++++- scanners/generic/generic_none.py | 7 +- .../generic/tools/generic_sample_script.sh | 1 - tests/scanners/generic/test_generic.py | 2 +- 6 files changed, 99 insertions(+), 33 deletions(-) diff --git a/config/config-template-generic-scan.yaml b/config/config-template-generic-scan.yaml index 05ca9282..737019c5 100644 --- a/config/config-template-generic-scan.yaml +++ b/config/config-template-generic-scan.yaml @@ -1,7 +1,6 @@ -# This is a configuration template file to perform scans using user-defined container images +# This is a configuration template file to perform scans using user-defined container images or scripts # # Author: Red Hat Product Security -# config: # WARNING: `configVersion` indicates the schema version of the config file. @@ -10,51 +9,87 @@ config: # It is intended to keep backward compatibility (newer RapiDAST running an older config) configVersion: 5 + # (Optional) configure to export scan results to OWASP Defect Dojo + #defectDojo: + #url: "https://mydefectdojo.example.com/" + # ssl: True|False|/path/to/CA/bundle (default: True). for SSL verification + #ssl: True + #authorization: + #username: "rapidast" + #password: "password" + # or + #token: "abc" + # `application` contains data related to the application, not to the scans. application: shortName: "MyApp-1.0" # `general` is a section that will be applied to all scanners. general: - container: # This configures what technology is to be used for RapiDAST to run each scanner. # Currently supported: `podman` and `none` # none: Default. RapiDAST runs each scanner in the same host or inside the RapiDAST image container # podman: RapiDAST orchestrates each scanner on its own using podman # When undefined, relies on rapidast-defaults.yaml, or `none` if nothing is set - #type: "none" + type: "none" # `scanners' is a section that configures scanning options scanners: generic_1: # This is a generic scanner configuration, defined by the user # Multiple items can be defined with "generic_" named - # This config can be combined into ZAP scan config file if the 'podman' container type is used for it too - # A path to file or directory where results are stored on the host. Note this needs to be used along with the 'volumes' configuration - # results: "/path/to/results" # if None or "*stdout", the command's standard output is selected + # results: + # An absolute path to file or directory where results are stored on the host. + # if it is "*stdout" or unspecified, the command's standard output will be selected + # When container.type is 'podman', this needs to be used along with the container.volumes configuration below + # If the result needs to be sent to DefectDojo, this must be a SARIF format file + #results: "/path/to/results" # this config is used when container.type is not 'podman' - # toolDir: scanners/generic/tools - # inline: "" + #toolDir: scanners/generic/tools + inline: "" # this config is only used when container.type is 'podman' - # container: - #parameters: - # Mandatory: image name to run - #image: "" + #container: + #parameters: + # Mandatory: image name to run + #image: "" + + # Optional: command to run. By default, the image's ENTRYPOINT will be run + #command: "" - # Optional: command to run. By default, the image's ENTRYPOINT will be run - #command: "" + # Optional: inject into an existing Pod + #podName: "mypod" - # Optional: inject into an existing Pod - #podName: "mypod" + # Optional: list of expected return codes, anything else will be considered as an error. by default: [0] + #validReturns: [ 0, 1 ] - # Optional: list of expected return codes, anything else will be considered as an error. by default: [0] - #validReturns: [ 0, 1 ] + # Optional: list of volume to mount, e.g.: to retrieve results on the host + #volumes: + # - "::Z" # for Linux + # - ":" # for Mac - # Optional: list of volume to mount, e.g.: to retrieve results on the host - #volumes: - # - "::Z" # for Linux - # - ":" # for Mac + # (Optional) configure to export scan results to OWASP Defect Dojo. + # `config.defectDojo` must be configured first. + #defectDojoExport: + #type: "reimport" # choose between: import, reimport, False (disable export). Default (or other content): re-import if test is set + # Parameters contain data that will directly be sent as parameters to DefectDojo's import/reimport endpoints. + # For example: commit tag, version, push_to_jira, etc. + # See https://demo.defectdojo.org/api/v2/doc/ for a list of possibilities + # The minimum set of data is whatever is needed to identify which engagement/test needs to be chosen. + # If neither a test ID (`test` parameter), nor product_name and engagement_name were provided, sane default will be attempted: + # - product_name chosen from either application.productName or application.shortName + # - engagement_name: "RapiDAST" [this way the same engagement will always be chosen, regardless of the scanner] + #parameters: + #product_name: "My Product" + #engagement_name: "RapiDAST" + # - or - + #engagement: 3 # engagement ID + # - or - + #test_title: "generic" + # - or - + #test: 5 # test ID, that will force "reimport" mode + # additional options, see https://demo.defectdojo.org/api/v2/doc/ for list + #auto_create_context: False # Optional. set to True to auto-create engagement (requires pr diff --git a/helm/README.md b/helm/README.md index ae7c679b..46496f8f 100644 --- a/helm/README.md +++ b/helm/README.md @@ -2,7 +2,7 @@ RapiDAST scans can be performed by using the Helm chart included in the reposito The Helm chart uses the official RapiDAST image: `quay.io/redhatproductsecurity/rapidast:latest` based on the code in the `main` branch. -If you want to run a scan with the custom RapiDAST image(e.g. using the latest code in the `development` branch), you'll need to [build your own image](https://github.com/RedHatProductSecurity/rapidast#build-a-rapidast-image) and push it to your container registry. And update [the image section](https://github.com/RedHatProductSecurity/rapidast/blob/development/helm/chart/values.yaml#L5) of your `chart/values.yaml` file, according to your image name and tag. +If you want to run a scan with the custom RapiDAST image(e.g. using the latest code in the `development` branch), you'll need to [build your own image](https://github.com/RedHatProductSecurity/rapidast#build-a-rapidast-image) and push it to your container registry. And update [the image section](https://github.com/RedHatProductSecurity/rapidast/blob/development/helm/chart/values.yaml#L5) of your `chart/values.yaml` file, according to your image name and tag. In addition, `values.yaml` contains various configuration items including a RapiDAST config template and default scan policy. Either you modify it for your environment or override by using `--set-file`, `--set` or `-f`. @@ -45,4 +45,3 @@ rapidast]$ bash helm/results.sh rapidast-pvc /tmp/results/ When running on OpenShift, make sure that your namespace you are running on has proper privileges for running a pod/container You'll need to set `secContext: '{"privileged": true}'` at [https://github.com/RedHatProductSecurity/rapidast/blob/development/helm/chart/values.yaml#L14](https://github.com/RedHatProductSecurity/rapidast/blob/development/helm/chart/values.yaml#L14) - diff --git a/scanners/generic/generic.py b/scanners/generic/generic.py index f78b7ef1..bd6ff13d 100644 --- a/scanners/generic/generic.py +++ b/scanners/generic/generic.py @@ -74,6 +74,8 @@ def postprocess(self): # pylint: disable=broad-exception-caught except Exception as excp: logging.error(f"Unable to save results: {excp}") + # pylint: disable=attribute-defined-outside-init + # it's a false positive: it's defined in the RapidastScanner class self.state = State.ERROR def cleanup(self): @@ -89,9 +91,41 @@ def data_for_defect_dojo(self): To "cancel", return the (None, None) tuple - Currently, this plugin does not support DD """ - return None, None + if not self._should_export_to_defect_dojo(): + return None, None + logging.debug("Preparing data for Defect Dojo") + + sarif_filename = os.path.basename(self.my_conf("results")) + + filename = f"{self.results_dir}/{sarif_filename}" + logging.debug(f"export {filename} to defect dojo") + + # default, mandatory values (which can be overloaded) + data = { + "scan_type": "SARIF", + "active": True, + "verified": False, + } + + # lists of configured import parameters + params_root = "defectDojoExport.parameters" + import_params = self.my_conf(params_root, default={}).keys() + + # overload that list onto the defaults + for param in import_params: + data[param] = self.my_conf(f"{params_root}.{param}") + + if data.get("test") is None: + # No test ID provided, so we need to make sure there is enough info + # But we can't make it default (they should not be filled if there is a test ID + if not data.get("product_name"): + data["product_name"] = self.config.get( + "application.ProductName" + ) or self.config.get("application.shortName") + if not data.get("engagement_name"): + data["engagement_name"] = "RapiDAST" + return data, filename ############################################################### # PROTECTED METHODS # diff --git a/scanners/generic/generic_none.py b/scanners/generic/generic_none.py index 7585d6df..e881256b 100644 --- a/scanners/generic/generic_none.py +++ b/scanners/generic/generic_none.py @@ -1,6 +1,5 @@ import logging import pprint -import shlex import subprocess from scanners import State @@ -89,7 +88,8 @@ def run(self): logging.info(f"Running a generic scan with the following command:\n{cli}") - # run the command in the tool_dir + # run the command in the tool_dir(cwd) + # shell=True is required to run the command in case it contains shell metacharacters such as redirectons scanning_stdout_results = "" with subprocess.Popen( @@ -98,6 +98,7 @@ def run(self): stdout=stdout_store, bufsize=1, universal_newlines=True, + shell=True, ) as scanning: if stdout_store: logging.debug("Storing standard output") @@ -168,7 +169,5 @@ def _setup_generic_cli(self): """ self.generic_cli = self.my_conf("inline") - if isinstance(self.generic_cli, str): - self.generic_cli = shlex.split(self.generic_cli) logging.debug(f"generic will run with: {self.generic_cli}") diff --git a/scanners/generic/tools/generic_sample_script.sh b/scanners/generic/tools/generic_sample_script.sh index cf24da35..bd124eba 100644 --- a/scanners/generic/tools/generic_sample_script.sh +++ b/scanners/generic/tools/generic_sample_script.sh @@ -48,5 +48,4 @@ cat <