Skip to content

feat: add parser and importer for BlackDuck API #6679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/content/en/integrations/parsers.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ Azure Security Center recommendations can be exported from the user interface in

JSON report format


### Blackduck API

Import findings from the BlackDuck API - no file required.

Follow these steps to setup API importing:

1. Configure the BlackDuck API Authentication details by navigating to
Configuration / Tool Configuration, selecting the Tool Type to "BlackDuck API",
and Authentication Type "API Key". Paste your BlackDuck API token in the
"API Key" field.
2. In the Product settings select "Add API Scan Configuration" and select the
previously added BlackDuck API Tool Configuration. Provide the ID
of the project from which to import findings in the field *Service key 1*.
Provide the version of the project from which to import findings in the field *Service key 2*.
3. After this is done, you can import the findings by selecting "BlackDuck API" as the scan type.

### Blackduck Hub

2 options:
Expand Down
2 changes: 2 additions & 0 deletions dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,7 @@ def saml2_attrib_map_format(dict):
'PWN SAST': ['title', 'description'],
'Whispers': ['vuln_id_from_tool', 'file_path', 'line'],
'Blackduck Hub Scan': ['title', 'vulnerability_ids', 'component_name', 'component_version'],
'BlackDuck API': ['unique_id_from_tool'],
'docker-bench-security Scan': ['unique_id_from_tool'],
}

Expand Down Expand Up @@ -1291,6 +1292,7 @@ def saml2_attrib_map_format(dict):
'PWN SAST': DEDUPE_ALGO_HASH_CODE,
'Whispers': DEDUPE_ALGO_HASH_CODE,
'Blackduck Hub Scan': DEDUPE_ALGO_HASH_CODE,
'BlackDuck API': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
'docker-bench-security Scan': DEDUPE_ALGO_HASH_CODE,
}

Expand Down
Empty file.
44 changes: 44 additions & 0 deletions dojo/tools/blackduck_api/api_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from blackduck import Client


class BlackduckAPI:
"""
A simple client for the BlackDuck API
"""

def __init__(self, tool_config):
if tool_config.authentication_type == "API":
self.api_token = tool_config.api_key
self.base_url = tool_config.url
self.client = Client(base_url=tool_config.url, token=tool_config.api_key)
else:
raise ValueError(
"Authentication type {} not supported".format(
tool_config.authentication_type
)
)

def test_product_connection(self, api_scan_configuration):
asset = self.get_asset(api_scan_configuration.service_key_1)
asset_name = asset["resource"]["title"]
api_scan_configuration.service_key_2 = asset_name
api_scan_configuration.save()
return f'You have access to asset "{asset_name}"'

def get_project_by_name(self, project_name):
for project in self.client.get_resource("projects"):
if project["name"] == project_name:
return project

def get_version_by_name(self, project, version_name):
for version in self.client.get_resource("versions", project):
if version["versionName"] == version_name:
return version

def get_vulnerable_bom_components(self, version):
return self.client.get_resource("vulnerable-components", version)

def get_vulnerabilities(self, component):
return self.client.get_json(
f'/api/vulnerabilities/{component["vulnerabilityWithRemediation"]["vulnerabilityName"]}'
)
41 changes: 41 additions & 0 deletions dojo/tools/blackduck_api/importer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

from django.core.exceptions import ValidationError
from dojo.models import Product_API_Scan_Configuration

from .api_client import BlackduckAPI


class BlackduckApiImporter(object):
"""
Import from BlackDuck API
"""
config_id = "BlackDuck API"

def get_findings(self, test):
client, config = self.prepare_client(test)
project = client.get_project_by_name(config.service_key_1)
version = client.get_version_by_name(project, config.service_key_2)
return client.get_vulnerable_bom_components(version)

def prepare_client(self, test):
product = test.engagement.product
if test.api_scan_configuration:
config = test.api_scan_configuration
# Double check of config
if config.product != product:
raise ValidationError(f'API Scan Configuration for "{self.config_id}" and Product do not match.')
else:
configs = Product_API_Scan_Configuration.objects.filter(product=product, tool_configuration__tool_type__name=self.config_id)
if configs.count() == 1:
config = configs.first()
elif configs.count() > 1:
raise ValidationError(
'More than one Product API Scan Configuration has been configured, but none of them has been chosen. Please specify at Test which one should be used.'
)
else:
raise ValidationError(
f'There are no API Scan Configurations for this Product. Please add at least one API Scan Configuration for "{self.config_id}" to this Product.'
)

tool_config = config.tool_configuration
return BlackduckAPI(tool_config), config
57 changes: 57 additions & 0 deletions dojo/tools/blackduck_api/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import json

from dojo.models import Finding

from .importer import BlackduckApiImporter

SCAN_TYPE_ID = 'BlackDuck API'


class BlackduckApiParser(object):
"""
Import from Synopsys BlackDuck API /findings
"""

def get_scan_types(self):
return [SCAN_TYPE_ID]

def get_label_for_scan_types(self, scan_type):
return SCAN_TYPE_ID

def get_description_for_scan_types(self, scan_type):
return "BlackDuck findings can be directly imported using the Synopsys BlackDuck API. An API Scan Configuration has to be setup in the Product."

def requires_file(self, scan_type):
return False

def requires_tool_type(self, scan_type):
return SCAN_TYPE_ID

def get_findings(self, file, test):
if file is None:
data = BlackduckApiImporter().get_findings(test)
else:
data = json.load(file)
findings = []
for entry in data:
vulnerability_id = entry["vulnerabilityWithRemediation"]["vulnerabilityName"]
component_name = entry["componentName"]
component_version = entry["componentVersionName"]
finding = Finding(
test=test,
title=f'{vulnerability_id} in {component_name}:{component_version}',
description=entry["vulnerabilityWithRemediation"].get("description"),
severity=entry["vulnerabilityWithRemediation"]["severity"].title(),
component_name=component_name,
component_version=component_version,
static_finding=True,
dynamic_finding=False,
unique_id_from_tool=entry["vulnerabilityWithRemediation"].get("vulnerabilityName"),
)
# get CWE
if entry["vulnerabilityWithRemediation"].get("cweId"):
cwe_raw = entry["vulnerabilityWithRemediation"]["cweId"].split("-")
if len(cwe_raw) == 2 and cwe_raw[1].isdigit():
finding.cwe = int(cwe_raw[1])
findings.append(finding)
return findings
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,6 @@ djangosaml2==1.5.1
drf-spectacular==0.23.1
django-ratelimit==3.0.1
argon2-cffi==21.3.0
blackduck==1.0.7
pycurl==7.45.1 # Required for Celery Broker AWS (SQS) support
boto3==1.24.49 # Required for Celery Broker AWS (SQS) support
Loading