2020# VulnerableCode is a free software code from nexB Inc. and others.
2121# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2222
23+ import logging
24+ import re
25+ from typing import Dict
26+ from typing import Iterable
27+ from typing import List
28+
2329import requests
2430from packageurl import PackageURL
31+ from univers .version_range import RpmVersionRange
2532
2633from vulnerabilities import severity_systems
27- from vulnerabilities .helpers import nearest_patched_package
34+ from vulnerabilities .helpers import get_item
2835from vulnerabilities .helpers import requests_with_5xx_retry
2936from vulnerabilities .importer import AdvisoryData
37+ from vulnerabilities .importer import AffectedPackage
3038from vulnerabilities .importer import Importer
3139from vulnerabilities .importer import Reference
3240from vulnerabilities .importer import VulnerabilitySeverity
41+ from vulnerabilities .rpm_utils import rpm_to_purl
3342
34-
35- class RedhatImporter (Importer ):
36- def __enter__ (self ):
37-
38- self .redhat_cves = fetch ()
39-
40- def updated_advisories (self ):
41- processed_advisories = list (map (to_advisory , self .redhat_cves ))
42- return self .batch_advisories (processed_advisories )
43-
43+ logger = logging .getLogger (__name__ )
4444
4545requests_session = requests_with_5xx_retry (max_retries = 5 , backoff_factor = 1 )
4646
4747
48- def fetch ():
49- """
50- Return a list of CVE data mappings fetched from the RedHat API.
51- See:
52- https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html/red_hat_security_data_api/index
53- """
54- cves = []
48+ def fetch_list_of_cves () -> Iterable [List [Dict ]]:
5549 page_no = 1
56- url_template = "https://access.redhat.com/hydra/rest/securitydata/cve.json?per_page=10000&page={}" # nopep8
57-
5850 cve_data = None
5951 while True :
60- current_url = url_template . format ( page_no )
52+ current_url = f"https://access.redhat.com/hydra/rest/securitydata/cve.json?per_page=10000&page= { page_no } " # nopep8
6153 try :
62- print (f"Fetching: { current_url } " )
6354 response = requests_session .get (current_url )
6455 if response .status_code != requests .codes .ok :
65- # TODO: log me
66- print (f"Failed to fetch results from { current_url } " )
56+ logger .error (f"Failed to fetch results from { current_url } " )
6757 break
6858 cve_data = response .json ()
6959 except Exception as e :
70- # TODO: log me
71- msg = f"Failed to fetch results from { current_url } :\n { e } "
72- print (msg )
60+ logger .error (f"Failed to fetch results from { current_url } { e } " )
7361 break
74-
7562 if not cve_data :
7663 break
77- cves .extend (cve_data )
7864 page_no += 1
65+ yield cve_data
66+
67+
68+ class RedhatImporter (Importer ):
69+
70+ spdx_license_expression = "CC-BY-4.0"
71+ license_url = "https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html/red_hat_security_data_api/legal-notice"
7972
80- return cves
73+ def advisory_data (self ) -> Iterable [AdvisoryData ]:
74+ for list_of_redhat_cves in fetch_list_of_cves ():
75+ for redhat_cve in list_of_redhat_cves :
76+ yield to_advisory (redhat_cve )
8177
8278
8379def to_advisory (advisory_data ):
84- affected_purls = []
85- if advisory_data .get ("affected_packages" ):
86- for rpm in advisory_data ["affected_packages" ]:
87- purl = rpm_to_purl (rpm )
88- if purl :
89- affected_purls .append (purl )
80+ affected_packages : List [AffectedPackage ] = []
81+ for rpm in advisory_data .get ("affected_packages" ) or []:
82+ purl = rpm_to_purl (rpm_string = rpm , namespace = "redhat" )
83+ if purl :
84+ try :
85+ affected_version_range = RpmVersionRange .from_versions (sequence = [purl .version ])
86+ affected_packages .append (
87+ AffectedPackage (
88+ package = PackageURL (
89+ type = purl .type ,
90+ name = purl .name ,
91+ namespace = purl .namespace ,
92+ qualifiers = purl .qualifiers ,
93+ subpath = purl .subpath ,
94+ ),
95+ affected_version_range = affected_version_range ,
96+ fixed_version = None ,
97+ )
98+ )
99+ except Exception as e :
100+ logger .error (f"Failed to parse version range { purl .version } for { purl } { e } " )
90101
91102 references = []
92103 bugzilla = advisory_data .get ("bugzilla" )
@@ -114,10 +125,14 @@ def to_advisory(advisory_data):
114125 )
115126 )
116127
117- for rh_adv in advisory_data [ "advisories" ]:
128+ for rh_adv in advisory_data . get ( "advisories" ) or [ ]:
118129 # RH provides 3 types of advisories RHSA, RHBA, RHEA. Only RHSA's contain severity score.
119130 # See https://access.redhat.com/articles/2130961 for more details.
120131
132+ if not isinstance (rh_adv , str ):
133+ logger .error (f"Invalid advisory type { rh_adv } " )
134+ continue
135+
121136 if "RHSA" in rh_adv .upper ():
122137 rhsa_data = requests_session .get (
123138 f"https://access.redhat.com/hydra/rest/securitydata/cvrf/{ rh_adv } .json"
@@ -126,7 +141,7 @@ def to_advisory(advisory_data):
126141 rhsa_aggregate_severities = []
127142 if rhsa_data .get ("cvrfdoc" ):
128143 # not all RHSA errata have a corresponding CVRF document
129- value = rhsa_data [ "cvrfdoc" ][ "aggregate_severity" ]
144+ value = get_item ( rhsa_data , "cvrfdoc" , "aggregate_severity" )
130145 rhsa_aggregate_severities .append (
131146 VulnerabilitySeverity (
132147 system = severity_systems .REDHAT_AGGREGATE ,
@@ -164,27 +179,14 @@ def to_advisory(advisory_data):
164179 )
165180 )
166181
182+ aliases = []
183+ alias = advisory_data .get ("CVE" )
184+ if alias :
185+ aliases .append (alias )
167186 references .append (Reference (severities = redhat_scores , url = advisory_data ["resource_url" ]))
168187 return AdvisoryData (
169- vulnerability_id = advisory_data [ "CVE" ] ,
170- summary = advisory_data [ "bugzilla_description" ] ,
171- affected_packages = nearest_patched_package ( affected_purls , []) ,
188+ aliases = aliases ,
189+ summary = advisory_data . get ( "bugzilla_description" ) or "" ,
190+ affected_packages = affected_packages ,
172191 references = references ,
173192 )
174-
175-
176- def rpm_to_purl (rpm_string ):
177- # FIXME: there is code in scancode to handle RPM conversion AND this should
178- # be all be part of the packageurl library
179-
180- # FIXME: the comment below is not correct, this is the Epoch in the RPM version and not redhat specific
181- # Red Hat uses `-:0` instead of just `-` to separate
182- # package name and version
183- components = rpm_string .split ("-0:" )
184- if len (components ) != 2 :
185- return
186-
187- name , version = components
188-
189- if version [0 ].isdigit ():
190- return PackageURL (namespace = "redhat" , name = name , type = "rpm" , version = version )
0 commit comments