From: Simon Falsig <[email protected]>
This provides support for building SBOMs in CycloneDX format.
A target is added alongside the other reports, that (based on the
fast-bsp-report) extracts name, version, cpe and license of each target
package, and puts these into a final sbom-report in CycloneDX/JSON
format.
This requires a working Python3 setup with the cyclonedx-bom package
installed.
---
bin/ptxdist | 3 ++-
rules/post/ptxd_make_report.make | 15 ++++++++++--
scripts/lib/ptxd_make_report.sh | 16 +++++++++++++
scripts/lib/ptxd_make_sbom_report.py | 35 ++++++++++++++++++++++++++++
4 files changed, 66 insertions(+), 3 deletions(-)
create mode 100644 scripts/lib/ptxd_make_sbom_report.py
diff --git a/bin/ptxdist b/bin/ptxdist
index dfb619cbd..15be851f5 100755
--- a/bin/ptxdist
+++ b/bin/ptxdist
@@ -780,6 +780,7 @@ Misc:
full-bsp-report generate a yaml file that describes the BSP and
all packages. More data but will build all
packages if necessary.
+ sbom-report generate a CycloneDX json SBOM
print <var> print the contents of a variable, in the way
it is known by "make"
printnext <var> assumes that the contents of <var> is another
@@ -1807,7 +1808,7 @@ EOF
ptxd_make_log export_src EXPORTDIR="${1}"
exit
;;
- fast-bsp-report|full-bsp-report)
+ fast-bsp-report|full-bsp-report|sbom-report)
check_premake_compiler &&
ptxd_make_log "${cmd}"
exit
diff --git a/rules/post/ptxd_make_report.make b/rules/post/ptxd_make_report.make
index eecd2a577..ffa398c95 100644
--- a/rules/post/ptxd_make_report.make
+++ b/rules/post/ptxd_make_report.make
@@ -10,7 +10,9 @@ ptx/report-env = \
$(image/env) \
ptx_report_target="$(strip $(1))" \
ptx_packages_selected="$(filter-out
$(IMAGE_PACKAGES),$(PTX_PACKAGES_SELECTED))" \
- ptx_image_packages="$(IMAGE_PACKAGES)"
+ ptx_image_packages="$(IMAGE_PACKAGES)" \
+ ptx_target_packages="$(PACKAGES)"
+
PHONY += full-bsp-report
full-bsp-report: $(RELEASEDIR)/full-bsp-report.yaml
@@ -26,13 +28,22 @@ $(RELEASEDIR)/full-bsp-report.yaml: \
@$(call ptx/report-env, $@) ptxd_make_full_bsp_report
@$(call finish)
+
PHONY += fast-bsp-report
fast-bsp-report: $(RELEASEDIR)/fast-bsp-report.yaml
-
$(RELEASEDIR)/fast-bsp-report.yaml: $(addprefix $(STATEDIR)/,$(addsuffix
.fast-report,$(PTX_PACKAGES_SELECTED)))
@$(call targetinfo)
@$(call ptx/report-env, $@) ptxd_make_fast_bsp_report
@$(call finish)
+
+PHONY += sbom-report
+sbom-report: $(RELEASEDIR)/sbom-report.json
+
+$(RELEASEDIR)/sbom-report.json: $(addprefix $(STATEDIR)/,$(addsuffix
.fast-report,$(PACKAGES)))
+ @$(call targetinfo)
+ @$(call ptx/report-env, $@) ptxd_make_sbom_report
+ @$(call finish)
+
# vim: syntax=make
diff --git a/scripts/lib/ptxd_make_report.sh b/scripts/lib/ptxd_make_report.sh
index a363ca5b3..e2da4c05f 100644
--- a/scripts/lib/ptxd_make_report.sh
+++ b/scripts/lib/ptxd_make_report.sh
@@ -144,3 +144,19 @@ ptxd_make_fast_bsp_report() {
}
export -f ptxd_make_fast_bsp_report
+ptxd_make_sbom_report() {
+ local -a ptxd_reply
+ local pkg_lic pkg
+
+ ptxd_make_layer_init || return
+
+ echo "Generating $(ptxd_print_path "${ptx_report_target}") ..."
+ echo
+
+ mkdir -p "$(dirname "${ptx_report_target}")" &&
+ python3 ${PTXDIST_LIB_DIR}/ptxd_make_sbom_report.py
"${ptx_report_dir}/fast/" ${ptx_target_packages} >
${PTXDIST_TEMPDIR}/sbom-report &&
+ mv "${PTXDIST_TEMPDIR}/sbom-report" "${ptx_report_target}" ||
+ ptxd_bailout "failed to create SBOM report"
+}
+export -f ptxd_make_sbom_report
+
diff --git a/scripts/lib/ptxd_make_sbom_report.py
b/scripts/lib/ptxd_make_sbom_report.py
new file mode 100644
index 000000000..aecb4fae5
--- /dev/null
+++ b/scripts/lib/ptxd_make_sbom_report.py
@@ -0,0 +1,35 @@
+from cyclonedx.factory.license import LicenseFactory
+from cyclonedx.factory.license import LicenseChoiceFactory
+from cyclonedx.model.bom import Bom
+from cyclonedx.model.component import Component
+from cyclonedx.output.json import JsonV1Dot4
+import sys
+import re
+
+lFac = LicenseFactory()
+lcFac = LicenseChoiceFactory(license_factory=lFac)
+bom = Bom()
+
+for i in range(2,len(sys.argv)):
+ pkg_report = sys.argv[1] + sys.argv[i] + ".yaml"
+ with open(pkg_report, 'r') as file:
+ content = file.read()
+ name_ = re.search("name: \'(.+)\'", content).group(1)
+ version_ = re.search("version: \'(.+)\'", content).group(1)
+ cpeMatch = re.search("cpe: \'(.+)\'", content)
+ cpe_ = None
+ if cpeMatch is not None:
+ cpe_ = cpeMatch.group(1)
+ licenses_ = re.search("licenses: \'(.+)\'", content).group(1)
+ comp = Component(
+ name = name_,
+ version = version_,
+ cpe = cpe_,
+ licenses = [lcFac.make_with_license(licenses_)],
+ bom_ref = name_ + "@" + version_
+ )
+ bom.components.add(comp)
+
+serializedJSON = JsonV1Dot4(bom).output_as_string()
+print(serializedJSON)
+
--
2.25.1