Skip to content

Commit cc9e30e

Browse files
damiencarolДмитрий Муковкин
authored andcommitted
feat: add parser and importer for BlackDuck API (DefectDojo#6679)
* feat: add parser and importer for BalckDuck API * Add lib * Update requirements.txt * Update parser.py * Fix settings
1 parent 34c2adc commit cc9e30e

File tree

9 files changed

+3385
-0
lines changed

9 files changed

+3385
-0
lines changed

docs/content/en/integrations/parsers.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ Azure Security Center recommendations can be exported from the user interface in
8585

8686
JSON report format
8787

88+
89+
### Blackduck API
90+
91+
Import findings from the BlackDuck API - no file required.
92+
93+
Follow these steps to setup API importing:
94+
95+
1. Configure the BlackDuck API Authentication details by navigating to
96+
Configuration / Tool Configuration, selecting the Tool Type to "BlackDuck API",
97+
and Authentication Type "API Key". Paste your BlackDuck API token in the
98+
"API Key" field.
99+
2. In the Product settings select "Add API Scan Configuration" and select the
100+
previously added BlackDuck API Tool Configuration. Provide the ID
101+
of the project from which to import findings in the field *Service key 1*.
102+
Provide the version of the project from which to import findings in the field *Service key 2*.
103+
3. After this is done, you can import the findings by selecting "BlackDuck API" as the scan type.
104+
88105
### Blackduck Hub
89106

90107
2 options:

dojo/settings/settings.dist.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,7 @@ def saml2_attrib_map_format(dict):
11421142
'PWN SAST': ['title', 'description'],
11431143
'Whispers': ['vuln_id_from_tool', 'file_path', 'line'],
11441144
'Blackduck Hub Scan': ['title', 'vulnerability_ids', 'component_name', 'component_version'],
1145+
'BlackDuck API': ['unique_id_from_tool'],
11451146
'docker-bench-security Scan': ['unique_id_from_tool'],
11461147
'Veracode SourceClear Scan': ['title', 'vulnerability_ids', 'component_name', 'component_version'],
11471148
}
@@ -1294,6 +1295,7 @@ def saml2_attrib_map_format(dict):
12941295
'PWN SAST': DEDUPE_ALGO_HASH_CODE,
12951296
'Whispers': DEDUPE_ALGO_HASH_CODE,
12961297
'Blackduck Hub Scan': DEDUPE_ALGO_HASH_CODE,
1298+
'BlackDuck API': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL,
12971299
'docker-bench-security Scan': DEDUPE_ALGO_HASH_CODE,
12981300
}
12991301

dojo/tools/blackduck_api/__init__.py

Whitespace-only changes.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from blackduck import Client
2+
3+
4+
class BlackduckAPI:
5+
"""
6+
A simple client for the BlackDuck API
7+
"""
8+
9+
def __init__(self, tool_config):
10+
if tool_config.authentication_type == "API":
11+
self.api_token = tool_config.api_key
12+
self.base_url = tool_config.url
13+
self.client = Client(base_url=tool_config.url, token=tool_config.api_key)
14+
else:
15+
raise ValueError(
16+
"Authentication type {} not supported".format(
17+
tool_config.authentication_type
18+
)
19+
)
20+
21+
def test_product_connection(self, api_scan_configuration):
22+
asset = self.get_asset(api_scan_configuration.service_key_1)
23+
asset_name = asset["resource"]["title"]
24+
api_scan_configuration.service_key_2 = asset_name
25+
api_scan_configuration.save()
26+
return f'You have access to asset "{asset_name}"'
27+
28+
def get_project_by_name(self, project_name):
29+
for project in self.client.get_resource("projects"):
30+
if project["name"] == project_name:
31+
return project
32+
33+
def get_version_by_name(self, project, version_name):
34+
for version in self.client.get_resource("versions", project):
35+
if version["versionName"] == version_name:
36+
return version
37+
38+
def get_vulnerable_bom_components(self, version):
39+
return self.client.get_resource("vulnerable-components", version)
40+
41+
def get_vulnerabilities(self, component):
42+
return self.client.get_json(
43+
f'/api/vulnerabilities/{component["vulnerabilityWithRemediation"]["vulnerabilityName"]}'
44+
)

dojo/tools/blackduck_api/importer.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
from django.core.exceptions import ValidationError
3+
from dojo.models import Product_API_Scan_Configuration
4+
5+
from .api_client import BlackduckAPI
6+
7+
8+
class BlackduckApiImporter(object):
9+
"""
10+
Import from BlackDuck API
11+
"""
12+
config_id = "BlackDuck API"
13+
14+
def get_findings(self, test):
15+
client, config = self.prepare_client(test)
16+
project = client.get_project_by_name(config.service_key_1)
17+
version = client.get_version_by_name(project, config.service_key_2)
18+
return client.get_vulnerable_bom_components(version)
19+
20+
def prepare_client(self, test):
21+
product = test.engagement.product
22+
if test.api_scan_configuration:
23+
config = test.api_scan_configuration
24+
# Double check of config
25+
if config.product != product:
26+
raise ValidationError(f'API Scan Configuration for "{self.config_id}" and Product do not match.')
27+
else:
28+
configs = Product_API_Scan_Configuration.objects.filter(product=product, tool_configuration__tool_type__name=self.config_id)
29+
if configs.count() == 1:
30+
config = configs.first()
31+
elif configs.count() > 1:
32+
raise ValidationError(
33+
'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.'
34+
)
35+
else:
36+
raise ValidationError(
37+
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.'
38+
)
39+
40+
tool_config = config.tool_configuration
41+
return BlackduckAPI(tool_config), config

dojo/tools/blackduck_api/parser.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import json
2+
3+
from dojo.models import Finding
4+
5+
from .importer import BlackduckApiImporter
6+
7+
SCAN_TYPE_ID = 'BlackDuck API'
8+
9+
10+
class BlackduckApiParser(object):
11+
"""
12+
Import from Synopsys BlackDuck API /findings
13+
"""
14+
15+
def get_scan_types(self):
16+
return [SCAN_TYPE_ID]
17+
18+
def get_label_for_scan_types(self, scan_type):
19+
return SCAN_TYPE_ID
20+
21+
def get_description_for_scan_types(self, scan_type):
22+
return "BlackDuck findings can be directly imported using the Synopsys BlackDuck API. An API Scan Configuration has to be setup in the Product."
23+
24+
def requires_file(self, scan_type):
25+
return False
26+
27+
def requires_tool_type(self, scan_type):
28+
return SCAN_TYPE_ID
29+
30+
def get_findings(self, file, test):
31+
if file is None:
32+
data = BlackduckApiImporter().get_findings(test)
33+
else:
34+
data = json.load(file)
35+
findings = []
36+
for entry in data:
37+
vulnerability_id = entry["vulnerabilityWithRemediation"]["vulnerabilityName"]
38+
component_name = entry["componentName"]
39+
component_version = entry["componentVersionName"]
40+
finding = Finding(
41+
test=test,
42+
title=f'{vulnerability_id} in {component_name}:{component_version}',
43+
description=entry["vulnerabilityWithRemediation"].get("description"),
44+
severity=entry["vulnerabilityWithRemediation"]["severity"].title(),
45+
component_name=component_name,
46+
component_version=component_version,
47+
static_finding=True,
48+
dynamic_finding=False,
49+
unique_id_from_tool=entry["vulnerabilityWithRemediation"].get("vulnerabilityName"),
50+
)
51+
# get CWE
52+
if entry["vulnerabilityWithRemediation"].get("cweId"):
53+
cwe_raw = entry["vulnerabilityWithRemediation"]["cweId"].split("-")
54+
if len(cwe_raw) == 2 and cwe_raw[1].isdigit():
55+
finding.cwe = int(cwe_raw[1])
56+
findings.append(finding)
57+
return findings

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,6 @@ djangosaml2==1.5.1
7575
drf-spectacular==0.23.1
7676
django-ratelimit==3.0.1
7777
argon2-cffi==21.3.0
78+
blackduck==1.0.7
7879
pycurl==7.45.1 # Required for Celery Broker AWS (SQS) support
7980
boto3==1.24.53 # Required for Celery Broker AWS (SQS) support

0 commit comments

Comments
 (0)