Merge ~litios/ubuntu-cve-tracker:oval-for-pkgs into ubuntu-cve-tracker:master
- Git
- lp:~litios/ubuntu-cve-tracker
- oval-for-pkgs
- Merge into master
Status: | Merged |
---|---|
Merged at revision: | b866b1c07fa73084a209f128cf8add7d4538ee35 |
Proposed branch: | ~litios/ubuntu-cve-tracker:oval-for-pkgs |
Merge into: | ubuntu-cve-tracker:master |
Diff against target: |
1580 lines (+1126/-145) 3 files modified
scripts/generate-oval (+75/-131) scripts/oval_lib.py (+1046/-14) scripts/source_map.py (+5/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eduardo Barretto | Approve | ||
Review via email: mp+440235@code.launchpad.net |
Commit message
Description of the change
A little bit of context:
* I created several metadata classes (Package, CVE, CVEPkgRelEntry) to hold the information and make it easy to generate multiple formats. Everything gets loaded and all of them are linked among themselves. This way, generating one format or the other will be a matter of what is used as the definition. Right now, this is not used for CVE or USN OVAL, that is working as always.
* OvalGenerator is the abstract class. Specific generators will inherit from here. Some functions have already been created there and I think more can be extracted from the Pkg generator (the ones related to generating the OVAL XML elements, it should work the same regardless of the OVAL type). Right now, this is not used for CVE or USN OVAL, that is working as always.
* xml.etree.
* All logic is on the oval_lib file. The idea is for generate_oval to create the OVAL generator with the desired options and ask him to generate the OVAL file.
Preview Diff
1 | diff --git a/scripts/generate-oval b/scripts/generate-oval |
2 | index d5d4c11..f703733 100755 |
3 | --- a/scripts/generate-oval |
4 | +++ b/scripts/generate-oval |
5 | @@ -26,20 +26,20 @@ |
6 | from __future__ import print_function, unicode_literals |
7 | |
8 | import argparse |
9 | -import functools |
10 | import glob |
11 | import json |
12 | import os |
13 | import re |
14 | import sys |
15 | -import tempfile |
16 | #from launchpadlib.launchpad import Launchpad |
17 | |
18 | import apt_pkg |
19 | from cve_lib import (kernel_srcs, product_series, load_cve, PRODUCT_UBUNTU, all_releases, eol_releases, devel_release, release_parent, release_name, release_ppa, release_progenitor, needs_oval) |
20 | from kernel_lib import meta_kernels |
21 | import oval_lib |
22 | +import functools |
23 | import lpl_common |
24 | +import tempfile |
25 | |
26 | # cope with apt_pkg api changes. |
27 | if 'init_system' in dir(apt_pkg): |
28 | @@ -61,6 +61,9 @@ packages_to_ignore = ("-dev", "-doc", "-dbg", "-dbgsym", "-udeb", "-locale-") |
29 | |
30 | debug_level = 0 |
31 | |
32 | +package_cache = None |
33 | + |
34 | +cve_cache = {} |
35 | |
36 | def main(): |
37 | """ parse command line options and iterate through files to be processed |
38 | @@ -95,12 +98,14 @@ def main(): |
39 | help="report debugging information") |
40 | parser.add_argument('--usn-oval', action='store_true', |
41 | help='generates oval from the USN database') |
42 | + parser.add_argument('--pkg-oval', action='store_true', |
43 | + help='generates oval from the Package database') |
44 | parser.add_argument('--usn-db-dir', default='./', type=str, |
45 | help='location of USN database.json to process ' |
46 | '(default is ./)') |
47 | parser.add_argument('--usn-number', default=None, type=str, |
48 | help='if passed specifics a USN for the oval_usn generator') |
49 | - parser.add_argument('--usn-oval-release', default=None, type=str, |
50 | + parser.add_argument('--oval-release', default=None, type=str, |
51 | help='specifies a release to generate the oval usn') |
52 | parser.add_argument('--packages', nargs='+', action='store', default=None, |
53 | help='generates oval for specific packages. Only for ' |
54 | @@ -134,139 +139,33 @@ def main(): |
55 | |
56 | if args.usn_oval: |
57 | if args.oci: |
58 | - generate_oval_usn(args.output_dir, args.usn_number, args.usn_oval_release, |
59 | + generate_oval_usn(args.output_dir, args.usn_number, args.oval_release, |
60 | args.cve_prefix_dir, args.usn_db_dir, ociprefix, ocioutdir) |
61 | else: |
62 | - generate_oval_usn(args.output_dir, args.usn_number, args.usn_oval_release, |
63 | + generate_oval_usn(args.output_dir, args.usn_number, args.oval_release, |
64 | args.cve_prefix_dir, args.usn_db_dir) |
65 | |
66 | return |
67 | |
68 | - ovals = dict() |
69 | - for i in supported_releases: |
70 | - # we can have nested parent releases |
71 | - parent = release_progenitor(i) |
72 | - index = '{0}_dpkg'.format(i) |
73 | - ovals[index] = oval_lib.OvalGenerator(i, release_name(i), parent, warn, outdir, prefix='', oval_format='dpkg') |
74 | - ovals[index].add_release_applicability_definition() |
75 | - if args.oci: |
76 | - index = '{0}_oci'.format(i) |
77 | - ovals[index] = oval_lib.OvalGenerator(i, release_name(i), parent, warn, ocioutdir, prefix=ociprefix, oval_format='oci') |
78 | - ovals[index].add_release_applicability_definition() |
79 | - |
80 | - # set up cachefile |
81 | + cve_cache = {} |
82 | cache = PackageCache(args.pkg_cache, args.force_cache_reload) |
83 | |
84 | - # loop through all CVE data files |
85 | - files = [] |
86 | - for pathname in pathnames: |
87 | - files = files + glob.glob(os.path.join(args.cve_prefix_dir, pathname)) |
88 | - files.sort() |
89 | - |
90 | - pkg_filter = None |
91 | - if args.packages: |
92 | - pkg_filter = args.packages |
93 | - |
94 | - files_count = len(files) |
95 | - for i_file, filepath in enumerate(files): |
96 | - cve_data = parse_cve_file(filepath, cache, pkg_filter) |
97 | - |
98 | - # skip CVEs without packages for supported releases |
99 | - if not cve_data['packages']: |
100 | - if not args.no_progress: |
101 | - progress_bar(i_file + 1, files_count) |
102 | - continue |
103 | - |
104 | - for i in ovals: |
105 | - ovals[i].generate_cve_definition(cve_data) |
106 | - |
107 | - if not args.no_progress: |
108 | - progress_bar(i_file + 1, files_count) |
109 | - |
110 | - for i in ovals: |
111 | - ovals[i].write_to_file() |
112 | - |
113 | - cache.write_cache() |
114 | - |
115 | + if args.pkg_oval: |
116 | + releases = [args.oval_release] if args.oval_release else supported_releases |
117 | + for release in releases: |
118 | + if args.oci: |
119 | + generate_oval_package(release, outdir, args.cve_prefix_dir, cache, cve_cache, args.oci, args.no_progress, args.packages, pathnames, ociprefix, ocioutdir) |
120 | + else: |
121 | + generate_oval_package(release, outdir, args.cve_prefix_dir, cache, cve_cache, args.oci, args.no_progress, args.packages, pathnames) |
122 | + return |
123 | |
124 | -def parse_package_status(release, package, status_text, filepath, cache): |
125 | - """ parse ubuntu package status string format: |
126 | - <status code> (<version/notes>) |
127 | - outputs dictionary: { |
128 | - 'status' : '<not-applicable | unknown | vulnerable | fixed>', |
129 | - 'note' : '<description of the status>', |
130 | - 'fix-version' : '<version with issue fixed, if applicable>', |
131 | - 'bin-pkgs' : [] |
132 | - } """ |
133 | - |
134 | - # break out status code and detail |
135 | - status_sections = status_text.strip().split(' ', 1) |
136 | - code = status_sections[0].strip().lower() |
137 | - detail = status_sections[1].strip('()') if len(status_sections) > 1 else None |
138 | - |
139 | - status = {} |
140 | - note_end = " (note: '{0}').".format(detail) if detail else '.' |
141 | - if code != 'dne': |
142 | - if detail and detail[0].isdigit() and code in ['released', 'not-affected']: |
143 | - status['bin-pkgs'] = cache.get_binarypkgs(package, release, version=detail) |
144 | - else: |
145 | - status['bin-pkgs'] = cache.get_binarypkgs(package, release) |
146 | - |
147 | - if code == 'dne': |
148 | - status['status'] = 'not-applicable' |
149 | - status['note'] = \ |
150 | - " package does not exist in {0}{1}".format(release, note_end) |
151 | - elif code == 'ignored': |
152 | - status['status'] = 'vulnerable' |
153 | - status['note'] = ": while related to the CVE in some way, a decision has been made to ignore this issue{0}".format(note_end) |
154 | - elif code == 'not-affected': |
155 | - # check if there is a release version and if so, test for |
156 | - # package existence with that version |
157 | - if detail and detail[0].isdigit(): |
158 | - status['status'] = 'fixed' |
159 | - status['note'] = " package in {0}, is related to the CVE in some way and has been fixed{1}".format(release, note_end) |
160 | - status['fix-version'] = detail |
161 | - else: |
162 | - status['status'] = 'not-vulnerable' |
163 | - status['note'] = " package in {0}, while related to the CVE in some way, is not affected{1}".format(release, note_end) |
164 | - elif code == 'needed': |
165 | - status['status'] = 'vulnerable' |
166 | - status['note'] = \ |
167 | - " package in {0} is affected and needs fixing{1}".format(release, note_end) |
168 | - elif code == 'pending': |
169 | - # pending means that packages have been prepared and are in |
170 | - # -proposed or in a ppa somewhere, and should have a version |
171 | - # attached. If there is a version, test for package existence |
172 | - # with that version, otherwise mark as vulnerable |
173 | - if detail and detail[0].isdigit(): |
174 | - status['status'] = 'fixed' |
175 | - status['note'] = " package in {0} is affected. An update containing the fix has been completed and is pending publication{1}".format(release, note_end) |
176 | - status['fix-version'] = detail |
177 | - else: |
178 | - status['status'] = 'vulnerable' |
179 | - status['note'] = " package in {0} is affected. An update containing the fix has been completed and is pending publication{1}".format(release, note_end) |
180 | - elif code == 'deferred': |
181 | - status['status'] = 'vulnerable' |
182 | - status['note'] = " package in {0} is affected, but a decision has been made to defer addressing it{1}".format(release, note_end) |
183 | - elif code in ['released']: |
184 | - # if there isn't a release version, then just mark |
185 | - # as vulnerable to test for package existence |
186 | - if not detail: |
187 | - status['status'] = 'vulnerable' |
188 | - status['note'] = " package in {0} was vulnerable and has been fixed, but no release version available for it{1}".format(release, note_end) |
189 | - else: |
190 | - status['status'] = 'fixed' |
191 | - status['note'] = " package in {0} was vulnerable but has been fixed{1}".format(release, note_end) |
192 | - status['fix-version'] = detail |
193 | - elif code == 'needs-triage': |
194 | - status['status'] = 'vulnerable' |
195 | - status['note'] = " package in {0} is affected and may need fixing{1}".format(release, note_end) |
196 | + if args.oci: |
197 | + generate_oval_cve(args.output_dir, args.cve_prefix_dir, cache, args.oci, |
198 | + args.no_progress, args.packages, pathnames, ociprefix, ocioutdir) |
199 | else: |
200 | - warn('Unsupported status "{0}" in {1}_{2} in "{3}". Setting to "unknown".'.format(code, release, package, filepath)) |
201 | - status['status'] = 'unknown' |
202 | - status['note'] = " package in {0} has a vulnerability that is not known (status: '{1}'). It is pending evaluation{2}".format(release, code, note_end) |
203 | + generate_oval_cve(args.output_dir, args.cve_prefix_dir, cache, args.oci, |
204 | + args.no_progress, args.packages, pathnames) |
205 | |
206 | - return status |
207 | |
208 | |
209 | # given a status generated by parse_package_status(), duplicate it for a |
210 | @@ -354,10 +253,7 @@ def parse_cve_file(filepath, cache, pkg_filter=None): |
211 | if rel not in supported_releases: |
212 | continue |
213 | state, details = data['pkgs'][pkg][rel] |
214 | - status_line = state |
215 | - if len(details) > 0: |
216 | - status_line += ' (' + details + ')' |
217 | - packages[pkg]['Releases'][rel] = parse_package_status(rel, pkg, status_line, filepath, cache) |
218 | + packages[pkg]['Releases'][rel] = oval_lib.CVEPkgRelEntry.parse_package_status(rel, pkg, state, details, filepath, cache) |
219 | |
220 | # add supplemental packages; usually kernels only need this special case. |
221 | for package in [name for name in packages if name in kernel_srcs]: |
222 | @@ -458,7 +354,6 @@ def prepend_usn_to_id(usn_database, usn_id): |
223 | if re.search(r'^[0-9]+-[0-9]$', usn_id): |
224 | usn_database[usn_id]['id'] = 'USN-' + usn_id |
225 | |
226 | - |
227 | # Class to contain the binary package cache |
228 | class PackageCache(): |
229 | |
230 | @@ -466,6 +361,7 @@ class PackageCache(): |
231 | cache_updates = 0 |
232 | releases = dict() |
233 | unpublished_sources = dict() |
234 | + packages_to_ignore = ("-dev", "-doc", "-dbg", "-dbgsym", "-udeb", "-locale-") |
235 | |
236 | def __init__(self, cachefile='data_file.json', force_reload=False): |
237 | self.cachefile = cachefile |
238 | @@ -597,7 +493,7 @@ class PackageCache(): |
239 | if pname.startswith('linux') and not (i.binary_package_name).startswith('linux-image-'): |
240 | continue |
241 | # skip ignored packages, with exception of golang*-dev pkgs |
242 | - if (i.binary_package_name).startswith(('golang-go')) or not any(s in i.binary_package_name for s in packages_to_ignore): |
243 | + if (i.binary_package_name).startswith(('golang-go')) or not any(s in i.binary_package_name for s in self.packages_to_ignore): |
244 | binlist.append(i.binary_package_name) |
245 | |
246 | # save current pkgcache to local cache |
247 | @@ -684,6 +580,54 @@ def generate_oval_usn(outdir, usn, usn_release, cve_dir, usn_db_dir, ociprefix=N |
248 | |
249 | return True |
250 | |
251 | +def generate_oval_package(release, outdir, cve_prefix_dir, pkg_cache, cve_cache, oci, no_progress, packages, pathnames, ociprefix=None, ocioutdir=None): |
252 | + print(f'[*] Generating OVAL for packages in release {release}') |
253 | + ov = oval_lib.OvalGeneratorPkg(release, release_name(release), pathnames,packages, not no_progress,pkg_cache=pkg_cache, cve_cache=cve_cache, oval_format='oci' if oci else 'dpkg', outdir=outdir, cve_prefix_dir=cve_prefix_dir, prefix=ociprefix) |
254 | + ov.generate_oval() |
255 | + pkg_cache.write_cache() |
256 | + print(f'[X] Done generating OVAL for packages in release {release}') |
257 | |
258 | +def generate_oval_cve(outdir, cve_prefix_dir, cache, oci, no_progress, packages, pathnames, ociprefix=None, ocioutdir=None): |
259 | + ovals = dict() |
260 | + for i in supported_releases: |
261 | + # we can have nested parent releases |
262 | + parent = release_progenitor(i) |
263 | + index = '{0}_dpkg'.format(i) |
264 | + ovals[index] = oval_lib.OvalGeneratorCVE(i, release_name(i), parent, warn, outdir, prefix='', oval_format='dpkg') |
265 | + ovals[index].add_release_applicability_definition() |
266 | + if oci: |
267 | + index = '{0}_oci'.format(i) |
268 | + ovals[index] = oval_lib.OvalGeneratorCVE(i, release_name(i), parent, warn, ocioutdir, prefix=ociprefix, oval_format='oci') |
269 | + ovals[index].add_release_applicability_definition() |
270 | + |
271 | + # loop through all CVE data files |
272 | + files = [] |
273 | + for pathname in pathnames: |
274 | + files = files + glob.glob(os.path.join(cve_prefix_dir, pathname)) |
275 | + files.sort() |
276 | + |
277 | + pkg_filter = None |
278 | + if packages: |
279 | + pkg_filter = packages |
280 | + |
281 | + files_count = len(files) |
282 | + for i_file, filepath in enumerate(files): |
283 | + cve_data = parse_cve_file(filepath, cache, pkg_filter) |
284 | + # skip CVEs without packages for supported releases |
285 | + if not cve_data['packages']: |
286 | + if not no_progress: |
287 | + progress_bar(i_file + 1, files_count) |
288 | + continue |
289 | + |
290 | + for i in ovals: |
291 | + ovals[i].generate_cve_definition(cve_data) |
292 | + |
293 | + if not no_progress: |
294 | + progress_bar(i_file + 1, files_count) |
295 | + |
296 | + for i in ovals: |
297 | + ovals[i].write_to_file() |
298 | + |
299 | + cache.write_cache() |
300 | if __name__ == '__main__': |
301 | main() |
302 | diff --git a/scripts/oval_lib.py b/scripts/oval_lib.py |
303 | index f17f57d..9ced3d3 100644 |
304 | --- a/scripts/oval_lib.py |
305 | +++ b/scripts/oval_lib.py |
306 | @@ -27,11 +27,18 @@ import shutil |
307 | import sys |
308 | import tempfile |
309 | import collections |
310 | +import glob |
311 | +import xml.etree.cElementTree as etree |
312 | |
313 | -from cve_lib import load_cve, get_subproject_description, release_name, release_stamp |
314 | +from source_map import load |
315 | +import cve_lib |
316 | |
317 | from xml.sax.saxutils import escape |
318 | |
319 | +sources = {} |
320 | +source_map_binaries = {} |
321 | +debug_level = 0 |
322 | + |
323 | def recursive_rm(dirPath): |
324 | '''recursively remove directory''' |
325 | names = os.listdir(dirPath) |
326 | @@ -107,8 +114,1015 @@ def process_kernel_binaries(binaries, oval_format): |
327 | |
328 | return None |
329 | |
330 | +def debug(message): |
331 | + """ print a debuging message """ |
332 | + if debug_level > 0: |
333 | + sys.stdout.write('\rDEBUG: {0}\n'.format(message)) |
334 | |
335 | class OvalGenerator: |
336 | + supported_oval_elements = ('definition', 'test', 'object', 'state', 'variable') |
337 | + generator_version = '1.1' |
338 | + oval_schema_version = '5.11.1' |
339 | + def __init__(self, release, release_name, parent = None, warn_method=False, outdir='./', prefix='', oval_format='dpkg') -> None: |
340 | + self.release = release |
341 | + # e.g. codename for trusty/esm should be trusty |
342 | + self.release_codename = parent if parent else self.release.replace('/', '_') |
343 | + self.release_name = release_name |
344 | + #self.warn = warn_method or self.warn |
345 | + self.tmpdir = tempfile.mkdtemp(prefix='oval_lib-') |
346 | + self.output_dir = outdir |
347 | + self.oval_format = oval_format |
348 | + self.output_filepath = \ |
349 | + '{0}com.ubuntu.{1}.cve.oval.xml'.format(prefix, self.release.replace('/', '_')) |
350 | + self.ns = 'oval:com.ubuntu.{0}'.format(self.release_codename) |
351 | + self.id = 100 |
352 | + self.host_def_id = self.id |
353 | + self.release_applicability_definition_id = '{0}:def:{1}0'.format(self.ns, self.id) |
354 | + |
355 | + def _add_structure(self, root) -> None: |
356 | + structure = {} |
357 | + for element in self.supported_oval_elements: |
358 | + structure_element = element + 's' |
359 | + etree.SubElement(root, structure_element) |
360 | + |
361 | + return structure |
362 | + |
363 | + def _get_root_element(self, type) -> etree.Element: |
364 | + oval_timestamp = datetime.now(tz=timezone.utc).strftime( |
365 | + '%Y-%m-%dT%H:%M:%S') |
366 | + |
367 | + root_element = etree.Element("oval_definitions", attrib= { |
368 | + "xmlns":"http://oval.mitre.org/XMLSchema/oval-definitions-5", |
369 | + "xmlns:ind-def":"http://oval.mitre.org/XMLSchema/oval-definitions-5#independent", |
370 | + "xmlns:oval":"http://oval.mitre.org/XMLSchema/oval-common-5", |
371 | + "xmlns:unix-def":"http://oval.mitre.org/XMLSchema/oval-definitions-5#unix", |
372 | + "xmlns:linux-def":"http://oval.mitre.org/XMLSchema/oval-definitions-5#linux", |
373 | + "xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance" , |
374 | + "xsi:schemaLocation":"http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#independent independent-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#macos linux-definitions-schema.xsd" |
375 | + }) |
376 | + |
377 | + generator = etree.SubElement(root_element, "generator") |
378 | + product_name = etree.SubElement(generator, "oval:product_name") |
379 | + product_version = etree.SubElement(generator, "oval:product_version") |
380 | + schema_version = etree.SubElement(generator, "oval:schema_version") |
381 | + timestamp = etree.SubElement(generator, "oval:timestamp") |
382 | + |
383 | + product_name.text = f"Canonical {type} OVAL Generator" |
384 | + product_version.text = self.generator_version |
385 | + schema_version.text = self.oval_schema_version |
386 | + timestamp.text = oval_timestamp |
387 | + |
388 | + xml_tree = etree.ElementTree(root_element) |
389 | + return xml_tree, root_element |
390 | + |
391 | + def _add_release_checks(self, root_element) -> None: |
392 | + rel_definition = self._create_release_definition() |
393 | + rel_family_test, rel_test = self._create_release_test() |
394 | + rel_family_obj, rel_obj = self._create_release_object() |
395 | + rel_family_state, rel_state = self._create_release_state() |
396 | + |
397 | + definitions = root_element.find("definitions") |
398 | + tests = root_element.find("tests") |
399 | + objects = root_element.find("objects") |
400 | + states = root_element.find("states") |
401 | + |
402 | + definitions.append(rel_definition) |
403 | + tests.append(rel_family_test) |
404 | + tests.append(rel_test) |
405 | + objects.append(rel_family_obj) |
406 | + objects.append(rel_obj) |
407 | + states.append(rel_family_state) |
408 | + states.append(rel_state) |
409 | + |
410 | + |
411 | + def _create_release_definition(self) -> etree.Element: |
412 | + if self.oval_format == 'dpkg': |
413 | + definition = etree.Element("definition") |
414 | + definition.set("class", "inventory") |
415 | + definition.set("id", f'{self.ns}:def:{self.id}') |
416 | + definition.set("version", "1") |
417 | + |
418 | + # Metadata tag |
419 | + metadata = etree.Element("metadata") |
420 | + title = etree.SubElement(metadata, "title") |
421 | + etree.SubElement(metadata, "description") |
422 | + title.text = f"Check that {self.release_name} ({self.release_codename}) is installed." |
423 | + |
424 | + # Criteria tag |
425 | + criteria = etree.Element("criteria") |
426 | + criterion_unix = etree.SubElement(criteria, "criterion") |
427 | + criterion_rel = etree.SubElement(criteria, "criterion") |
428 | + |
429 | + |
430 | + criterion_unix.set("test_ref", f'{self.ns}:tst:{self.id}') |
431 | + criterion_unix.set("comment", "The host is part of the unix family.") |
432 | + |
433 | + criterion_rel.set("test_ref", f'{self.ns}:tst:{self.id+1}') |
434 | + criterion_rel.set("comment", f"The host is running Ubuntu {self.release_codename}") |
435 | + |
436 | + |
437 | + definition.append(metadata) |
438 | + definition.append(criteria) |
439 | + else: |
440 | + definition = etree.Element() |
441 | + |
442 | + return definition |
443 | + |
444 | + def _create_release_test(self) -> tuple[etree.Element, etree.Element]: |
445 | + if self.oval_format == 'dpkg': |
446 | + family_test = etree.Element("ind-def:family_test", attrib={ |
447 | + "id": f'{self.ns}:tst:{self.id}', |
448 | + "check":"at least one", |
449 | + "check_existence":"at_least_one_exists", |
450 | + "version":"1", |
451 | + "comment":"Is the host part of the unix family?" |
452 | + }) |
453 | + |
454 | + family_test_obj = etree.SubElement(family_test, "ind-def:object") |
455 | + family_test_state = etree.SubElement(family_test, "ind-def:state") |
456 | + family_test_obj.set("object_ref", f'{self.ns}:obj:{self.id}') |
457 | + family_test_state.set("state_ref", f'{self.ns}:ste:{self.id}') |
458 | + |
459 | + textfilecontent54_test = etree.Element("ind-def:textfilecontent54_test", attrib={ |
460 | + "id": f'{self.ns}:tst:{self.id+1}', |
461 | + "check":"at least one", |
462 | + "check_existence":"at_least_one_exists", |
463 | + "version":"1", |
464 | + "comment":f"Is the host running Ubuntu {self.release_codename}?" |
465 | + }) |
466 | + |
467 | + textfc54_test_obj = etree.SubElement(textfilecontent54_test, "ind-def:object") |
468 | + textfc54_test_state = etree.SubElement(textfilecontent54_test, "ind-def:state") |
469 | + textfc54_test_obj.set("object_ref", f'{self.ns}:obj:{self.id+1}') |
470 | + textfc54_test_state.set("state_ref", f'{self.ns}:ste:{self.id+1}') |
471 | + |
472 | + else: |
473 | + family_test = etree.Element() |
474 | + textfilecontent54_test = etree.Element() |
475 | + |
476 | + return family_test, textfilecontent54_test |
477 | + |
478 | + def _create_release_object(self) -> tuple[etree.Element, etree.Element]: |
479 | + if self.oval_format == 'dpkg': |
480 | + family_object = etree.Element("ind-def:family_object", |
481 | + attrib={ |
482 | + 'id' : f"{self.ns}:obj:{self.id}", |
483 | + 'version': "1", |
484 | + "comment": "The singleton family object." |
485 | + }) |
486 | + |
487 | + object = etree.Element("ind-def:textfilecontent54_object", |
488 | + attrib={ |
489 | + 'id' : f"{self.ns}:obj:{self.id+1}", |
490 | + 'version': "1", |
491 | + "comment": f"The singleton {self.release_codename} object." |
492 | + }) |
493 | + filepath = etree.SubElement(object, "ind-def:filepath") |
494 | + pattern = etree.SubElement(object, "ind-def:pattern",attrib={"operation": "pattern match"}) |
495 | + instance = etree.SubElement(object, "ind-def:instance",attrib={"datatype": "int"}) |
496 | + |
497 | + filepath.text = "/etc/lsb-release" |
498 | + pattern.text = "^[\s\S]*DISTRIB_CODENAME=([a-z]+)$" |
499 | + instance.text = "1" |
500 | + else: |
501 | + family_object = etree.Element("") |
502 | + object = etree.Element("") |
503 | + |
504 | + return family_object, object |
505 | + |
506 | + def _create_release_state(self) -> tuple[etree.Element, etree.Element]: |
507 | + if self.oval_format == 'dpkg': |
508 | + |
509 | + family_state= etree.Element("ind-def:family_state", |
510 | + attrib={ |
511 | + 'id' : f"{self.ns}:ste:{self.id}", |
512 | + 'version': "1", |
513 | + "comment": "The singleton family state." |
514 | + }) |
515 | + |
516 | + state = etree.Element("ind-def:textfilecontent54_state", |
517 | + attrib={ |
518 | + 'id' : f"{self.ns}:ste:{self.id+1}", |
519 | + 'version': "1", |
520 | + "comment": f"The singleton {self.release_codename} state." |
521 | + }) |
522 | + |
523 | + family = etree.SubElement(family_state, "ind-def:family") |
524 | + subexpression = etree.SubElement(state, "ind-def:subexpression") |
525 | + |
526 | + family.text = "unix" |
527 | + subexpression.text = cve_lib.product_series(self.release)[1] |
528 | + else: |
529 | + family_state = etree.Element() |
530 | + state = etree.Element() |
531 | + |
532 | + return family_state, state |
533 | + |
534 | +class CVEPkgRelEntry: |
535 | + def __init__(self, pkg, cve, status, note) -> None: |
536 | + self.pkg = pkg |
537 | + self.cve = cve |
538 | + self.orig_status = status |
539 | + self.orig_note = note |
540 | + cve_info = CVEPkgRelEntry.parse_package_status(pkg.rel, pkg.name, status, note, cve.number, None) |
541 | + |
542 | + self.note = cve_info['note'] |
543 | + self.status = cve_info['status'] |
544 | + self.fixed_version = cve_info['fix-version'] if self.status == 'fixed' else None |
545 | + |
546 | + @staticmethod |
547 | + def parse_package_status(release, package, status_text, note, filepath, cache): |
548 | + """ parse ubuntu package status string format: |
549 | + <status code> (<version/notes>) |
550 | + outputs dictionary: { |
551 | + 'status' : '<not-applicable | unknown | vulnerable | fixed>', |
552 | + 'note' : '<description of the status>', |
553 | + 'fix-version' : '<version with issue fixed, if applicable>', |
554 | + 'bin-pkgs' : [] |
555 | + } """ |
556 | + |
557 | + # TODO fix for CVE Generator |
558 | + |
559 | + # break out status code and detail |
560 | + code = status_text.lower() |
561 | + detail = note.strip('()') if note else None |
562 | + status = {} |
563 | + |
564 | + if cache and code != 'dne': |
565 | + if detail and detail[0].isdigit() and code in ['released', 'not-affected']: |
566 | + status['bin-pkgs'] = cache.get_binarypkgs(package, release, version=detail) |
567 | + else: |
568 | + status['bin-pkgs'] = cache.get_binarypkgs(package, release) |
569 | + |
570 | + note_end = " (note: '{0}').".format(detail) if detail else '.' |
571 | + if code == 'dne': |
572 | + status['status'] = 'not-applicable' |
573 | + status['note'] = \ |
574 | + " package does not exist in {0}{1}".format(release, note_end) |
575 | + elif code == 'ignored': |
576 | + status['status'] = 'vulnerable' |
577 | + status['note'] = ": while related to the CVE in some way, a decision has been made to ignore this issue{0}".format(note_end) |
578 | + elif code == 'not-affected': |
579 | + # check if there is a release version and if so, test for |
580 | + # package existence with that version |
581 | + if detail and detail[0].isdigit(): |
582 | + status['status'] = 'fixed' |
583 | + status['note'] = " package in {0}, is related to the CVE in some way and has been fixed{1}".format(release, note_end) |
584 | + status['fix-version'] = detail |
585 | + else: |
586 | + status['status'] = 'not-vulnerable' |
587 | + status['note'] = " package in {0}, while related to the CVE in some way, is not affected{1}".format(release, note_end) |
588 | + elif code == 'needed': |
589 | + status['status'] = 'vulnerable' |
590 | + status['note'] = \ |
591 | + " package in {0} is affected and needs fixing{1}".format(release, note_end) |
592 | + elif code == 'pending': |
593 | + # pending means that packages have been prepared and are in |
594 | + # -proposed or in a ppa somewhere, and should have a version |
595 | + # attached. If there is a version, test for package existence |
596 | + # with that version, otherwise mark as vulnerable |
597 | + if detail and detail[0].isdigit(): |
598 | + status['status'] = 'fixed' |
599 | + status['note'] = " package in {0} is affected. An update containing the fix has been completed and is pending publication{1}".format(release, note_end) |
600 | + status['fix-version'] = detail |
601 | + else: |
602 | + status['status'] = 'vulnerable' |
603 | + status['note'] = " package in {0} is affected. An update containing the fix has been completed and is pending publication{1}".format(release, note_end) |
604 | + elif code == 'deferred': |
605 | + status['status'] = 'vulnerable' |
606 | + status['note'] = " package in {0} is affected, but a decision has been made to defer addressing it{1}".format(release, note_end) |
607 | + elif code in ['released']: |
608 | + # if there isn't a release version, then just mark |
609 | + # as vulnerable to test for package existence |
610 | + if not detail: |
611 | + status['status'] = 'vulnerable' |
612 | + status['note'] = " package in {0} was vulnerable and has been fixed, but no release version available for it{1}".format(release, note_end) |
613 | + else: |
614 | + status['status'] = 'fixed' |
615 | + status['note'] = " package in {0} was vulnerable but has been fixed{1}".format(release, note_end) |
616 | + status['fix-version'] = detail |
617 | + elif code == 'needs-triage': |
618 | + status['status'] = 'vulnerable' |
619 | + status['note'] = " package in {0} is affected and may need fixing{1}".format(release, note_end) |
620 | + else: |
621 | + # TODO LOGGIN |
622 | + print('Unsupported status "{0}" in {1}_{2} in "{3}". Setting to "unknown".'.format(code, release, package, filepath)) |
623 | + status['status'] = 'unknown' |
624 | + status['note'] = " package in {0} has a vulnerability that is not known (status: '{1}'). It is pending evaluation{2}".format(release, code, note_end) |
625 | + |
626 | + return status |
627 | + |
628 | + def __str__(self) -> str: |
629 | + return f'{str(self.pkg)}:{self.status} {self.fixed_version}' |
630 | + |
631 | +class CVE: |
632 | + def __init__(self, number, info, pkgs=[]) -> None: |
633 | + self.number = number |
634 | + self.description = info['Description'] |
635 | + self.pkg_rel_entries = {} |
636 | + self.pkgs = pkgs |
637 | + |
638 | + def add_pkg(self, pkg_object, state, note): |
639 | + cve_pkg_entry = CVEPkgRelEntry(pkg_object, self, state, note) |
640 | + self.pkg_rel_entries[Package.get_unique_id(pkg_object.name, pkg_object.rel)] = cve_pkg_entry |
641 | + self.pkgs.append(pkg_object) |
642 | + |
643 | + def __str__(self) -> str: |
644 | + return self.number |
645 | + |
646 | + def __repr__(self): |
647 | + return self.__str__() |
648 | + |
649 | +class Package: |
650 | + def __init__(self, pkgname, rel, binaries, version): |
651 | + self.name = pkgname |
652 | + self.rel = rel |
653 | + self.description = cve_lib.lookup_package_override_description(pkgname) |
654 | + |
655 | + if not self.description: |
656 | + if 'description' in sources[rel][pkgname]: |
657 | + self.description = sources[rel][pkgname]['description'] |
658 | + elif pkgname in source_map_binaries[rel] and \ |
659 | + 'description' in source_map_binaries[rel][pkgname]: |
660 | + self.description = source_map_binaries[rel][pkgname]['description'] |
661 | + else: |
662 | + # Get first description found |
663 | + if 'binaries' in sources[self.rel][self.name]: |
664 | + for binary in sources[self.rel][self.name]['binaries']: |
665 | + if binary in source_map_binaries[self.rel] and 'description' in source_map_binaries[self.rel][binary]: |
666 | + self.description = source_map_binaries[self.rel][binary]["description"] |
667 | + break |
668 | + |
669 | + self.section = sources[rel][pkgname]['section'] |
670 | + self.version = version |
671 | + self.binaries = binaries if binaries else [] |
672 | + self.cves = [] |
673 | + |
674 | + @staticmethod |
675 | + def get_unique_id(name, rel): |
676 | + return f'{name}/{rel}' |
677 | + |
678 | + def add_cve(self, cve) -> None: |
679 | + self.cves.append(cve) |
680 | + |
681 | + def __str__(self) -> str: |
682 | + return f"{self.name}/{self.rel}" |
683 | + |
684 | + def __repr__(self): |
685 | + return self.__str__() |
686 | + |
687 | +class OvalGeneratorPkg(OvalGenerator): |
688 | + def __init__(self, release, release_name, cve_paths, packages, progress, pkg_cache, cve_cache=None, cve_prefix_dir=None, parent=None, warn_method=False, outdir='./', prefix='', oval_format='dpkg') -> None: |
689 | + super().__init__(release, release_name, parent, warn_method, outdir, prefix, oval_format) |
690 | + ### |
691 | + # ID schema: 2204|00001|0001 |
692 | + # * The first four digits are the ubuntu release number |
693 | + # * The next 5 digits is # just a package counter, we increase it for each definition |
694 | + # * The last 4 digits is a counter for the criterion |
695 | + ### |
696 | + release_code = int(release_name.split(' ')[1].replace('.', '')) if release not in cve_lib.external_releases else 1111 |
697 | + self.definition_id = release_code * 10 ** 10 |
698 | + self.definition_step = 1 * 10 ** 5 |
699 | + self.criterion_step = 10 |
700 | + self.progress = progress |
701 | + self.cve_cache = cve_cache |
702 | + self.pkg_cache = pkg_cache |
703 | + self.cve_paths = cve_paths |
704 | + self.packages = self._load_pkgs(cve_prefix_dir, packages) |
705 | + |
706 | + def _generate_advisory(self, package: Package) -> etree.Element: |
707 | + advisory = etree.Element("advisory") |
708 | + rights = etree.SubElement(advisory, "rights") |
709 | + component = etree.SubElement(advisory, "component") |
710 | + version = etree.SubElement(advisory, "current_version") |
711 | + |
712 | + rights.text = f"Copyright (C) {datetime.now().year} Canonical Ltd." |
713 | + component.text = package.section |
714 | + version.text = package.version |
715 | + |
716 | + return advisory |
717 | + |
718 | + def _generate_metadata(self, package: Package) -> etree.Element: |
719 | + metadata = etree.Element("metadata") |
720 | + title = etree.SubElement(metadata, "title") |
721 | + reference = self._generate_reference(package) |
722 | + advisory = self._generate_advisory(package) |
723 | + metadata.append(reference) |
724 | + description = etree.SubElement(metadata, "description") |
725 | + affected = etree.SubElement(metadata, "affected", attrib = {"family": "unix"}) |
726 | + platform = etree.SubElement(affected, "platform") |
727 | + metadata.append(advisory) |
728 | + |
729 | + platform.text = self.release_name |
730 | + title.text = package.name |
731 | + description.text = package.description |
732 | + |
733 | + return metadata |
734 | + |
735 | + def _generate_criteria(self) -> etree.Element: |
736 | + criteria = etree.Element("criteria") |
737 | + if self.oval_format == 'dpkg': |
738 | + extend_definition = etree.SubElement(criteria, "extend_definition") |
739 | + |
740 | + extend_definition.set("definition_ref", f"{self.ns}:def:{self.host_def_id}") |
741 | + extend_definition.set("comment", f"{self.release_name} is installed.") |
742 | + extend_definition.set("applicability_check", "true") |
743 | + |
744 | + return criteria |
745 | + |
746 | + # Element generators |
747 | + def _generate_reference(self, package) -> etree.Element: |
748 | + reference = etree.Element("reference", attrib={ |
749 | + "source": "Package", |
750 | + "ref_id": package.name, |
751 | + "ref_url": f'https://launchpad.net/ubuntu/+source/{package.name}' |
752 | + }) |
753 | + |
754 | + return reference |
755 | + |
756 | + def _generate_definition_object(self, package) -> None: |
757 | + id = f"{self.ns}:def:{self.definition_id}" |
758 | + definition = etree.Element("definition") |
759 | + definition.set("class", "vulnerability") |
760 | + definition.set("id", id) |
761 | + definition.set("version", "1") |
762 | + |
763 | + metadata = self._generate_metadata(package) |
764 | + criteria = self._generate_criteria() |
765 | + definition.append(metadata) |
766 | + definition.append(criteria) |
767 | + return definition |
768 | + |
769 | + def _generate_var_object(self, comment, id, binaries) -> etree.Element: |
770 | + var = etree.Element("constant_variable", |
771 | + attrib={ |
772 | + 'id' : f"{self.ns}:var:{id}", |
773 | + 'version': "1", |
774 | + "datatype": "string", |
775 | + "comment": comment |
776 | + }) |
777 | + |
778 | + for binary in binaries: |
779 | + item = etree.SubElement(var, "value") |
780 | + item.text = binary |
781 | + |
782 | + return var |
783 | + |
784 | + def _generate_object_object(self, comment, id, var_id) -> etree.Element: |
785 | + if self.oval_format == 'dpkg': |
786 | + object = etree.Element("linux-def:dpkginfo_object", |
787 | + attrib={ |
788 | + 'id' : f"{self.ns}:obj:{id}", |
789 | + 'version': "1", |
790 | + "comment": comment |
791 | + }) |
792 | + |
793 | + etree.SubElement(object, "linux-def:name", attrib={ |
794 | + "var_ref": f"{self.ns}:var:{var_id}", |
795 | + "var_check": "at least one" |
796 | + }) |
797 | + elif self.oval_format == 'oci': |
798 | + object = etree.Element("ind-def:textfilecontent54_object", |
799 | + attrib={ |
800 | + 'id' : f"{self.ns}:obj:{id}", |
801 | + 'version': "1", |
802 | + "comment": comment |
803 | + }) |
804 | + path = etree.SubElement(object, 'ind-def:path') |
805 | + filename = etree.SubElement(object, 'ind-def:filename') |
806 | + etree.SubElement(object, "ind-def:pattern", attrib={ |
807 | + "operation": "pattern match", |
808 | + "datatype": "string", |
809 | + "var_ref": f"{self.ns}:var:{var_id}", |
810 | + "var_check": "at least one" |
811 | + }) |
812 | + instance = etree.SubElement(object, 'ind-def:instance', attrib={ |
813 | + "operation": "greater than or equal", |
814 | + "datatype": "int" |
815 | + }) |
816 | + path.text = '.' |
817 | + filename.text = 'manifest' |
818 | + instance.text = '1' |
819 | + return object |
820 | + |
821 | + def _generate_test_element(self, comment, id, create_state, type, obj_id = None, state_id=None) -> etree.Element: |
822 | + if type == 'pkg': |
823 | + if self.oval_format == 'dpkg': |
824 | + tag = 'dpkginfo_test' |
825 | + pre_tag = 'linux-def' |
826 | + elif self.oval_format == 'oci': |
827 | + tag = 'textfilecontent54_test' |
828 | + pre_tag = 'ind-def' |
829 | + else: |
830 | + ValueError() |
831 | + elif type == 'kernel': |
832 | + tag = 'variable_test' |
833 | + pre_tag = 'ind-def' |
834 | + |
835 | + test = etree.Element(f'{pre_tag}:{tag}', attrib={ |
836 | + "id": f"{self.ns}:tst:{id}", |
837 | + "version": "1", |
838 | + "check_existence": "at_least_one_exists", |
839 | + "check": "at least one", |
840 | + "comment": comment |
841 | + }) |
842 | + textfc54_test_obj = etree.SubElement(test, f"{pre_tag}:object") |
843 | + textfc54_test_obj.set("object_ref", f'{self.ns}:obj:{obj_id if obj_id else id}') |
844 | + |
845 | + if create_state: |
846 | + textfc54_test_state = etree.SubElement(test, f"{pre_tag}:state") |
847 | + textfc54_test_state.set("state_ref", f'{self.ns}:ste:{state_id if state_id else id}') |
848 | + |
849 | + return test |
850 | + |
851 | + def _generate_state_object(self, comment, id, version) -> None: |
852 | + if self.oval_format == 'dpkg': |
853 | + object = etree.Element("linux-def:dpkginfo_state", |
854 | + attrib={ |
855 | + 'id' : f"{self.ns}:ste:{id}", |
856 | + 'version': "1", |
857 | + "comment": comment |
858 | + }) |
859 | + |
860 | + version_check = etree.SubElement(object, "linux-def:evr", attrib={ |
861 | + "datatype": "debian_evr_string", |
862 | + "operation": "less than" |
863 | + }) |
864 | + |
865 | + version_check.text = f"0:{version}" |
866 | + elif self.oval_format == 'oci': |
867 | + object = etree.Element("ind-def:textfilecontent54_state", |
868 | + attrib={ |
869 | + 'id' : f"{self.ns}:ste:{id}", |
870 | + 'version': "1", |
871 | + "comment": comment |
872 | + }) |
873 | + |
874 | + version_check = etree.SubElement(object, "ind-def:subexpression", attrib={ |
875 | + "datatype": "debian_evr_string", |
876 | + "operation": "less than" |
877 | + }) |
878 | + |
879 | + version_check.text = f"0:{version}" |
880 | + else: |
881 | + ValueError(f"Format not {self.oval_format} not supported") |
882 | + |
883 | + return object |
884 | + |
885 | + def _generate_criterion_element(self, comment, id) -> etree.Element: |
886 | + criterion = etree.Element("criterion", attrib={ |
887 | + "test_ref": f"{self.ns}:tst:{id}", |
888 | + "comment": comment |
889 | + }) |
890 | + |
891 | + return criterion |
892 | + |
893 | + # Running kernel element generators |
894 | + def _add_running_kernel_checks(self, root_element): |
895 | + objects = root_element.find("objects") |
896 | + variables = root_element.find("variables") |
897 | + states = root_element.find("states") |
898 | + |
899 | + variable_local_kernel_check = self._generate_local_variable_kernel(self.definition_id, "Kernel version in evr format", self.definition_id) |
900 | + obj_running_kernel = self._generate_uname_object_element(self.definition_id) |
901 | + state_kernel_version = self._generate_state_kernel_element("Kernel check", self.definition_id, self.definition_id) |
902 | + |
903 | + objects.append(obj_running_kernel) |
904 | + variables.append(variable_local_kernel_check) |
905 | + states.append(state_kernel_version) |
906 | + |
907 | + def _generate_local_variable_kernel(self, id, comment, uname_obj_id): |
908 | + var = etree.Element("local_variable", |
909 | + attrib={ |
910 | + 'id' : f"{self.ns}:var:{id}", |
911 | + 'version': "1", |
912 | + "datatype": "debian_evr_string", |
913 | + "comment": comment |
914 | + }) |
915 | + concat = etree.SubElement(var, "concat") |
916 | + component = etree.SubElement(concat, "literal_component") |
917 | + regex = etree.SubElement(concat, "regex_capture", attrib={ |
918 | + "pattern": "^([\d|\.]+-\d+)[-|\w]+$" |
919 | + }) |
920 | + |
921 | + etree.SubElement(regex, "object_component", attrib={ |
922 | + "object_ref": f"{self.ns}:obj:{uname_obj_id}", |
923 | + "item_field": "os_release" |
924 | + }) |
925 | + |
926 | + component.text = "0:" |
927 | + |
928 | + return var |
929 | + |
930 | + def _generate_uname_object_element(self, id): |
931 | + object = etree.Element("unix-def:uname_object", |
932 | + attrib={ |
933 | + 'id' : f"{self.ns}:obj:{id}", |
934 | + 'version': "1", |
935 | + "comment": "The uname object." |
936 | + }) |
937 | + |
938 | + return object |
939 | + |
940 | + def _generate_uname_state_element(self, id, regex, comment): |
941 | + object = etree.Element("unix-def:uname_state", |
942 | + attrib={ |
943 | + 'id' : f"{self.ns}:ste:{id}", |
944 | + 'version': "1", |
945 | + "comment": comment |
946 | + }) |
947 | + |
948 | + version_check = etree.SubElement(object, "unix-def:os_release", attrib={ |
949 | + "operation": "pattern match" |
950 | + }) |
951 | + |
952 | + version_check.text = regex |
953 | + |
954 | + return object |
955 | + |
956 | + def _generate_variable_kernel_version(self, comment, id, version): |
957 | + var = etree.Element("constant_variable", |
958 | + attrib={ |
959 | + 'id' : f"{self.ns}:var:{id}", |
960 | + 'version': "1", |
961 | + "datatype": "debian_evr_string", |
962 | + "comment": comment |
963 | + }) |
964 | + |
965 | + item = etree.SubElement(var, "value") |
966 | + item.text = f"0:{version.rsplit('.', 1)[0]}" |
967 | + |
968 | + return var |
969 | + |
970 | + def _generate_test_element_running_kernel(self, id, comment, obj_id): |
971 | + test = etree.Element("unix-def:uname_test", attrib={ |
972 | + "id": f"{self.ns}:tst:{id}", |
973 | + "version": "1", |
974 | + "check": "at least one", |
975 | + "comment": comment |
976 | + }) |
977 | + |
978 | + textfc54_test_obj = etree.SubElement(test, "unix-def:object") |
979 | + textfc54_test_obj.set("object_ref", f'{self.ns}:obj:{obj_id}') |
980 | + |
981 | + textfc54_test_state = etree.SubElement(test, "unix-def:state") |
982 | + textfc54_test_state.set("state_ref", f'{self.ns}:ste:{id}') |
983 | + |
984 | + return test |
985 | + |
986 | + # Kernel elements generators |
987 | + def _generate_criteria_kernel(self, operator) -> etree.Element: |
988 | + return etree.Element("criteria", attrib={ |
989 | + "operator": operator |
990 | + }) |
991 | + |
992 | + def _generate_kernel_version_object_element(self, id, var_id) -> etree.Element: |
993 | + object = etree.Element("ind-def:variable_object", |
994 | + attrib={ |
995 | + 'id' : f"{self.ns}:obj:{id}", |
996 | + 'version': "1", |
997 | + }) |
998 | + |
999 | + var_ref = etree.SubElement(object, 'ind-def:var_ref') |
1000 | + var_ref.text = f"{self.ns}:var:{var_id}" |
1001 | + |
1002 | + return object |
1003 | + |
1004 | + def _generate_state_kernel_element(self, comment, id, var_id) -> None: |
1005 | + state = etree.Element("ind-def:variable_state", |
1006 | + attrib={ |
1007 | + 'id' : f"{self.ns}:ste:{id}", |
1008 | + 'version': "1", |
1009 | + "comment": comment |
1010 | + }) |
1011 | + |
1012 | + etree.SubElement(state, "ind-def:value", attrib={ |
1013 | + "datatype": "debian_evr_string", |
1014 | + "operation": "greater than", |
1015 | + "var_check": "at least one", |
1016 | + "var_ref": f"{self.ns}:var:{var_id}" |
1017 | + }) |
1018 | + |
1019 | + return state |
1020 | + |
1021 | + def _generate_kernel_package_elements(self, package: Package, root_element, running_kernel_check_id) -> etree.Element: |
1022 | + tests = root_element.find("tests") |
1023 | + states = root_element.find("states") |
1024 | + |
1025 | + comment_running_kernel = f'Is kernel {package.name} running?' |
1026 | + regex = process_kernel_binaries(package.binaries, self.oval_format) |
1027 | + |
1028 | + criterion_running_kernel = self._generate_criterion_element(comment_running_kernel, self.definition_id) |
1029 | + test_running_kernel = self._generate_test_element_running_kernel(self.definition_id, comment_running_kernel, running_kernel_check_id) |
1030 | + state_running_kernel = self._generate_uname_state_element(self.definition_id, regex, f"Regex match for kernel {package.name}") |
1031 | + |
1032 | + self.definition_id += self.criterion_step |
1033 | + |
1034 | + tests.append(test_running_kernel) |
1035 | + states.append(state_running_kernel) |
1036 | + |
1037 | + return criterion_running_kernel |
1038 | + |
1039 | + def _add_fixed_kernel_elements(self, cve: CVE, package: Package, package_rel_entry:CVEPkgRelEntry, root_element, running_kernel_id) -> etree.Element: |
1040 | + tests = root_element.find("tests") |
1041 | + objects = root_element.find("objects") |
1042 | + variables = root_element.find("variables") |
1043 | + |
1044 | + comment_version = f'Kernel {package.name} version comparison ({package_rel_entry.fixed_version})' |
1045 | + comment_criterion = f'({cve.number}) {package.name} {package_rel_entry.note}' |
1046 | + criterion_version = self._generate_criterion_element(comment_criterion, self.definition_id) |
1047 | + test_kernel_version = self._generate_test_element(comment_version, self.definition_id, True, 'kernel', state_id=running_kernel_id) |
1048 | + |
1049 | + obj_kernel_version = self._generate_kernel_version_object_element(self.definition_id, self.definition_id) |
1050 | + var_version_kernel = self._generate_variable_kernel_version(comment_version, self.definition_id, package_rel_entry.fixed_version) |
1051 | + |
1052 | + tests.append(test_kernel_version) |
1053 | + objects.append(obj_kernel_version) |
1054 | + variables.append(var_version_kernel) |
1055 | + |
1056 | + return criterion_version |
1057 | + |
1058 | + # General functions |
1059 | + def _increase_id(self, is_definition): |
1060 | + if is_definition: |
1061 | + self.definition_id += self.definition_step |
1062 | + clean_value = self.definition_step / 10 |
1063 | + self.definition_id = int(int(self.definition_id / clean_value) * clean_value) |
1064 | + else: |
1065 | + self.definition_id += self.criterion_step |
1066 | + |
1067 | + def _add_to_criteria(self, definition, element, depth=2, operator='OR'): |
1068 | + criteria = definition |
1069 | + for _ in range(depth): |
1070 | + prev_criteria = criteria |
1071 | + criteria = criteria.find('criteria') |
1072 | + if criteria == None: |
1073 | + criteria = etree.SubElement(prev_criteria, "criteria") |
1074 | + criteria.set("operator", operator) |
1075 | + |
1076 | + criteria.append(element) |
1077 | + |
1078 | + def _add_criterion(self, id, package_entry, cve, definition, depth=2) -> None: |
1079 | + criterion_note = f'({cve.number}) {package_entry.pkg.name}{package_entry.note}' |
1080 | + criterion = self._generate_criterion_element(criterion_note, id) |
1081 | + self._add_to_criteria(definition, criterion, depth) |
1082 | + |
1083 | + def _generate_vulnerable_elements(self, package, obj_id=None): |
1084 | + binary_keyword = 'binaries' if len(package.binaries) > 1 else 'binary' |
1085 | + test_note = f"Does the '{package.name}' package exist?" |
1086 | + object_note = f"The '{package.name}' package {binary_keyword}" |
1087 | + |
1088 | + test = self._generate_test_element(test_note, self.definition_id, False, 'pkg', obj_id=obj_id) |
1089 | + |
1090 | + if not obj_id: |
1091 | + object = self._generate_object_object(object_note, self.definition_id, self.definition_id) |
1092 | + |
1093 | + binaries = package.binaries |
1094 | + if self.oval_format == 'oci': |
1095 | + if is_kernel_binaries(package.binaries): |
1096 | + regex = process_kernel_binaries(package.binaries, 'oci') |
1097 | + binaries = [f'^{regex}(?::\w+|)\s+(.*)$\s+(.*)'] |
1098 | + else: |
1099 | + variable_values = '(?::\w+|)\s+(.*)$\s+(.*)' |
1100 | + |
1101 | + binaries = [] |
1102 | + for binary in package.binaries: |
1103 | + binaries.append(f'^{binary}{variable_values}') |
1104 | + var = self._generate_var_object(object_note, self.definition_id, binaries) |
1105 | + else: |
1106 | + object = None |
1107 | + var = None |
1108 | + return test, object, var |
1109 | + |
1110 | + def _generate_fixed_elements(self, package, pkg_rel_entry, obj_id=None): |
1111 | + binary_keyword = 'binaries' if len(package.binaries) > 1 else 'binary' |
1112 | + test_note = f"Does the '{package.name}' package exist and is the version less than '{pkg_rel_entry.fixed_version}'?" |
1113 | + object_note = f"The '{package.name}' package {binary_keyword}" |
1114 | + state_note = f"The package version is less than '{pkg_rel_entry.fixed_version}'" |
1115 | + |
1116 | + test = self._generate_test_element(test_note, self.definition_id, True, 'pkg', obj_id=obj_id) |
1117 | + if not obj_id: |
1118 | + object = self._generate_object_object(object_note, self.definition_id, self.definition_id) |
1119 | + |
1120 | + binaries = package.binaries |
1121 | + if self.oval_format == 'oci': |
1122 | + if is_kernel_binaries(package.binaries): |
1123 | + regex = process_kernel_binaries(package.binaries, 'oci') |
1124 | + binaries = [f'^{regex}(?::\w+|)\s+(.*)$\s+(.*)'] |
1125 | + else: |
1126 | + variable_values = '(?::\w+|)\s+(.*)$\s+(.*)' |
1127 | + |
1128 | + binaries = [] |
1129 | + for binary in package.binaries: |
1130 | + binaries.append(f'^{binary}{variable_values}') |
1131 | + |
1132 | + var = self._generate_var_object(object_note, self.definition_id, binaries) |
1133 | + else: |
1134 | + object = None |
1135 | + var = None |
1136 | + state = self._generate_state_object(state_note, self.definition_id, pkg_rel_entry.fixed_version) |
1137 | + |
1138 | + return test, object, var, state |
1139 | + |
1140 | + def _populate_pkg(self, package, root_element): |
1141 | + pkg_id = Package.get_unique_id(package.name, self.release) |
1142 | + tests = root_element.find("tests") |
1143 | + objects = root_element.find("objects") |
1144 | + variables = root_element.find("variables") |
1145 | + states = root_element.find("states") |
1146 | + |
1147 | + # Add package definition |
1148 | + definitions = root_element.find("definitions") |
1149 | + definition_element = self._generate_definition_object(package) |
1150 | + |
1151 | + # Control/cache variables |
1152 | + one_time_added_id = None |
1153 | + fixed_versions = {} |
1154 | + binaries_id = None |
1155 | + cve_added = False |
1156 | + |
1157 | + for cve in package.cves: |
1158 | + pkg_rel_entry = cve.pkg_rel_entries[pkg_id] |
1159 | + if pkg_rel_entry.status == 'vulnerable': |
1160 | + cve_added = True |
1161 | + if one_time_added_id: |
1162 | + self._add_criterion(one_time_added_id, pkg_rel_entry, cve, definition_element) |
1163 | + else: |
1164 | + self._add_criterion(self.definition_id, pkg_rel_entry, cve, definition_element) |
1165 | + |
1166 | + test, object, var = self._generate_vulnerable_elements(package, binaries_id) |
1167 | + tests.append(test) |
1168 | + |
1169 | + if not binaries_id: |
1170 | + objects.append(object) |
1171 | + variables.append(var) |
1172 | + binaries_id = self.definition_id |
1173 | + |
1174 | + one_time_added_id = self.definition_id |
1175 | + self._increase_id(is_definition=False) |
1176 | + elif pkg_rel_entry.status == 'fixed': |
1177 | + cve_added = True |
1178 | + |
1179 | + if pkg_rel_entry.fixed_version in fixed_versions: |
1180 | + self._add_criterion(fixed_versions[pkg_rel_entry.fixed_version], pkg_rel_entry, cve, definition_element) |
1181 | + else: |
1182 | + self._add_criterion(self.definition_id, pkg_rel_entry, cve, definition_element) |
1183 | + |
1184 | + test, object, var, state = self._generate_fixed_elements(package, pkg_rel_entry, binaries_id) |
1185 | + tests.append(test) |
1186 | + states.append(state) |
1187 | + |
1188 | + if not binaries_id: |
1189 | + objects.append(object) |
1190 | + variables.append(var) |
1191 | + binaries_id = self.definition_id |
1192 | + |
1193 | + fixed_versions[pkg_rel_entry.fixed_version] = self.definition_id |
1194 | + self._increase_id(is_definition=False) |
1195 | + |
1196 | + if cve_added: |
1197 | + definitions.append(definition_element) |
1198 | + |
1199 | + self._increase_id(is_definition=True) |
1200 | + |
1201 | + def _populate_kernel_pkg(self, package, root_element, running_kernel_id): |
1202 | + pkg_id = Package.get_unique_id(package.name, self.release) |
1203 | + tests = root_element.find("tests") |
1204 | + objects = root_element.find("objects") |
1205 | + variables = root_element.find("variables") |
1206 | + |
1207 | + # Add package definition |
1208 | + definitions = root_element.find("definitions") |
1209 | + definition_element = self._generate_definition_object(package) |
1210 | + |
1211 | + # Control/cache variables |
1212 | + one_time_added_id = None |
1213 | + fixed_versions = [] |
1214 | + binaries_id = None |
1215 | + cve_added = False |
1216 | + |
1217 | + # Generate one-time elements |
1218 | + kernel_criterion = self._generate_kernel_package_elements(package, root_element, running_kernel_id) |
1219 | + criteria = self._generate_criteria_kernel('OR') |
1220 | + |
1221 | + self._add_to_criteria(definition_element, kernel_criterion, operator='AND') |
1222 | + self._add_to_criteria(definition_element, criteria, operator='AND') |
1223 | + |
1224 | + for cve in package.cves: |
1225 | + pkg_rel_entry = cve.pkg_rel_entries[pkg_id] |
1226 | + if pkg_rel_entry.status == 'vulnerable': |
1227 | + cve_added = True |
1228 | + if one_time_added_id: |
1229 | + self._add_criterion(one_time_added_id, pkg_rel_entry, cve, definition_element, depth=3) |
1230 | + else: |
1231 | + self._add_criterion(self.definition_id, pkg_rel_entry, cve, definition_element, depth=3) |
1232 | + |
1233 | + test, object, var = self._generate_vulnerable_elements(package, binaries_id) |
1234 | + tests.append(test) |
1235 | + objects.append(object) |
1236 | + |
1237 | + if not binaries_id: |
1238 | + variables.append(var) |
1239 | + binaries_id = self.definition_id |
1240 | + |
1241 | + one_time_added_id = self.definition_id |
1242 | + self._increase_id(is_definition=False) |
1243 | + elif pkg_rel_entry.status == 'fixed': |
1244 | + cve_added = True |
1245 | + |
1246 | + if not pkg_rel_entry.fixed_version in fixed_versions: |
1247 | + kernel_version_criterion = self._add_fixed_kernel_elements(cve, package, pkg_rel_entry, root_element, running_kernel_id) |
1248 | + self._add_to_criteria(definition_element, kernel_version_criterion, depth=3) |
1249 | + fixed_versions.append(pkg_rel_entry.fixed_version) |
1250 | + self._increase_id(is_definition=False) |
1251 | + |
1252 | + if cve_added: |
1253 | + definitions.append(definition_element) |
1254 | + self._increase_id(is_definition=True) |
1255 | + |
1256 | + def _load_pkgs(self, cve_prefix_dir, packages_filter=None) -> None: |
1257 | + cve_lib.load_external_subprojects() |
1258 | + |
1259 | + cves = [] |
1260 | + for pathname in self.cve_paths: |
1261 | + cves = cves + glob.glob(os.path.join(cve_prefix_dir, pathname)) |
1262 | + cves.sort() |
1263 | + |
1264 | + packages = {} |
1265 | + sources[self.release] = load(releases=[self.release], skip_eol_releases=False)[self.release] |
1266 | + orig_name = cve_lib.get_orig_rel_name(self.release) |
1267 | + if '/' in orig_name: |
1268 | + orig_name = orig_name.split('/', maxsplit=1)[1] |
1269 | + source_map_binaries[self.release] = load(data_type='packages',releases=[orig_name], skip_eol_releases=False)[orig_name] \ |
1270 | + if self.release not in cve_lib.external_releases else {} |
1271 | + |
1272 | + i = 0 |
1273 | + for cve_path in cves: |
1274 | + cve_number = cve_path.rsplit('/', 1)[1] |
1275 | + i += 1 |
1276 | + |
1277 | + if self.progress: |
1278 | + print(f'[{i:5}/{len(cves)}] Processing {cve_number:18}', end='\r') |
1279 | + |
1280 | + if not cve_number in self.cve_cache: |
1281 | + self.cve_cache[cve_number] = cve_lib.load_cve(cve_path) |
1282 | + |
1283 | + info = self.cve_cache[cve_number] |
1284 | + cve_obj = CVE(cve_number, info) |
1285 | + |
1286 | + for pkg in info['pkgs']: |
1287 | + if packages_filter and pkg not in packages_filter: |
1288 | + continue |
1289 | + |
1290 | + if self.release in info['pkgs'][pkg] and \ |
1291 | + info['pkgs'][pkg][self.release][0] != 'DNE' and \ |
1292 | + pkg in sources[self.release]: |
1293 | + pkg_id = Package.get_unique_id(pkg, self.release) |
1294 | + if pkg_id not in packages: |
1295 | + binaries = self.pkg_cache.get_binarypkgs(pkg, self.release) |
1296 | + version = '' |
1297 | + if binaries: |
1298 | + version = self.pkg_cache.pkgcache[pkg]['Releases'][self.release]['source_version'] |
1299 | + pkg_obj = Package(pkg, self.release, binaries, version) |
1300 | + packages[pkg_id] = pkg_obj |
1301 | + |
1302 | + pkg_obj = packages[pkg_id] |
1303 | + pkg_obj.cves.append(cve_obj) |
1304 | + # add_pkg (pkg, status, note) |
1305 | + cve_obj.add_pkg(pkg_obj, info['pkgs'][pkg][self.release][0],info['pkgs'][pkg][self.release][1]) |
1306 | + |
1307 | + packages = dict(sorted(packages.items())) |
1308 | + print(' ' * 40, end='\r') |
1309 | + return packages |
1310 | + |
1311 | + def generate_oval(self) -> None: |
1312 | + xml_tree, root_element = self._get_root_element("Package") |
1313 | + self._add_structure(root_element) |
1314 | + |
1315 | + if self.oval_format == 'dpkg': |
1316 | + # One time kernel check |
1317 | + self._add_release_checks(root_element) |
1318 | + self._add_running_kernel_checks(root_element) |
1319 | + running_kernel_id = self.definition_id |
1320 | + self._increase_id(is_definition=True) |
1321 | + |
1322 | + for pkg in self.packages: |
1323 | + if len(self.packages[pkg].binaries) == 0: |
1324 | + continue |
1325 | + |
1326 | + if is_kernel_binaries(self.packages[pkg].binaries) and self.oval_format != 'oci': |
1327 | + self._populate_kernel_pkg(self.packages[pkg], root_element, running_kernel_id) |
1328 | + else: |
1329 | + self._populate_pkg(self.packages[pkg], root_element) |
1330 | + |
1331 | + etree.indent(xml_tree, level=0) |
1332 | + filename = f"com.ubuntu.{self.release_codename}.pkg.oval.xml" |
1333 | + if self.oval_format == 'oci': |
1334 | + filename = f'oci.{filename}' |
1335 | + xml_tree.write(os.path.join(self.output_dir, filename)) |
1336 | + return |
1337 | + |
1338 | +class OvalGeneratorCVE: |
1339 | supported_oval_elements = ('definition', 'test', 'object', 'state', |
1340 | 'variable') |
1341 | generator_version = '1.1' |
1342 | @@ -179,9 +1193,9 @@ class OvalGenerator: |
1343 | # prepare update instructions if package is fixed |
1344 | if pkg['status'] == 'fixed': |
1345 | if 'parent' in release_status: |
1346 | - product_description = get_subproject_description(release_status['parent']) |
1347 | + product_description = cve_lib.get_subproject_description(release_status['parent']) |
1348 | else: |
1349 | - product_description = get_subproject_description(release) |
1350 | + product_description = cve_lib.get_subproject_description(release) |
1351 | instruction = prepare_instructions(instruction, header['Candidate'], product_description, pkg) |
1352 | |
1353 | # if no packages for this release, then we're done |
1354 | @@ -407,6 +1421,7 @@ class OvalGenerator: |
1355 | package['note'] = package['name'] + package['note'] |
1356 | return {'id': self.id_unknown_test, 'comment': package['note']} |
1357 | |
1358 | + # TODO: xml lib |
1359 | def add_release_applicability_definition(self): |
1360 | """ add platform/release applicability OVAL definition for codename """ |
1361 | |
1362 | @@ -464,6 +1479,7 @@ class OvalGenerator: |
1363 | <ind-def:subexpression>{codename}</ind-def:subexpression> |
1364 | </ind-def:textfilecontent54_state>\n""".format(**mapping)) |
1365 | |
1366 | + # TODO: xml lib |
1367 | def get_package_object_id(self, name, bin_pkgs, id_base, version=1): |
1368 | """ create unique object for each package and return its OVAL id """ |
1369 | if not hasattr(self, 'package_objects'): |
1370 | @@ -532,6 +1548,7 @@ class OvalGenerator: |
1371 | |
1372 | return self.package_objects[key] |
1373 | |
1374 | + # TODO: xml lib |
1375 | def get_package_version_state_id(self, id_base, fix_version, version=1): |
1376 | """ create unique states for each version and return its OVAL id """ |
1377 | if not hasattr(self, 'package_version_states'): |
1378 | @@ -555,6 +1572,7 @@ class OvalGenerator: |
1379 | |
1380 | return self.package_version_states[key] |
1381 | |
1382 | + # TODO: xml lib |
1383 | def get_package_test_id(self, name, id_base, test_title, object_id, state_id=None, version=1, check_existence='at_least_one_exists'): |
1384 | """ create unique test for each parameter set and return its OVAL id """ |
1385 | if not hasattr(self, 'package_tests'): |
1386 | @@ -579,6 +1597,7 @@ class OvalGenerator: |
1387 | |
1388 | return self.package_tests[key] |
1389 | |
1390 | + # TODO: xml lib |
1391 | def get_running_kernel_object_id(self, id_base, var_id, version=1): |
1392 | """ creates a uname_object so we can use the value from uname -r for |
1393 | mainly two things: |
1394 | @@ -611,6 +1630,7 @@ class OvalGenerator: |
1395 | |
1396 | return (self.kernel_uname_obj_id, object_id_2) |
1397 | |
1398 | + # TODO: xml lib |
1399 | def get_running_kernel_state_id(self, uname_regex, id_base, var_id, version=1): |
1400 | """ create uname_state to compare the system uname to the affected kernel |
1401 | uname regex, allowing us to verify we are running the same major version |
1402 | @@ -643,6 +1663,7 @@ class OvalGenerator: |
1403 | |
1404 | return (self.uname_states[uname_regex], self.kernel_state_id) |
1405 | |
1406 | + # TODO: xml lib |
1407 | def get_running_kernel_variable_id(self, uname_regex, id_base, fixed_version, version=1): |
1408 | """ creates a local variable to store running kernel version in devian evr string""" |
1409 | if not hasattr(self, 'uname_variables'): |
1410 | @@ -675,6 +1696,7 @@ class OvalGenerator: |
1411 | |
1412 | return (self.uname_variables['local_variable'], var_id_2) |
1413 | |
1414 | + # TODO: xml lib |
1415 | def get_running_kernel_test_id(self, uname_regex, id_base, name, object_id, state_id, object_id_2, state_id_2, version=1): |
1416 | """ create uname test and return its OVAL id """ |
1417 | if not hasattr(self, 'uname_tests'): |
1418 | @@ -724,6 +1746,7 @@ class OvalGenerator: |
1419 | |
1420 | self.tmp[element].write(xml + '\n') |
1421 | |
1422 | + # TODO: xml lib |
1423 | def write_to_file(self): |
1424 | """ dequeue all elements into one OVAL definitions file and clean up """ |
1425 | if not hasattr(self, 'tmp'): |
1426 | @@ -792,7 +1815,6 @@ class OvalGenerator: |
1427 | """ print a warning message """ |
1428 | print('WARNING: {0}'.format(message)) |
1429 | |
1430 | - |
1431 | class OvalGeneratorUSN(): |
1432 | supported_oval_elements = ('definition', 'test', 'object', 'state', |
1433 | 'variable') |
1434 | @@ -851,6 +1873,7 @@ class OvalGeneratorUSN(): |
1435 | self.oval_structure['object'].write(oval_rel_struct['object']) |
1436 | self.oval_structure['state'].write(oval_rel_struct['state']) |
1437 | |
1438 | + # TODO: xml lib |
1439 | def create_release_definition(self): |
1440 | if self.oval_format == 'dpkg': |
1441 | mapping = { |
1442 | @@ -876,6 +1899,7 @@ class OvalGeneratorUSN(): |
1443 | |
1444 | return definition |
1445 | |
1446 | + # TODO: xml lib |
1447 | def create_release_test(self): |
1448 | if self.oval_format == 'dpkg': |
1449 | mapping = { |
1450 | @@ -895,6 +1919,7 @@ class OvalGeneratorUSN(): |
1451 | |
1452 | return test |
1453 | |
1454 | + # TODO: xml lib |
1455 | def create_release_object(self): |
1456 | if self.oval_format == 'dpkg': |
1457 | mapping = { |
1458 | @@ -914,6 +1939,7 @@ class OvalGeneratorUSN(): |
1459 | |
1460 | return _object |
1461 | |
1462 | + # TODO: xml lib |
1463 | def create_release_state(self): |
1464 | if self.oval_format == 'dpkg': |
1465 | mapping = { |
1466 | @@ -980,6 +2006,7 @@ class OvalGeneratorUSN(): |
1467 | if key[1] == max_severity][0][0] |
1468 | return usn_severity.capitalize() |
1469 | |
1470 | + # TODO: xml lib |
1471 | def create_usn_definition(self, usn_object, usn_number, id_base, test_refs, cve_dir, instructions): |
1472 | urls, cves_info = self.format_cves_info(usn_object['cves'], cve_dir) |
1473 | cve_references = self.create_cves_references(cves_info) |
1474 | @@ -1058,6 +2085,7 @@ class OvalGeneratorUSN(): |
1475 | |
1476 | return definition |
1477 | |
1478 | + # TODO: xml lib |
1479 | def create_usn_test(self, test_ref): |
1480 | mapping = { |
1481 | 'id': test_ref['testref_id'], |
1482 | @@ -1113,6 +2141,7 @@ class OvalGeneratorUSN(): |
1483 | |
1484 | return test |
1485 | |
1486 | + # TODO: xml lib |
1487 | def create_usn_object(self, test_ref): |
1488 | mapping = { |
1489 | 'id': test_ref['testref_id'], |
1490 | @@ -1168,7 +2197,8 @@ class OvalGeneratorUSN(): |
1491 | </ind:textfilecontent54_object>""".format(**mapping) |
1492 | |
1493 | return _object |
1494 | - |
1495 | + |
1496 | + # TODO: xml lib |
1497 | def create_usn_state(self, test_ref): |
1498 | mapping = { |
1499 | 'id': test_ref['testref_id'], |
1500 | @@ -1229,7 +2259,8 @@ class OvalGeneratorUSN(): |
1501 | </ind:textfilecontent54_state>""".format(**mapping) |
1502 | |
1503 | return state |
1504 | - |
1505 | + |
1506 | + # TODO: xml lib |
1507 | def create_usn_variable(self, test_ref): |
1508 | binaries_list = test_ref['pkgs'] |
1509 | |
1510 | @@ -1300,7 +2331,7 @@ class OvalGeneratorUSN(): |
1511 | else: |
1512 | return None |
1513 | |
1514 | - cve_object = load_cve(cve_file_path) |
1515 | + cve_object = cve_lib.load_cve(cve_file_path) |
1516 | if not cve_object: |
1517 | return None |
1518 | |
1519 | @@ -1407,23 +2438,23 @@ class OvalGeneratorUSN(): |
1520 | break |
1521 | except KeyError: |
1522 | # trusty usns don't have pocket, so try to check on timestamp |
1523 | - if self.release_codename == 'trusty' and stamp >= release_stamp('esm/trusty'): |
1524 | + if self.release_codename == 'trusty' and stamp >= cve_lib.release_stamp('esm/trusty'): |
1525 | self.pocket = 'esm' |
1526 | else: |
1527 | self.pocket = 'security' |
1528 | break |
1529 | |
1530 | if self.pocket in ['security', 'updates', 'livepatch']: |
1531 | - self.release_name = release_name(self.release_codename) |
1532 | - self.product_description = get_subproject_description(self.release_codename) |
1533 | + self.release_name = cve_lib.release_name(self.release_codename) |
1534 | + self.product_description = cve_lib.get_subproject_description(self.release_codename) |
1535 | else: |
1536 | # deal with trusty's weirdness |
1537 | if self.release_codename == 'trusty': |
1538 | - self.release_name = release_name('esm/' + self.release_codename) |
1539 | - self.product_description = get_subproject_description('esm/' + self.release_codename) |
1540 | + self.release_name = cve_lib.release_name('esm/' + self.release_codename) |
1541 | + self.product_description = cve_lib.get_subproject_description('esm/' + self.release_codename) |
1542 | else: |
1543 | - self.release_name = release_name(self.pocket + '/' + self.release_codename) |
1544 | - self.product_description = get_subproject_description(self.pocket + '/' + self.release_codename) |
1545 | + self.release_name = cve_lib.release_name(self.pocket + '/' + self.release_codename) |
1546 | + self.product_description = cve_lib.get_subproject_description(self.pocket + '/' + self.release_codename) |
1547 | |
1548 | def generate_usn_oval(self, usn_object, usn_number, cve_dir): |
1549 | if self.release_codename not in usn_object['releases'].keys(): |
1550 | @@ -1479,6 +2510,7 @@ class OvalGeneratorUSN(): |
1551 | if usn_variable: |
1552 | self.oval_structure['variable'].write(usn_variable) |
1553 | |
1554 | + # TODO: xml lib |
1555 | def write_oval_elements(self): |
1556 | """ write OVAL elements to .xml file w. OVAL header and footer """ |
1557 | for key in self.oval_structure: |
1558 | diff --git a/scripts/source_map.py b/scripts/source_map.py |
1559 | index e4d221e..4bb69d1 100755 |
1560 | --- a/scripts/source_map.py |
1561 | +++ b/scripts/source_map.py |
1562 | @@ -304,6 +304,8 @@ def load_sources_collection(item, map): |
1563 | pkg = parser.section['Package'] |
1564 | map.setdefault(release, dict()).setdefault(pkg, {'section': 'unset', 'version': '~', 'pocket': 'unset'}) |
1565 | map[release][pkg]['section'] = section |
1566 | + if 'Description' in parser.section: |
1567 | + map[release][pkg]['description'] = parser.section['Description'] |
1568 | if not pocket: |
1569 | map[release][pkg]['release_version'] = parser.section['Version'] |
1570 | if apt_pkg.version_compare(parser.section['Version'], map[release][pkg]['version']) > 0: |
1571 | @@ -322,6 +324,9 @@ def load_packages_collection(item, map): |
1572 | pkg = parser.section['Package'] |
1573 | map.setdefault(release, dict()).setdefault(pkg, {'section': 'unset', 'version': '~', 'pocket': 'unset'}) |
1574 | map[release][pkg]['section'] = section |
1575 | + if 'Description' in parser.section: |
1576 | + map[release][pkg]['description'] = parser.section['Description'] |
1577 | + |
1578 | if not pocket: |
1579 | map[release][pkg]['release_version'] = parser.section['Version'] |
1580 | if apt_pkg.version_compare(parser.section['Version'], map[release][pkg]['version']) > 0: |
Thanks for returning the PackageCache to generate-oval!
For others to understand, we discussed that we don't want this in oval_lib as we are expecting to at one point not have this cache generation inside OVAL generation, and for it to be its own script running separate, much like we do not generate USN database or LSN database during OVAL generation, but instead we just fetch the latest databases.
Known missing things in this PR:
1. In the <advisory> we don't have any reference to CVEs/USNs, this will come later in a next PR where we will add a <cve> field with CVSS score, and that will be add for all OVAL types.
2. For now we are using source_map to get package description, at one point we want to have this inside the package cache file.
Overall it looks good to me, I haven't had the time to test the code locally, but I did review the Package based OVAL you generated and we are going in the write direction. I believe we should add some tests in $UCT/test/, as we currently only have tests for USN OVAL. We should also do it for CVE OVAL.
I did notice a small difference on indentation when comparing your sample data and our CVE and USN data, we should work on getting all in the same style, so we can provide a more consistent diff. We can discuss more about it in another PR.