Merge ~ebarretto/ubuntu-cve-tracker:oval-bugs into ubuntu-cve-tracker:master

Proposed by Eduardo Barretto
Status: Merged
Merge reported by: Eduardo Barretto
Merged at revision: 11ea9be3dc481fe183ffb6bf66401461cb8fb189
Proposed branch: ~ebarretto/ubuntu-cve-tracker:oval-bugs
Merge into: ubuntu-cve-tracker:master
Diff against target: 933 lines (+134/-351)
7 files modified
scripts/cve_lib.py (+2/-0)
scripts/generate-oval (+74/-291)
scripts/oval_lib.py (+42/-44)
test/test_oval_lib_end_to_end.py (+4/-4)
test/test_oval_lib_functional.py (+6/-6)
test/test_oval_lib_unit.py (+3/-3)
test/test_utils.py (+3/-3)
Reviewer Review Type Date Requested Status
David Fernandez Gonzalez Approve
Ubuntu Security Team Pending
Review via email: mp+461249@code.launchpad.net

Description of the change

Some clean-up on generate-oval:
- removed unused variables, fuctions and imports
- removed --cve-prefix-dir
- improved helper descriptions
- added --type flag, removed --usn-oval and --pkg-oval
- rework calls to OCI OVAL generation
- rework tests to address new changes

PS: There's a few commits that launchpad is not showing, you might want to look directly at the branch.

To post a comment you must log in.
11ea9be... by Eduardo Barretto

generate-oval: make oval-releases use nargs instead of appending

This allow single calls with --oval-releases bionic xenial
instead of --oval-releases bionic --oval-releases xenial

Revision history for this message
David Fernandez Gonzalez (litios) wrote :

LGTM, no changes in the OVAL output before and after this PR. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/scripts/cve_lib.py b/scripts/cve_lib.py
2index 416d5f7..090cdea 100755
3--- a/scripts/cve_lib.py
4+++ b/scripts/cve_lib.py
5@@ -492,6 +492,7 @@ subprojects = {
6 },
7 "ubuntu/trusty": {
8 "eol": True,
9+ "oval": True,
10 "components": ["main", "restricted", "universe", "multiverse"],
11 "name": "Ubuntu 14.04 LTS",
12 "version": 14.04,
13@@ -532,6 +533,7 @@ subprojects = {
14 },
15 "ubuntu/xenial": {
16 "eol": True,
17+ "oval": True,
18 "components": ["main", "restricted", "universe", "multiverse"],
19 "name": "Ubuntu 16.04 LTS",
20 "version": 16.04,
21diff --git a/scripts/generate-oval b/scripts/generate-oval
22index 920bba6..c555304 100755
23--- a/scripts/generate-oval
24+++ b/scripts/generate-oval
25@@ -32,31 +32,11 @@ import os
26 import re
27 import sys
28
29-import apt_pkg
30-from cve_lib import (kernel_srcs, product_series, load_cve, PRODUCT_UBUNTU, all_releases, eol_releases, devel_release, release_parent, release_name, release_progenitor, needs_oval)
31-from kernel_lib import meta_kernels
32+from cve_lib import (product_series, PRODUCT_UBUNTU, all_releases, eol_releases, devel_release, release_parent, release_name, needs_oval)
33 import oval_lib
34
35-# cope with apt_pkg api changes.
36-if 'init_system' in dir(apt_pkg):
37- apt_pkg.init_system()
38-else:
39- apt_pkg.InitSystem()
40-
41-supported_releases = []
42-for r in set(all_releases).difference(set(eol_releases)).difference(set([devel_release])):
43- if needs_oval(r):
44- supported_releases.append(r)
45- parent = release_parent(r)
46- if parent and parent not in supported_releases:
47- supported_releases.append(parent)
48-
49-default_cves_to_process = ['active/CVE-*', 'retired/CVE-*']
50-
51 debug_level = 0
52
53-package_cache = None
54-
55 def main():
56 """ parse command line options and iterate through files to be processed
57 """
58@@ -64,9 +44,10 @@ def main():
59 global supported_releases
60
61 # parse command line options
62- parser = argparse.ArgumentParser(description='Generate CVE OVAL from '
63- 'CVE metadata files.')
64- parser.add_argument('pathname', nargs='*',
65+ parser = argparse.ArgumentParser(description='Generate OVAL for CVE, PKG or USNs.')
66+ parser.add_argument('--type', required=True, choices=['cve', 'pkg', 'usn'],
67+ help='OVAL format')
68+ parser.add_argument('--cves', nargs='*', default=['active/CVE-*', 'retired/CVE-*'],
69 help='pathname patterns (globs) specifying CVE '
70 'metadata files to be converted into OVAL '
71 '(default: "./active/CVE-*" "./retired/CVE-*")')
72@@ -78,255 +59,76 @@ def main():
73 help='output directory for OCI manifest OVAL files (default is to use the same directory as --output-dir)')
74 parser.add_argument('--oci-prefix', nargs='?', default='oci.',
75 help='Prefix to use for OCI manifest OVAL files names (required if oci-output-dir is the same as output-dir)')
76- parser.add_argument('--cve-prefix-dir', nargs='?', default='./',
77- help='location of CVE metadata files to process '
78- '(default is ./)')
79 parser.add_argument('--no-progress', action='store_true',
80 help='do not show progress meter')
81 parser.add_argument('--pkg-cache-dir', action='store', default='./',
82 help='cache location for binary packages')
83 parser.add_argument('-d', '--debug', action='count', default=0,
84 help="report debugging information")
85- parser.add_argument('--usn-oval', action='store_true',
86- help='generates oval from the USN database')
87- parser.add_argument('--pkg-oval', action='store_true',
88- help='generates oval from the Package database')
89 parser.add_argument('--usn-db-dir', default='./', type=str,
90 help='location of USN database.json to process '
91 '(default is ./)')
92 parser.add_argument('--usn-number', default=None, type=str,
93 help='if passed specifics a USN for the oval_usn generator')
94- parser.add_argument('--oval-releases', default=None, action='append',
95- help='specifies releases to generate the oval')
96+ parser.add_argument('--oval-releases', default=None, nargs='+',
97+ help='only generate OVAL for specific releases')
98 parser.add_argument('--packages', nargs='+', action='store', default=None,
99- help='generates oval for specific packages. Only for '
100- 'CVE OVAL')
101+ help='generates OVAL for specific packages')
102 parser.add_argument('--fixed-only', action='store_true',
103- help='only generate pkg oval for fixed CVEs')
104- parser.add_argument('--expand', action='store_true',
105- help='avoids combining all the oval data into one '
106- 'file and expands the output oval files into different '
107+ help='only generate OVAL for fixed CVEs')
108+ parser.add_argument('--expand', action='store_true', default=False,
109+ help='avoids combining all the OVAL data into one '
110+ 'file and expands the output OVAL files into different '
111 'releases, such as esm-apps, esm-infra, and ubuntu')
112
113 args = parser.parse_args()
114- pathnames = args.pathname or default_cves_to_process
115 debug_level = args.debug
116
117- # debugging; caution, can expose credentials
118- if debug_level >= 2:
119- import httplib2
120- httplib2.debuglevel = 1
121-
122- # create oval generators for each supported release
123- outdir = './'
124- if args.output_dir:
125- outdir = args.output_dir
126- if not os.path.isdir(outdir):
127- raise FileNotFoundError("Could not find '%s'" % outdir)
128- if args.oci:
129- if args.oci_output_dir:
130- ocioutdir = args.oci_output_dir
131- else:
132- ocioutdir = args.output_dir
133- if not os.path.isdir(ocioutdir):
134- raise FileNotFoundError("Could not find '%s'" % ocioutdir)
135- ociprefix = args.oci_prefix
136- if outdir == ocioutdir and len(ociprefix) < 1:
137- raise ValueError("oci-prefix must be set when output-dir and oci-output-dir are the same")
138-
139- if args.usn_oval:
140- if args.oci:
141- generate_oval_usn(args.output_dir, args.usn_number, args.oval_releases,
142- args.cve_prefix_dir, args.usn_db_dir, args.no_progress, ociprefix, ocioutdir)
143- else:
144- generate_oval_usn(args.output_dir, args.usn_number, args.oval_releases,
145- args.cve_prefix_dir, args.usn_db_dir, args.no_progress,)
146+ if args.output_dir and not os.path.isdir(args.output_dir):
147+ raise FileNotFoundError("Could not find '%s'" % args.output_dir)
148
149- return
150+ if args.oci_prefix:
151+ if not args.oci_output_dir:
152+ args.oci_output_dir = args.output_dir
153+ elif args.output_dir == args.oci_output_dir and len(args.oci_prefix) < 1:
154+ raise ValueError("oci-prefix must be set when output-dir and oci-output-dir are the same")
155
156- # if --oval-release, we still need to load parents cache
157- # so we can generate a complete oval for those releases that
158- # have a parent
159- releases = supported_releases
160+ releases = []
161 if args.oval_releases:
162 releases = args.oval_releases
163 for release in releases:
164- if release not in supported_releases:
165+ if not needs_oval(release):
166 error(f"unknown oval release {release}")
167+ else:
168+ releases = []
169+ for r in set(all_releases).difference(set(eol_releases)).difference(set([devel_release])):
170+ if needs_oval(r):
171+ releases.append(r)
172+
173+ # for each release we need to get its parent to also
174+ # load their cache data in order to generate a complete
175+ # oval for such release
176+ parent_releases = set()
177+ for release in releases:
178+ while(release_parent(release)):
179+ release = release_parent(release)
180+ parent_releases.add(release)
181
182- expand = args.expand
183-
184- cache = {}
185- for release in supported_releases:
186- cve_cache = {}
187- cache.update({release: get_package_cache(args.pkg_cache_dir, release)})
188-
189- if args.pkg_oval:
190- if args.oci:
191- generate_oval_package(releases, outdir, args.cve_prefix_dir, cache, cve_cache, args.oci, args.no_progress, args.packages, pathnames, args.fixed_only, ocioutdir, expand)
192- else:
193- generate_oval_package(releases, outdir, args.cve_prefix_dir, cache, cve_cache, args.oci, args.no_progress, args.packages, pathnames, args.fixed_only, expand)
194- return
195+ releases = set(releases + list(parent_releases))
196
197- if args.oci:
198- generate_oval_cve(releases, outdir, args.cve_prefix_dir, cache, cve_cache, args.oci, args.no_progress, args.packages, pathnames, args.fixed_only, ocioutdir, expand)
199+ if args.type == 'usn':
200+ generate_oval_usn(args.output_dir, args.usn_number, releases,
201+ args.cves, args.usn_db_dir, args.no_progress, args.oci_prefix, args.oci_output_dir)
202 else:
203- generate_oval_cve(releases, outdir, args.cve_prefix_dir, cache, cve_cache, args.oci, args.no_progress, args.packages, pathnames, args.fixed_only, expand)
204- return
205-
206-
207-# given a status generated by parse_package_status(), duplicate it for a
208-# different source package, computing binary packages for the new source
209-# package
210-def duplicate_package_status(release, package, original_status, cache, override_version=None):
211- copied_status = {}
212- copied_status['status'] = original_status['status']
213- copied_status['note'] = original_status['note']
214- if override_version:
215- copied_status['fix-version'] = override_version
216- elif 'fix-version' in original_status:
217- copied_status['fix-version'] = original_status['fix-version']
218-
219- if 'bin-pkgs' in original_status:
220- copied_status['bin-pkgs'] = original_status['bin-pkgs']
221-
222- return copied_status
223-
224-
225-# returns True if we should ignore this source package; primarily used
226-# for -edge kernels
227-def ignore_source_package(source):
228- if re.match('linux-.*-edge$', source):
229- return True
230- if re.match('linux-riscv.*$', source):
231- # linux-riscv.* currently causes a lot of false positives, skip
232- # it altogether while we don't land a better fix
233- return True
234- return False
235-
236-
237-def parse_cve_file(filepath, cache, pkg_filter=None, expand=False):
238- """ parse CVE data file into a dictionary using cve_lib """
239-
240- cve_header_data = {
241- 'Candidate': '',
242- 'CRD': '',
243- 'PublicDate': '',
244- 'PublicDateAtUSN': '',
245- 'References': [get_cve_url(filepath)],
246- 'Description': '',
247- 'Ubuntu-Description': '',
248- 'Notes': '',
249- 'Mitigation': '',
250- 'Bugs': [],
251- 'Priority': '',
252- 'Discovered-by': '',
253- 'Assigned-to': '',
254- 'CVSS': '',
255- 'Unknown-Fields': [],
256- 'Source-note': filepath
257- }
258- data = load_cve(filepath)
259- # first try a naive translation of fields
260- for f in cve_header_data:
261- try:
262- cve_header_data[f] = data[f]
263- except KeyError:
264- pass
265-
266- # then handle any particular fields which are expected to have a
267- # different format
268- cve_header_data['Description'] = cve_header_data['Description'].strip().replace('\n', ' ')
269- cve_header_data['Ubuntu-Description'] = cve_header_data['Ubuntu-Description'].strip().replace('\n', ' ')
270- cve_header_data['References'] = [ref.strip() for ref in cve_header_data['References'].split('\n') if len(ref.strip()) > 0]
271- cve_header_data['References'].insert(0, get_cve_url(filepath))
272- cve_header_data['Bugs'] = [bug.strip() for bug in cve_header_data['Bugs'].split('\n') if len(bug.strip()) > 0]
273- cve_header_data['Notes'] = ' '.join(user + '> ' + note.replace('\n', ' ') for user, note in cve_header_data['Notes'])
274- cve_header_data['Priority'] = cve_header_data['Priority'][0]
275- packages = {}
276- for pkg in data['pkgs']:
277- if ignore_source_package(pkg):
278- continue
279- if pkg_filter:
280- if pkg not in pkg_filter:
281- continue
282-
283- packages[pkg] = {'Releases': {},
284- 'Priority': '',
285- 'Tags': []}
286- for rel in data['pkgs'][pkg]:
287- if rel in ['upstream', 'devel']:
288- continue
289- if rel not in supported_releases:
290- continue
291- state, details = data['pkgs'][pkg][rel]
292- packages[pkg]['Releases'][rel] = oval_lib.CVEPkgRelEntry.parse_package_status(rel, pkg, state, details, filepath, cache, oval_lib.find_release_codename(rel), expand)
293-
294- # add supplemental packages; usually kernels only need this special case.
295- for package in [name for name in packages if name in kernel_srcs]:
296- for release in [
297- rel for rel in packages[package]['Releases']
298- if packages[package]['Releases'][rel]['status'] != 'not-applicable'
299- ]:
300- # add signed package
301- # handle signed package of subprojects without needing to have them in kernel_lib
302- progenitor = release_progenitor(release)
303- if progenitor:
304- signed_pkg = meta_kernels.get_signed(progenitor, package, quiet=(debug_level < 1))
305- else:
306- signed_pkg = meta_kernels.get_signed(release, package, quiet=(debug_level < 1))
307- if signed_pkg:
308- if signed_pkg not in packages:
309- packages[signed_pkg] = {
310- 'Priority': packages[package]['Priority'],
311- 'Tags': packages[package]['Tags'],
312- 'Releases': {}
313- }
314- if release not in packages[signed_pkg]['Releases']:
315- packages[signed_pkg]['Releases'][release] = \
316- duplicate_package_status(release, signed_pkg, packages[package]['Releases'][release], cache)
317-
318- # if subproject is based on an ubuntu release
319- for pkg in packages:
320- # if subproject is DNE for a given package, then copy parent's status
321- for rel in packages[pkg]['Releases']:
322- parent = release_parent(rel)
323- if parent and parent in packages[pkg]['Releases']:
324- if packages[pkg]['Releases'][parent]['status'] != 'not-applicable' and packages[pkg]['Releases'][rel]['status'] == 'not-applicable':
325- packages[pkg]['Releases'][rel] = \
326- duplicate_package_status(parent, pkg, packages[pkg]['Releases'][parent], cache)
327- # this is needed for update instructions
328- progenitor = release_progenitor(parent)
329- if progenitor and progenitor in packages[pkg]['Releases']:
330- if packages[pkg]['Releases'][parent]['status'] == packages[pkg]['Releases'][progenitor]['status']:
331- packages[pkg]['Releases'][rel]['parent'] = progenitor
332- else:
333- packages[pkg]['Releases'][rel]['parent'] = parent
334- else:
335- packages[pkg]['Releases'][rel]['parent'] = parent
336- # if supported release not in CVE file, then copy parent's status
337- for rel in supported_releases:
338- if rel not in packages[pkg]['Releases']:
339- parent = release_parent(rel)
340- if parent and parent not in packages[pkg]['Releases']:
341- parent = release_parent(parent)
342-
343- if not parent or parent not in packages[pkg]['Releases']:
344- continue
345-
346- packages[pkg]['Releases'][rel] = \
347- duplicate_package_status(parent, pkg, packages[pkg]['Releases'][parent], cache)
348- packages[pkg]['Releases'][rel]['parent'] = parent
349-
350- return {'header': cve_header_data, 'packages': packages}
351-
352-
353-def get_cve_url(filepath):
354- """ returns a url to CVE data from a filepath """
355- path = os.path.realpath(filepath).split(os.sep)
356- url = "https://ubuntu.com/security"
357- cve = path[-1]
358- return "%s/%s" % (url, cve)
359+ cache = {}
360+ for release in releases:
361+ cve_cache = {}
362+ cache.update({release: get_package_cache(args.pkg_cache_dir, release)})
363+
364+ if args.type == 'pkg':
365+ generate_oval_package(releases, args.output_dir, args.cves, cache, cve_cache, args.oci, args.no_progress, args.packages, args.fixed_only, args.oci_output_dir, args.expand)
366+ elif args.type == 'cve':
367+ generate_oval_cve(releases, args.output_dir, args.cves, cache, cve_cache, args.oci, args.no_progress, args.packages, args.fixed_only, args.oci_output_dir, args.expand)
368
369
370 def warn(message):
371@@ -343,17 +145,6 @@ def debug(message):
372 if debug_level > 0:
373 sys.stdout.write('\rDEBUG: {0}\n'.format(message))
374
375-def progress_bar(current, total, size=20):
376- """ show a simple progress bar on the CLI """
377- current_percent = float(current) / total
378- hashes = '#' * int(round(current_percent * size))
379- spaces = ' ' * (size - len(hashes))
380- sys.stdout.write('\rProgress: [{0}] {1}% ({2} of {3} CVEs processed)'.format(hashes + spaces, int(round(current_percent * 100)), current, total))
381- if (current == total):
382- sys.stdout.write('\n')
383-
384- sys.stdout.flush()
385-
386 def prepend_usn_to_id(usn_database, usn_id):
387 if re.search(r'^[0-9]+-[0-9]$', usn_id):
388 usn_database[usn_id]['id'] = 'USN-' + usn_id
389@@ -381,15 +172,15 @@ def get_usn_database(usn_db_dir):
390
391 # Usage:
392 # for a given release only:
393-# ./generate-oval --usn-oval --usn-db-dir ~/usndb --usn-oval-release=focal --output-dir /tmp/oval_usn
394+# ./generate-oval --type usn --usn-db-dir ~/usndb --oval-releases=focal --output-dir /tmp/oval_usn
395 # for all the releases:
396-# ./generate-oval --usn-oval --usn-db-dir ~/usndb --output-dir /tmp/oval_usn
397+# ./generate-oval --type usn --usn-db-dir ~/usndb --output-dir /tmp/oval_usn
398 # for a specific release and USN-number
399-# ./generate-oval --usn-oval --usn-db-dir ~/usndb --usn-oval-release=focal --usn-number=1234
400+# ./generate-oval --type usn --usn-db-dir ~/usndb --oval-releases=focal --usn-number=1234
401 # WARNING:
402 # be sure the release you are passing is in the usn-number passed
403 # otherwise it will generate an oval file without the usn info.
404-def generate_oval_usn(outdir, usn, usn_releases, cve_dir, usn_db_dir, no_progress, ociprefix=None, ocioutdir=None):
405+def generate_oval_usn(outdir, usn, usn_releases, cves, usn_db_dir, no_progress, ociprefix, ocioutdir):
406 # Get the usn database.json data
407 usn_database = get_usn_database(usn_db_dir)
408 if not usn_database:
409@@ -404,33 +195,27 @@ def generate_oval_usn(outdir, usn, usn_releases, cve_dir, usn_db_dir, no_progres
410 valid_releases = []
411
412 # Check or generate valid releases
413- if usn_releases:
414- for usn_release in usn_releases:
415- if usn_release not in supported_releases:
416- error(f"Invalid release name '{usn_release}'.")
417- valid_releases = usn_releases
418- else:
419- valid_releases = list(filter(lambda release: product_series(release)[0] == PRODUCT_UBUNTU, supported_releases))
420+ valid_releases = list(filter(lambda release: product_series(release)[0] == PRODUCT_UBUNTU, usn_releases))
421
422 if not no_progress:
423 print('[*] Generating OVAL USN for packages in releases', ', '.join(valid_releases))
424
425 for release in valid_releases:
426- ovals.append(oval_lib.OvalGeneratorUSN(release, release_name(release), outdir, cve_dir))
427+ ovals.append(oval_lib.OvalGeneratorUSN(release, release_name(release), outdir, cves))
428 # Also produce oval generator object for OCI
429 if ocioutdir:
430 ovals.append(oval_lib.OvalGeneratorUSN(release, release_name(release), ocioutdir,
431- cve_dir, ociprefix, 'oci'))
432+ cves, ociprefix, 'oci'))
433 # Generate OVAL USN data
434 if usn:
435 prepend_usn_to_id(usn_database, usn)
436 for oval in ovals:
437- oval.generate_usn_oval(usn_database[usn], usn_database[usn]['id'], cve_dir)
438+ oval.generate_usn_oval(usn_database[usn], usn_database[usn]['id'], cves)
439 else:
440 for usn in sorted(usn_database.keys()):
441 prepend_usn_to_id(usn_database, usn)
442 for oval in ovals:
443- oval.generate_usn_oval(usn_database[usn], usn_database[usn]['id'], cve_dir)
444+ oval.generate_usn_oval(usn_database[usn], usn_database[usn]['id'], cves)
445
446 for oval in ovals:
447 oval.write_oval_elements()
448@@ -452,21 +237,20 @@ def filter_releases_for_output(releases, ov):
449 return output_releases
450
451
452-def generate_oval_package(releases, outdir, cve_prefix_dir, pkg_cache, cve_cache, oci, no_progress, packages, pathnames, fixed_only, ocioutdir=None, expand=False):
453+def generate_oval_package(releases, outdir, cves, pkg_cache, cve_cache, oci, no_progress, packages, fixed_only, ocioutdir, expand):
454 if not no_progress:
455 print('[*] Initiating OVAL Generation for PKG')
456
457 ov = oval_lib.OvalGeneratorPkg(
458- releases,
459- pathnames,
460- packages,
461- not no_progress,
462- pkg_cache=pkg_cache,
463- fixed_only=fixed_only,
464- cve_cache=cve_cache,
465+ releases,
466+ cves,
467+ packages,
468+ not no_progress,
469+ pkg_cache=pkg_cache,
470+ fixed_only=fixed_only,
471+ cve_cache=cve_cache,
472 oval_format='dpkg',
473- outdir=outdir,
474- cve_prefix_dir=cve_prefix_dir,
475+ outdir=outdir,
476 expand=expand
477 )
478
479@@ -485,21 +269,20 @@ def generate_oval_package(releases, outdir, cve_prefix_dir, pkg_cache, cve_cache
480 if not no_progress:
481 print(f'[X] Done generating OVAL PKG for packages in releases {", ".join(output_releases)}')
482
483-def generate_oval_cve(releases, outdir, cve_prefix_dir, pkg_cache, cve_cache, oci, no_progress, packages, pathnames, fixed_only, ocioutdir=None, expand=False):
484+def generate_oval_cve(releases, outdir, cves, pkg_cache, cve_cache, oci, no_progress, packages, fixed_only, ocioutdir, expand):
485 if not no_progress:
486 print('[*] Initiating OVAL Generation for CVE')
487
488 ov = oval_lib.OvalGeneratorCVE(
489- releases,
490- pathnames,
491- packages,
492+ releases,
493+ cves,
494+ packages,
495 not no_progress,
496- pkg_cache=pkg_cache,
497- fixed_only=fixed_only,
498- cve_cache=cve_cache,
499+ pkg_cache=pkg_cache,
500+ fixed_only=fixed_only,
501+ cve_cache=cve_cache,
502 oval_format='dpkg',
503- outdir=outdir,
504- cve_prefix_dir=cve_prefix_dir,
505+ outdir=outdir,
506 expand=expand
507 )
508
509diff --git a/scripts/oval_lib.py b/scripts/oval_lib.py
510index d60c6dc..a974eaf 100644
511--- a/scripts/oval_lib.py
512+++ b/scripts/oval_lib.py
513@@ -29,7 +29,6 @@ import tempfile
514 import collections
515 import glob
516 import xml.etree.cElementTree as etree
517-import json
518 from xml.dom import minidom
519 from typing import Tuple # Needed because of Python < 3.9 and to also support < 3.7
520
521@@ -172,7 +171,7 @@ def get_binarypkgs(cache, source_name, release):
522 """ return a list of binary packages from the source package version """
523 packages_to_ignore = ("-dev", "-doc", "-dbg", "-dbgsym", "-udeb", "-locale-")
524 binaries_map = collections.defaultdict(dict)
525-
526+
527 rel = get_real_release(cache, source_name, release)
528 if not rel: return None, None
529
530@@ -316,7 +315,7 @@ class CVE:
531 self.bugs.append(url)
532 elif url:
533 self.references.append(url)
534-
535+
536 for bug in info['Bugs'].split('\n'):
537 if bug:
538 self.bugs.append(bug)
539@@ -331,7 +330,7 @@ class CVE:
540 for pkg in self.pkgs:
541 if pkg.rel not in releases:
542 continue
543-
544+
545 if pkg.name not in pkg_rel:
546 pkg_rel[pkg.name] = pkg
547 else:
548@@ -356,7 +355,7 @@ class CVE:
549
550 if pkg.name in pkg_rel and pkg_rel[pkg.name].rel == pkg.rel:
551 pkgs.append(pkg)
552-
553+
554 return pkgs
555
556
557@@ -429,7 +428,7 @@ class Package:
558
559 def version_exists(self, source_version):
560 return source_version in self.versions_binaries
561-
562+
563 def all_binaries_same_version(self, source_version):
564 if source_version not in self.versions_binaries:
565 return len(self.versions_binaries[self.earliest_version]) <= 1
566@@ -514,7 +513,7 @@ class OvalGenerator:
567 supported_oval_elements = ('definition', 'test', 'object', 'state', 'variable')
568 generator_version = '2'
569 oval_schema_version = '5.11.1'
570- def __init__(self, type, releases, cve_paths, packages, progress, pkg_cache, fixed_only=True, cve_cache=None, cve_prefix_dir=None, outdir='./', oval_format='dpkg', expand=False) -> None:
571+ def __init__(self, type, releases, cve_paths, packages, progress, pkg_cache, fixed_only=True, cve_cache=None, outdir='./', oval_format='dpkg', expand=False) -> None:
572 self.releases = releases
573 self.output_dir = outdir
574 self.oval_format = oval_format
575@@ -524,7 +523,7 @@ class OvalGenerator:
576 self.pkg_cache = pkg_cache
577 self.cve_paths = cve_paths
578 self.fixed_only = fixed_only
579- self.packages, self.cves = self._load(cve_prefix_dir, packages)
580+ self.packages, self.cves = self._load(packages)
581 self.expand = expand
582
583 def _init_ids(self, release):
584@@ -532,7 +531,7 @@ class OvalGenerator:
585 self.release = release
586 self.release_codename = cve_lib.release_progenitor(self.release) if cve_lib.release_progenitor(self.release) else self.release.replace('/', '_')
587 self.release_name = cve_lib.release_name(self.release)
588-
589+
590 self.parent_releases = list()
591 current_release = self.release
592 while(cve_lib.release_parent(current_release)):
593@@ -540,7 +539,7 @@ class OvalGenerator:
594 if current_release != self.release and \
595 current_release not in self.parent_releases:
596 self.parent_releases.append(current_release)
597-
598+
599 self.ns = 'oval:com.ubuntu.{0}'.format(self.release_codename)
600 self.id = 100
601 self.host_def_id = self.id
602@@ -564,7 +563,7 @@ class OvalGenerator:
603 self.output_filepath = \
604 '{0}com.ubuntu.{1}.{2}.oval.xml'.format('oci.' if self.oval_format == 'oci' else '', self.release.replace('/', '_'), self.generator_type)
605
606-
607+
608 def _add_structure(self, root) -> None:
609 structure = {}
610 for element in self.supported_oval_elements:
611@@ -756,14 +755,14 @@ class OvalGenerator:
612 pkg_obj = packages[package_name]
613 cve.add_pkg(pkg_obj, release, cve_data['pkgs'][package_name][release][0],cve_data['pkgs'][package_name][release][1], self.expand)
614
615- def _load(self, cve_prefix_dir, packages_filter=None) -> None:
616+ def _load(self, packages_filter=None) -> None:
617 cve_lib.load_external_subprojects()
618
619- cve_paths = []
620+ path_to_cves = []
621 for pathname in self.cve_paths:
622- cve_paths = cve_paths + glob.glob(os.path.join(cve_prefix_dir, pathname))
623+ path_to_cves = path_to_cves + glob.glob(pathname)
624
625- cve_paths.sort(key=lambda cve:
626+ path_to_cves.sort(key=lambda cve:
627 (int(cve.split('/')[-1].split('-')[1]), int(cve.split('/')[-1].split('-')[2])) \
628 if cve.split('/')[-1].split('-')[2].isnumeric() \
629 else (int(cve.split('/')[-1].split('-')[1]), 0)
630@@ -790,12 +789,12 @@ class OvalGenerator:
631 if release not in cve_lib.external_releases else {}
632
633 i = 0
634- for cve_path in cve_paths:
635+ for cve_path in path_to_cves:
636 cve_number = cve_path.rsplit('/', 1)[1]
637 i += 1
638
639 if self.progress:
640- print(f'[{i:5}/{len(cve_paths)}] Processing {cve_number:18}', end='\r')
641+ print(f'[{i:5}/{len(path_to_cves)}] Processing {cve_number:18}', end='\r')
642
643 if not cve_number in self.cve_cache:
644 self.cve_cache[cve_number] = cve_lib.load_cve(cve_path)
645@@ -849,7 +848,7 @@ class OvalGenerator:
646 extend_definition.set("applicability_check", "true")
647
648 return criteria
649-
650+
651 def _generate_definition_object(self, object) -> etree.Element:
652 id = f"{self.ns}:def:{self.definition_id}"
653 definition = etree.Element("definition")
654@@ -889,7 +888,7 @@ class OvalGenerator:
655 cve_tag.set('usns', ','.join(cve.usns))
656
657 return cve_tag
658-
659+
660 def _generate_var_element(self, comment, id, binaries) -> etree.Element:
661 var = etree.Element("constant_variable",
662 attrib={
663@@ -1323,9 +1322,9 @@ class OvalGenerator:
664
665
666 class OvalGeneratorPkg(OvalGenerator):
667- def __init__(self, releases, cve_paths, packages, progress, pkg_cache, fixed_only=True, cve_cache=None, cve_prefix_dir=None, outdir='./', oval_format='dpkg',expand=False) -> None:
668+ def __init__(self, releases, cve_paths, packages, progress, pkg_cache, fixed_only=True, cve_cache=None, outdir='./', oval_format='dpkg',expand=False) -> None:
669 self.expand = expand
670- super().__init__('pkg', releases, cve_paths, packages, progress, pkg_cache, fixed_only, cve_cache, cve_prefix_dir, outdir, oval_format, expand)
671+ super().__init__('pkg', releases, cve_paths, packages, progress, pkg_cache, fixed_only, cve_cache, outdir, oval_format, expand)
672
673 def _generate_advisory(self, package: Package) -> etree.Element:
674 advisory = etree.Element("advisory")
675@@ -1431,7 +1430,7 @@ class OvalGeneratorPkg(OvalGenerator):
676 binary_id_version = binary_version
677
678 binaries_ids.setdefault(binary_id_version, None)
679- binaries_id = binaries_ids[binary_id_version]
680+ binaries_id = binaries_ids[binary_id_version]
681 test, obj, var, state = self._generate_elements(package, binaries, binary_version, pkg_rel_entry, binaries_id)
682
683 if state:
684@@ -1451,7 +1450,7 @@ class OvalGeneratorPkg(OvalGenerator):
685
686 self._increase_id(is_definition=True)
687
688- def _populate_kernel_pkg(self, package, root_element, running_kernel_id):
689+ def _populate_kernel_pkg(self, package, root_element, running_kernel_id):
690 for cve in package.cves:
691 pkg_rel_entry = cve.pkg_rel_entries[str(package)]
692 version_to_check = package.get_version_to_check(pkg_rel_entry.fixed_version)
693@@ -1482,7 +1481,7 @@ class OvalGeneratorPkg(OvalGenerator):
694 cve_added = True
695
696 # Determine if CVE is linked to existing test_ref
697- test_ref_id = self.definition_id
698+ test_ref_id = self.definition_id
699 if fixed_versions.get(pkg_rel_entry.fixed_version, False):
700 test_ref_id = fixed_versions[pkg_rel_entry.fixed_version]
701 self._add_test_ref_to_cve_tag(test_ref_id, cve, definition_element)
702@@ -1513,7 +1512,7 @@ class OvalGeneratorPkg(OvalGenerator):
703 all_pkgs = dict()
704 for parent_release in self.parent_releases[::-1]:
705 all_pkgs.update(self.packages[parent_release])
706-
707+
708 all_pkgs.update(self.packages[self.release])
709
710 for pkg in all_pkgs:
711@@ -1529,9 +1528,9 @@ class OvalGeneratorPkg(OvalGenerator):
712 self._write_oval_xml(xml_tree, root_element)
713
714 class OvalGeneratorCVE(OvalGenerator):
715- def __init__(self, releases, cve_paths, packages, progress, pkg_cache, fixed_only=True, cve_cache=None, cve_prefix_dir=None, outdir='./', oval_format='dpkg', expand=False) -> None:
716+ def __init__(self, releases, cve_paths, packages, progress, pkg_cache, fixed_only=True, cve_cache=None, outdir='./', oval_format='dpkg', expand=False) -> None:
717 self.expand = expand
718- super().__init__('cve', releases, cve_paths, packages, progress, pkg_cache, fixed_only, cve_cache, cve_prefix_dir, outdir, oval_format, expand)
719+ super().__init__('cve', releases, cve_paths, packages, progress, pkg_cache, fixed_only, cve_cache, outdir, oval_format, expand)
720
721 # For CVE OVAL, the definition ID is generated
722 # from the CVE ID
723@@ -1551,7 +1550,7 @@ class OvalGeneratorCVE(OvalGenerator):
724 if cve.assigned_to:
725 assigned_to = etree.SubElement(advisory, "assigned_to")
726 assigned_to.text = cve.assigned_to
727-
728+
729 if cve.discoverd_by:
730 discoverd_by = etree.SubElement(advisory, "discoverd_by")
731 discoverd_by.text = cve.discoverd_by
732@@ -1596,7 +1595,7 @@ class OvalGeneratorCVE(OvalGenerator):
733 "ref_url": f'https://cve.mitre.org/cgi-bin/cvename.cgi?name={cve.number}'
734 })
735
736- return reference
737+ return reference
738
739 def prepare_instructions(self, instruction, cve: CVE, product_description, package: Package, fixed_version):
740 if "LSN" in cve.number:
741@@ -1729,13 +1728,13 @@ class OvalGeneratorCVE(OvalGenerator):
742 pkg_added = True
743 else:
744 pkg_added = self._populate_pkg(cve, pkg, root_element, definition_element, pkg_cache, fixed_versions)
745-
746+
747 if pkg_rel_entry.status == 'fixed' and pkg_added:
748 product_description = cve_lib.get_subproject_description(pkg_rel_entry.release)
749 instructions = self.prepare_instructions(instructions, cve, product_description, pkg, pkg_rel_entry.fixed_version)
750
751 cve_added = cve_added | pkg_added
752-
753+
754 if cve_added:
755 definitions = root_element.find("definitions")
756 metadata = definition_element.find('metadata')
757@@ -1780,8 +1779,8 @@ class OvalGeneratorCVE(OvalGenerator):
758 self._write_oval_xml(xml_tree, root_element)
759
760 class OvalGeneratorUSNs(OvalGenerator):
761- def __init__(self, releases, cve_paths, packages, progress, pkg_cache, usn_database, fixed_only=True, cve_cache=None, cve_prefix_dir=None, outdir='./', oval_format='dpkg') -> None:
762- super().__init__('usn', releases, cve_paths, packages, progress, pkg_cache, fixed_only, cve_cache, cve_prefix_dir, outdir, oval_format)
763+ def __init__(self, releases, cve_paths, packages, progress, pkg_cache, usn_database, fixed_only=True, cve_cache=None, outdir='./', oval_format='dpkg') -> None:
764+ super().__init__('usn', releases, cve_paths, packages, progress, pkg_cache, fixed_only, cve_cache, outdir, oval_format)
765 self.usns = self._load_usns(usn_database)
766
767 def _load_usns(self, usn_database):
768@@ -1838,7 +1837,7 @@ class OvalGeneratorUSNs(OvalGenerator):
769 platform = etree.SubElement(affected, "platform")
770
771 reference = self._generate_reference(usn)
772- metadata.append(reference)
773+ metadata.append(reference)
774 advisory = self._generate_advisory(usn)
775 metadata.append(reference)
776 metadata.append(advisory)
777@@ -1920,7 +1919,7 @@ class OvalGeneratorUSNs(OvalGenerator):
778 self._increase_id(is_definition=False)
779 else:
780 self._add_to_criteria(main_criteria, cache[package.name], depth=2, operator='OR')
781-
782+
783 self._increase_id(is_definition=True)
784
785 def generate_oval(self) -> None:
786@@ -1952,11 +1951,11 @@ class OvalGeneratorUSNs(OvalGenerator):
787 self._populate_kernel_pkg(self.cves[cve], pkg, root_element, definition_element, running_kernel_id, pkg_cache, fixed_versions)
788 else:
789 self._populate_pkg(self.cves[cve], pkg, root_element, definition_element, pkg_cache, fixed_versions)
790-
791+
792 if pkg_rel_entry.status == 'fixed' and pkg.binaries:
793 product_description = cve_lib.get_subproject_description(pkg_rel_entry.release)
794 instructions = prepare_instructions(instructions, self.cves[cve].number, product_description, {'binaries': pkg.binaries, 'fix-version': pkg_rel_entry.fixed_version})
795-
796+
797 metadata = definition_element.find('metadata')
798 metadata.find('description').text = metadata.find('description').text + instructions
799 definitions.append(definition_element)
800@@ -2467,14 +2466,13 @@ class OvalGeneratorUSN():
801 return constant_variable
802
803 def get_cve_info_from_file(self, cve, cve_dir):
804- cve_active_file_path = os.path.join(cve_dir, 'active', cve)
805- cve_retired_file_path = os.path.join(cve_dir, 'retired', cve)
806+ cve_file_path = ""
807+ for path in cve_dir:
808+ if os.path.exists(os.path.join(path, cve)):
809+ cve_file_path = os.path.join(path, cve)
810+ break
811
812- if os.path.exists(cve_active_file_path):
813- cve_file_path = cve_active_file_path
814- elif os.path.exists(cve_retired_file_path):
815- cve_file_path = cve_retired_file_path
816- else:
817+ if not cve_file_path:
818 return None
819
820 cve_object = cve_lib.load_cve(cve_file_path)
821diff --git a/test/test_oval_lib_end_to_end.py b/test/test_oval_lib_end_to_end.py
822index bfad6c4..db74957 100644
823--- a/test/test_oval_lib_end_to_end.py
824+++ b/test/test_oval_lib_end_to_end.py
825@@ -6,13 +6,13 @@ from test_utils import TestUtilities as util
826 from test_utils import OVALType
827 class TestOvalLibEndToEnd:
828 @pytest.mark.parametrize("output_file,oscap_args",
829- [(util.focal_dpkg_file, ["--oval-release", "focal"]),
830- (util.xenial_dpkg_file, ["--oval-release", "xenial"])])
831+ [(util.focal_dpkg_file, ["--oval-releases", "focal"]),
832+ (util.xenial_dpkg_file, ["--oval-releases", "xenial"])])
833 def test_validate_entire_dpkg_oval(self, output_file, oscap_args):
834 """Coherence check of entire generated dpkg OVAL"""
835 write_file = util.rel_test_path + output_file
836
837- subprocess.check_output(["./scripts/generate-oval", "--usn-oval",
838+ subprocess.check_output(["./scripts/generate-oval", "--type=usn",
839 "--output-dir={}".format(util.rel_test_path)] + oscap_args,
840 stderr=subprocess.STDOUT)
841
842@@ -29,4 +29,4 @@ class TestOvalLibEndToEnd:
843 def test_validate_entire_oci_oval(self, dpkg_file, manifest, release):
844 """Coherence check of entire generated oci OVAL"""
845 util.create_validate_oci(dpkg_file, "{}_full".format(release),
846- ["--oval-release", release], manifest, release, OVALType.USN)
847+ ["--oval-releases", release], manifest, release, OVALType.USN)
848diff --git a/test/test_oval_lib_functional.py b/test/test_oval_lib_functional.py
849index 8b400fb..aad0116 100644
850--- a/test/test_oval_lib_functional.py
851+++ b/test/test_oval_lib_functional.py
852@@ -12,18 +12,18 @@ class TestOvalLibFunctional:
853 def test_multiple_binary_package(self, manifest):
854 """Test a package with multiple binaries"""
855 util.create_validate_oci(util.focal_dpkg_file, "focal_4410",
856- ["--usn-number", "4410-1", "--oval-release", "focal"],
857+ ["--usn-number", "4410-1", "--oval-releases", "focal"],
858 manifest, "focal_mock_4410_vul", OVALType.USN)
859
860 @pytest.mark.parametrize("manifest",
861- # Binary is in manifest and not vulnerable
862+ # Binary is in manifest and not vulnerable
863 [("bionic_mock_3642_not_vul"),
864- # Binary is in manifest and vulnerable
865+ # Binary is in manifest and vulnerable
866 ("bionic_mock_3642_vul")])
867 def test_single_binary_package(self, manifest):
868 """Test a package with a single binary"""
869 util.create_validate_oci(util.bionic_dpkg_file, "bionic_3642",
870- ["--usn-number", "3642-1", "--oval-release", "bionic"],
871+ ["--usn-number", "3642-1", "--oval-releases", "bionic"],
872 manifest, manifest, OVALType.USN)
873
874 @pytest.mark.parametrize("manifest",
875@@ -34,7 +34,7 @@ class TestOvalLibFunctional:
876 def test_multiple_packages_usn(self, manifest):
877 """Test USN with multiple source packages"""
878 util.create_validate_oci(util.bionic_dpkg_file, "bionic_4428",
879- ["--usn-number", "4428-1", "--oval-release", "bionic"],
880+ ["--usn-number", "4428-1", "--oval-releases", "bionic"],
881 manifest, "bionic_mock_4428", OVALType.USN)
882
883 @pytest.mark.parametrize("manifest,gold_file,usn",
884@@ -68,7 +68,7 @@ class TestOvalLibFunctional:
885 def test_epoch(self, manifest, gold_file, usn):
886 """Test epochs are used correctly in vulnerability assesments"""
887 util.create_validate_oci(util.focal_dpkg_file, "focal_{}".format(usn),
888- ["--usn-number", usn, "--oval-release", "focal"], manifest,
889+ ["--usn-number", usn, "--oval-releases", "focal"], manifest,
890 gold_file, OVALType.USN)
891
892
893diff --git a/test/test_oval_lib_unit.py b/test/test_oval_lib_unit.py
894index c3abf85..ab3a758 100644
895--- a/test/test_oval_lib_unit.py
896+++ b/test/test_oval_lib_unit.py
897@@ -429,8 +429,8 @@ No subscription required"""
898 assert cve_info is None
899
900 def test_create_dict_from_cve_file(self):
901- cve_dir = os.environ["UCT"]
902- dst_cve_file = os.path.join(cve_dir, "active", self.test_cve_file)
903+ cve_dir = os.path.join(os.environ["UCT"], "active/")
904+ dst_cve_file = os.path.join(cve_dir, self.test_cve_file)
905 src_cve_file = os.path.join(rel_test_path, self.test_cve_file)
906 copyfile(src_cve_file, dst_cve_file)
907 corr_cve_info = {
908@@ -451,7 +451,7 @@ No subscription required"""
909 }
910
911 cve_info = oval_lib.OvalGeneratorUSN.get_cve_info_from_file(
912- self.oval_gen_mock, self.test_cve_file, cve_dir)
913+ self.oval_gen_mock, self.test_cve_file, [cve_dir])
914
915 try:
916 assert cve_info == corr_cve_info
917diff --git a/test/test_utils.py b/test/test_utils.py
918index 682e45a..d62a842 100644
919--- a/test/test_utils.py
920+++ b/test/test_utils.py
921@@ -6,9 +6,9 @@ import subprocess
922 from enum import Enum
923
924 class OVALType(Enum):
925- USN = '--usn-oval'
926- CVE = ''
927- PKG = '--pkg-oval'
928+ USN = '--type=usn'
929+ CVE = '--type=cve'
930+ PKG = '--type=pkg'
931
932 class TestUtilities:
933 cwd = os.getcwd()

Subscribers

People subscribed via source and target branches