Skip to content

Commit df63948

Browse files
committed
sbom: expand CI to prove toolchain neutrality and cross-target coverage
The SBOM canary now guards the properties customers rely on, not only that each route runs. sbom_canary job adds three checks: * Toolchain neutrality - build the same sim config with gcc and with clang and require a byte-identical CycloneDX and SPDX result. The driver captures configuration with the host compiler and a source list, so the cross-toolchain that builds the firmware does not change the SBOM. clang, LLVM and vendor compilers need no separate front end. * Reproducibility - build the same config from a second absolute path and require an identical SBOM, so no build path leaks into the output. * Path scrub in a real build - build rp2350 with an absolute PICO_SDK_PATH and assert the path is redacted while the macro key is kept. This exercises the scrub through arch.mk, not a synthetic line. New cross_targets job runs make sbom for a spread of architectures with no IDE and no cross-toolchain installed: stm32h7, nrf52840, imx-rt1060 and sama5d3 (Arm), nxp-t1040 (PowerPC), renesas-rx65n (Renesas RX) and hifive1 (RISC-V). The driver never calls the cross compiler, so each target produces a valid SBOM on a plain runner. This proves the "any target, any toolchain, no hardware" guarantee. Every produced document is checked with validate_sbom.py and uploaded as a build artifact. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
1 parent 9380897 commit df63948

1 file changed

Lines changed: 160 additions & 1 deletion

File tree

.github/workflows/test-sbom.yml

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ name: Wolfboot SBOM Canary
1313
#
1414
# Each route must emit a schema-valid CycloneDX 1.6 + SPDX 2.3 document whose
1515
# top-level component is wolfboot.
16+
#
17+
# The sbom_canary job also guards three properties of the SBOM itself:
18+
#
19+
# * Toolchain neutrality - gcc and clang must give a byte-identical SBOM.
20+
# The driver captures configuration with the host compiler and the source
21+
# list, so the cross-toolchain that builds the firmware does not change the
22+
# SBOM. No per-compiler front end is needed for clang, LLVM or a vendor
23+
# compiler.
24+
# * Reproducibility - the same configuration built from a different absolute
25+
# path gives a byte-identical SBOM.
26+
# * Path scrub - an absolute host path in a -D macro (the synthetic check and
27+
# a real rp2350 build with PICO_SDK_PATH) must not reach the SBOM.
28+
#
29+
# The cross_targets job proves the same source route works for many embedded
30+
# targets with no IDE and no cross-toolchain installed. It runs make sbom for a
31+
# spread of architectures (Arm-M, Arm-A, PowerPC, Renesas RX and RISC-V). The
32+
# driver never calls the cross compiler, so the SBOM is produced for a target
33+
# whose toolchain is absent.
1634

1735
on:
1836
push:
@@ -36,7 +54,7 @@ jobs:
3654
- name: Install tooling
3755
run: |
3856
sudo apt-get update
39-
sudo apt-get install -y cmake python3 build-essential
57+
sudo apt-get install -y cmake python3 build-essential clang
4058
4159
# gen-sbom ships with wolfSSL. Use the vendored submodule copy if the
4260
# pinned revision already carries it, otherwise fetch it from wolfSSL
@@ -128,6 +146,78 @@ jobs:
128146
python3 tools/scripts/ide-sbom/validate_sbom.py \
129147
zephyr.cdx.json zephyr.spdx.json
130148
149+
# Toolchain neutrality: the cross-toolchain that builds the firmware must
150+
# not change the SBOM. Capture the same config with gcc and with clang and
151+
# require a byte-identical result. SOURCE_DATE_EPOCH is fixed so the two
152+
# runs are comparable.
153+
- name: Toolchain neutrality (gcc vs clang)
154+
run: |
155+
export SOURCE_DATE_EPOCH=1700000000
156+
cp config/examples/sim.config .config
157+
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
158+
make sbom TARGET=sim HOSTCC=gcc \
159+
GEN_SBOM="${{ steps.gensbom.outputs.path }}"
160+
mv wolfboot-*.cdx.json /tmp/gcc.cdx.json
161+
mv wolfboot-*.spdx.json /tmp/gcc.spdx.json
162+
make sbom TARGET=sim HOSTCC=clang \
163+
GEN_SBOM="${{ steps.gensbom.outputs.path }}"
164+
mv wolfboot-*.cdx.json /tmp/clang.cdx.json
165+
mv wolfboot-*.spdx.json /tmp/clang.spdx.json
166+
if ! diff -u /tmp/gcc.cdx.json /tmp/clang.cdx.json \
167+
|| ! diff -u /tmp/gcc.spdx.json /tmp/clang.spdx.json; then
168+
echo "ERROR: gcc and clang produced different SBOMs." >&2
169+
echo "The SBOM must not depend on the toolchain." >&2
170+
exit 1
171+
fi
172+
echo "neutrality OK: gcc and clang SBOMs are byte-identical"
173+
174+
# Reproducibility: the same configuration built from a different absolute
175+
# path must give a byte-identical SBOM. This catches any absolute build
176+
# path that leaks into the source list or the config record.
177+
- name: Reproducibility (path independence)
178+
run: |
179+
export SOURCE_DATE_EPOCH=1700000000
180+
cp config/examples/sim.config .config
181+
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
182+
make sbom TARGET=sim GEN_SBOM="${{ steps.gensbom.outputs.path }}"
183+
cp wolfboot-*.cdx.json /tmp/repro-a.cdx.json
184+
rm -rf /tmp/wb-copy && mkdir -p /tmp/wb-copy
185+
cp -a . /tmp/wb-copy/
186+
git config --global --add safe.directory /tmp/wb-copy
187+
( cd /tmp/wb-copy \
188+
&& export SOURCE_DATE_EPOCH=1700000000 \
189+
&& rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json \
190+
&& make sbom TARGET=sim \
191+
GEN_SBOM="${{ steps.gensbom.outputs.path }}" )
192+
cp /tmp/wb-copy/wolfboot-*.cdx.json /tmp/repro-b.cdx.json
193+
if ! diff -u /tmp/repro-a.cdx.json /tmp/repro-b.cdx.json; then
194+
echo "ERROR: SBOM is not reproducible across build paths." >&2
195+
exit 1
196+
fi
197+
echo "reproducibility OK: identical SBOM from two paths"
198+
199+
# Path scrub in a real build: rp2350 injects -DPICO_SDK_PATH=<abs path>
200+
# through arch.mk. The absolute path must be redacted while the macro key
201+
# is kept. This exercises the scrub on a genuine build, not a synthetic
202+
# command line.
203+
- name: Path scrub in a real build (rp2350 / PICO_SDK_PATH)
204+
run: |
205+
cp config/examples/rp2350.config .config
206+
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
207+
PICO_SDK_PATH=/home/ci-secret/pico-sdk \
208+
make sbom TARGET=rp2350 \
209+
GEN_SBOM="${{ steps.gensbom.outputs.path }}"
210+
if grep -q 'ci-secret' wolfboot-*.cdx.json wolfboot-*.spdx.json; then
211+
echo "ERROR: absolute PICO_SDK_PATH leaked in a real rp2350 build." >&2
212+
exit 1
213+
fi
214+
grep -q 'PICO_SDK_PATH' wolfboot-*.cdx.json || {
215+
echo "ERROR: PICO_SDK_PATH macro was dropped (should be redacted)." >&2
216+
exit 1; }
217+
python3 tools/scripts/ide-sbom/validate_sbom.py \
218+
wolfboot-*.cdx.json wolfboot-*.spdx.json
219+
echo "rp2350 scrub OK: path redacted, macro key preserved"
220+
131221
- name: Upload SBOM artifacts
132222
if: always()
133223
uses: actions/upload-artifact@v4
@@ -145,3 +235,72 @@ jobs:
145235
zephyr.cdx.json
146236
zephyr.spdx.json
147237
if-no-files-found: warn
238+
239+
# Proves the source route (make sbom) works for a spread of embedded targets
240+
# with no IDE and no cross-toolchain installed. The driver never calls the
241+
# cross compiler, so every target below produces a valid SBOM on a plain
242+
# runner. This is the "any target, any toolchain, no hardware" guarantee.
243+
cross_targets:
244+
runs-on: ubuntu-latest
245+
timeout-minutes: 15
246+
strategy:
247+
fail-fast: false
248+
matrix:
249+
# A spread of architectures whose cross-toolchains are NOT installed:
250+
# stm32h7 Arm Cortex-M7 nrf52840 Arm Cortex-M4
251+
# imx-rt1060 Arm Cortex-M7 sama5d3 Arm Cortex-A5
252+
# nxp-t1040 PowerPC e5500 renesas-rx65n Renesas RX
253+
# hifive1 RISC-V
254+
target:
255+
- stm32h7
256+
- nrf52840
257+
- imx-rt1060
258+
- sama5d3
259+
- nxp-t1040
260+
- renesas-rx65n
261+
- hifive1
262+
263+
steps:
264+
- uses: actions/checkout@v4
265+
with:
266+
submodules: true
267+
268+
- name: Trust workspace
269+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
270+
271+
- name: Install tooling
272+
run: |
273+
sudo apt-get update
274+
sudo apt-get install -y python3
275+
276+
- name: Locate gen-sbom
277+
id: gensbom
278+
run: |
279+
if [ -f lib/wolfssl/scripts/gen-sbom ]; then
280+
echo "path=$GITHUB_WORKSPACE/lib/wolfssl/scripts/gen-sbom" >> "$GITHUB_OUTPUT"
281+
else
282+
mkdir -p .sbom-tools
283+
curl -fsSL \
284+
https://raw.githubusercontent.com/wolfSSL/wolfssl/master/scripts/gen-sbom \
285+
-o .sbom-tools/gen-sbom
286+
chmod +x .sbom-tools/gen-sbom
287+
echo "path=$GITHUB_WORKSPACE/.sbom-tools/gen-sbom" >> "$GITHUB_OUTPUT"
288+
fi
289+
290+
- name: make sbom (no cross-toolchain present)
291+
run: |
292+
cp config/examples/${{ matrix.target }}.config .config
293+
make sbom TARGET=${{ matrix.target }} \
294+
GEN_SBOM="${{ steps.gensbom.outputs.path }}"
295+
python3 tools/scripts/ide-sbom/validate_sbom.py \
296+
wolfboot-*.cdx.json wolfboot-*.spdx.json
297+
298+
- name: Upload SBOM artifact
299+
if: always()
300+
uses: actions/upload-artifact@v4
301+
with:
302+
name: wolfboot-sbom-${{ matrix.target }}
303+
path: |
304+
wolfboot-*.cdx.json
305+
wolfboot-*.spdx.json
306+
if-no-files-found: warn

0 commit comments

Comments
 (0)