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+ from typing import Dict
25+ from typing import Iterable
26+ from typing import List
27+
2328import requests
2429from packageurl import PackageURL
30+ from univers .version_range import RpmVersionRange
2531
2632from vulnerabilities import severity_systems
27- from vulnerabilities .helpers import nearest_patched_package
33+ from vulnerabilities .helpers import get_item
2834from vulnerabilities .helpers import requests_with_5xx_retry
2935from vulnerabilities .importer import AdvisoryData
36+ from vulnerabilities .importer import AffectedPackage
3037from vulnerabilities .importer import Importer
3138from vulnerabilities .importer import Reference
3239from vulnerabilities .importer import VulnerabilitySeverity
40+ from vulnerabilities .rpm_utils import rpm_to_purl
3341
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-
42+ logger = logging .getLogger (__name__ )
4443
4544requests_session = requests_with_5xx_retry (max_retries = 5 , backoff_factor = 1 )
4645
4746
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 = []
47+ def fetch_list_of_cves () -> Iterable [List [Dict ]]:
5548 page_no = 1
56- url_template = "https://access.redhat.com/hydra/rest/securitydata/cve.json?per_page=10000&page={}" # nopep8
57-
5849 cve_data = None
5950 while True :
60- current_url = url_template . format ( page_no )
51+ current_url = f"https://access.redhat.com/hydra/rest/securitydata/cve.json?per_page=10000&page= { page_no } " # nopep8
6152 try :
62- print (f"Fetching: { current_url } " )
6353 response = requests_session .get (current_url )
6454 if response .status_code != requests .codes .ok :
65- # TODO: log me
66- print (f"Failed to fetch results from { current_url } " )
55+ logger .error (f"Failed to fetch results from { current_url } " )
6756 break
6857 cve_data = response .json ()
6958 except Exception as e :
70- # TODO: log me
71- msg = f"Failed to fetch results from { current_url } :\n { e } "
72- print (msg )
59+ logger .error (f"Failed to fetch results from { current_url } { e } " )
7360 break
74-
7561 if not cve_data :
7662 break
77- cves .extend (cve_data )
7863 page_no += 1
64+ yield cve_data
65+
66+
67+ def get_bugzilla_data (bugzilla ):
68+ return requests_session .get (f"https://bugzilla.redhat.com/rest/bug/{ bugzilla } " ).json ()
7969
80- return cves
70+
71+ def get_rhsa_data (rh_adv ):
72+ return requests_session .get (
73+ f"https://access.redhat.com/hydra/rest/securitydata/cvrf/{ rh_adv } .json"
74+ ).json ()
75+
76+
77+ class RedhatImporter (Importer ):
78+
79+ spdx_license_expression = "CC-BY-4.0"
80+ license_url = "https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html/red_hat_security_data_api/legal-notice"
81+
82+ def advisory_data (self ) -> Iterable [AdvisoryData ]:
83+ for list_of_redhat_cves in fetch_list_of_cves ():
84+ for redhat_cve in list_of_redhat_cves :
85+ yield to_advisory (redhat_cve )
8186
8287
8388def 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 )
89+ affected_packages : List [AffectedPackage ] = []
90+ for rpm in advisory_data .get ("affected_packages" ) or []:
91+ purl = rpm_to_purl (rpm_string = rpm , namespace = "redhat" )
92+ if purl :
93+ try :
94+ affected_version_range = RpmVersionRange .from_versions (sequence = [purl .version ])
95+ affected_packages .append (
96+ AffectedPackage (
97+ package = PackageURL (
98+ type = purl .type ,
99+ name = purl .name ,
100+ namespace = purl .namespace ,
101+ qualifiers = purl .qualifiers ,
102+ subpath = purl .subpath ,
103+ ),
104+ affected_version_range = affected_version_range ,
105+ fixed_version = None ,
106+ )
107+ )
108+ except Exception as e :
109+ logger .error (f"Failed to parse version range { purl .version } for { purl } { e } " )
90110
91111 references = []
92112 bugzilla = advisory_data .get ("bugzilla" )
93113 if bugzilla :
94114 url = "https://bugzilla.redhat.com/show_bug.cgi?id={}" .format (bugzilla )
95- bugzilla_data = requests_session .get (
96- f"https://bugzilla.redhat.com/rest/bug/{ bugzilla } "
97- ).json ()
115+ bugzilla_data = get_bugzilla_data (bugzilla )
98116 if (
99117 bugzilla_data .get ("bugs" )
100118 and len (bugzilla_data ["bugs" ])
@@ -114,25 +132,28 @@ def to_advisory(advisory_data):
114132 )
115133 )
116134
117- for rh_adv in advisory_data [ "advisories" ]:
135+ for rh_adv in advisory_data . get ( "advisories" ) or [ ]:
118136 # RH provides 3 types of advisories RHSA, RHBA, RHEA. Only RHSA's contain severity score.
119137 # See https://access.redhat.com/articles/2130961 for more details.
120138
139+ if not isinstance (rh_adv , str ):
140+ logger .error (f"Invalid advisory type { rh_adv } " )
141+ continue
142+
121143 if "RHSA" in rh_adv .upper ():
122- rhsa_data = requests_session .get (
123- f"https://access.redhat.com/hydra/rest/securitydata/cvrf/{ rh_adv } .json"
124- ).json () # nopep8
144+ rhsa_data = get_rhsa_data (rh_adv )
125145
126146 rhsa_aggregate_severities = []
127147 if rhsa_data .get ("cvrfdoc" ):
128148 # not all RHSA errata have a corresponding CVRF document
129- value = rhsa_data ["cvrfdoc" ]["aggregate_severity" ]
130- rhsa_aggregate_severities .append (
131- VulnerabilitySeverity (
132- system = severity_systems .REDHAT_AGGREGATE ,
133- value = value ,
149+ value = get_item (rhsa_data , "cvrfdoc" , "aggregate_severity" )
150+ if value :
151+ rhsa_aggregate_severities .append (
152+ VulnerabilitySeverity (
153+ system = severity_systems .REDHAT_AGGREGATE ,
154+ value = value ,
155+ )
134156 )
135- )
136157
137158 references .append (
138159 Reference (
@@ -164,27 +185,14 @@ def to_advisory(advisory_data):
164185 )
165186 )
166187
188+ aliases = []
189+ alias = advisory_data .get ("CVE" )
190+ if alias :
191+ aliases .append (alias )
167192 references .append (Reference (severities = redhat_scores , url = advisory_data ["resource_url" ]))
168193 return AdvisoryData (
169- vulnerability_id = advisory_data [ "CVE" ] ,
170- summary = advisory_data [ "bugzilla_description" ] ,
171- affected_packages = nearest_patched_package ( affected_purls , []) ,
194+ aliases = aliases ,
195+ summary = advisory_data . get ( "bugzilla_description" ) or "" ,
196+ affected_packages = affected_packages ,
172197 references = references ,
173198 )
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