summaryrefslogtreecommitdiffstats
path: root/meta/classes/cve-check.bbclass
diff options
context:
space:
mode:
authorMarta Rybczynska <rybczynska@gmail.com>2022-03-29 14:54:31 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2022-03-30 13:07:41 +0100
commit777f1d42b62ab482efa5a24600f4aeba1b156c64 (patch)
tree57f34b66df4db825abf4802101e689ca38abd2c0 /meta/classes/cve-check.bbclass
parentbbdf96885dbd8c3f5e2e9f084571ca659a809016 (diff)
downloadpoky-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.bbclass144
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"
34CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve" 34CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve"
35CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary" 35CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary"
36CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}" 36CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}"
37CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json"
38CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt"
39
40CVE_CHECK_LOG_JSON ?= "${T}/cve.json"
37 41
38CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve" 42CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve"
39CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}" 43CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}"
44CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json"
40CVE_CHECK_MANIFEST ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.cve" 45CVE_CHECK_MANIFEST ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.cve"
46CVE_CHECK_MANIFEST_JSON ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.json"
41CVE_CHECK_COPY_FILES ??= "1" 47CVE_CHECK_COPY_FILES ??= "1"
42CVE_CHECK_CREATE_MANIFEST ??= "1" 48CVE_CHECK_CREATE_MANIFEST ??= "1"
43 49
44CVE_CHECK_REPORT_PATCHED ??= "1" 50CVE_CHECK_REPORT_PATCHED ??= "1"
45 51
52# Provide text output
53CVE_CHECK_FORMAT_TEXT ??= "1"
54
55# Provide JSON output
56CVE_CHECK_FORMAT_JSON ??= "1"
57
46# Skip CVE Check for packages (PN) 58# Skip CVE Check for packages (PN)
47CVE_CHECK_SKIP_RECIPE ?= "" 59CVE_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
125addhandler cve_check_cleanup 138addhandler 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
159ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest; ' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 196ROOTFS_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
283def cve_write_data(d, patched, unpatched, ignored, cve_data): 320def 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
387def 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
417def 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
482def 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)