Skip to content

Commit 905aee9

Browse files
committed
Migrate debian importer to importer-improver model
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 554384b commit 905aee9

5 files changed

Lines changed: 120 additions & 52 deletions

File tree

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ toml==0.10.2
107107
tomli==2.0.1
108108
traitlets==5.1.1
109109
typing_extensions==4.1.1
110-
univers==30.4.0
110+
univers==30.5.1
111111
urllib3==1.26.9
112112
wcwidth==0.2.5
113113
websocket-client==0.59.0

vulnerabilities/importer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ def get_fixed_purl(self):
150150
return fixed_purl
151151

152152
@classmethod
153-
def merge(cls, affected_packages: Iterable):
153+
def merge(
154+
cls, affected_packages: Iterable
155+
) -> Tuple[PackageURL, List[VersionRange], List[Version]]:
154156
"""
155157
Return a tuple with all attributes of AffectedPackage as a set
156158
for all values in the given iterable of AffectedPackage

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# VulnerableCode is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222
from vulnerabilities.importers import alpine_linux
23+
from vulnerabilities.importers import debian
2324
from vulnerabilities.importers import github
2425
from vulnerabilities.importers import nginx
2526
from vulnerabilities.importers import nvd
@@ -31,6 +32,7 @@
3132
github.GitHubAPIImporter,
3233
nvd.NVDImporter,
3334
openssl.OpensslImporter,
35+
debian.DebianImporter,
3436
]
3537

3638
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

vulnerabilities/importers/debian.py

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,47 +21,55 @@
2121
# VulnerableCode is a free software tool from nexB Inc. and others.
2222
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2323

24-
import dataclasses
24+
import logging
2525
from typing import Any
26+
from typing import Iterable
2627
from typing import List
2728
from typing import Mapping
28-
from typing import Set
2929

3030
import requests
31-
from dateutil import parser as dateparser
31+
from django.db.models.query import QuerySet
3232
from packageurl import PackageURL
33+
from univers.version_range import DebianVersionRange
3334

35+
from vulnerabilities.helpers import AffectedPackage as LegacyAffectedPackage
36+
from vulnerabilities.helpers import get_item
3437
from vulnerabilities.helpers import nearest_patched_package
3538
from vulnerabilities.importer import AdvisoryData
39+
from vulnerabilities.importer import AffectedPackage
3640
from vulnerabilities.importer import Importer
3741
from vulnerabilities.importer import Reference
42+
from vulnerabilities.importer import UnMergeablePackageError
43+
from vulnerabilities.improver import MAX_CONFIDENCE
44+
from vulnerabilities.improver import Improver
45+
from vulnerabilities.improver import Inference
46+
from vulnerabilities.models import Advisory
3847

48+
logger = logging.getLogger(__name__)
3949

40-
class DebianImporter(Importer):
41-
def __enter__(self):
42-
if self.response_is_new():
43-
self._api_response = self._fetch()
4450

45-
else:
46-
self._api_response = {}
51+
class DebianImporter(Importer):
4752

48-
def updated_advisories(self) -> Set[AdvisoryData]:
49-
advisories = []
53+
spdx_license_expression = "MIT"
54+
license_url = "https://www.debian.org/license"
5055

51-
for pkg_name, records in self._api_response.items():
52-
advisories.extend(self._parse(pkg_name, records))
56+
api_url = "https://security-tracker.debian.org/tracker/data/json"
5357

54-
return self.batch_advisories(advisories)
58+
def get_response(self):
59+
return requests.get(self.api_url).json()
5560

56-
def _fetch(self) -> Mapping[str, Any]:
57-
return requests.get(self.config.debian_tracker_url).json()
61+
def advisory_data(self) -> Iterable[AdvisoryData]:
62+
response = self.get_response()
63+
print("got response")
64+
for pkg_name, records in response.items():
65+
yield from self.parse(pkg_name, records)
5866

59-
def _parse(self, pkg_name: str, records: Mapping[str, Any]) -> List[AdvisoryData]:
60-
advisories = []
67+
def parse(self, pkg_name: str, records: Mapping[str, Any]) -> Iterable[AdvisoryData]:
68+
print("parsing")
6169
ignored_versions = {"3.8.20-4."}
6270

6371
for cve_id, record in records.items():
64-
impacted_purls, resolved_purls = [], []
72+
affected_versions, fixed_versions = set(), set()
6573
if not cve_id.startswith("CVE"):
6674
continue
6775

@@ -70,10 +78,10 @@ def _parse(self, pkg_name: str, records: Mapping[str, Any]) -> List[AdvisoryData
7078
# TODO: this would need to be revisited though to ensure we are not missing out on anything
7179

7280
for release_name, release_record in record["releases"].items():
73-
if not release_record.get("repositories", {}).get(release_name):
74-
continue
81+
version = get_item(release_record, "repositories", release_name)
7582

76-
version = release_record["repositories"][release_name]
83+
if not version:
84+
continue
7785

7886
if version in ignored_versions:
7987
continue
@@ -82,54 +90,109 @@ def _parse(self, pkg_name: str, records: Mapping[str, Any]) -> List[AdvisoryData
8290
name=pkg_name,
8391
type="deb",
8492
namespace="debian",
85-
version=version,
8693
qualifiers={"distro": release_name},
8794
)
8895

8996
if release_record.get("status", "") == "resolved":
90-
resolved_purls.append(purl)
97+
fixed_versions.add(version)
9198
else:
92-
impacted_purls.append(purl)
99+
affected_versions.add(version)
93100

94101
if (
95102
"fixed_version" in release_record
96103
and release_record["fixed_version"] not in ignored_versions
97104
):
98-
resolved_purls.append(
99-
PackageURL(
100-
name=pkg_name,
101-
type="deb",
102-
namespace="debian",
103-
version=release_record["fixed_version"],
104-
qualifiers={"distro": release_name},
105-
)
106-
)
105+
fixed_versions.add(release_record["fixed_version"])
107106

108107
references = []
109108
debianbug = record.get("debianbug")
110109
if debianbug:
111110
bug_url = f"https://bugs.debian.org/cgi-bin/bugreport.cgi?bug={debianbug}"
112111
references.append(Reference(url=bug_url, reference_id=debianbug))
113-
advisories.append(
114-
AdvisoryData(
115-
vulnerability_id=cve_id,
116-
affected_packages=nearest_patched_package(impacted_purls, resolved_purls),
117-
summary=record.get("description", ""),
118-
references=references,
112+
affected_version_range = (
113+
DebianVersionRange.from_versions(list(affected_versions))
114+
if affected_versions
115+
else None
116+
)
117+
affected_packages = []
118+
for fixed_version in fixed_versions:
119+
affected_packages.append(
120+
AffectedPackage(
121+
package=purl,
122+
affected_version_range=affected_version_range,
123+
fixed_version=fixed_version,
124+
)
119125
)
126+
yield AdvisoryData(
127+
aliases=[cve_id],
128+
summary=record.get("description", ""),
129+
affected_packages=affected_packages,
130+
references=references,
120131
)
121132

122-
return advisories
123133

124-
def response_is_new(self):
134+
class DebianBasicImprover(Improver):
135+
@property
136+
def interesting_advisories(self) -> QuerySet:
137+
return Advisory.objects.filter(created_by=DebianImporter.qualified_name)
138+
139+
def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
125140
"""
126-
Return True if a request response is for new data likely changed or
127-
updated since we last checked.
141+
Yield Inferences for the given advisory data
128142
"""
129-
head = requests.head(self.config.debian_tracker_url)
130-
date_str = head.headers.get("last-modified")
131-
last_modified_date = dateparser.parse(date_str)
132-
if self.config.last_run_date:
133-
return self.config.last_run_date < last_modified_date
143+
if not advisory_data.affected_packages:
144+
return Inference(
145+
aliases=advisory_data.aliases,
146+
confidence=MAX_CONFIDENCE,
147+
summary=advisory_data.summary,
148+
affected_purls=[],
149+
fixed_purl=None,
150+
references=advisory_data.references,
151+
)
152+
try:
153+
purl, affected_version_ranges, fixed_versions = AffectedPackage.merge(
154+
advisory_data.affected_packages
155+
)
156+
except UnMergeablePackageError:
157+
logger.error(f"Cannot merge with different purls {advisory_data.affected_packages!r}")
158+
return iter([])
159+
160+
pkg_type = purl.type
161+
pkg_namespace = purl.namespace
162+
pkg_name = purl.name
163+
aff_versions = set()
164+
for affected_version_range in affected_version_ranges:
165+
for constraint in affected_version_range.constraints:
166+
aff_versions.add(constraint.version.string)
167+
affected_purls = [
168+
PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version)
169+
for version in aff_versions
170+
]
171+
172+
unaffected_purls = [
173+
PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=str(version))
174+
for version in fixed_versions
175+
]
176+
177+
affected_packages: List[LegacyAffectedPackage] = nearest_patched_package(
178+
vulnerable_packages=affected_purls, resolved_packages=unaffected_purls
179+
)
180+
181+
unique_patched_packages_with_affected_packages = {}
182+
for package in affected_packages:
183+
if package.patched_package not in unique_patched_packages_with_affected_packages:
184+
unique_patched_packages_with_affected_packages[package.patched_package] = []
185+
unique_patched_packages_with_affected_packages[package.patched_package].append(
186+
package.vulnerable_package
187+
)
134188

135-
return True
189+
for (
190+
fixed_package,
191+
affected_packages,
192+
) in unique_patched_packages_with_affected_packages.items():
193+
yield Inference.from_advisory_data(
194+
advisory_data,
195+
confidence=MAX_CONFIDENCE, # We are getting all valid versions to get this inference
196+
affected_purls=affected_packages,
197+
fixed_purl=fixed_package,
198+
)

vulnerabilities/improvers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
default.DefaultImprover,
2929
importers.nginx.NginxBasicImprover,
3030
importers.github.GitHubBasicImprover,
31+
importers.debian.DebianBasicImprover,
3132
]
3233

3334
IMPROVERS_REGISTRY = {x.qualified_name: x for x in IMPROVERS_REGISTRY}

0 commit comments

Comments
 (0)