build: an SPDX SBOM with every board image, and of the source tree, by syft

make boot_image now ends by writing build/boards/<board>/<image>.spdx(.json):
the image and every file copied off it, each with its SHA-256 -- the SBOM
of what is on the medium.  It also refreshes sbom.spdx(.json), the source
SBOM: the tracked files the product is built from (kernel, v3, v4,
capsules, boards, tooling, proofs), staged to build/sbom/src so nothing
untracked in the working directory is swept in.  Versioned as the kernel
version and git describe.  mk/syft.yaml turns on file cataloging with
SHA-256 (a C tree has almost no packages for syft to find: the old SBOM
held one file with a zero checksum).  No syft: the build stops.

syft 1.52.0 installed in ~/.local/bin from its GitHub release, checked
against the release's published SHA-256.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-14 16:58:10 -04:00
co-authored by Claude Opus 5.5
parent f8fbc37698
commit fcb89b0695
2 changed files with 66 additions and 2 deletions
+57 -2
View File
@@ -16,7 +16,9 @@
# make [all] TARGET=<board> that board's boot image + its book
# make [all] TARGET=<profile> hosted v3 build with that profile
#
# make boot_image TARGET=<board> only the board's boot image
# make boot_image TARGET=<board> only the board's boot image, with its SBOM
# (build/boards/<board>/*.spdx*) and a fresh
# source SBOM -- needs syft
# make boot_images every board that has a kernel port
# make docs [TARGET=<board>] the book; a board adds its appendix
#
@@ -25,6 +27,7 @@
# make clean TARGET=<profile> build/<arch>/<profile> for every arch
# make clean-docs build/docs/
#
# make sbom SPDX SBOM of the source tree (sbom.spdx, sbom.spdx.json)
# make kernel ARCH=<arch> [GOAL=g] kernel/Makefile goal (default: all)
# make boards list boards and whether they can be built
# make <other goal> goes to v3/Makefile (make help-hosted)
@@ -53,7 +56,7 @@ BOARD_DIR := $(if $(TARGET),$(BOARD_DIR_$(TARGET)))
SUBMAKE := $(MAKE) --no-print-directory
.DEFAULT_GOAL := all
.PHONY: all boot_image boot_images docs clean clean-docs kernel boards help help-hosted
.PHONY: all boot_image boot_images docs clean clean-docs kernel boards help help-hosted sbom sbom-board
# ------------------------------------------------------------------------------
# all
@@ -104,6 +107,9 @@ boot_image:
@$(SUBMAKE) -f kernel/Makefile \
BOARD=$(BOARD_DIR) ARCH=$(call board_arch,$(TARGET)) TARGET=kernel \
STARFORTH_V4=1 V4_DEFAULT=0 boot_image
@# Its SBOM, and the source tree's, are part of the build (2026-10-14).
@$(SUBMAKE) sbom-board TARGET=$(TARGET)
@$(SUBMAKE) sbom
boot_images:
@for b in $(READY_BOARDS); do \
@@ -111,6 +117,55 @@ boot_images:
done
@$(foreach b,$(UNREADY_BOARDS),echo "Skipped $(b): needs kernel/src/arch/$(call board_arch,$(b)), which does not exist yet";)
# ------------------------------------------------------------------------------
# SBOMs (SPDX 2.3, by syft; config mk/syft.yaml) -- part of every board build
# ------------------------------------------------------------------------------
# The source SBOM covers what the product is built from: the tracked files of
# the kernel, v3, v4, capsules, boards, build tooling and proofs, each with its
# SHA-256 -- copied to build/sbom/src first, so nothing untracked in the
# working directory is ever swept in. Not logs, documents, experiment data or
# the disk images. A board's SBOM covers
# its image and every file on it, copied off the image, so the SBOM is of what
# is on the medium, not of what was meant to be. Versioned as the kernel
# version and the commit. No syft: the build stops, as for any missing tool.
SYFT ?= syft
SBOM_VERSION = $(shell sed -n 's/^LITHOS_VERSION *?= *//p' kernel/Makefile)+$(shell git describe --always --dirty 2>/dev/null)
# What the product is built from: tracked files only, in these places (never
# untracked or ignored files in the working directory).
SBOM_SOURCES := kernel v3 v4 capsules boards mk tools scripts proof configs \
Makefile Kconfig Kconfig.arch Kconfig.heartbeat Kconfig.kernel \
Kconfig.physics Kconfig.variant LICENSE NOTICE.md
GIT_PLAIN := git -c filter.lfs.process= -c filter.lfs.smudge=cat -c filter.lfs.required=false
define need_syft
@command -v $(SYFT) >/dev/null 2>&1 || { \
echo "Error: syft not found -- the SBOM is part of this build."; \
echo " Install a release from https://github.com/anchore/syft/releases"; \
echo " (verify it against the release's checksums), or SYFT=/path/to/syft."; exit 1; }
endef
sbom:
$(need_syft)
@echo " SBOM source tree -> sbom.spdx, sbom.spdx.json ($(SBOM_VERSION))"
@rm -rf build/sbom/src && mkdir -p build/sbom/src && \
$(GIT_PLAIN) ls-files -z -- $(SBOM_SOURCES) | xargs -0 cp --parents -t build/sbom/src && \
$(SYFT) -q -c mk/syft.yaml dir:build/sbom/src \
--source-name LithosAnanke --source-version "$(SBOM_VERSION)" \
-o spdx-json=sbom.spdx.json -o spdx=sbom.spdx
sbom-board:
$(need_syft)
@if [ -z "$(BOARD_DIR)" ]; then echo "Usage: make sbom-board TARGET=<$(subst $(space),|,$(BOARDS))>"; exit 1; fi
@img=build/boards/$(BOARD_DIR)/$(call board_field,$(TARGET),BOARD_IMAGE); \
if [ ! -f "$$img" ]; then echo "Error: $$img not built (make boot_image TARGET=$(TARGET))"; exit 1; fi; \
rm -rf build/boards/$(BOARD_DIR)/contents && mkdir -p build/boards/$(BOARD_DIR)/contents && \
mcopy -s -n -i "$$img@@1M" '::/*' build/boards/$(BOARD_DIR)/contents/ && \
echo " SBOM $$img -> $${img%.img}.spdx, $${img%.img}.spdx.json" && \
$(SYFT) -q -c mk/syft.yaml dir:build/boards/$(BOARD_DIR) \
--exclude './*.spdx' --exclude './*.spdx.json' \
--source-name "LithosAnanke-$(BOARD_DIR)" --source-version "$(SBOM_VERSION)" \
-o spdx-json=$${img%.img}.spdx.json -o spdx=$${img%.img}.spdx
# ------------------------------------------------------------------------------
# docs
# ------------------------------------------------------------------------------
+9
View File
@@ -0,0 +1,9 @@
# syft configuration for this repository's SBOMs (make sbom, make boot_image).
# A C kernel has almost no "packages" for syft's catalogers to find, so the
# SBOM's substance is the file list: every file in scope, with its SHA-256
# (2026-10-14; until then the SBOM held one file, with a zero checksum).
file:
metadata:
selection: all
digests:
- sha256