diff --git a/Makefile b/Makefile index af14610b..77f22ca3 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,9 @@ # make [all] TARGET= that board's boot image + its book # make [all] TARGET= hosted v3 build with that profile # -# make boot_image TARGET= only the board's boot image +# make boot_image TARGET= only the board's boot image, with its SBOM +# (build/boards//*.spdx*) and a fresh +# source SBOM -- needs syft # make boot_images every board that has a kernel port # make docs [TARGET=] the book; a board adds its appendix # @@ -25,6 +27,7 @@ # make clean TARGET= build// for every arch # make clean-docs build/docs/ # +# make sbom SPDX SBOM of the source tree (sbom.spdx, sbom.spdx.json) # make kernel ARCH= [GOAL=g] kernel/Makefile goal (default: all) # make boards list boards and whether they can be built # make 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 # ------------------------------------------------------------------------------ diff --git a/mk/syft.yaml b/mk/syft.yaml new file mode 100644 index 00000000..84415f71 --- /dev/null +++ b/mk/syft.yaml @@ -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