Skip to content

Commit dc21a60

Browse files
committed
Revert "Harden Ubuntu OVAL importer"
This reverts commit 36f43d1.
1 parent 36f43d1 commit dc21a60

3 files changed

Lines changed: 76 additions & 142 deletions

File tree

vulnerabilities/data_source.py

Lines changed: 43 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ class DataSource(ContextManager):
106106
def __init__(
107107
self,
108108
batch_size: int,
109-
last_run_date: Optional[datetime]=None,
110-
cutoff_date: Optional[datetime]=None,
111-
config: Optional[Mapping[str, Any]]=None,
109+
last_run_date: Optional[datetime] = None,
110+
cutoff_date: Optional[datetime] = None,
111+
config: Optional[Mapping[str, Any]] = None,
112112
):
113113
"""
114114
Create a DataSource instance.
@@ -240,9 +240,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
240240

241241
def file_changes(
242242
self,
243-
subdir: str=None,
244-
recursive: bool=False,
245-
file_ext: Optional[str]=None,
243+
subdir: str = None,
244+
recursive: bool = False,
245+
file_ext: Optional[str] = None,
246246
) -> Tuple[Set[str], Set[str]]:
247247
"""
248248
Returns all added and modified files since last_run_date or cutoff_date (whichever is more
@@ -382,9 +382,9 @@ def _update_from_remote(self, remote, branch) -> None:
382382

383383
def _include_file(
384384
path: str,
385-
subdir: Optional[str]=None,
386-
recursive: bool=False,
387-
file_ext: Optional[str]=None,
385+
subdir: Optional[str] = None,
386+
recursive: bool = False,
387+
file_ext: Optional[str] = None,
388388
) -> bool:
389389
match = True
390390

@@ -408,7 +408,6 @@ class OvalDataSource(DataSource):
408408
All data sources which collect data from OVAL files must inherit from this
409409
`OvalDataSource` class. Subclasses must implement the methods `_fetch` and `set_api`.
410410
"""
411-
412411
@staticmethod
413412
def create_purl(pkg_name: str, pkg_version: str, pkg_data: Mapping) -> PackageURL:
414413
"""
@@ -434,48 +433,29 @@ def _collect_pkgs(parsed_oval_data: Mapping) -> Set:
434433

435434
def _fetch(self) -> Tuple[Mapping, Iterable[ET.ElementTree]]:
436435
"""
437-
Return a two-tuple of ({mapping of Package URL data}, it's ET.ElementTree)
436+
This method contains logic to fetch OVAL files and yield them into
437+
a tuple of file's metadata and it's ET.ElementTree.
438438
Subclasses must implement this method.
439439
440-
Note: Package URL data MUST INCLUDE a Package URL "type" key so
441-
implement _fetch() accordingly.
442-
For example::
443-
440+
Note: Mapping MUST INCLUDE "type" key. Example values of Mapping
444441
{"type":"deb","qualifiers":{"distro":"buster"} }
442+
445443
"""
446-
# TODO: enforce that we receive the proper data here
447444
raise NotImplementedError
448445

449446
def updated_advisories(self) -> List[Advisory]:
450-
for purl_data, oval_etree in self._fetch():
451-
if 'type' not in purl_data:
452-
ets = (oval_etree and ET.tostring(oval_etree)) or 'NO DATA'
453-
msg = (
454-
"Failed to get updated_advisories for Ubuntu: purl_data is "
455-
f"missing a package type {purl_data!r}\n"
456-
f"with OVAL XML:\n"
457-
f"{ets}\n"
458-
f"... continuing..."
459-
)
460-
print(msg)
461-
logger.error(msg)
462-
continue
463-
447+
"""
448+
Note: metadata MUST INCLUDE "type" key, implement _fetch accordingly.
449+
"""
450+
for metadata, oval_file in self._fetch():
464451
try:
465-
oval_data = self.get_data_from_xml_doc(oval_etree, purl_data)
452+
oval_data = self.get_data_from_xml_doc(oval_file, metadata)
466453
yield oval_data
467454
except Exception:
468-
ets = (oval_etree and ET.tostring(oval_etree)) or 'NO DATA'
469-
tb = traceback.format_exc()
470-
msg = (
471-
f"Failed to get updated_advisories for Ubuntu:"
472-
f"with {purl_data!r}\n"
473-
f"and with OVAL XML:\n"
474-
f"{ets}\n{tb}\n"
475-
f"... continuing..."
455+
logger.error(
456+
f"Failed to get updated_advisories: {oval_file!r} "
457+
"with {metadata!r}:\n" + traceback.format_exc()
476458
)
477-
print(msg)
478-
logger.error(msg)
479459
continue
480460

481461
def set_api(self, all_pkgs: Iterable[str]):
@@ -493,89 +473,57 @@ def set_api(self, all_pkgs: Iterable[str]):
493473

494474
def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> List[Advisory]:
495475
"""
496-
The orchestration method of the OvalDataSource. This method breaks an
497-
OVAL xml ElementTree into a list of `Advisory`.
476+
The orchestration method of the OvalDataSource. This method breaks an OVAL xml
477+
ElementTree into a list of `Advisory`.
498478
499-
Note: pkg_metadata is a mapping of Package URL data that MUST INCLUDE
500-
"type" key.
501-
502-
Example value of pkg_metadata:
479+
Note: pkg_metadata MUST INCLUDE "type" key. Example value of pkg_metadata,
503480
{"type":"deb","qualifiers":{"distro":"buster"} }
504481
"""
505-
if 'type' not in pkg_metadata:
506-
ets = xml_doc and ET.tostring(xml_doc) or 'NO DATA'
507-
msg = (
508-
"Failed to get_data_from_xml_doc: pkg_metadata is "
509-
f"missing a package type {pkg_metadata!r}\n"
510-
f"with OVAL XML:\n"
511-
f"{ets}"
512-
)
513-
print(msg)
514-
logger.error(msg)
515-
raise Exception(msg)
516-
517482
all_adv = []
518483
oval_doc = OvalParser(self.translations, xml_doc)
519-
try:
520-
raw_data = oval_doc.get_data()
521-
except Exception:
522-
ets = xml_doc and ET.tostring(xml_doc) or 'NO DATA'
523-
tb = traceback.format_exc()
524-
msg = (
525-
f"Failed to get_data_from_xml_doc:"
526-
f"with {pkg_metadata!r}\n"
527-
f"and with OVAL XML:\n"
528-
f"{ets}\n{tb}"
529-
)
530-
print(msg)
531-
logger.error(msg)
532-
raise Exception(msg)
533-
484+
raw_data = oval_doc.get_data()
534485
all_pkgs = self._collect_pkgs(raw_data)
535486
self.set_api(all_pkgs)
487+
for definition_data in raw_data: # definition_data -> Advisory
536488

537-
# convert definition_data to Advisory objects
538-
for definition_data in raw_data:
539-
# These fields are definition level, i.e common for all elements
540-
# connected/linked to an OvalDefinition
489+
# These fields are definition level, i.e common for all
490+
# elements connected/linked to an OvalDefinition
541491
vuln_id = definition_data['vuln_id']
542492
description = definition_data['description']
543493
affected_purls = set()
544494
safe_purls = set()
545-
references = [Reference(url=url) for url in definition_data['reference_urls']]
495+
references = [Reference(url=url)
496+
for url in definition_data['reference_urls']]
497+
546498
for test_data in definition_data['test_data']:
547499
for package in test_data['package_list']:
548500
pkg_name = package
549501
if package and len(pkg_name) >= 50:
550502
continue
551-
aff_ver_range = test_data['version_ranges'] or set()
503+
aff_ver_range = test_data['version_ranges']
552504
all_versions = self.pkg_manager_api.get(package)
553-
554-
# FIXME: what is this 50 DB limit? that's too small for versions
555-
# FIXME: we should not drop data this way
556505
# This filter is for filtering out long versions.
557506
# 50 is limit because that's what db permits atm.
558-
all_versions = set(filter(lambda x: len(x) < 50, all_versions))
507+
all_versions = set(
508+
filter(
509+
lambda x: len(x) < 50,
510+
all_versions))
559511
if not all_versions:
560512
continue
561-
562-
affected_versions = set(filter(lambda x: x in aff_ver_range, all_versions))
513+
affected_versions = set(
514+
filter(
515+
lambda x: x in aff_ver_range,
516+
all_versions))
563517
safe_versions = all_versions - affected_versions
564518

565519
for version in affected_versions:
566520
pkg_url = self.create_purl(
567-
pkg_name=pkg_name,
568-
pkg_version=version,
569-
pkg_data=pkg_metadata,
570-
)
521+
pkg_name=pkg_name, pkg_version=version, pkg_data=pkg_metadata)
571522
affected_purls.add(pkg_url)
572523

573524
for version in safe_versions:
574525
pkg_url = self.create_purl(
575-
pkg_name=pkg_name,
576-
pkg_version=version,
577-
pkg_data=pkg_metadata,
578-
)
526+
pkg_name=pkg_name, pkg_version=version, pkg_data=pkg_metadata)
579527
safe_purls.add(pkg_url)
580528

581529
all_adv.append(
@@ -584,8 +532,5 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
584532
impacted_package_urls=affected_purls,
585533
resolved_package_urls=safe_purls,
586534
cve_id=vuln_id,
587-
vuln_references=references,
588-
))
589-
590-
print(f"Processed {len(all_adv)} Advisory from Oval data for {pkg_metadata}")
535+
vuln_references=references))
591536
return all_adv

vulnerabilities/importers/ubuntu.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,21 @@ def __init__(self, *args, **kwargs):
5959

6060
def _fetch(self):
6161
releases = self.config.releases
62-
for i, release in enumerate(releases, 1):
62+
for release in releases:
6363
file_url = f"https://people.canonical.com/~ubuntu-security/oval/com.ubuntu.{release}.cve.oval.xml.bz2" # nopep8
64-
# if not create_etag(data_src=self, url=file_url, etag_key="ETag"):
65-
# print(f"Ubuntu Oval Etag not changed, not re-fetching: {file_url}")
66-
# continue
67-
68-
print(f"Fetching Ubuntu Oval: {file_url}")
69-
response = requests.get(file_url)
70-
if response.status_code != requests.codes.ok:
71-
print(f"Failed to fetch Ubuntu Oval: HTTP {response.status_code} : {file_url}")
64+
if not create_etag(data_src=self, url=file_url, etag_key="ETag"):
7265
continue
73-
74-
extracted = bz2.decompress(response.content)
66+
resp = requests.get(file_url)
67+
extracted = bz2.decompress(resp.content)
7568
yield (
7669
{"type": "deb", "namespace": "ubuntu"},
7770
ET.ElementTree(ET.fromstring(extracted.decode("utf-8"))),
7871
)
79-
80-
print(f"Fetched {i} Ubuntu Oval releases https://people.canonical.com/~ubuntu-security/oval/")
72+
# In case every file is latest, _fetch won't yield anything(due to checking for new etags),
73+
# this would return None to added_advisories
74+
# which will cause error, hence this
75+
# function return an empty list
76+
return []
8177

8278
def set_api(self, packages):
8379
asyncio.run(self.pkg_manager_api.load_api(packages))

vulnerabilities/oval_parser.py

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,8 @@
3030

3131
from dephell_specifier import RangeSpecifier
3232

33-
from vulnerabilities.lib_oval import OvalDefinition
34-
from vulnerabilities.lib_oval import OvalDocument
35-
from vulnerabilities.lib_oval import OvalObject
36-
from vulnerabilities.lib_oval import OvalState
37-
from vulnerabilities.lib_oval import OvalTest
38-
import traceback
33+
from vulnerabilities.lib_oval import (
34+
OvalDefinition, OvalDocument, OvalTest, OvalObject, OvalState)
3935

4036

4137
class OvalParser:
@@ -49,7 +45,8 @@ def __init__(self, translations: Dict, oval_document: ET.ElementTree):
4945

5046
def get_data(self) -> List[Dict]:
5147
"""
52-
Return a list of OvalDefinition mappings.
48+
This is the orchestration method, it returns a list of dictionaries,
49+
where each dictionary represents data from an OvalDefinition
5350
"""
5451
oval_data = []
5552
for definition in self.all_definitions:
@@ -58,22 +55,27 @@ def get_data(self) -> List[Dict]:
5855
if not matching_tests:
5956
continue
6057
definition_data = {'test_data': []}
61-
# TODO:this could use some data cleaning
62-
definition_data['description'] = definition.getMetadata().getDescription() or ''
58+
definition_data['description'] = definition.getMetadata(
59+
).getDescription() # this could use some data cleaning
6360

64-
definition_data['vuln_id'] = self.get_vuln_id_from_definition(definition)
65-
definition_data['reference_urls'] = self.get_urls_from_definition(definition)
61+
if not definition_data['description']:
62+
definition_data['description'] = ''
6663

64+
definition_data['vuln_id'] = self.get_vuln_id_from_definition(
65+
definition)
66+
definition_data['reference_urls'] = self.get_urls_from_definition(
67+
definition
68+
)
6769
for test in matching_tests:
6870
test_obj, test_state = self.get_object_state_of_test(test)
6971
if not test_obj or not test_state:
7072
continue
7173
test_data = {'package_list': []}
72-
test_data['package_list'].extend(self.get_pkgs_from_obj(test_obj))
73-
version_ranges = self.get_versionsrngs_from_state(test_state)
74-
test_data['version_ranges'] = version_ranges
74+
test_data['package_list'].extend(
75+
self.get_pkgs_from_obj(test_obj))
76+
test_data['version_ranges'] = self.get_versionsrngs_from_state(
77+
test_state)
7578
definition_data['test_data'].append(test_data)
76-
7779
oval_data.append(definition_data)
7880

7981
return oval_data
@@ -136,27 +138,18 @@ def get_pkgs_from_obj(self, obj: OvalObject) -> List[str]:
136138

137139
return pkg_list
138140

139-
# TODO: this method needs a better name
140141
def get_versionsrngs_from_state(self, state: OvalState) -> Optional[RangeSpecifier]:
141142
"""
142-
Return a version range(s)? from a state
143+
returns all related version ranges within a state
143144
"""
144145
for var in state.element:
145-
operation = var.get('operation')
146-
if not operation:
147-
continue
148-
operand = self.translations.get(operation) or ''
149-
if not operand:
150-
continue
151-
version = var.text or ''
152-
if not version:
153-
continue
154-
version_range = operand + version
155-
try:
146+
if var.get('operation'):
147+
if var.get('operation') not in self.translations:
148+
continue
149+
operand = self.translations[var.get('operation')]
150+
version = var.text
151+
version_range = operand + version
156152
return RangeSpecifier(version_range)
157-
except Exception:
158-
# FIXME: we should not continue
159-
print(f"Failed to process invalid version_range in OvalState: {version_range}...continuing")
160153

161154
@staticmethod
162155
def get_urls_from_definition(definition: OvalDefinition) -> Set[str]:

0 commit comments

Comments
 (0)