add minimal sbom converter
This commit is contained in:
@ -32,6 +32,7 @@ WORKDIR $APP_DIR
|
|||||||
|
|
||||||
COPY src/requirements.txt .
|
COPY src/requirements.txt .
|
||||||
COPY src/sbom-dt-dd.py .
|
COPY src/sbom-dt-dd.py .
|
||||||
|
COPY src/converter.py .
|
||||||
COPY src/entrypoint.sh .
|
COPY src/entrypoint.sh .
|
||||||
COPY dependencytrack-client/ ./dependencytrack-client
|
COPY dependencytrack-client/ ./dependencytrack-client
|
||||||
COPY defectdojo-client/ ./defectdojo-client
|
COPY defectdojo-client/ ./defectdojo-client
|
||||||
|
96
src/converter.py
Normal file
96
src/converter.py
Normal 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")
|
||||||
|
|
@ -1,3 +1,5 @@
|
|||||||
regex==2024.11.6
|
regex==2024.11.6
|
||||||
loguru==0.7.3
|
loguru==0.7.3
|
||||||
|
PyYAML==6.0.2
|
||||||
|
cyclonedx-python-lib==10.4.1
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ from dateutil.relativedelta import relativedelta
|
|||||||
import dependencytrack_api
|
import dependencytrack_api
|
||||||
from dependencytrack_api.rest import ApiException as DependencyTrackApiException
|
from dependencytrack_api.rest import ApiException as DependencyTrackApiException
|
||||||
|
|
||||||
|
from converter import minimalSbomFormatConverter
|
||||||
|
|
||||||
|
|
||||||
class MyLocalException(Exception): pass
|
class MyLocalException(Exception): pass
|
||||||
|
|
||||||
def executeApiCall(apiClient, ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams=[]):
|
def executeApiCall(apiClient, ApiClass, EndpointMethod, RequestClass, requestParams, additionalParams=[]):
|
||||||
@ -84,6 +87,10 @@ parser.add_argument('--uploadsbom', '-U',
|
|||||||
parser.add_argument('--sbomfile', '-F',
|
parser.add_argument('--sbomfile', '-F',
|
||||||
help='Filename of existing SBOM file to upload, use together with -U, do not use together with -T',
|
help='Filename of existing SBOM file to upload, use together with -U, do not use together with -T',
|
||||||
required=False)
|
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',
|
parser.add_argument('--target', '-T',
|
||||||
help='Target to scan, either path name for sources or docker image tag',
|
help='Target to scan, either path name for sources or docker image tag',
|
||||||
required=False)
|
required=False)
|
||||||
@ -102,6 +109,7 @@ projectClassifier = args.classifier
|
|||||||
uploadSbomFlag = args.uploadsbom
|
uploadSbomFlag = args.uploadsbom
|
||||||
if uploadSbomFlag:
|
if uploadSbomFlag:
|
||||||
sbomFileName = args.sbomfile
|
sbomFileName = args.sbomfile
|
||||||
|
minimalSbomFormat = args.minimalsbomformat
|
||||||
else:
|
else:
|
||||||
target = args.target
|
target = args.target
|
||||||
|
|
||||||
@ -115,6 +123,11 @@ if uploadSbomFlag:
|
|||||||
logger.info(f"Reading SBOM from file {sbomFileName}")
|
logger.info(f"Reading SBOM from file {sbomFileName}")
|
||||||
with open(sbomFileName, 'r') as sbomFile:
|
with open(sbomFileName, 'r') as sbomFile:
|
||||||
sbom = sbomFile.read()
|
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.")
|
logger.info("Done.")
|
||||||
else:
|
else:
|
||||||
# ------- generate SBOM ------------
|
# ------- generate SBOM ------------
|
||||||
|
Reference in New Issue
Block a user