add minimal sbom converter
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline was successful

This commit is contained in:
2025-07-09 11:26:50 +02:00
parent bd92d8eb87
commit 9afa00f61f
4 changed files with 112 additions and 0 deletions

View File

@ -32,6 +32,7 @@ WORKDIR $APP_DIR
COPY src/requirements.txt .
COPY src/sbom-dt-dd.py .
COPY src/converter.py .
COPY src/entrypoint.sh .
COPY dependencytrack-client/ ./dependencytrack-client
COPY defectdojo-client/ ./defectdojo-client

96
src/converter.py Normal file
View File

@ -0,0 +1,96 @@
from loguru import logger
import yaml
import uuid
from packageurl import PackageURL
from cyclonedx.builder.this import this_component as cdx_lib_component
from cyclonedx.factory.license import LicenseFactory
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Component, ComponentType
from cyclonedx.model.contact import OrganizationalEntity
from cyclonedx.model import XsUri
from cyclonedx.output.json import JsonV1Dot5
class MyLocalConverterException(Exception): pass
def __converterClassifierToComponentType(classifier):
componentType = ''
match classifier:
case 'APPLICATION':
componentType = ComponentType.APPLICATION
case 'FRAMEWORK':
componentType = ComponentType.FRAMEWORK
case 'LIBRARY':
componentType = ComponentType.LIBRARY
case 'CONTAINER':
componentType = ComponentType.CONTAINER
case 'OPERATING_SYSTEM':
componentType = ComponentType.OPERATING_SYSTEM
case 'DEVICE':
componentType = ComponentType.DEVICE
case 'FIRMWARE':
componentType = ComponentType.FIRMWARE
case 'FILE':
componentType = ComponentType.FILE
case 'PLATFORM':
componentType = ComponentType.PLATFORM
case 'DEVICE_DRIVER':
componentType = ComponentType.DEVICE_DRIVER
case 'MACHINE_LEARNING_MODEL':
componentType = ComponentType.MACHINE_LEARNING_MODEL
case 'DATA':
componentType = ComponentType.DATA
case _:
raise MyLocalConverterException(f"No componentType for {classifier} found")
return componentType
def minimalSbomFormatConverter(minimalSbom, classifier):
logger.info(f"Minimal input: {minimalSbom}")
lc_factory = LicenseFactory()
minimalSbomObject = yaml.safe_load(minimalSbom)
logger.debug(f"{minimalSbomObject=}")
bom = Bom()
bom.metadata.tools.components.add(cdx_lib_component())
bom.metadata.tools.components.add(Component(
name='sbom-dt-dd',
type=ComponentType.APPLICATION
))
bom.metadata.component = root_component = Component(
name=minimalSbomObject['product'],
type=__converterClassifierToComponentType(classifier),
version=minimalSbomObject['version'],
licenses=[lc_factory.make_from_string(minimalSbomObject['license'])],
supplier=OrganizationalEntity(
name=minimalSbomObject['supplier']['name'],
urls=[XsUri(minimalSbomObject['supplier']['url'])]
),
bom_ref = f"urn:uuid:{uuid.uuid4()}"
)
for minimalComponentDescription in minimalSbomObject['components']:
component = Component(
type=ComponentType.LIBRARY,
name=minimalComponentDescription['name'],
version=minimalComponentDescription['version'],
licenses=[lc_factory.make_from_string(minimalComponentDescription['license'])],
bom_ref = f"urn:uuid:{uuid.uuid4()}"
)
if 'cpe' in minimalComponentDescription:
component.cpe = minimalComponentDescription['cpe']
if 'purl' in minimalComponentDescription:
component.purl = PackageURL.from_string(minimalComponentDescription['purl'])
bom.components.add(component)
bom.register_dependency(root_component, [component])
outputSbom = JsonV1Dot5(bom).output_as_string(indent=2)
logger.info(outputSbom)
raise Exception("Conversion aborted")

View File

@ -1,3 +1,5 @@
regex==2024.11.6
loguru==0.7.3
PyYAML==6.0.2
cyclonedx-python-lib==10.4.1

View File

@ -12,6 +12,9 @@ from dateutil.relativedelta import relativedelta
import dependencytrack_api
from dependencytrack_api.rest import ApiException as DependencyTrackApiException
from converter import minimalSbomFormatConverter
class MyLocalException(Exception): pass
def executeApiCall(apiClient, ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams=[]):
@ -84,6 +87,10 @@ parser.add_argument('--uploadsbom', '-U',
parser.add_argument('--sbomfile', '-F',
help='Filename of existing SBOM file to upload, use together with -U, do not use together with -T',
required=False)
parser.add_argument('--minimalsbomformat', '-K',
help='SBOM file comes in dedicated minimal format and will be converted into cyclonedx before uploading',
action='store_true',
default=False)
parser.add_argument('--target', '-T',
help='Target to scan, either path name for sources or docker image tag',
required=False)
@ -102,6 +109,7 @@ projectClassifier = args.classifier
uploadSbomFlag = args.uploadsbom
if uploadSbomFlag:
sbomFileName = args.sbomfile
minimalSbomFormat = args.minimalsbomformat
else:
target = args.target
@ -115,6 +123,11 @@ if uploadSbomFlag:
logger.info(f"Reading SBOM from file {sbomFileName}")
with open(sbomFileName, 'r') as sbomFile:
sbom = sbomFile.read()
logger.info("SBOM file read.")
if minimalSbomFormat:
logger.info("Start converting from minimal format into cyclonedx")
sbom = minimalSbomFormatConverter(sbom, projectClassifier)
logger.info("Converted")
logger.info("Done.")
else:
# ------- generate SBOM ------------