diff --git a/.woodpecker.yml b/.woodpecker.yml index 4ea4f1a..138d5e6 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -18,6 +18,8 @@ steps: -o dependencytrack-client \ --package-name dependencytrack_api \ -t dependencytrack-openapi-custom-template + when: + - event: [ push, tag ] generate-defectdojo: image: openapitools/openapi-generator-cli:v7.12.0 @@ -30,4 +32,22 @@ steps: -g python \ -o defectdojo-client \ --package-name defectdojo_api \ + when: + - event: [ push, tag ] + + build: + image: plugins/kaniko + settings: + repo: ${FORGE_NAME}/${CI_REPO} + registry: + from_secret: container_registry + tags: latest,${CI_COMMIT_SHA},${CI_COMMIT_TAG} + username: + from_secret: container_registry_username + password: + from_secret: container_registry_password + dockerfile: Dockerfile + when: + - event: [ push, tag ] + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d199278 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12.10-alpine3.21 + +APP_DIR=/opt/app + +RUN \ + mkdir -p $APP_DIR + +COPY src/requirements.txt $APP_DIR +COPY src/sbom-dt-dd.py $APP_DIR +COPY dependencytrack-client/ $APP_DIR +COPY defectdojo-client/ $APP_DIR + + + + diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..1d61f74 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,3 @@ +regex==2024.11.6 +loguru==0.7.3 + diff --git a/src/sbom-dt-dd.py b/src/sbom-dt-dd.py new file mode 100644 index 0000000..5c29914 --- /dev/null +++ b/src/sbom-dt-dd.py @@ -0,0 +1,175 @@ +import os +from loguru import logger +import argparse +import subprocess +import json + +import defectdojo_api +from defectdojo_api.rest import ApiException as DefectDojoApiException +import datetime +from dateutil.relativedelta import relativedelta + +import dependencytrack_api +from dependencytrack_api.rest import ApiException as DependencyTrackApiException + +class MyLocalException(Exception): pass + +def executeApiCall(apiClient, ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams=[]): + try: + logger.info(f"Calling {ApiClass}.{EndpointMethod} with {RequestClass} ({additionalParams}, {requestParams})") + instance = ApiClass(apiClient) + if RequestClass: + request = RequestClass(**requestParams) + response = EndpointMethod(instance, *additionalParams, request) + else: + response = EndpointMethod(instance, *additionalParams) + logger.info(f"Response is {response}") + return response + except Exception as e: + logger.error(f"Caught error {e} with {str(e)}") + raise MyLocalException(e) + +def generateSBOM(target='.', name='dummyName', version='0.0.0'): + try: + result = subprocess.run( + ["syft", "scan", target, "-o", "cyclonedx-json", "--source-name", name, "--source-version", version], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + sbom = json.loads(result.stdout) + return sbom + except subprocess.CalledProcessError as e: + logger.error(f"SBOM scanner failed: {e.stderr}") + raise MyLocalException(e) + + +try: + DTRACK_API_URL = os.environ["DTRACK_API_URL"] + DTRACK_TOKEN = os.environ["DTRACK_TOKEN"] + DEFECTDOJO_URL = os.environ["DEFECTDOJO_URL"] + DEFECTDOJO_TOKEN = os.environ["DEFECTDOJO_TOKEN"] +except KeyError as e: + raise Exception(f"Env variable {e} is shall be set") + + +parser = argparse.ArgumentParser(description='snippet/test03') +parser.add_argument('--name', '-n', + help='Project Name', + required=True) +parser.add_argument('--version', '-v', + help='Project Version', + required=True) +parser.add_argument('--description', '-d', + help='Project Description', + required=True) +parser.add_argument('--type', '-t', + help='Product Type from DefectDojo', + type=int, + required=True) +parser.add_argument('--classifier', '-c', + help='Project Classifier from DependencyTrack', + choices=['APPLICATION', 'FRAMEWORK', 'LIBRARY', 'CONTAINER', 'OPERATING_SYSTEM', 'DEVICE', 'FIRMWARE', 'FILE', 'PLATFORM', 'DEVICE_DRIVER', 'MACHINE_LEARNING_MODEL', 'DATA'], + required=True) +parser.add_argument('--target', '-T', + help='Target to scan, either path name for sources or docker image tag', + required=True) +args = parser.parse_args() +projectName = args.name +projectVersion = args.version +projectDescription = args.description +productType = args.type +projectClassifier = args.classifier +target = args.target + + +logger.info(f"Generating SBOM for {target}") +sbom = generateSBOM(target, projectName, projectVersion) +logger.info("Done.") + + +defectdojo_configuration = defectdojo_api.Configuration( + host = DEFECTDOJO_URL +) +defectdojo_configuration.api_key['tokenAuth'] = DEFECTDOJO_TOKEN +defectdojo_configuration.api_key_prefix['tokenAuth'] = 'Token' + +dependencytrack_configuration = dependencytrack_api.Configuration( + host = f"{DTRACK_API_URL}/api" +) +dependencytrack_configuration.debug = False +dependencytrack_configuration.api_key['ApiKeyAuth'] = DTRACK_TOKEN + +with defectdojo_api.ApiClient(defectdojo_configuration) as defectdojo_api_client: + print("Create product in DefectDojo") + productName = f"{projectName}:{projectVersion}" + product_response = \ + executeApiCall( + defectdojo_api_client, + defectdojo_api.ProductsApi, + defectdojo_api.ProductsApi.products_create, + defectdojo_api.ProductRequest, + { 'name': productName, 'description': projectDescription, 'prod_type': productType }, + [] + ) + + product_id = product_response.id + print(f"{product_id=}") + + print("Create engagement in DefectDojo") + start_time = datetime.date.today() + end_time = start_time + relativedelta(years=10) + engagementName = f"{productName} DTrack Link" + engagement_response = \ + executeApiCall( + defectdojo_api_client, + defectdojo_api.EngagementsApi, + defectdojo_api.EngagementsApi.engagements_create, + defectdojo_api.EngagementRequest, + { 'name': engagementName, 'target_start': start_time, 'target_end': end_time, 'status': 'In Progress', 'product': product_id }, + [] + ) + + engagement_id = engagement_response.id + print(f"{engagement_id=}") + +with dependencytrack_api.ApiClient(dependencytrack_configuration) as dependencytrack_api_client: + project_response = \ + executeApiCall( + dependencytrack_api_client, + dependencytrack_api.ProjectApi, + dependencytrack_api.ProjectApi.create_project, + dependencytrack_api.Project, + { 'name': projectName, 'version': projectVersion, 'classifier': projectClassifier, 'uuid': "", 'last_bom_import': 0 }, + [] + ) + + project_uuid = project_response.uuid + print(f"{project_uuid=}") + + properties = [ + { 'group_name': "integrations", 'property_name': "defectdojo.engagementId", 'property_value': str(engagement_id), 'property_type': "STRING" }, + { 'group_name': "integrations", 'property_name': "defectdojo.doNotReactivate", 'property_value': "true", 'property_type': "BOOLEAN" }, + { 'group_name': "integrations", 'property_name': "defectdojo.reimport", 'property_value': "true", 'property_type': "BOOLEAN" } + ] + for property in properties: + executeApiCall( + dependencytrack_api_client, + dependencytrack_api.ProjectPropertyApi, + dependencytrack_api.ProjectPropertyApi.create_property1, + dependencytrack_api.ProjectProperty, + property, + [ project_uuid ] + ) + + bom_response = \ + executeApiCall( + dependencytrack_api_client, + dependencytrack_api.BomApi, + dependencytrack_api.BomApi.upload_bom, + None, + None, + [ None, False, projectName, projectVersion, None, None, None, None, True, json.dumps(sbom) ] + ) +