diff --git a/src/vunnel/providers/sles/parser.py b/src/vunnel/providers/sles/parser.py index 45f6c16c..20aed8ea 100644 --- a/src/vunnel/providers/sles/parser.py +++ b/src/vunnel/providers/sles/parser.py @@ -8,6 +8,7 @@ from decimal import Decimal, DecimalException from typing import TYPE_CHECKING +import requests from cvss import CVSS3 from cvss.exceptions import CVSS3MalformedError @@ -39,6 +40,8 @@ severity_map={ "low": "Low", "moderate": "Medium", + "medium": "Medium", + "high": "High", "important": "High", "critical": "Critical", }, @@ -46,8 +49,8 @@ class Parser: - __oval_url__ = "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.server.{}.xml.gz" - __oval_file_name__ = "suse-linux-enterprise-server-{}.xml.gz" + __oval_url__ = "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.server.{}.xml.bz2" + __oval_file_name__ = "suse-linux-enterprise-server-{}.xml.bz2" __oval_dir_path__ = "oval" __source_dir_path__ = "source" @@ -79,6 +82,12 @@ def _download(self, major_version: str) -> str: oval_file_path = os.path.join(self.oval_dir_path, self.__oval_file_name__.format(major_version)) download_url = self.__oval_url__.format(major_version) + + # We should prefer the .bz2 files going forward, but fall back to .gz if unavailable for a particular release + if requests.head(download_url, timeout=self.download_timeout).status_code == 404: + download_url = download_url.removesuffix(".bz2") + ".gz" + oval_file_path = oval_file_path.removesuffix(".bz2") + ".gz" + self.urls.append(download_url) self.logger.info( diff --git a/src/vunnel/utils/oval_parser.py b/src/vunnel/utils/oval_parser.py index 1547bb0b..44352340 100644 --- a/src/vunnel/utils/oval_parser.py +++ b/src/vunnel/utils/oval_parser.py @@ -1,10 +1,12 @@ from __future__ import annotations +import bz2 import copy import gzip import logging import os import re +from typing import Callable import defusedxml.ElementTree as ET @@ -48,6 +50,14 @@ class Config: ns_format = None +def get_opener(filename: str) -> Callable: + if filename.endswith(".gz"): + return gzip.open + if filename.endswith(".bz2"): + return bz2.open + return open + + def parse(dest_file: str, config: Config, vuln_dict: dict | None = None): # noqa: C901, PLR0912 """ Parse the oval file and return a dictionary with tuple (ID, namespace) as the key @@ -67,10 +77,7 @@ def parse(dest_file: str, config: Config, vuln_dict: dict | None = None): # noq if os.path.exists(dest_file): processing = False - opener = open - - if dest_file.endswith(".gz"): - opener = gzip.open + opener = get_opener(dest_file) with opener(dest_file, "rb") as f: # noqa: F841 for event, element in ET.iterparse(dest_file, events=("start", "end")): diff --git a/src/vunnel/utils/oval_v2.py b/src/vunnel/utils/oval_v2.py index bff1c834..3c87644f 100644 --- a/src/vunnel/utils/oval_v2.py +++ b/src/vunnel/utils/oval_v2.py @@ -6,6 +6,7 @@ from __future__ import annotations +import bz2 import enum import gzip import logging @@ -15,6 +16,7 @@ from abc import ABC, abstractmethod from collections import defaultdict from dataclasses import dataclass +from typing import Callable from defusedxml.ElementTree import iterparse @@ -399,6 +401,14 @@ def get_oval_element(self, xml_element: ET.Element, config: OVALParserConfig) -> return result +def get_opener(filename: str) -> Callable: + if filename.endswith(".gz"): + return gzip.open + if filename.endswith(".bz2"): + return bz2.open + return open + + def iter_parse_vulnerability_file( oval_file_path: str, parser_config: OVALParserConfig, @@ -417,10 +427,7 @@ def iter_parse_vulnerability_file( if os.path.exists(oval_file_path): ingress = False - opener = open - - if oval_file_path.endswith(".gz"): - opener = gzip.open + opener = get_opener(oval_file_path) with opener(oval_file_path, "rb") as f: for event, xml_element in iterparse(f, events=("start", "end")):