Skip to content

Commit ee162af

Browse files
committed
Add github pipeline
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 5561c66 commit ee162af

File tree

6 files changed

+588
-16
lines changed

6 files changed

+588
-16
lines changed

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@
4343
from vulnerabilities.pipelines import nvd_importer
4444
from vulnerabilities.pipelines import pypa_importer
4545
from vulnerabilities.pipelines import pysec_importer
46+
from vulnerabilities.pipelines.v2_importers import github_importer as github_importer_v2
4647
from vulnerabilities.pipelines.v2_importers import nvd_importer as nvd_importer_v2
4748

4849
IMPORTERS_REGISTRY = [
4950
nvd_importer_v2.NVDImporterPipeline,
51+
github_importer_v2.GitHubAPIImporterPipeline,
5052
nvd_importer.NVDImporterPipeline,
5153
github_importer.GitHubAPIImporterPipeline,
5254
gitlab_importer.GitLabImporterPipeline,

vulnerabilities/models.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def with_package_counts(self):
164164
)
165165

166166

167+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
167168
class VulnerabilitySeverity(models.Model):
168169
url = models.URLField(
169170
max_length=1024,
@@ -203,6 +204,7 @@ class Meta:
203204
ordering = ["url", "scoring_system", "value"]
204205

205206

207+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
206208
class VulnerabilityStatusType(models.IntegerChoices):
207209
"""List of vulnerability statuses."""
208210

@@ -211,6 +213,7 @@ class VulnerabilityStatusType(models.IntegerChoices):
211213
INVALID = 3, "Invalid"
212214

213215

216+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
214217
class Vulnerability(models.Model):
215218
"""
216219
A software vulnerability with a unique identifier and alternate ``aliases``.
@@ -503,6 +506,7 @@ def get_cwes(self):
503506
Database.get_cwes = get_cwes
504507

505508

509+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
506510
class Weakness(models.Model):
507511
"""
508512
A Common Weakness Enumeration model
@@ -549,6 +553,7 @@ def to_dict(self):
549553
return {"cwe_id": self.cwe_id, "name": self.name, "description": self.description}
550554

551555

556+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
552557
class VulnerabilityReferenceQuerySet(BaseQuerySet):
553558
def for_cpe(self):
554559
"""
@@ -557,6 +562,7 @@ def for_cpe(self):
557562
return self.filter(reference_id__startswith="cpe")
558563

559564

565+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
560566
class VulnerabilityReference(models.Model):
561567
"""
562568
A reference to a vulnerability such as a security advisory from a Linux distribution or language
@@ -614,6 +620,7 @@ def is_cpe(self):
614620
return self.reference_id.startswith("cpe")
615621

616622

623+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
617624
class VulnerabilityRelatedReference(models.Model):
618625
"""
619626
A reference related to a vulnerability.
@@ -634,6 +641,7 @@ class Meta:
634641
ordering = ["vulnerability", "reference"]
635642

636643

644+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
637645
class PackageQuerySet(BaseQuerySet, PackageURLQuerySet):
638646
def get_fixed_by_package_versions(self, purl: PackageURL, fix=True):
639647
"""
@@ -800,6 +808,7 @@ def get_purl_query_lookups(purl):
800808
return purl_to_dict(plain_purl, with_empty=False)
801809

802810

811+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
803812
class Package(PackageURLMixin):
804813
"""
805814
A software package with related vulnerabilities.
@@ -1132,6 +1141,7 @@ def affecting_vulns(self):
11321141
)
11331142

11341143

1144+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
11351145
class PackageRelatedVulnerabilityBase(models.Model):
11361146
"""
11371147
Abstract base class for package-vulnerability relations.
@@ -1228,11 +1238,13 @@ def add_package_vulnerability_changelog(self, advisory):
12281238
)
12291239

12301240

1241+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
12311242
class FixingPackageRelatedVulnerability(PackageRelatedVulnerabilityBase):
12321243
class Meta(PackageRelatedVulnerabilityBase.Meta):
12331244
verbose_name_plural = "Fixing Package Related Vulnerabilities"
12341245

12351246

1247+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
12361248
class AffectedByPackageRelatedVulnerability(PackageRelatedVulnerabilityBase):
12371249

12381250
severities = models.ManyToManyField(
@@ -1254,6 +1266,7 @@ def for_cve(self):
12541266
return self.filter(alias__startswith="CVE")
12551267

12561268

1269+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
12571270
class Alias(models.Model):
12581271
"""
12591272
An alias is a unique vulnerability identifier in some database, such as
@@ -1311,6 +1324,7 @@ class AdvisoryQuerySet(BaseQuerySet):
13111324
pass
13121325

13131326

1327+
# FIXME: Remove when migration from Vulnerability to Advisory is completed
13141328
class Advisory(models.Model):
13151329
"""
13161330
An advisory represents data directly obtained from upstream transformed
@@ -2055,6 +2069,8 @@ class AdvisoryV2(models.Model):
20552069
help_text="A list of packages that are reported by this advisory.",
20562070
)
20572071

2072+
# TODO: Add Advisory Status
2073+
20582074
objects = AdvisoryQuerySet.as_manager()
20592075

20602076
class Meta:
@@ -2083,6 +2099,16 @@ def to_advisory_data(self) -> "AdvisoryDataV2":
20832099
)
20842100

20852101

2102+
class PackageQuerySetV2(BaseQuerySet, PackageURLQuerySet):
2103+
def get_or_create_from_purl(self, purl: Union[PackageURL, str]):
2104+
"""
2105+
Return a new or existing Package given a ``purl`` PackageURL object or PURL string.
2106+
"""
2107+
package, is_created = PackageV2.objects.get_or_create(**purl_to_dict(purl=purl))
2108+
2109+
return package, is_created
2110+
2111+
20862112
class PackageV2(PackageURLMixin):
20872113
"""
20882114
A software package with related vulnerabilities.
@@ -2155,6 +2181,8 @@ def save(self, *args, **kwargs):
21552181
self.plain_package_url = str(plain_purl)
21562182
super().save(*args, **kwargs)
21572183

2184+
objects = PackageQuerySetV2.as_manager()
2185+
21582186
@property
21592187
def calculate_version_rank(self):
21602188
"""
@@ -2177,11 +2205,3 @@ def calculate_version_rank(self):
21772205
package.version_rank = rank
21782206
Package.objects.bulk_update(sorted_packages, fields=["version_rank"])
21792207
return self.version_rank
2180-
2181-
def get_or_create_from_purl(self, purl: Union[PackageURL, str]):
2182-
"""
2183-
Return a new or existing Package given a ``purl`` PackageURL object or PURL string.
2184-
"""
2185-
package, is_created = Package.objects.get_or_create(**purl_to_dict(purl=purl))
2186-
2187-
return package, is_created

vulnerabilities/pipelines/__init__.py

Lines changed: 162 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,29 @@
1313
from timeit import default_timer as timer
1414
from traceback import format_exc as traceback_format_exc
1515
from typing import Iterable
16+
from typing import List
17+
from typing import Optional
1618

1719
from aboutcode.pipeline import BasePipeline
1820
from aboutcode.pipeline import LoopProgress
1921
from aboutcode.pipeline import humanize_time
22+
from fetchcode import package_versions
23+
from packageurl import PackageURL
2024

2125
from vulnerabilities.importer import AdvisoryData
26+
from vulnerabilities.importer import AffectedPackage
27+
from vulnerabilities.importer import UnMergeablePackageError
2228
from vulnerabilities.improver import MAX_CONFIDENCE
2329
from vulnerabilities.models import Advisory
2430
from vulnerabilities.models import PackageV2
2531
from vulnerabilities.pipes.advisory import import_advisory
2632
from vulnerabilities.pipes.advisory import insert_advisory
2733
from vulnerabilities.pipes.advisory import insert_advisory_v2
34+
from vulnerabilities.utils import AffectedPackage as LegacyAffectedPackage
2835
from vulnerabilities.utils import classproperty
36+
from vulnerabilities.utils import get_affected_packages_by_patched_package
37+
from vulnerabilities.utils import nearest_patched_package
38+
from vulnerabilities.utils import resolve_version_range
2939

3040
module_logger = logging.getLogger(__name__)
3141

@@ -210,13 +220,12 @@ class VulnerableCodeBaseImporterPipelineV2(VulnerableCodePipeline):
210220
repo_url = None
211221
importer_name = None
212222
advisory_confidence = MAX_CONFIDENCE
223+
ignorable_versions = []
224+
unfurl_version_ranges = False
213225

214226
@classmethod
215227
def steps(cls):
216-
return (
217-
cls.collect_and_store_advisories,
218-
cls.import_new_advisories,
219-
)
228+
return (cls.collect_and_store_advisories,)
220229

221230
def collect_advisories(self) -> Iterable[AdvisoryData]:
222231
"""
@@ -270,6 +279,15 @@ def get_advisory_packages(self, advisory_data: AdvisoryData) -> list:
270279
affected_purls.extend(package_affected_purls)
271280
fixed_purls.extend(package_fixed_purls)
272281

282+
283+
if self.unfurl_version_ranges:
284+
vulnerable_pvs, fixed_pvs = self.get_impacted_packages(
285+
affected_packages=advisory_data.affected_packages,
286+
advisory_date_published=advisory_data.date_published,
287+
)
288+
affected_purls.extend(vulnerable_pvs)
289+
fixed_purls.extend(fixed_pvs)
290+
273291
vulnerable_packages = []
274292
fixed_packages = []
275293

@@ -282,3 +300,143 @@ def get_advisory_packages(self, advisory_data: AdvisoryData) -> list:
282300
fixed_packages.append(fixed_package)
283301

284302
return vulnerable_packages, fixed_packages
303+
304+
def get_published_package_versions(
305+
self, package_url: PackageURL, until: Optional[datetime] = None
306+
) -> List[str]:
307+
"""
308+
Return a list of versions published before `until` for the `package_url`
309+
"""
310+
versions = package_versions.versions(str(package_url))
311+
versions_before_until = []
312+
for version in versions or []:
313+
if until and version.release_date and version.release_date > until:
314+
continue
315+
versions_before_until.append(version.value)
316+
317+
return versions_before_until
318+
319+
def get_impacted_packages(self, affected_packages, advisory_date_published):
320+
"""
321+
Return a tuple of lists of affected and fixed PackageURLs
322+
"""
323+
if not affected_packages:
324+
return [], []
325+
326+
mergable = True
327+
328+
# TODO: We should never had the exception in first place
329+
try:
330+
purl, affected_version_ranges, fixed_versions = AffectedPackage.merge(affected_packages)
331+
except UnMergeablePackageError:
332+
self.log(f"Cannot merge with different purls {affected_packages!r}", logging.ERROR)
333+
mergable = False
334+
335+
if not mergable:
336+
for affected_package in affected_packages:
337+
purl = affected_package.package
338+
affected_version_range = affected_package.affected_version_range
339+
fixed_version = affected_package.fixed_version
340+
pkg_type = purl.type
341+
pkg_namespace = purl.namespace
342+
pkg_name = purl.name
343+
if not affected_version_range and fixed_version:
344+
# FIXME: Handle the receving end to address the concern of looping the data
345+
return [], [
346+
PackageURL(
347+
type=pkg_type,
348+
namespace=pkg_namespace,
349+
name=pkg_name,
350+
version=str(fixed_version),
351+
)
352+
]
353+
else:
354+
# FIXME: Handle the receving end to address the concern of looping the data
355+
valid_versions = self.get_published_package_versions(
356+
package_url=purl, until=advisory_date_published
357+
)
358+
return self.resolve_package_versions(
359+
affected_version_range=affected_version_range,
360+
pkg_type=pkg_type,
361+
pkg_namespace=pkg_namespace,
362+
pkg_name=pkg_name,
363+
valid_versions=valid_versions,
364+
)
365+
366+
else:
367+
pkg_type = purl.type
368+
pkg_namespace = purl.namespace
369+
pkg_name = purl.name
370+
pkg_qualifiers = purl.qualifiers
371+
fixed_purls = [
372+
PackageURL(
373+
type=pkg_type,
374+
namespace=pkg_namespace,
375+
name=pkg_name,
376+
version=str(version),
377+
qualifiers=pkg_qualifiers,
378+
)
379+
for version in fixed_versions
380+
]
381+
if not affected_version_ranges:
382+
return [], fixed_purls
383+
else:
384+
valid_versions = self.get_published_package_versions(
385+
package_url=purl, until=advisory_date_published
386+
)
387+
for affected_version_range in affected_version_ranges:
388+
return self.resolve_package_versions(
389+
affected_version_range=affected_version_range,
390+
pkg_type=pkg_type,
391+
pkg_namespace=pkg_namespace,
392+
pkg_name=pkg_name,
393+
valid_versions=valid_versions,
394+
)
395+
396+
def resolve_package_versions(
397+
self,
398+
affected_version_range,
399+
pkg_type,
400+
pkg_namespace,
401+
pkg_name,
402+
valid_versions,
403+
):
404+
"""
405+
Return a tuple of lists of ``affected_packages`` and ``fixed_packages`` PackageURL for the given `affected_version_range` and `valid_versions`.
406+
407+
``valid_versions`` are the valid version listed on the package registry for that package
408+
409+
"""
410+
aff_vers, unaff_vers = resolve_version_range(
411+
affected_version_range=affected_version_range,
412+
ignorable_versions=self.ignorable_versions,
413+
package_versions=valid_versions,
414+
)
415+
416+
affected_purls = list(
417+
self.expand_verion_range_to_purls(pkg_type, pkg_namespace, pkg_name, aff_vers)
418+
)
419+
420+
unaffected_purls = list(
421+
self.expand_verion_range_to_purls(pkg_type, pkg_namespace, pkg_name, unaff_vers)
422+
)
423+
424+
fixed_packages = []
425+
affected_packages = []
426+
427+
patched_packages = nearest_patched_package(
428+
vulnerable_packages=affected_purls, resolved_packages=unaffected_purls
429+
)
430+
431+
for (fixed_package, affected_purls,) in get_affected_packages_by_patched_package(
432+
patched_packages
433+
).items():
434+
if fixed_package:
435+
fixed_packages.append(fixed_package)
436+
affected_packages.extend(affected_purls)
437+
438+
return affected_packages, fixed_packages
439+
440+
def expand_verion_range_to_purls(self, pkg_type, pkg_namespace, pkg_name, versions):
441+
for version in versions:
442+
yield PackageURL(type=pkg_type, namespace=pkg_namespace, name=pkg_name, version=version)

0 commit comments

Comments
 (0)