From 72b0d28d8267e133bbfb305c16b32c94a58f8524 Mon Sep 17 00:00:00 2001 From: alvacoder Date: Wed, 19 Mar 2025 00:17:16 +0100 Subject: [PATCH 1/4] feat: implement scan output upload --- defectdojo.py | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 defectdojo.py diff --git a/defectdojo.py b/defectdojo.py new file mode 100644 index 0000000..bdfbcdd --- /dev/null +++ b/defectdojo.py @@ -0,0 +1,102 @@ +""" + * @author Idris Adeniji (alvacoder) + * @email idrisadeniji01@gmail.com + * @create date 2025-03-18 22:01:56 +""" + +import requests +import os +import sys +import logging +#implement grepping dependabot alerts through graphql +class Defectdojo(object): + + def __init__(self): + + # Required Secrets for this awesome code to function. + self.github_token=os.environ["INPUT_GITHUB_PERSONAL_TOKEN"] + self.defectdojo_api_key=os.environ["INPUT_DEFECTDOJO_API_KEY"] + self.defectdojo_username=os.environ["INPUT_DEFECTDOJO_USERNAME"] + self.defectdojo_password=os.environ["INPUT_DEFECTDOJO_PASSWORD"] + self.defectdojo_service_account=os.environ["INPUT_DEFECTDOJO_SERVICE_ACCOUNT"] + + # Required Defectdojo Variables. + self.defectdojo_url=os.environ["INPUT_DEFECTDOJO_URL"] + self.defectdojo_product_type=os.environ["INPUT_DEFECTDOJO_PRODUCT_TYPE"] + self.defectdojo_product=os.environ["INPUT_DEFECTDOJO_PRODUCT"] + self.defectdojo_environment_type=os.environ["INPUT_DEFECTDOJO_ENVIRONMENT_TYPE"] + self.defectdojo_scan_type=os.environ["INPUT_DEFECTDOJO_SCAN_TYPE"] + self.defectdojo_engagement_name=os.environ["INPUT_DEFECTDOJO_ENGAGEMENT_NAME"] + self.scan_results_file_path=os.environ["INPUT_RESULTS_FILE_PATH"] + self.scan_results_file_path=os.environ["INPUT_RESULTS_FILE_PATH"] + self.scan_results_file_path=os.environ["INPUT_RESULTS_FILE_PATH"] + self.client_certificate_file_path=os.environ["INPUT_CLIENT_CERTIFICATE_FILE_PATH"] + self.client_key_file_path=os.environ["INPUT_CLIENT_KEY_FILE_PATH"] + + def import_scan_results_to_defectdojo( + self, + logger: logging.Logger, + ) -> int: + + requests_timeout = 180 + + headers = { + "Authorization": f"Token {self.defectdojo_api_key}", + } + + api_endpoint = f"{self.defectdojo_url}/api/v2/import-scan/" + + files = {} + files["file"] = open(self.scan_results_file_path) + + data = { + "product_type_name": self.defectdojo_product_type, + "product_name": self.defectdojo_product, + "environment": self.defectdojo_environment_type, + "scan_type": self.defectdojo_scan_type, + "engagement_name": self.defectdojo_engagement_name, + "auto_create_context": True, + "close_old_findings": True, + "verified": False, + } + if self.client_certificate_file_path and self.client_key_file_path: + r = requests.post( + api_endpoint, + headers=headers, + files=files, + data=data, + verify=True, + cert=(client_certificate_file_path, client_key_file_path), + timeout=requests_timeout, + ) + elif not self.client_certificate_file_path and not self.client_key_file_path: + r = requests.post( + api_endpoint, + headers=headers, + files=files, + data=data, + verify=True, + timeout=requests_timeout, + ) + else: + logger.error( + "either the client certificate or client key file paths were not set correctly" + ) + sys.exit(1) + if r.status_code == 201: + return r.status_code + else: + logger.error(f"upload of results to {self.defectdojo_url} failed") + sys.exit(1) + + def run(self): + try: + self.import_scan_results_to_defectdojo() + + except Exception as e: + print("Exception:{}".format(e)) + sys.exit(1) + + +Defectdojo().run() + From 75d1e3043e41381505d5bb4fc4fd1fb79133d946 Mon Sep 17 00:00:00 2001 From: alvacoder Date: Wed, 19 Mar 2025 00:17:48 +0100 Subject: [PATCH 2/4] feat: optimize codebase --- defectdojo.py | 122 +++++++++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/defectdojo.py b/defectdojo.py index bdfbcdd..95a69ff 100644 --- a/defectdojo.py +++ b/defectdojo.py @@ -5,50 +5,38 @@ """ import requests -import os +import os import sys import logging -#implement grepping dependabot alerts through graphql -class Defectdojo(object): - def __init__(self): +class Defectdojo: + def __init__(self): # Required Secrets for this awesome code to function. - self.github_token=os.environ["INPUT_GITHUB_PERSONAL_TOKEN"] - self.defectdojo_api_key=os.environ["INPUT_DEFECTDOJO_API_KEY"] - self.defectdojo_username=os.environ["INPUT_DEFECTDOJO_USERNAME"] - self.defectdojo_password=os.environ["INPUT_DEFECTDOJO_PASSWORD"] - self.defectdojo_service_account=os.environ["INPUT_DEFECTDOJO_SERVICE_ACCOUNT"] + self.github_token = os.environ["INPUT_GITHUB_PERSONAL_TOKEN"] # Required Defectdojo Variables. - self.defectdojo_url=os.environ["INPUT_DEFECTDOJO_URL"] - self.defectdojo_product_type=os.environ["INPUT_DEFECTDOJO_PRODUCT_TYPE"] - self.defectdojo_product=os.environ["INPUT_DEFECTDOJO_PRODUCT"] - self.defectdojo_environment_type=os.environ["INPUT_DEFECTDOJO_ENVIRONMENT_TYPE"] - self.defectdojo_scan_type=os.environ["INPUT_DEFECTDOJO_SCAN_TYPE"] - self.defectdojo_engagement_name=os.environ["INPUT_DEFECTDOJO_ENGAGEMENT_NAME"] - self.scan_results_file_path=os.environ["INPUT_RESULTS_FILE_PATH"] - self.scan_results_file_path=os.environ["INPUT_RESULTS_FILE_PATH"] - self.scan_results_file_path=os.environ["INPUT_RESULTS_FILE_PATH"] - self.client_certificate_file_path=os.environ["INPUT_CLIENT_CERTIFICATE_FILE_PATH"] - self.client_key_file_path=os.environ["INPUT_CLIENT_KEY_FILE_PATH"] + self.defectdojo_api_key = os.environ["INPUT_DEFECTDOJO_API_KEY"] + self.defectdojo_username = os.environ["INPUT_DEFECTDOJO_USERNAME"] + self.defectdojo_password = os.environ["INPUT_DEFECTDOJO_PASSWORD"] + self.defectdojo_service_account = os.environ["INPUT_DEFECTDOJO_SERVICE_ACCOUNT"] + self.defectdojo_url = os.environ["INPUT_DEFECTDOJO_URL"] + self.defectdojo_product_type = os.environ["INPUT_DEFECTDOJO_PRODUCT_TYPE"] + self.defectdojo_product = os.environ["INPUT_DEFECTDOJO_PRODUCT"] + self.defectdojo_environment_type = os.environ["INPUT_DEFECTDOJO_ENVIRONMENT_TYPE"] + self.defectdojo_scan_type = os.environ["INPUT_DEFECTDOJO_SCAN_TYPE"] + self.defectdojo_engagement_name = os.environ["INPUT_DEFECTDOJO_ENGAGEMENT_NAME"] + self.scan_results_file_path = os.environ["INPUT_RESULTS_FILE_PATH"] def import_scan_results_to_defectdojo( self, logger: logging.Logger, + client_certificate_file_path=None, + client_key_file_path=None ) -> int: - requests_timeout = 180 - - headers = { - "Authorization": f"Token {self.defectdojo_api_key}", - } - api_endpoint = f"{self.defectdojo_url}/api/v2/import-scan/" - - files = {} - files["file"] = open(self.scan_results_file_path) - + headers = {"Authorization": f"Token {self.defectdojo_api_key}"} data = { "product_type_name": self.defectdojo_product_type, "product_name": self.defectdojo_product, @@ -59,44 +47,48 @@ def import_scan_results_to_defectdojo( "close_old_findings": True, "verified": False, } - if self.client_certificate_file_path and self.client_key_file_path: - r = requests.post( - api_endpoint, - headers=headers, - files=files, - data=data, - verify=True, - cert=(client_certificate_file_path, client_key_file_path), - timeout=requests_timeout, - ) - elif not self.client_certificate_file_path and not self.client_key_file_path: - r = requests.post( - api_endpoint, - headers=headers, - files=files, - data=data, - verify=True, - timeout=requests_timeout, - ) - else: - logger.error( - "either the client certificate or client key file paths were not set correctly" - ) - sys.exit(1) - if r.status_code == 201: - return r.status_code - else: - logger.error(f"upload of results to {self.defectdojo_url} failed") + + try: + with open(self.scan_results_file_path, "rb") as scan_file: + files = {"file": scan_file} + + if client_certificate_file_path and client_key_file_path: + r = requests.post( + api_endpoint, + headers=headers, + files=files, + data=data, + verify=True, + cert=(client_certificate_file_path, client_key_file_path), + timeout=180 + ) + else: + r = requests.post( + api_endpoint, + headers=headers, + files=files, + data=data, + verify=True, + timeout=180 + ) + + if r.status_code == 201: + return r.status_code + else: + logger.error(f"Upload of results failed with status code {r.status_code}") + sys.exit(1) + except requests.exceptions.RequestException as e: + logger.error(f"Network error during scan upload: {e}") sys.exit(1) - + def run(self): try: - self.import_scan_results_to_defectdojo() - + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + self.import_scan_results_to_defectdojo(logger) except Exception as e: - print("Exception:{}".format(e)) + print(f"Exception: {e}") sys.exit(1) - -Defectdojo().run() - +if __name__ == "__main__": + Defectdojo().run() From 1f4b4fce494ffdf02e3663eaa0ab265e313adcc8 Mon Sep 17 00:00:00 2001 From: alvacoder Date: Wed, 19 Mar 2025 01:13:15 +0100 Subject: [PATCH 3/4] feat: add action script --- action.yml | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ defectdojo.py | 5 ++--- 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 action.yml diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..a2b0cd6 --- /dev/null +++ b/action.yml @@ -0,0 +1,50 @@ +name: "Defectdojo Upload" +author: "Adeniji Idris (Alvacoder)" +description: "A github action that sends scan reports to a DefectDojo instance with support for multiple authentication methods" +branding: + icon: 'code' + color: 'green' +inputs: + defectdojo_url: + description: the url of your defectdojo instance + required: true + defectdojo_username: + description: the username to login into your defectdojo instance if using basic authentication + required: false + defectdojo_password: + description: the password to login into your defectdojo instance if using basic authentication + required: false + defectdojo_api_key: + description: the API key to authenticate with your defectdojo instance if using API key authentication + required: false + defectdojo_product_type: + description: the defectdojo product type that the scan result relates to + required: true + defectdojo_product: + description: the defectdojo product that the scan result relates to + required: true + defectdojo_environment_type: + description: the defectdojo environment type that the scan result relates to + required: true + defectdojo_scan_type: + description: the defectdojo scan type that the scan result relates to + required: true + defectdojo_engagement_name: + description: the defectdojo engagement name that the scan result relates to + required: true + scan_results_file_name: + description: the file name of the scan result to be uploaded + required: true + client_certificate_file_path: + description: the file path for a client side certificate if required + required: false + client_key_file-path: + description: the file path for a client side private key if required + required: false +# outputs: + # defectdojo-report-id: + # description: the id of the defectdojo report that was created + # value: ${{ steps.import_scan_results.outputs.defectdojo-report-id }} +runs: + using: 'docker' + image: 'Dockerfile' \ No newline at end of file diff --git a/defectdojo.py b/defectdojo.py index 95a69ff..4c9fedf 100644 --- a/defectdojo.py +++ b/defectdojo.py @@ -13,13 +13,12 @@ class Defectdojo: def __init__(self): # Required Secrets for this awesome code to function. - self.github_token = os.environ["INPUT_GITHUB_PERSONAL_TOKEN"] - - # Required Defectdojo Variables. self.defectdojo_api_key = os.environ["INPUT_DEFECTDOJO_API_KEY"] self.defectdojo_username = os.environ["INPUT_DEFECTDOJO_USERNAME"] self.defectdojo_password = os.environ["INPUT_DEFECTDOJO_PASSWORD"] self.defectdojo_service_account = os.environ["INPUT_DEFECTDOJO_SERVICE_ACCOUNT"] + + # Required Defectdojo Variables. self.defectdojo_url = os.environ["INPUT_DEFECTDOJO_URL"] self.defectdojo_product_type = os.environ["INPUT_DEFECTDOJO_PRODUCT_TYPE"] self.defectdojo_product = os.environ["INPUT_DEFECTDOJO_PRODUCT"] From bbf09c6b310486672941f2227b356faacc82b5ca Mon Sep 17 00:00:00 2001 From: alvacoder Date: Wed, 19 Mar 2025 01:18:39 +0100 Subject: [PATCH 4/4] feat: setup docker runner image --- Dockerfile | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7ab9366 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.7-slim AS builder +ADD . /app +RUN pip install --target=/app requests logging setuptools + + +FROM gcr.io/distroless/python3-debian10 +COPY --from=builder /app /app +WORKDIR /app +ENV PYTHONPATH /app +CMD ["/app/defectdojo.py"] \ No newline at end of file