[v2,3/4] meta: add SBOM generation with debsbom

Message ID 20250917063314.44769-3-christoph.steiger@siemens.com
State New
Headers show
Series Add SBOM generation with debsbom | expand

Commit Message

Christoph Steiger Sept. 17, 2025, 6:33 a.m. UTC
Generate SBOMs for every rootfs that is created. These SBOMs are placed
in the image deploy directory.

For the generation a small chroot with debsbom installed is created and
from that the rootfs of the image is scanned.

The sbom generation is bound to the rootfs feature `generate-sbom`
which is activated per default now.

Signed-off-by: Christoph Steiger <christoph.steiger@siemens.com>
Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
---
 meta/classes/image.bbclass                    |  8 ++-
 meta/classes/rootfs.bbclass                   |  7 ++-
 meta/classes/sbom.bbclass                     | 62 +++++++++++++++++++
 meta/classes/sdk.bbclass                      |  2 +-
 .../sbom-chroot/sbom-chroot.bb                | 30 +++++++++
 5 files changed, 106 insertions(+), 3 deletions(-)
 create mode 100644 meta/classes/sbom.bbclass
 create mode 100644 meta/recipes-devtools/sbom-chroot/sbom-chroot.bb

Patch

diff --git a/meta/classes/image.bbclass b/meta/classes/image.bbclass
index bd1b8552..220f5aa3 100644
--- a/meta/classes/image.bbclass
+++ b/meta/classes/image.bbclass
@@ -66,7 +66,13 @@  inherit multiarch
 inherit essential
 
 ROOTFSDIR = "${IMAGE_ROOTFS}"
-ROOTFS_FEATURES += "clean-package-cache clean-pycache generate-manifest export-dpkg-status clean-log-files clean-debconf-cache"
+ROOTFS_FEATURES += "clean-package-cache clean-pycache generate-manifest export-dpkg-status clean-log-files clean-debconf-cache generate-sbom"
+# only supported from bookworm / jammy on
+ROOTFS_FEATURES:remove:buster = "generate-sbom"
+ROOTFS_FEATURES:remove:bullseye = "generate-sbom"
+ROOTFS_FEATURES:remove:jammy = "generate-sbom"
+ROOTFS_FEATURES:remove:focal = "generate-sbom"
+
 # when using a custom initrd, do not generate one as part of the image rootfs
 ROOTFS_FEATURES += "${@ '' if d.getVar('INITRD_IMAGE') == '' else 'no-generate-initrd'}"
 ROOTFS_PACKAGES += "${IMAGE_PREINSTALL} ${@isar_multiarch_packages('IMAGE_INSTALL', d)}"
diff --git a/meta/classes/rootfs.bbclass b/meta/classes/rootfs.bbclass
index 7b7859b9..98f5b24c 100644
--- a/meta/classes/rootfs.bbclass
+++ b/meta/classes/rootfs.bbclass
@@ -3,6 +3,8 @@ 
 
 inherit deb-dl-dir
 
+inherit sbom
+
 ROOTFS_ARCH ?= "${DISTRO_ARCH}"
 ROOTFS_DISTRO ?= "${DISTRO}"
 ROOTFS_PACKAGES ?= ""
@@ -350,6 +352,9 @@  cache_dbg_pkgs() {
     fi
 }
 
+# The sbom generator needs the apt-cache, hence run before cleaning it
+ROOTFS_POSTPROCESS_COMMAND += "${@bb.utils.contains('ROOTFS_FEATURES', 'generate-sbom', 'do_generate_sbom', '', d)}"
+
 ROOTFS_POSTPROCESS_COMMAND += "${@bb.utils.contains('ROOTFS_FEATURES', 'clean-package-cache', 'rootfs_postprocess_clean_package_cache', '', d)}"
 rootfs_postprocess_clean_package_cache() {
     sudo -E chroot '${ROOTFSDIR}' \
@@ -512,7 +517,7 @@  python do_rootfs() {
 }
 addtask rootfs before do_build
 
-do_rootfs_postprocess[depends] = "base-apt:do_cache isar-apt:do_cache_config"
+do_rootfs_postprocess[depends] = "base-apt:do_cache isar-apt:do_cache_config ${@bb.utils.contains('ROOTFS_FEATURES', 'generate-sbom', 'sbom-chroot:do_sbomchroot_deploy', '', d)}"
 
 SSTATETASKS += "do_rootfs_install"
 SSTATECREATEFUNCS += "rootfs_install_sstate_prepare"
diff --git a/meta/classes/sbom.bbclass b/meta/classes/sbom.bbclass
new file mode 100644
index 00000000..60c89877
--- /dev/null
+++ b/meta/classes/sbom.bbclass
@@ -0,0 +1,62 @@ 
+# This software is a part of ISAR.
+# Copyright (C) 2025 Siemens
+#
+# SPDX-License-Identifier: MIT
+
+# sbom type to generate, accepted are "cdx" or "spdx"
+SBOM_TYPES ?= "spdx cdx"
+
+SBOM_DEBSBOM_TYPE_ARGS = "${@"-t " + " -t ".join(d.getVar("SBOM_TYPES").split())}"
+
+# general user variables
+SBOM_DISTRO_SUPPLIER ?= "ISAR"
+SBOM_DISTRO_NAME ?= "ISAR-Debian-GNU-Linux"
+SBOM_DISTRO_VERSION ?= "1"
+SBOM_DISTRO_SUMMARY ?= "Linux distribution built with ISAR"
+SBOM_BASE_DISTRO_VENDOR ??= "debian"
+SBOM_DOCUMENT_UUID ?= ""
+
+# SPDX specific user variables
+SBOM_SPDX_NAMESPACE_PREFIX ?= "https://spdx.org/spdxdocs"
+
+DEPLOY_DIR_SBOM = "${DEPLOY_DIR_IMAGE}"
+
+SBOM_DIR = "${DEPLOY_DIR}/sbom"
+SBOM_CHROOT = "${SBOM_DIR}/sbom-chroot"
+
+# adapted from the isar-cip-core image_uuid.bbclass
+def generate_document_uuid(d):
+    import uuid
+
+    base_hash = d.getVar("BB_TASKHASH")
+    if base_hash is None:
+        bb.warn("no BB_TASKHASH available, SBOM UUID is not reproducible")
+        return uuid.uuid4()
+    return str(uuid.UUID(base_hash[:32], version=4))
+
+def sbom_doc_uuid(d):
+    if not d.getVar("SBOM_DOCUMENT_UUID"):
+        d.setVar("SBOM_DOCUMENT_UUID", generate_document_uuid(d))
+
+generate_sbom() {
+    sudo mkdir -p ${SBOM_CHROOT}/mnt/rootfs ${SBOM_CHROOT}/mnt/deploy-dir
+
+    TIMESTAMP=$(date --iso-8601=s -d @${SOURCE_DATE_EPOCH})
+    bwrap \
+        --unshare-user \
+        --unshare-pid \
+        --bind ${SBOM_CHROOT} / \
+        --bind ${ROOTFSDIR} /mnt/rootfs \
+        --bind ${DEPLOY_DIR_SBOM} /mnt/deploy-dir \
+        -- debsbom generate ${SBOM_DEBSBOM_TYPE_ARGS} -r /mnt/rootfs -o /mnt/deploy-dir/'${PN}-${DISTRO}-${MACHINE}' \
+            --distro-name '${SBOM_DISTRO_NAME}' --distro-supplier '${SBOM_DISTRO_SUPPLIER}' \
+            --distro-version '${SBOM_DISTRO_VERSION}' --base-distro-vendor '${SBOM_BASE_DISTRO_VENDOR}' \
+            --cdx-serialnumber '${SBOM_DOCUMENT_UUID}' \
+            --spdx-namespace '${SBOM_SPDX_NAMESPACE_PREFIX}'-'${SBOM_DOCUMENT_UUID}' \
+            --timestamp $TIMESTAMP
+}
+
+python do_generate_sbom() {
+    sbom_doc_uuid(d)
+    bb.build.exec_func("generate_sbom", d)
+}
diff --git a/meta/classes/sdk.bbclass b/meta/classes/sdk.bbclass
index 46436d97..644b0623 100644
--- a/meta/classes/sdk.bbclass
+++ b/meta/classes/sdk.bbclass
@@ -55,7 +55,7 @@  def get_rootfs_distro(d):
 ROOTFS_ARCH:class-sdk = "${HOST_ARCH}"
 ROOTFS_DISTRO:class-sdk = "${@get_rootfs_distro(d)}"
 ROOTFS_PACKAGES:class-sdk = "sdk-files ${SDK_TOOLCHAIN} ${SDK_PREINSTALL} ${@isar_multiarch_packages('SDK_INSTALL', d)}"
-ROOTFS_FEATURES:append:class-sdk = " clean-package-cache generate-manifest export-dpkg-status"
+ROOTFS_FEATURES:append:class-sdk = " clean-package-cache generate-manifest export-dpkg-status generate-sbom"
 ROOTFS_MANIFEST_DEPLOY_DIR:class-sdk = "${DEPLOY_DIR_SDKCHROOT}"
 ROOTFS_DPKGSTATUS_DEPLOY_DIR:class-sdk = "${DEPLOY_DIR_SDKCHROOT}"
 
diff --git a/meta/recipes-devtools/sbom-chroot/sbom-chroot.bb b/meta/recipes-devtools/sbom-chroot/sbom-chroot.bb
new file mode 100644
index 00000000..a9afcbbe
--- /dev/null
+++ b/meta/recipes-devtools/sbom-chroot/sbom-chroot.bb
@@ -0,0 +1,30 @@ 
+# This software is a part of ISAR.
+#
+# Copyright (C) 2025 Siemens
+
+LICENSE = "gpl-2.0"
+LIC_FILES_CHKSUM = "file://${LAYERDIR_core}/licenses/COPYING.GPLv2;md5=751419260aa954499f7abaabaa882bbe"
+
+PV = "1.0"
+
+inherit rootfs
+
+ROOTFS_ARCH = "${HOST_ARCH}"
+ROOTFS_DISTRO = "${HOST_DISTRO}"
+ROOTFS_BASE_DISTRO = "${HOST_BASE_DISTRO}"
+
+ROOTFS_FEATURES = "no-generate-initrd"
+
+# additional packages for the SBOM chroot
+SBOM_IMAGE_INSTALL = "python3-debsbom"
+
+DEPENDS = "python3-debsbom"
+
+ROOTFSDIR = "${WORKDIR}/rootfs"
+ROOTFS_PACKAGES = "${SBOM_IMAGE_INSTALL}"
+
+do_sbomchroot_deploy[dirs] = "${SBOM_DIR}"
+do_sbomchroot_deploy() {
+    ln -Tfsr "${ROOTFSDIR}" "${SBOM_CHROOT}"
+}
+addtask do_sbomchroot_deploy before do_build after do_rootfs