Skip to content

Commit 7369247

Browse files
committed
Migrate VulnerableCode integration to API v3
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent cf76025 commit 7369247

6 files changed

Lines changed: 436 additions & 797 deletions

File tree

scanpipe/pipes/vulnerablecode.py

Lines changed: 10 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
VULNERABLECODE_API_URL = None
3636
VULNERABLECODE_URL = settings.VULNERABLECODE_URL
3737
if VULNERABLECODE_URL:
38-
VULNERABLECODE_API_URL = f"{VULNERABLECODE_URL}/api/"
38+
VULNERABLECODE_API_URL = f"{VULNERABLECODE_URL.rstrip('/')}/api/v3"
3939

4040
# Basic Authentication
4141
VULNERABLECODE_USER = settings.VULNERABLECODE_USER
@@ -63,7 +63,7 @@ def is_available():
6363
return False
6464

6565
try:
66-
response = session.head(VULNERABLECODE_API_URL)
66+
response = session.head(VULNERABLECODE_API_URL, allow_redirects=True)
6767
response.raise_for_status()
6868
except requests.exceptions.RequestException as request_exception:
6969
logger.debug(f"{label} is_available() error: {request_exception}")
@@ -91,28 +91,6 @@ def get_purls(packages):
9191
return [package_url for package in packages if (package_url := package.package_url)]
9292

9393

94-
def request_get(
95-
url,
96-
payload=None,
97-
timeout=None,
98-
):
99-
"""Send a GET request to `url` with optional `payload` and return the response."""
100-
if not url:
101-
return
102-
103-
params = {"format": "json"}
104-
if payload:
105-
params.update(payload)
106-
107-
logger.debug(f"{label}: url={url} params={params}")
108-
try:
109-
response = session.get(url, params=params, timeout=timeout)
110-
response.raise_for_status()
111-
return response.json()
112-
except (requests.RequestException, ValueError, TypeError) as exception:
113-
logger.debug(f"{label} [Exception] {exception}")
114-
115-
11694
def request_post(
11795
url,
11896
data,
@@ -127,88 +105,29 @@ def request_post(
127105
logger.debug(f"{label} [Exception] {exception}")
128106

129107

130-
def _get_vulnerabilities(
131-
url,
132-
field_name,
133-
field_value,
134-
timeout=None,
135-
):
136-
"""Get the list of vulnerabilities."""
137-
payload = {field_name: field_value}
138-
139-
response = request_get(url=url, payload=payload, timeout=timeout)
140-
if response and response.get("count"):
141-
results = response["results"]
142-
return results
143-
144-
145-
def get_vulnerabilities_by_purl(
146-
purl,
147-
timeout=None,
148-
api_url=VULNERABLECODE_API_URL,
149-
):
150-
"""Get the list of vulnerabilities providing a package `purl`."""
151-
return _get_vulnerabilities(
152-
url=f"{api_url}packages/",
153-
field_name="purl",
154-
field_value=purl,
155-
timeout=timeout,
156-
)
157-
158-
159-
def get_vulnerabilities_by_cpe(
160-
cpe,
161-
timeout=None,
162-
api_url=VULNERABLECODE_API_URL,
163-
):
164-
"""Get the list of vulnerabilities providing a package or component `cpe`."""
165-
return _get_vulnerabilities(
166-
url=f"{api_url}cpes/",
167-
field_name="cpe",
168-
field_value=cpe,
169-
timeout=timeout,
170-
)
171-
172-
173108
def bulk_search_by_purl(
174109
purls,
175110
timeout=None,
176111
api_url=VULNERABLECODE_API_URL,
177112
):
178113
"""Bulk search of vulnerabilities using the provided list of `purls`."""
179-
url = f"{api_url}packages/bulk_search"
114+
url = f"{api_url.rstrip('/')}/packages"
180115

181116
data = {
182117
"purls": purls,
183-
"vulnerabilities_only": True,
118+
"details": True,
184119
}
185120

186121
logger.debug(f"VulnerableCode: url={url} purls_count={len(purls)}")
187122
return request_post(url, data, timeout)
188123

189124

190-
def bulk_search_by_cpes(
191-
cpes,
192-
timeout=None,
193-
api_url=VULNERABLECODE_API_URL,
194-
):
195-
"""Bulk search of vulnerabilities using the provided list of `cpes`."""
196-
url = f"{api_url}cpes/bulk_search"
197-
198-
data = {
199-
"cpes": cpes,
200-
}
201-
202-
logger.debug(f"VulnerableCode: url={url} cpes_count={len(cpes)}")
203-
return request_post(url, data, timeout)
204-
205-
206125
def filter_vulnerabilities(vulnerabilities, ignore_set):
207126
"""Filter out vulnerabilities based on a list of ignored IDs and aliases."""
208127
return [
209128
vulnerability
210129
for vulnerability in vulnerabilities
211-
if vulnerability.get("vulnerability_id") not in ignore_set
130+
if vulnerability.get("advisory_id") not in ignore_set
212131
and not any(alias in ignore_set for alias in vulnerability.get("aliases", []))
213132
]
214133

@@ -223,9 +142,12 @@ def fetch_vulnerabilities(
223142
vulnerabilities_by_purl = {}
224143

225144
for purls_batch in chunked(get_purls(packages), chunk_size):
145+
# Add support for pagination
146+
# {'count': 17, 'next': None, 'previous': None, 'results': [....]
226147
response_data = bulk_search_by_purl(purls_batch)
227-
for vulnerability_data in response_data:
228-
vulnerabilities_by_purl[vulnerability_data["purl"]] = vulnerability_data
148+
for vulnerability_data in response_data["results"]:
149+
purl = vulnerability_data["purl"]
150+
vulnerabilities_by_purl[purl] = vulnerability_data
229151

230152
unsaved_objects = []
231153
for package in packages:

scanpipe/templates/scanpipe/includes/vulnerability_id.html

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
{% if vulnerability.vulnerability_id|slice:":4" == "VCID" and VULNERABLECODE_URL %}
2-
<a href="{{ VULNERABLECODE_URL }}/vulnerabilities/{{ vulnerability.vulnerability_id }}" target="_blank">
3-
{{ vulnerability.vulnerability_id }}
4-
<i class="fa-solid fa-up-right-from-square is-small"></i>
5-
</a>
6-
{% else %}
7-
{{ vulnerability.vulnerability_id }}
8-
{% endif %}
1+
<!--{% if vulnerability.vulnerability_id|slice:":4" == "VCID" and VULNERABLECODE_URL %}-->
2+
<!-- <a href="{{ VULNERABLECODE_URL }}/vulnerabilities/{{ vulnerability.vulnerability_id }}" target="_blank">-->
3+
<!-- {{ vulnerability.vulnerability_id }}-->
4+
<!-- <i class="fa-solid fa-up-right-from-square is-small"></i>-->
5+
<!-- </a>-->
6+
<!--{% else %}-->
7+
<!-- {{ vulnerability.vulnerability_id }}-->
8+
<!--{% endif %}-->
99

10-
<ul class="list-unstyled mb-0">
11-
{% for alias in aliases %}
10+
{{ vulnerability.advisory_id }}
11+
12+
<ul class="mb-0">
13+
{% for alias in vulnerability.aliases %}
1214
<li>
1315
{% if alias|slice:":3" == "CVE" %}
1416
<a href="https://nvd.nist.gov/vuln/detail/{{ alias }}" target="_blank">{{ alias }}

scanpipe/templates/scanpipe/tabset/tab_vulnerabilities.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<tr>
55
<th style="width: 220px;">Affected by</th>
66
<th>Summary</th>
7+
<th>Exploitability</th>
8+
<th>Severity</th>
9+
<th>Risk</th>
710
<th>Analysis</th>
811
</tr>
912
</thead>
@@ -16,6 +19,15 @@
1619
<td>
1720
{% include 'scanpipe/includes/vulnerability_summary.html' with vulnerability=vulnerability only %}
1821
</td>
22+
<td>
23+
{{ vulnerability.exploitability }}
24+
</td>
25+
<td>
26+
{{ vulnerability.weighted_severity }}
27+
</td>
28+
<td>
29+
{{ vulnerability.risk_score|default_if_none:"" }}
30+
</td>
1931
<td>
2032
{% for key, value in vulnerability.cdx_vulnerability.analysis.items %}
2133
<strong>{{ key }}:</strong> {{ value }}{% if not forloop.last %}<br>{% endif %}

0 commit comments

Comments
 (0)