Skip to content

Commit c217f85

Browse files
committed
Migrate debian-oval and ubuntu importer
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent f71776b commit c217f85

File tree

7 files changed

+169
-79
lines changed

7 files changed

+169
-79
lines changed

vulnerabilities/importer.py

Lines changed: 36 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,17 @@
3636
from typing import Tuple
3737

3838
from binaryornot.helpers import is_binary_string
39+
from dateutil import parser as dateparser
3940
from git import DiffIndex
4041
from git import Repo
4142
from license_expression import Licensing
4243
from packageurl import PackageURL
44+
from univers.version_range import RANGE_CLASS_BY_SCHEMES
4345
from univers.version_range import VersionRange
4446
from univers.versions import Version
4547

4648
from vulnerabilities.helpers import classproperty
4749
from vulnerabilities.helpers import evolve_purl
48-
from vulnerabilities.helpers import nearest_patched_package
4950
from vulnerabilities.oval_parser import OvalParser
5051
from vulnerabilities.severity_systems import SCORING_SYSTEMS
5152
from vulnerabilities.severity_systems import ScoringSystem
@@ -510,13 +511,13 @@ class OvalImporter(Importer):
510511
"""
511512

512513
@staticmethod
513-
def create_purl(pkg_name: str, pkg_version: str, pkg_data: Mapping) -> PackageURL:
514+
def create_purl(pkg_name: str, pkg_data: Mapping) -> PackageURL:
514515
"""
515516
Helper method for creating different purls for subclasses without them reimplementing
516517
get_data_from_xml_doc method
517518
Note: pkg_data must include 'type' of package
518519
"""
519-
return PackageURL(name=pkg_name, version=pkg_version, **pkg_data)
520+
return PackageURL(name=pkg_name, **pkg_data)
520521

521522
@staticmethod
522523
def _collect_pkgs(parsed_oval_data: Mapping) -> Set:
@@ -550,28 +551,17 @@ def advisory_data(self) -> List[AdvisoryData]:
550551
for metadata, oval_file in self._fetch():
551552
try:
552553
oval_data = self.get_data_from_xml_doc(oval_file, metadata)
553-
yield oval_data
554+
yield from oval_data
554555
except Exception:
555556
logger.error(
556557
f"Failed to get updated_advisories: {oval_file!r} "
557558
f"with {metadata!r}:\n" + traceback.format_exc()
558559
)
559560
continue
560561

561-
def set_api(self, all_pkgs: Iterable[str]):
562-
"""
563-
This method loads the self.pkg_manager_api with the specified packages.
564-
It fetches and caches all the versions of these packages and exposes
565-
them through self.pkg_manager_api.get(<package_name>). Example
566-
567-
>> self.set_api(['electron'])
568-
Assume 'electron' has only versions 1.0.0 and 1.2.0
569-
>> assert self.pkg_manager_api.get('electron') == {'1.0.0','1.2.0'}
570-
571-
"""
572-
raise NotImplementedError
573-
574-
def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> List[AdvisoryData]:
562+
def get_data_from_xml_doc(
563+
self, xml_doc: ET.ElementTree, pkg_metadata={}
564+
) -> Iterable[AdvisoryData]:
575565
"""
576566
The orchestration method of the OvalDataSource. This method breaks an
577567
OVAL xml ElementTree into a list of `Advisory`.
@@ -584,10 +574,11 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
584574
"""
585575

586576
all_adv = []
587-
oval_doc = OvalParser(self.translations, xml_doc)
588-
raw_data = oval_doc.get_data()
589-
all_pkgs = self._collect_pkgs(raw_data)
590-
self.set_api(all_pkgs)
577+
oval_parsed_data = OvalParser(self.translations, xml_doc)
578+
raw_data = oval_parsed_data.get_data()
579+
oval_doc = oval_parsed_data.oval_document
580+
timestamp = oval_doc.getGenerator().getTimestamp()
581+
# all_pkgs = self._collect_pkgs(raw_data)
591582

592583
# convert definition_data to Advisory objects
593584
for definition_data in raw_data:
@@ -599,49 +590,28 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
599590
affected_packages = []
600591
for test_data in definition_data["test_data"]:
601592
for package_name in test_data["package_list"]:
602-
if package_name and len(package_name) >= 50:
603-
continue
604-
605-
affected_version_range = test_data["version_ranges"] or set()
606-
version_class = version_class_by_package_type[pkg_metadata["type"]]
607-
version_scheme = version_class.scheme
608-
609-
affected_version_range = VersionRange.from_scheme_version_spec_string(
610-
version_scheme, affected_version_range
611-
)
612-
all_versions = self.pkg_manager_api.get(package_name).valid_versions
613-
614-
# FIXME: what is this 50 DB limit? that's too small for versions
615-
# FIXME: we should not drop data this way
616-
# This filter is for filtering out long versions.
617-
# 50 is limit because that's what db permits atm.
618-
all_versions = [version for version in all_versions if len(version) < 50]
619-
if not all_versions:
620-
continue
621-
622-
affected_purls = []
623-
safe_purls = []
624-
for version in all_versions:
625-
purl = self.create_purl(
626-
pkg_name=package_name,
627-
pkg_version=version,
628-
pkg_data=pkg_metadata,
593+
affected_version_range = test_data["version_ranges"]
594+
vrc = RANGE_CLASS_BY_SCHEMES[pkg_metadata["type"]]
595+
if affected_version_range:
596+
try:
597+
affected_version_range = vrc.from_native(affected_version_range)
598+
except Exception as e:
599+
logger.error(
600+
f"Failed to parse version range {affected_version_range!r} "
601+
f"for package {package_name!r}:\n{e}"
602+
)
603+
continue
604+
if package_name:
605+
affected_packages.append(
606+
AffectedPackage(
607+
package=self.create_purl(package_name, pkg_metadata),
608+
affected_version_range=affected_version_range,
609+
)
629610
)
630-
if version_class(version) in affected_version_range:
631-
affected_purls.append(purl)
632-
else:
633-
safe_purls.append(purl)
634-
635-
affected_packages.extend(
636-
nearest_patched_package(affected_purls, safe_purls),
637-
)
638-
639-
all_adv.append(
640-
AdvisoryData(
641-
summary=description,
642-
affected_packages=affected_packages,
643-
vulnerability_id=vuln_id,
644-
references=references,
645-
)
611+
yield AdvisoryData(
612+
aliases=[vuln_id],
613+
summary=description,
614+
affected_packages=affected_packages,
615+
references=references,
616+
date_published=dateparser.parse(timestamp),
646617
)
647-
return all_adv

vulnerabilities/importers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222
from vulnerabilities.importers import alpine_linux
2323
from vulnerabilities.importers import debian
24+
from vulnerabilities.importers import debian_oval
2425
from vulnerabilities.importers import github
2526
from vulnerabilities.importers import nginx
2627
from vulnerabilities.importers import nvd
2728
from vulnerabilities.importers import openssl
2829
from vulnerabilities.importers import pysec
2930
from vulnerabilities.importers import redhat
31+
from vulnerabilities.importers import ubuntu
3032

3133
IMPORTERS_REGISTRY = [
3234
nginx.NginxImporter,
@@ -37,6 +39,8 @@
3739
redhat.RedhatImporter,
3840
pysec.PyPIImporter,
3941
debian.DebianImporter,
42+
debian_oval.DebianOvalImporter,
43+
ubuntu.UbuntuImporter,
4044
]
4145

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

vulnerabilities/importers/debian_oval.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222

2323

24-
import asyncio
2524
import xml.etree.ElementTree as ET
2625

2726
import requests
@@ -32,16 +31,17 @@
3231

3332

3433
class DebianOvalImporter(OvalImporter):
34+
spdx_license_expression = "LicenseRef-scancode-unknown"
35+
3536
def __init__(self, *args, **kwargs):
3637
super().__init__(*args, **kwargs)
3738
# we could avoid setting translations, and have it
3839
# set by default in the OvalParser, but we don't yet know
3940
# whether all OVAL providers use the same format
4041
self.translations = {"less than": "<"}
41-
self.pkg_manager_api = DebianVersionAPI()
4242

4343
def _fetch(self):
44-
releases = self.config.releases
44+
releases = ["wheezy", "stretch", "jessie", "buster"]
4545
for release in releases:
4646
file_url = f"https://www.debian.org/security/oval/oval-definitions-{release}.xml"
4747
if not create_etag(data_src=self, url=file_url, etag_key="ETag"):
@@ -53,6 +53,3 @@ def _fetch(self):
5353
ET.ElementTree(ET.fromstring(resp.decode("utf-8"))),
5454
)
5555
return []
56-
57-
def set_api(self, packages):
58-
asyncio.run(self.pkg_manager_api.load_api(packages))

vulnerabilities/importers/github.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050
from vulnerabilities.improver import Inference
5151
from vulnerabilities.models import Advisory
5252
from vulnerabilities.package_managers import ComposerVersionAPI
53+
from vulnerabilities.package_managers import DebianVersionAPI
5354
from vulnerabilities.package_managers import GoproxyVersionAPI
55+
from vulnerabilities.package_managers import LaunchpadVersionAPI
5456
from vulnerabilities.package_managers import MavenVersionAPI
5557
from vulnerabilities.package_managers import NugetVersionAPI
5658
from vulnerabilities.package_managers import PypiVersionAPI
@@ -286,7 +288,7 @@ def get_api_package_name(purl: PackageURL) -> str:
286288
if purl.type == "composer":
287289
return f"{purl.namespace}/{purl.name}"
288290

289-
if purl.type in ("nuget", "pypi", "gem", "golang"):
291+
if purl.type in ("nuget", "pypi", "gem", "golang", "deb"):
290292
return purl.name
291293

292294
logger.error(f"get_api_package_name: Unknown PURL {purl!r}")

vulnerabilities/importers/ubuntu.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222

2323

24-
import asyncio
2524
import bz2
2625
import logging
2726
import xml.etree.ElementTree as ET
@@ -35,17 +34,19 @@
3534

3635

3736
class UbuntuImporter(OvalImporter):
37+
spdx_license_expression = "GPL"
38+
license_url = "https://ubuntu.com/legal/terms"
39+
3840
def __init__(self, *args, **kwargs):
3941
super().__init__(*args, **kwargs)
4042
# we could avoid setting translations, and have it
4143
# set by default in the OvalParser, but we don't yet know
4244
# whether all OVAL providers use the same format
4345
self.translations = {"less than": "<"}
44-
self.pkg_manager_api = LaunchpadVersionAPI()
4546

4647
def _fetch(self):
4748
base_url = "https://people.canonical.com/~ubuntu-security/oval"
48-
releases = self.config.releases
49+
releases = ["bionic", "trusty", "focal", "eoan", "xenial"]
4950
for i, release in enumerate(releases, 1):
5051
file_url = f"{base_url}/com.ubuntu.{release}.cve.oval.xml.bz2" # nopep8
5152
logger.info(f"Fetching Ubuntu Oval: {file_url}")
@@ -63,6 +64,3 @@ def _fetch(self):
6364
)
6465

6566
logger.info(f"Fetched {i} Ubuntu Oval releases from {base_url}")
66-
67-
def set_api(self, packages):
68-
asyncio.run(self.pkg_manager_api.load_api(packages))

vulnerabilities/improvers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323

2424
from vulnerabilities import importers
2525
from vulnerabilities.improvers import default
26+
from vulnerabilities.improvers import oval
2627

2728
IMPROVERS_REGISTRY = [
2829
default.DefaultImprover,
2930
importers.nginx.NginxBasicImprover,
3031
importers.github.GitHubBasicImprover,
3132
importers.debian.DebianBasicImprover,
33+
oval.OvalBasicImprover,
3234
]
3335

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

0 commit comments

Comments
 (0)