From 3b91d9af12cc16fe919f3e5496f7e993e1317dd8 Mon Sep 17 00:00:00 2001 From: gnaponie Date: Sep 03 2019 06:49:30 +0000 Subject: Handle bugzilla whiteboard empty Due to security reasons, "whiteboard" in bugzillas is now empty. Get the information about the severity from the "bug_severity" instead of "status_whiteboard". Also the affected packages are now unknown. In this case let's just return an empty list, that will be handled in freshmaker/handlers/koji/rebuild_images_on_rpm_advisory_change.py where all RPMs will be rebuilt. ref: FACTORY-5074 Signed-off-by: gnaponie --- diff --git a/freshmaker/bugzilla.py b/freshmaker/bugzilla.py index 1761550..d626b80 100644 --- a/freshmaker/bugzilla.py +++ b/freshmaker/bugzilla.py @@ -38,6 +38,14 @@ class BugzillaAPI(object): "critical", ] + SEVERITY_MAPPING = { + # severity: impact + "low": "low", + "medium": "moderate", + "high": "important", + "critical": "critical", + } + def __init__(self, server_url=None): """ Creates new BugzillaAPI instance. @@ -49,14 +57,15 @@ class BugzillaAPI(object): else: self.server_url = conf.bugzilla_server_url.rstrip('/') - def _get_cve_whiteboard(self, cve): + def query_bugzilla(self, cve): """ - Returns the whiteboard dict about `cve` obtained from - show_bug.cgi?ctype=xml&id=$cve endpoint + Queries Bugzilla to find out infos about the cve, specifically + the list of major xml elements in the cve. + It queries show_bug.cgi?ctype=xml&id=$cve endpoint. :param str cve: CVE, for example "CVE-2017-10268". - :rtype: dict - :return: the status whiteboard of the bugzilla CVE. + :rtype: list + :return: list of major xml elements. """ log.debug("Querying bugzilla for %s", cve) r = requests.get( @@ -69,7 +78,17 @@ class BugzillaAPI(object): # List the major xml elements elements = list(list(root)[0]) + return elements + + def _get_cve_whiteboard(self, elements): + """ + Returns the whiteboard dict about `cve` obtained from + show_bug.cgi?ctype=xml&id=$cve endpoint + :param str elements: list of major xml elements, returned by `query_bugzilla`. + :rtype: dict + :return: the status whiteboard of the bugzilla CVE. + """ # Extract the whiteboard string whiteboard = [e.text for e in elements if e.tag == 'status_whiteboard'] @@ -97,10 +116,12 @@ class BugzillaAPI(object): and "pkg_name" of the affected packages. """ max_rating = -1 + elements = [] affected_pkgs = [] + severity = None for cve in cve_list: try: - data = self._get_cve_whiteboard(cve) + elements = self.query_bugzilla(cve) except requests.exceptions.HTTPError as e: if e.response.status_code == 404: log.warning( @@ -114,22 +135,37 @@ class BugzillaAPI(object): "threat_severity unknown.", cve) continue - try: - severity = data["impact"].lower() - except KeyError: - log.warning( - "CVE %s has no 'impact' in bugzilla whiteboard, " - "threat_severity unknown.", cve) - continue - - try: - affected_pkgs.extend([ - {'product': pkg.split('/')[0], 'pkg_name': pkg.split('/')[-1]} - for pkg, isaffected in data.items() if isaffected == 'affected']) - except IndexError: - log.warning("CVE %s has no affected packages in bugzilla whiteboard", cve) - continue - + whiteboard = self._get_cve_whiteboard(elements) + + # Since Aug 1st 2019 the "whiteboard" field was removed. This is kept for backward + # compatibility. But in case the field is not present, instead of "impact" we need to + # check "severity", and in this case we also won't filter any package from RHSA builds, + # and just use all the packages attached to the advisory. + if whiteboard: + try: + severity = whiteboard["impact"].lower() + except KeyError: + log.info( + "CVE %s has no 'impact' in bugzilla whiteboard, " + "we'll try to get it from the 'severity field'.", cve) + + try: + affected_pkgs.extend([ + {'product': pkg.split('/')[0], 'pkg_name': pkg.split('/')[-1]} + for pkg, isaffected in whiteboard.items() if isaffected == 'affected']) + except IndexError: + log.warning("CVE %s has no affected packages in bugzilla whiteboard", cve) + + if not severity: + try: + # extract the severity string + severity = [e.text for e in elements if e.tag == 'bug_severity'] + severity = BugzillaAPI.SEVERITY_MAPPING[severity[0].lower()] + except (IndexError, ValueError): + log.warning( + "CVE %s has no 'severity' in bugzilla cve, " + "threat_severity unknown.", cve) + continue try: rating = BugzillaAPI.THREAT_SEVERITIES.index(severity) except ValueError: diff --git a/tests/test_bugzilla.py b/tests/test_bugzilla.py index d65f924..ed84e82 100644 --- a/tests/test_bugzilla.py +++ b/tests/test_bugzilla.py @@ -55,6 +55,12 @@ xml_with_affected_pkgs = """ xml_without_status = """""" xml_with_empty_bug = """""" +xml_with_severity = """ + +{severity} + +""" + class TestBugzillaAPI(helpers.FreshmakerTestCase): @@ -119,3 +125,13 @@ class TestBugzillaAPI(helpers.FreshmakerTestCase): self.assertEqual(highest_cve_severity, "low") self.assertEqual(affected_pkgs[0]['product'], 'openshift-enterprise-3.11') self.assertEqual(affected_pkgs[0]['pkg_name'], 'atomic-openshift') + + @patch("freshmaker.bugzilla.requests.get") + def test_fetch_cve_metadata_with_severity(self, requests_get): + severities = ["low", "medium", "high", "critical"] + impacts = ["low", "moderate", "important", "critical"] + bugzilla = BugzillaAPI() + for i in range(0, 4): + requests_get.side_effect = [MockResponse(xml_with_severity.format(severity=severities[i]))] + highest_cve_severity, _ = bugzilla.fetch_cve_metadata(["CVE-1"]) + self.assertEqual(highest_cve_severity, impacts[i])