diff mbox series

[RFC,3/5] support/misc/cyclonedx.mk: support CycloneDX format

Message ID 20240404124329.768546-4-thomas.perale@mind.be
State New
Headers show
Series Support SBOM in CycloneDX format | expand

Commit Message

Thomas Perale April 4, 2024, 12:43 p.m. UTC
CycloneDX is a software bill of materials (SBOM) specification.

There is a growing need to generate SBOM from buildroot
configurations. Right now there are different solutions available
for buildroot users `show-info`, `legal-info` and `pkg-stats`.
They all generate similar information (`show-info` showing more) but in
a format that is specific to buildroot.

This is the reason this patch introduces a new SBOM output type for
buildroot: CycloneDX. CycloneDX is a format already supported by tools
such as https://dependencytrack.org/ that helps track softwares,
vulnerabilities, etc ...

To match the functionality of `show-info`, buildroot internal packages
will also be present in the SBOM with a reduced set of property.
Internal packages are defined as packages without `<pkg>_SOURCE`
defined.
In a future patch more properties can be added to cover the
functionality of `show-info`, `legal-info` and `pkg-stats`.

The CycloneDX SBOM output as a stripped JSON formatted line as there are
already macros available to work with JSON in buildroot.

For more information, see https://cyclonedx.org/
and https://cyclonedx.org/docs/1.5/json/

Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
 support/misc/cyclonedx.mk | 197 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 197 insertions(+)
 create mode 100644 support/misc/cyclonedx.mk

--
2.44.0
diff mbox series

Patch

diff --git a/support/misc/cyclonedx.mk b/support/misc/cyclonedx.mk
new file mode 100644
index 0000000000..3906a3b60a
--- /dev/null
+++ b/support/misc/cyclonedx.mk
@@ -0,0 +1,197 @@ 
+################################################################################
+#
+# This file contains various utility functions used to create a SBOM
+# in the JSON CycloneDX format.
+#
+# https://cyclonedx.org/docs/1.5/json/
+#
+################################################################################
+
+# Note: to avoid conflict with <pkg>_VERSION `_SPEC` is added
+CYCLONEDX_VERSION_SPEC = 1.5
+
+#
+# Licenses list helper functions
+# Since licenses in buildroot are comma separated list and the 'make' language
+# uses spaces to create list we need to replace the spaces of licenses name
+# by a character not used in any licenses name.
+#
+# _cyclonedx-licenses-as-list: create a list from the url-encoded comma
+#                              separeted license list.
+#
+# $(1): an url-encoded comma separeted list
+#
+# Turns "Public%20Domain,%20GPL-2.0" into "Public%20Domain GPL-2.0"
+_cyclonedx-licenses-as-list = $(subst $(comma)%20,$(space),$(1))
+
+# _cyclonedx-license -- create an entry of a cyclonedx component license list
+#
+# For more information on license object see
+# https://cyclonedx.org/docs/1.5/json/#components_items_licenses_oneOf_i0_items_license
+#
+# $(1): a single url-encoded license name
+define _cyclonedx-license
+	{
+		"license": {
+			"name": $(call mk-json-str,$(1))
+		}
+	},
+endef
+
+# _cyclonedx-licenses -- create a licenses list formatted for a CycloneDX
+#                        component
+#
+# $(1): a comma separated license list
+#
+# KNOWN ISSUE: Licenses name that include a parenthesis with comma inside,
+#   will result be misinterpreted as multiple licenses name:
+#   - host-util-linux: LGPL-2.1+ (libblkid, libfdisk, libmount)
+define _cyclonedx-licenses
+	$(foreach license,$(call _cyclonedx-licenses-as-list,$(call urlencode,$(1))),
+		$(call urldecode,$(call _cyclonedx-license,$(license)))
+	)
+endef
+
+# Note about patch list: this patch list might not be complete.
+# There is no variable yet that stores the patch list without applying them.
+_cyclonedx-patches-list = $(foreach patchdir,\
+	$(addsuffix /$($(1)_PKGDIR),$(CURDIR)) $(addsuffix /$($(1)_RAWNAME),$(call qstrip,$(BR2_GLOBAL_PATCH_DIR))),\
+	$(wildcard $(addsuffix /*.patch,$(patchdir)))\
+)
+
+# _cyclonedx-patch -- single entry of a patch list.
+#                     It's required to pass the type of the patch it can be
+#                     either: unofficial, monkey, backport or cherry-pick.
+#                     Since there is no information available about each
+#                     patches, we mark them as "unofficial".
+#
+# $(1): single patch path
+define _cyclonedx-patch
+	{
+		"type": "unofficial",
+		"diff": {
+			"text": {
+				"content": $(call mk-json-str,$(file < $(1)))
+			}
+		}
+	},
+endef
+
+# _cyclonedx-patches -- patch list are stored under the pedigree entry used to
+#                       document how a component is modified.
+#
+# $(1): patch path list
+define _cyclonedx-patches
+	$(intcmp $(words $(1)),0,,,
+		"pedigree": {\
+			"patches": [\
+				$(foreach patch,$(1),\
+					$(call _cyclonedx-patch,$(patch))\
+				)\
+			]\
+		}$(comma)\
+	)
+endef
+
+# _cyclonedx-component -- representation of a package for the CycloneDX format
+#  - bom-ref: is a unique identifier used to refer to this component in the
+#      'dependencies' section.
+#  - type: is a required property since we don't have enough information about
+#      the package from its definition CycloneDX spec recommend setting it
+#      to 'library'.
+#  - properties: is used to add additional information that doesn't fit the
+#      current CycloneDX specification.
+#    - BR_TYPE: {host|target}
+#
+# $(1): upper-case package name
+#
+# KNWON ISSUE: packages with a custom tarball (linux,uboot,...) will have the
+#   'version' property set to 'custom'.
+define _cyclonedx-component
+	{
+		"bom-ref": $(call mk-json-str,$($(1)_NAME)),
+		"name": $(call mk-json-str,$(if $($(1)_RAWNAME),$($(1)_RAWNAME),$($(1)_NAME))),
+		"type": "library",
+		$(if $($(1)_SOURCE),
+			"version": $(call mk-json-str,$($(1)_DL_VERSION))$(comma)
+			"licenses": [
+				$(call _cyclonedx-licenses,$($(1)_LICENSE)) \
+			]$(comma)
+		)
+		$(if $($(1)_PURL), \
+			"purl": $(call mk-json-str,$($(1)_PURL))$(comma) \
+		)
+		$(if $($(1)_CPE_ID_VALID), \
+			"cpe": $(call mk-json-str,$($(1)_CPE_ID))$(comma) \
+		)
+		$(call _cyclonedx-patches,$(call _cyclonedx-patches-list,$(1)))
+		"properties": [{
+			"name": "BR_TYPE",
+			"value": $(call mk-json-str,$($(1)_TYPE))
+		}],
+	},
+endef
+
+# _cyclonedx-dependency -- create dependency relationships between components.
+#  - ref: reference to a component bom-ref.
+#  - dependsOn: array of component bom-ref identifier to create the dependencies.
+#
+# $(1): upper-case package name
+define _cyclonedx-dependency
+	$(if $($(1)_FINAL_RECURSIVE_DEPENDENCIES),
+		{
+			"ref": $(call mk-json-str,$($(1)_NAME))$(comma)
+			"dependsOn": [
+				$(call make-comma-list,$(foreach p,\
+					$($(1)_FINAL_RECURSIVE_DEPENDENCIES), \
+					$(call mk-json-str,$(p))\
+				))
+			]
+		}$(comma)
+	)
+endef
+
+# cyclonedx-json -- return a CycloneDX SBOM formatted as a JSON dictionnary.
+#  - bomFormat: required field is always "CycloneDX"
+#  - specVersion: required field with CycloneDX spec version
+#  - version: is used by software that accept CycloneDX SBOM to differentiate
+#    the different SBOM. The bigger the number the newer the SBOM is.
+#    Here it's set to '1' and it should be incremented when the resulting
+#    SBOM is edited later.
+#    The CycloneDX spec mentions that an 'uuid' property can also be used to
+#    differentiate SBOM but is not included because there is no native command
+#    to generate an uuid in buildroot.
+#
+# $(1): packages list
+define cyclonedx-json
+	$(call clean-json,{
+		"bomFormat": "CycloneDX"$(comma)
+		"$$schema": "http://cyclonedx.org/schema/bom-$(CYCLONEDX_VERSION_SPEC).schema.json"$(comma)
+		"specVersion": $(call mk-json-str,$(CYCLONEDX_VERSION_SPEC))$(comma)
+		"version": 1$(comma)
+		"components": [ \
+			$(foreach p,$(1), \
+				$(call _cyclonedx-component,$(call UPPERCASE,$(p))) \
+			) \
+		]$(comma)
+		"dependencies": [
+			{
+				"ref": "buildroot"$(comma)\
+				"dependsOn": [$(call make-comma-list,\
+					$(foreach p,$(1),$(call mk-json-str,$(p)))\
+				)]\
+			}$(comma)
+			$(foreach p,$(1),\
+				$(call _cyclonedx-dependency,$(call UPPERCASE,$(p)),$(2)) \
+			) \
+		]$(comma)
+		"metadata": {
+			"component": {
+				"bom-ref": "buildroot"$(comma)
+				"name": "buildroot"$(comma)
+				"type": "firmware"$(comma)
+				"version": $(call mk-json-str,$(BR2_VERSION_FULL))
+			}
+		}
+	})
+endef