mirror of https://github.com/wolfSSL/wolfBoot.git
437 lines
18 KiB
YAML
437 lines
18 KiB
YAML
name: Wolfboot SBOM Canary
|
|
|
|
# Exercises every wolfBoot SBOM route so a change to the build system, the
|
|
# vendored wolfGlass tooling under tools/sbom/, or an IDE extractor cannot
|
|
# silently break SBOM generation:
|
|
#
|
|
# * Make path (methods 1-4) -> make sbom TARGET=sim
|
|
# * CMake path (method 5) -> cmake --build --target sbom
|
|
# * IAR extractor (method 7) -> tools/sbom/frontends/iar_sbom.py
|
|
# * compdb extractor (6, 8-11) -> tools/sbom/frontends/compdb_sbom.py
|
|
# * per-HAL SBOM -> make sbom-hal TARGET=sim
|
|
# * Zephyr module SBOM -> tools/sbom/frontends/zephyr_sbom.py
|
|
#
|
|
# Each route must emit a schema-valid CycloneDX 1.6 + SPDX 2.3 document whose
|
|
# top-level component is wolfboot.
|
|
#
|
|
# The sbom_canary job also guards three properties of the SBOM itself:
|
|
#
|
|
# * Toolchain neutrality - gcc and clang must give a byte-identical SBOM.
|
|
# The driver captures configuration with the host compiler and the source
|
|
# list, so the cross-toolchain that builds the firmware does not change the
|
|
# SBOM. No per-compiler front end is needed for clang, LLVM or a vendor
|
|
# compiler.
|
|
# * Reproducibility - the same configuration built from a different absolute
|
|
# path gives a byte-identical SBOM.
|
|
# * Path scrub - an absolute host path in a -D macro (the synthetic check and
|
|
# a real rp2350 build with PICO_SDK_PATH) must not reach the SBOM.
|
|
#
|
|
# The cross_targets job proves the same source route works for many embedded
|
|
# targets with no IDE and no cross-toolchain installed. It runs make sbom for a
|
|
# spread of architectures (Arm-M, Arm-A, PowerPC, Renesas RX and RISC-V). The
|
|
# driver never calls the cross compiler, so the SBOM is produced for a target
|
|
# whose toolchain is absent.
|
|
#
|
|
# The wolfglass_drift job holds tools/sbom/ to the revision it claims, so the
|
|
# vendored copy cannot drift from wolfGlass unnoticed.
|
|
|
|
on:
|
|
push:
|
|
branches: [ 'master', 'main', 'release/**' ]
|
|
pull_request:
|
|
branches: [ '*' ]
|
|
|
|
jobs:
|
|
sbom_canary:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- name: Trust workspace
|
|
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
|
|
- name: Install tooling
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y cmake python3 build-essential clang
|
|
|
|
- name: Verify vendored gen-sbom
|
|
run: test -f tools/sbom/gen-sbom
|
|
|
|
# Reproducibility guard: a -D macro carrying an absolute host path (e.g.
|
|
# arch.mk's -DPICO_SDK_PATH=$(PICO_SDK_PATH)) must never reach the SBOM.
|
|
- name: Path scrub check
|
|
run: |
|
|
printf 'src/image.c\n' > /tmp/scrub-srcs.txt
|
|
tools/sbom/sbom-driver \
|
|
--srcs-file /tmp/scrub-srcs.txt \
|
|
--cflags "-DWOLFBOOT_HASH_SHA256 -DPICO_SDK_PATH=/home/ci-secret/pico-sdk" \
|
|
--name wolfboot --version 0.0.0-scrubtest \
|
|
--cdx-out /tmp/scrub.cdx.json --spdx-out /tmp/scrub.spdx.json
|
|
if grep -q 'ci-secret' /tmp/scrub.cdx.json /tmp/scrub.spdx.json; then
|
|
echo "ERROR: absolute host path leaked into the SBOM (scrub failed)." >&2
|
|
exit 1
|
|
fi
|
|
grep -q 'PICO_SDK_PATH' /tmp/scrub.cdx.json || {
|
|
echo "ERROR: PICO_SDK_PATH macro was dropped entirely (should be redacted, not removed)." >&2
|
|
exit 1; }
|
|
echo "scrub OK: path redacted, macro key preserved"
|
|
|
|
- name: Make path - make sbom (sim)
|
|
run: |
|
|
cp config/examples/sim.config .config
|
|
make sbom TARGET=sim
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot --min-properties 20 \
|
|
wolfboot-*.cdx.json wolfboot-*.spdx.json
|
|
|
|
- name: CMake path - cmake --target sbom (sim)
|
|
run: |
|
|
export SOURCE_DATE_EPOCH=1700000000
|
|
rm -rf build-sim
|
|
cmake -S . -B build-sim -G "Unix Makefiles" \
|
|
-DWOLFBOOT_TARGET=sim -DARCH=HOST -DSIGN=ED25519 -DHASH=SHA256 \
|
|
-DWOLFBOOT_SECTOR_SIZE=256 -DWOLFBOOT_PARTITION_SIZE=0x6400 \
|
|
-DWOLFBOOT_PARTITION_BOOT_ADDRESS=0x08003000 \
|
|
-DWOLFBOOT_PARTITION_UPDATE_ADDRESS=0x08009400 \
|
|
-DWOLFBOOT_PARTITION_SWAP_ADDRESS=0x0800F800 \
|
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
|
cmake --build build-sim --target sbom
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot \
|
|
build-sim/wolfboot-*.cdx.json build-sim/wolfboot-*.spdx.json
|
|
|
|
- name: IAR extractor (method 7)
|
|
run: |
|
|
python3 tools/sbom/frontends/iar_sbom.py IDE/IAR/wolfboot.ewp \
|
|
--name wolfboot \
|
|
--driver tools/sbom/sbom-driver \
|
|
--version-file include/wolfboot/version.h \
|
|
--version-macro LIBWOLFBOOT_VERSION_STRING \
|
|
--cdx-out iar.cdx.json --spdx-out iar.spdx.json
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot \
|
|
iar.cdx.json iar.spdx.json
|
|
|
|
- name: compdb extractor (methods 6, 8-11)
|
|
run: |
|
|
python3 tools/sbom/frontends/compdb_sbom.py \
|
|
build-sim/compile_commands.json \
|
|
--name wolfboot \
|
|
--driver tools/sbom/sbom-driver \
|
|
--version-file include/wolfboot/version.h \
|
|
--version-macro LIBWOLFBOOT_VERSION_STRING \
|
|
--cdx-out compdb.cdx.json --spdx-out compdb.spdx.json
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot \
|
|
compdb.cdx.json compdb.spdx.json
|
|
|
|
- name: Per-HAL SBOM (make sbom-hal)
|
|
run: |
|
|
cp config/examples/sim.config .config
|
|
make sbom-hal TARGET=sim
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot-hal- \
|
|
wolfboot-hal-sim-*.cdx.json wolfboot-hal-sim-*.spdx.json
|
|
|
|
- name: Zephyr module SBOM
|
|
run: |
|
|
python3 tools/sbom/frontends/zephyr_sbom.py \
|
|
--driver tools/sbom/sbom-driver \
|
|
--name wolfboot-zephyr \
|
|
--cmakelists zephyr/CMakeLists.txt \
|
|
--version-file include/wolfboot/version.h \
|
|
--version-macro LIBWOLFBOOT_VERSION_STRING \
|
|
--cdx-out zephyr.cdx.json --spdx-out zephyr.spdx.json
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot-zephyr \
|
|
zephyr.cdx.json zephyr.spdx.json
|
|
|
|
- name: Make vs CMake equivalence (sim, advisory)
|
|
continue-on-error: true
|
|
run: |
|
|
export SOURCE_DATE_EPOCH=1700000000
|
|
cp config/examples/sim.config .config
|
|
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
|
|
make sbom TARGET=sim
|
|
cp wolfboot-*.cdx.json /tmp/make.cdx.json
|
|
cp wolfboot-*.spdx.json /tmp/make.spdx.json
|
|
rm -rf build-sim-compare
|
|
cmake -S . -B build-sim-compare -G "Unix Makefiles" \
|
|
-DWOLFBOOT_TARGET=sim -DARCH=HOST -DSIGN=ED25519 -DHASH=SHA256 \
|
|
-DWOLFBOOT_SECTOR_SIZE=256 -DWOLFBOOT_PARTITION_SIZE=0x6400 \
|
|
-DWOLFBOOT_PARTITION_BOOT_ADDRESS=0x08003000 \
|
|
-DWOLFBOOT_PARTITION_UPDATE_ADDRESS=0x08009400 \
|
|
-DWOLFBOOT_PARTITION_SWAP_ADDRESS=0x0800F800 \
|
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
|
cmake --build build-sim-compare --target sbom
|
|
cp build-sim-compare/wolfboot-*.cdx.json /tmp/cmake.cdx.json
|
|
cp build-sim-compare/wolfboot-*.spdx.json /tmp/cmake.spdx.json
|
|
diff -u /tmp/make.cdx.json /tmp/cmake.cdx.json
|
|
diff -u /tmp/make.spdx.json /tmp/cmake.spdx.json
|
|
|
|
# Toolchain neutrality: the cross-toolchain that builds the firmware must
|
|
# not change the SBOM. Capture the same config with gcc and with clang and
|
|
# require a byte-identical result. SOURCE_DATE_EPOCH is fixed so the two
|
|
# runs are comparable.
|
|
- name: Toolchain neutrality (gcc vs clang)
|
|
run: |
|
|
export SOURCE_DATE_EPOCH=1700000000
|
|
cp config/examples/sim.config .config
|
|
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
|
|
make sbom TARGET=sim HOSTCC=gcc
|
|
mv wolfboot-*.cdx.json /tmp/gcc.cdx.json
|
|
mv wolfboot-*.spdx.json /tmp/gcc.spdx.json
|
|
make sbom TARGET=sim HOSTCC=clang
|
|
mv wolfboot-*.cdx.json /tmp/clang.cdx.json
|
|
mv wolfboot-*.spdx.json /tmp/clang.spdx.json
|
|
if ! diff -u /tmp/gcc.cdx.json /tmp/clang.cdx.json \
|
|
|| ! diff -u /tmp/gcc.spdx.json /tmp/clang.spdx.json; then
|
|
echo "ERROR: gcc and clang produced different SBOMs." >&2
|
|
echo "The SBOM must not depend on the toolchain." >&2
|
|
exit 1
|
|
fi
|
|
echo "neutrality OK: gcc and clang SBOMs are byte-identical"
|
|
|
|
# Reproducibility: the same configuration built from a different absolute
|
|
# path must give a byte-identical SBOM. This catches any absolute build
|
|
# path that leaks into the source list or the config record.
|
|
- name: Reproducibility (path independence)
|
|
run: |
|
|
export SOURCE_DATE_EPOCH=1700000000
|
|
cp config/examples/sim.config .config
|
|
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
|
|
make sbom TARGET=sim
|
|
cp wolfboot-*.cdx.json /tmp/repro-a.cdx.json
|
|
rm -rf /tmp/wb-copy && mkdir -p /tmp/wb-copy
|
|
cp -a . /tmp/wb-copy/
|
|
git config --global --add safe.directory /tmp/wb-copy
|
|
( cd /tmp/wb-copy \
|
|
&& export SOURCE_DATE_EPOCH=1700000000 \
|
|
&& rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json \
|
|
&& make sbom TARGET=sim )
|
|
cp /tmp/wb-copy/wolfboot-*.cdx.json /tmp/repro-b.cdx.json
|
|
if ! diff -u /tmp/repro-a.cdx.json /tmp/repro-b.cdx.json; then
|
|
echo "ERROR: SBOM is not reproducible across build paths." >&2
|
|
exit 1
|
|
fi
|
|
echo "reproducibility OK: identical SBOM from two paths"
|
|
|
|
# Path scrub in a real build: rp2350 injects -DPICO_SDK_PATH=<abs path>
|
|
# through arch.mk. The absolute path must be redacted while the macro key
|
|
# is kept. This exercises the scrub on a genuine build, not a synthetic
|
|
# command line.
|
|
- name: Path scrub in a real build (rp2350 / PICO_SDK_PATH)
|
|
run: |
|
|
cp config/examples/rp2350.config .config
|
|
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
|
|
PICO_SDK_PATH=/home/ci-secret/pico-sdk \
|
|
make sbom TARGET=rp2350
|
|
if grep -q 'ci-secret' wolfboot-*.cdx.json wolfboot-*.spdx.json; then
|
|
echo "ERROR: absolute PICO_SDK_PATH leaked in a real rp2350 build." >&2
|
|
exit 1
|
|
fi
|
|
grep -q 'PICO_SDK_PATH' wolfboot-*.cdx.json || {
|
|
echo "ERROR: PICO_SDK_PATH macro was dropped (should be redacted)." >&2
|
|
exit 1; }
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot --min-properties 20 \
|
|
wolfboot-*.cdx.json wolfboot-*.spdx.json
|
|
echo "rp2350 scrub OK: path redacted, macro key preserved"
|
|
|
|
- name: Upload SBOM artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: wolfboot-sboms
|
|
path: |
|
|
wolfboot-*.cdx.json
|
|
wolfboot-*.spdx.json
|
|
build-sim/wolfboot-*.cdx.json
|
|
build-sim/wolfboot-*.spdx.json
|
|
iar.cdx.json
|
|
iar.spdx.json
|
|
compdb.cdx.json
|
|
compdb.spdx.json
|
|
zephyr.cdx.json
|
|
zephyr.spdx.json
|
|
if-no-files-found: warn
|
|
|
|
# Proves the source route (make sbom) works for a spread of embedded targets
|
|
# with no IDE and no cross-toolchain installed. The driver never calls the
|
|
# cross compiler, so every target below produces a valid SBOM on a plain
|
|
# runner. This is the "any target, any toolchain, no hardware" guarantee.
|
|
cross_targets:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# A spread of architectures whose cross-toolchains are NOT installed:
|
|
# stm32h7 Arm Cortex-M7 nrf52840 Arm Cortex-M4
|
|
# imx-rt1060 Arm Cortex-M7 sama5d3 Arm Cortex-A5
|
|
# nxp-t1040 PowerPC e5500 renesas-rx65n Renesas RX
|
|
# hifive1 RISC-V
|
|
#
|
|
# These are config file names, which are not always the TARGET the file
|
|
# selects: nrf52840.config sets TARGET=nrf52, imx-rt1060.config sets
|
|
# imx_rt, nxp-t1040.config sets nxp_t1040, renesas-rx65n.config sets
|
|
# rx65n. The config is the only source of truth, so the step below does
|
|
# not pass TARGET at all.
|
|
config:
|
|
- stm32h7
|
|
- nrf52840
|
|
- imx-rt1060
|
|
- sama5d3
|
|
- nxp-t1040
|
|
- renesas-rx65n
|
|
- hifive1
|
|
include:
|
|
# imx_rt compiles three MCUXpresso SDK drivers (fsl_clock, fsl_cache,
|
|
# fsl_flexspi) from outside the source tree. The SDK is not on a
|
|
# runner, so this target cannot produce a complete source set here.
|
|
- config: imx-rt1060
|
|
allow_missing: 1
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- name: Trust workspace
|
|
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
|
|
- name: Install tooling
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y python3
|
|
|
|
- name: Verify vendored gen-sbom
|
|
run: test -f tools/sbom/gen-sbom
|
|
|
|
# No TARGET= here on purpose. The config file sets it, and passing the
|
|
# config file name instead selects a HAL that does not exist: TARGET=
|
|
# nrf52840 asks for hal/nrf52840.c, which is really hal/nrf52.c, and the
|
|
# SBOM then described this bootloader with no target HAL at all (29
|
|
# sources, hal.c but no nrf52.c) while CI stayed green.
|
|
- name: make sbom (no cross-toolchain present)
|
|
env:
|
|
SBOM_ALLOW_MISSING: ${{ matrix.allow_missing }}
|
|
run: |
|
|
cp config/examples/${{ matrix.config }}.config .config
|
|
make sbom
|
|
python3 tools/sbom/validate_sbom.py \
|
|
--name-prefix wolfboot --min-properties 20 \
|
|
wolfboot-*.cdx.json wolfboot-*.spdx.json
|
|
|
|
- name: Upload SBOM artifact
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: wolfboot-sbom-${{ matrix.config }}
|
|
path: |
|
|
wolfboot-*.cdx.json
|
|
wolfboot-*.spdx.json
|
|
if-no-files-found: warn
|
|
|
|
# tools/sbom/ is a vendored copy of the wolfGlass share/ set, pinned by
|
|
# tools/sbom/.wolfglass-rev. Nothing else proves the copy still matches the
|
|
# pin, so a stale copy, a local patch, or an unresolvable revision would all
|
|
# pass unnoticed and wolfBoot would ship a different generator from the one
|
|
# wolfSSL controls. wolfGlass owns the comparison: tools/wolfglass-sync
|
|
# --check diffs share/ against the vendored tree and compares HEAD against
|
|
# .wolfglass-rev.
|
|
wolfglass_drift:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Read the pinned wolfGlass revision
|
|
id: pin
|
|
run: |
|
|
rev="$(tr -d '[:space:]' < tools/sbom/.wolfglass-rev)"
|
|
if [ -z "$rev" ]; then
|
|
echo "ERROR: tools/sbom/.wolfglass-rev is empty." >&2
|
|
exit 1
|
|
fi
|
|
echo "rev=$rev" >> "$GITHUB_OUTPUT"
|
|
echo "Pinned wolfGlass revision: $rev"
|
|
|
|
# A fork PR has no secret, so the pinned revision cannot be fetched. Skip
|
|
# rather than fail there; the run on wolfSSL/wolfBoot is the gate.
|
|
- name: Check out wolfGlass at the pinned revision
|
|
id: fetch
|
|
continue-on-error: true
|
|
uses: actions/checkout@v4
|
|
with:
|
|
repository: wolfSSL/wolfGlass
|
|
ref: ${{ steps.pin.outputs.rev }}
|
|
token: ${{ secrets.WOLFGLASS_TOKEN }}
|
|
path: .wolfglass-src
|
|
|
|
- name: Drift check against wolfGlass
|
|
env:
|
|
HAVE_TOKEN: ${{ secrets.WOLFGLASS_TOKEN != '' }}
|
|
PINNED_REV: ${{ steps.pin.outputs.rev }}
|
|
run: |
|
|
if [ ! -d .wolfglass-src/share ]; then
|
|
if [ "$HAVE_TOKEN" != "true" ]; then
|
|
echo "::notice::No WOLFGLASS_TOKEN available (fork PR); skipping the wolfGlass drift check."
|
|
exit 0
|
|
fi
|
|
echo "ERROR: wolfGlass revision $PINNED_REV does not resolve." >&2
|
|
echo "tools/sbom/.wolfglass-rev must name a commit that exists in" >&2
|
|
echo "wolfSSL/wolfGlass. Re-run tools/wolfglass-sync to re-pin." >&2
|
|
exit 1
|
|
fi
|
|
git config --global --add safe.directory "$GITHUB_WORKSPACE/.wolfglass-src"
|
|
python3 .wolfglass-src/tools/wolfglass-sync \
|
|
--check --src .wolfglass-src --dest .
|
|
|
|
windows_sbom:
|
|
runs-on: windows-latest
|
|
timeout-minutes: 10
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.x'
|
|
|
|
- name: Native driver path scrub
|
|
shell: pwsh
|
|
run: |
|
|
Set-Content -Path scrub-srcs.txt -Value "src/image.c"
|
|
python tools/sbom/sbom-driver.py `
|
|
--srcs-file scrub-srcs.txt `
|
|
--cflags "-DWOLFBOOT_HASH_SHA256 -DSDK_PATH=C:\Users\ci-secret\sdk -DSDK_SHARE=\\server\share\sdk" `
|
|
--name wolfboot `
|
|
--version-file include/wolfboot/version.h `
|
|
--version-macro LIBWOLFBOOT_VERSION_STRING `
|
|
--cdx-out wolfboot-win.cdx.json `
|
|
--spdx-out wolfboot-win.spdx.json
|
|
if (Select-String -Path wolfboot-win.cdx.json,wolfboot-win.spdx.json -Pattern 'ci-secret|server\\share' -Quiet) {
|
|
throw "Windows absolute path leaked into the SBOM."
|
|
}
|
|
python tools/sbom/validate_sbom.py --name-prefix wolfboot wolfboot-win.cdx.json wolfboot-win.spdx.json
|
|
|
|
- name: Upload Windows SBOM artifact
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: wolfboot-sbom-windows
|
|
path: |
|
|
wolfboot-win.cdx.json
|
|
wolfboot-win.spdx.json
|
|
if-no-files-found: warn
|