diff options
author | Marta Rybczynska <rybczynska@gmail.com> | 2022-03-29 14:54:31 +0200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2022-03-30 13:07:41 +0100 |
commit | 777f1d42b62ab482efa5a24600f4aeba1b156c64 (patch) | |
tree | 57f34b66df4db825abf4802101e689ca38abd2c0 /meta/classes/cve-check.bbclass | |
parent | bbdf96885dbd8c3f5e2e9f084571ca659a809016 (diff) | |
download | poky-777f1d42b62ab482efa5a24600f4aeba1b156c64.tar.gz |
cve-check: add json format
Add an option to output the CVE check in a JSON-based format.
This format is easier to parse in software than the original
text-based one and allows post-processing by other tools.
Output formats are now handed by CVE_CHECK_FORMAT_TEXT and
CVE_CHECK_FORMAT_JSON. Both of them are enabled by default.
The JSON output format gets generated in a similar way to the
text format with the exception of the manifest: appending to
JSON arrays requires parsing the file. Because of that we
first write JSON fragments and then assemble them in one pass
at the end.
(From OE-Core rev: df567de36ae5964bee433ebb97e8bf702034994a)
Signed-off-by: Marta Rybczynska <marta.rybczynska@huawei.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/classes/cve-check.bbclass')
-rw-r--r-- | meta/classes/cve-check.bbclass | 144 |
1 files changed, 143 insertions, 1 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass index dfad10c22b..f574f5daa4 100644 --- a/meta/classes/cve-check.bbclass +++ b/meta/classes/cve-check.bbclass | |||
@@ -34,15 +34,27 @@ CVE_CHECK_TMP_FILE ?= "${TMPDIR}/cve_check" | |||
34 | CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve" | 34 | CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve" |
35 | CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary" | 35 | CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary" |
36 | CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}" | 36 | CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}" |
37 | CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json" | ||
38 | CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt" | ||
39 | |||
40 | CVE_CHECK_LOG_JSON ?= "${T}/cve.json" | ||
37 | 41 | ||
38 | CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve" | 42 | CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve" |
39 | CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}" | 43 | CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}" |
44 | CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json" | ||
40 | CVE_CHECK_MANIFEST ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.cve" | 45 | CVE_CHECK_MANIFEST ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.cve" |
46 | CVE_CHECK_MANIFEST_JSON ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.json" | ||
41 | CVE_CHECK_COPY_FILES ??= "1" | 47 | CVE_CHECK_COPY_FILES ??= "1" |
42 | CVE_CHECK_CREATE_MANIFEST ??= "1" | 48 | CVE_CHECK_CREATE_MANIFEST ??= "1" |
43 | 49 | ||
44 | CVE_CHECK_REPORT_PATCHED ??= "1" | 50 | CVE_CHECK_REPORT_PATCHED ??= "1" |
45 | 51 | ||
52 | # Provide text output | ||
53 | CVE_CHECK_FORMAT_TEXT ??= "1" | ||
54 | |||
55 | # Provide JSON output | ||
56 | CVE_CHECK_FORMAT_JSON ??= "1" | ||
57 | |||
46 | # Skip CVE Check for packages (PN) | 58 | # Skip CVE Check for packages (PN) |
47 | CVE_CHECK_SKIP_RECIPE ?= "" | 59 | CVE_CHECK_SKIP_RECIPE ?= "" |
48 | 60 | ||
@@ -120,6 +132,7 @@ python cve_check_cleanup () { | |||
120 | Delete the file used to gather all the CVE information. | 132 | Delete the file used to gather all the CVE information. |
121 | """ | 133 | """ |
122 | bb.utils.remove(e.data.getVar("CVE_CHECK_TMP_FILE")) | 134 | bb.utils.remove(e.data.getVar("CVE_CHECK_TMP_FILE")) |
135 | bb.utils.remove(e.data.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")) | ||
123 | } | 136 | } |
124 | 137 | ||
125 | addhandler cve_check_cleanup | 138 | addhandler cve_check_cleanup |
@@ -131,11 +144,15 @@ python cve_check_write_rootfs_manifest () { | |||
131 | """ | 144 | """ |
132 | 145 | ||
133 | import shutil | 146 | import shutil |
147 | from oe.cve_check import cve_check_merge_jsons | ||
134 | 148 | ||
135 | if d.getVar("CVE_CHECK_COPY_FILES") == "1": | 149 | if d.getVar("CVE_CHECK_COPY_FILES") == "1": |
136 | deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE") | 150 | deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE") |
137 | if os.path.exists(deploy_file): | 151 | if os.path.exists(deploy_file): |
138 | bb.utils.remove(deploy_file) | 152 | bb.utils.remove(deploy_file) |
153 | deploy_file_json = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") | ||
154 | if os.path.exists(deploy_file_json): | ||
155 | bb.utils.remove(deploy_file_json) | ||
139 | 156 | ||
140 | if os.path.exists(d.getVar("CVE_CHECK_TMP_FILE")): | 157 | if os.path.exists(d.getVar("CVE_CHECK_TMP_FILE")): |
141 | bb.note("Writing rootfs CVE manifest") | 158 | bb.note("Writing rootfs CVE manifest") |
@@ -154,6 +171,26 @@ python cve_check_write_rootfs_manifest () { | |||
154 | os.remove(manifest_link) | 171 | os.remove(manifest_link) |
155 | os.symlink(os.path.basename(manifest_name), manifest_link) | 172 | os.symlink(os.path.basename(manifest_name), manifest_link) |
156 | bb.plain("Image CVE report stored in: %s" % manifest_name) | 173 | bb.plain("Image CVE report stored in: %s" % manifest_name) |
174 | |||
175 | if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")): | ||
176 | import json | ||
177 | bb.note("Generating JSON CVE manifest") | ||
178 | deploy_dir = d.getVar("DEPLOY_DIR_IMAGE") | ||
179 | link_name = d.getVar("IMAGE_LINK_NAME") | ||
180 | manifest_name = d.getVar("CVE_CHECK_MANIFEST_JSON") | ||
181 | index_file = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH") | ||
182 | manifest = {"version":"1", "package": []} | ||
183 | with open(index_file) as f: | ||
184 | filename = f.readline() | ||
185 | while filename: | ||
186 | with open(filename.rstrip()) as j: | ||
187 | data = json.load(j) | ||
188 | cve_check_merge_jsons(manifest, data) | ||
189 | filename = f.readline() | ||
190 | |||
191 | with open(manifest_name, "w") as f: | ||
192 | json.dump(manifest, f, indent=2) | ||
193 | bb.plain("Image CVE report stored in: %s" % manifest_name) | ||
157 | } | 194 | } |
158 | 195 | ||
159 | ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest; ' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" | 196 | ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest; ' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" |
@@ -280,7 +317,7 @@ def get_cve_info(d, cves): | |||
280 | conn.close() | 317 | conn.close() |
281 | return cve_data | 318 | return cve_data |
282 | 319 | ||
283 | def cve_write_data(d, patched, unpatched, ignored, cve_data): | 320 | def cve_write_data_text(d, patched, unpatched, ignored, cve_data): |
284 | """ | 321 | """ |
285 | Write CVE information in WORKDIR; and to CVE_CHECK_DIR, and | 322 | Write CVE information in WORKDIR; and to CVE_CHECK_DIR, and |
286 | CVE manifest if enabled. | 323 | CVE manifest if enabled. |
@@ -346,3 +383,108 @@ def cve_write_data(d, patched, unpatched, ignored, cve_data): | |||
346 | 383 | ||
347 | with open(d.getVar("CVE_CHECK_TMP_FILE"), "a") as f: | 384 | with open(d.getVar("CVE_CHECK_TMP_FILE"), "a") as f: |
348 | f.write("%s" % write_string) | 385 | f.write("%s" % write_string) |
386 | |||
387 | def cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file): | ||
388 | """ | ||
389 | Write CVE information in the JSON format: to WORKDIR; and to | ||
390 | CVE_CHECK_DIR, if CVE manifest if enabled, write fragment | ||
391 | files that will be assembled at the end in cve_check_write_rootfs_manifest. | ||
392 | """ | ||
393 | |||
394 | import json | ||
395 | |||
396 | write_string = json.dumps(output, indent=2) | ||
397 | with open(direct_file, "w") as f: | ||
398 | bb.note("Writing file %s with CVE information" % direct_file) | ||
399 | f.write(write_string) | ||
400 | |||
401 | if d.getVar("CVE_CHECK_COPY_FILES") == "1": | ||
402 | bb.utils.mkdirhier(os.path.dirname(deploy_file)) | ||
403 | with open(deploy_file, "w") as f: | ||
404 | f.write(write_string) | ||
405 | |||
406 | if d.getVar("CVE_CHECK_CREATE_MANIFEST") == "1": | ||
407 | cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR") | ||
408 | index_path = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH") | ||
409 | bb.utils.mkdirhier(cvelogpath) | ||
410 | fragment_file = os.path.basename(deploy_file) | ||
411 | fragment_path = os.path.join(cvelogpath, fragment_file) | ||
412 | with open(fragment_path, "w") as f: | ||
413 | f.write(write_string) | ||
414 | with open(index_path, "a+") as f: | ||
415 | f.write("%s\n" % fragment_path) | ||
416 | |||
417 | def cve_write_data_json(d, patched, unpatched, ignored, cve_data): | ||
418 | """ | ||
419 | Prepare CVE data for the JSON format, then write it. | ||
420 | """ | ||
421 | |||
422 | output = {"version":"1", "package": []} | ||
423 | nvd_link = "https://nvd.nist.gov/vuln/detail/" | ||
424 | |||
425 | fdir_name = d.getVar("FILE_DIRNAME") | ||
426 | layer = fdir_name.split("/")[-3] | ||
427 | |||
428 | include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() | ||
429 | exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() | ||
430 | |||
431 | if exclude_layers and layer in exclude_layers: | ||
432 | return | ||
433 | |||
434 | if include_layers and layer not in include_layers: | ||
435 | return | ||
436 | |||
437 | unpatched_cves = [] | ||
438 | |||
439 | package_version = "%s%s" % (d.getVar("EXTENDPE"), d.getVar("PV")) | ||
440 | package_data = { | ||
441 | "name" : d.getVar("PN"), | ||
442 | "layer" : layer, | ||
443 | "version" : package_version | ||
444 | } | ||
445 | cve_list = [] | ||
446 | |||
447 | for cve in sorted(cve_data): | ||
448 | is_patched = cve in patched | ||
449 | status = "Unpatched" | ||
450 | if is_patched and (d.getVar("CVE_CHECK_REPORT_PATCHED") != "1"): | ||
451 | continue | ||
452 | if cve in ignored: | ||
453 | status = "Ignored" | ||
454 | elif is_patched: | ||
455 | status = "Patched" | ||
456 | else: | ||
457 | # default value of status is Unpatched | ||
458 | unpatched_cves.append(cve) | ||
459 | |||
460 | issue_link = "%s%s" % (nvd_link, cve) | ||
461 | |||
462 | cve_item = { | ||
463 | "id" : cve, | ||
464 | "summary" : cve_data[cve]["summary"], | ||
465 | "scorev2" : cve_data[cve]["scorev2"], | ||
466 | "scorev3" : cve_data[cve]["scorev3"], | ||
467 | "vector" : cve_data[cve]["vector"], | ||
468 | "status" : status, | ||
469 | "link": issue_link | ||
470 | } | ||
471 | cve_list.append(cve_item) | ||
472 | |||
473 | package_data["issue"] = cve_list | ||
474 | output["package"].append(package_data) | ||
475 | |||
476 | direct_file = d.getVar("CVE_CHECK_LOG_JSON") | ||
477 | deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") | ||
478 | manifest_file = d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON") | ||
479 | |||
480 | cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file) | ||
481 | |||
482 | def cve_write_data(d, patched, unpatched, ignored, cve_data): | ||
483 | """ | ||
484 | Write CVE data in each enabled format. | ||
485 | """ | ||
486 | |||
487 | if d.getVar("CVE_CHECK_FORMAT_TEXT") == "1": | ||
488 | cve_write_data_text(d, patched, unpatched, ignored, cve_data) | ||
489 | if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": | ||
490 | cve_write_data_json(d, patched, unpatched, ignored, cve_data) | ||