Skip to content
Draft
34 changes: 34 additions & 0 deletions .github/scripts/verify-sbom-scan-flag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash -ex

set -o pipefail

cd "$(git rev-parse --show-toplevel)"

#
# Every package must explicitly set SBOM_DEEP_SCAN to "true" or "false" in
# its config.sh -- see docs on generate_sbom() in lib/common.sh. There is
# no default: a package that hasn't been classified yet must fail CI
# rather than silently ship without a CycloneDX sidecar or without an
# explicit decision that it doesn't need one.
#
unclassified=$(./query-packages.sh list -o name,sbom-deep-scan all |
awk -F'\t' '$2 == "none" { print $1 }')

if [[ -n "$unclassified" ]]; then
echo "The following packages have not set SBOM_DEEP_SCAN (\"true\" or" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add some context in this error that generally, 3rd-party forks of debian packages, or packages that are not included in a shipping product should set this to false, while 1st-party packages should set this to true so that their internal packaged components are included in the product's aggregate sbom.

"\"false\") in their config.sh:"
echo "$unclassified"
echo
echo "Set it to \"true\" for 1st-party packages, so that the" \
"third-party components they package internally (jars, npm" \
"modules, Rust crates, ...) are included in the product's" \
"aggregate SBOM."
echo "Set it to \"false\" for 3rd-party forks of Debian packages, and" \
"for packages that are not included in a shipping product:" \
"those are already covered as a flat pkg:deb component by" \
"appliance-build's image-level scan, so a deep scan here would" \
"add nothing."
exit 1
fi

echo "All packages have classified SBOM_DEEP_SCAN"
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ jobs:
steps:
- uses: actions/checkout@v1
- run: ./.github/scripts/verify-query-packages.sh
verify-sbom-scan-flag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: ./.github/scripts/verify-sbom-scan-flag.sh
3 changes: 3 additions & 0 deletions buildpkg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ stage build
logmust cd "$WORKDIR"
stage store_build_info

logmust cd "$WORKDIR"
stage generate_sbom

logmust cd "$WORKDIR"
stage post_build_checks

Expand Down
350 changes: 350 additions & 0 deletions docs/specs/2026-09-08-sbom-per-package-sidecar-design.md

Large diffs are not rendered by default.

144 changes: 144 additions & 0 deletions lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,66 @@ function install_shfmt() {
echo "shfmt version $(shfmt -version) is installed."
}

#
# Install the tooling generate_sbom() needs. That is a default hook, shared
# unmodified by every package that sets SBOM_DEEP_SCAN, so the tools it runs
# belong with the rest of the generic build tooling installed here rather than
# being declared as a dependency by each of those packages individually.
#
# Unlike everything else installed from setup.sh these are Delphix-built
# packages with no apt source, so they are fetched from the same S3 location
# fetch_dependencies() pulls a package's dependencies from, then installed by
# path. apt rather than dpkg, because delphix-cyclonedx-cli has real
# dependencies (libicu and the usual shared libraries) that dpkg will not
# resolve.
#
# Best-effort by design: this runs before *every* package build, including the
# builds of syft and cyclonedx-cli themselves, and on a branch where neither
# has been published yet there is nothing to fetch. Failing hard would break
# every build on such a branch rather than just SBOM generation, so a missing
# artifact warns and moves on; generate_sbom() checks for the tools itself and
# fails loudly, for the only builds that actually need them.
#
function install_sbom_tools() {
local pkg s3url tmpdir
local debs=()

tmpdir="$(mktemp -d)" || die "Failed to create a temporary directory"

for pkg in syft cyclonedx-cli; do
#
# Run in a command substitution so that a failure to resolve
# the URL (get_package_dependency_s3_url dies when a package
# has no published artifacts) leaves $s3url empty here instead
# of aborting setup.
#
s3url="$(
get_package_dependency_s3_url "$pkg" >/dev/null 2>&1
echo "$_RET"
)"
if [[ -z "$s3url" ]]; then
echo "WARNING: no published artifacts found for '$pkg';" \
"skipping the SBOM tooling install. Builds of" \
"packages that set SBOM_DEEP_SCAN will fail until" \
"'$pkg' has been built for this branch."
logmust rm -rf "$tmpdir"
return 0
fi

[[ "$s3url" != */ ]] && s3url="$s3url/"
logmust mkdir -p "$tmpdir/$pkg"
logmust aws s3 cp --only-show-errors --recursive \
"$s3url" "$tmpdir/$pkg/"
done

debs=("$tmpdir"/*/*.deb)
[[ -e "${debs[0]}" ]] ||
die "No .deb found in the fetched syft/cyclonedx-cli artifacts"

logmust install_pkgs "${debs[@]}"
logmust rm -rf "$tmpdir"
}

#
# Install kernel headers packages for all target kernels.
# The kernel packages are fetched from S3.
Expand Down Expand Up @@ -1461,6 +1521,90 @@ function store_build_info() {
fi
}

#
# Generate a CycloneDX SBOM sidecar for each of this package's built
# .deb(s) by running Syft against it. Only packages that bundle
# third-party composition (jars, npm, wheels, Rust crates, ...) opt in
# via SBOM_DEEP_SCAN="true" in their config.sh -- everything else is
# left as a flat pkg:deb component by appliance-build's base chroot
# scan, so a deb scan here would add nothing. Each <deb-filename>.cdx.json
# is dropped in $WORKDIR/artifacts/ alongside the .deb it describes --
# a strict 1:1 mapping, no merging across a package's .deb(s) -- where
# it's picked up by the same S3 sync as every other build artifact, no
# separate upload path needed.
#
function generate_sbom() {
if [[ "$SBOM_DEEP_SCAN" != "true" ]]; then
return 0
fi

local debs=("$WORKDIR/artifacts/"*.deb)
if [[ ! -e "${debs[0]}" ]]; then
die "SBOM_DEEP_SCAN is set but no .deb was found in" \
"'$WORKDIR/artifacts'"
fi

#
# syft/cyclonedx-cli are part of the generic build tooling installed
# by setup.sh (install_sbom_tools()) before any package is built,
# rather than something each SBOM_DEEP_SCAN package declares for
# itself -- this is a default hook, so what it needs is its own
# concern, not its callers'. That install is best-effort, so check
# here rather than letting "command not found" surface from the
# middle of a scan.
#
local tool
for tool in syft cyclonedx-cli; do
command -v "$tool" >/dev/null ||
die "'$tool' is not installed, so no SBOM can be" \
"generated for '$PACKAGE'. It is provisioned by" \
"install_sbom_tools() in setup.sh; check that" \
"run's output for why it was skipped."
done

#
# One sidecar per .deb, not per package: a package that emits more
# than one .deb (e.g. "zfs" splits into zfs-dkms, zfsutils-linux,
# etc.) gets one <deb-filename>.deb.cdx.json per .deb, each a
# standalone document scoped to that .deb alone. No merging across
# .debs -- keeps a strict 1:1 mapping between a .deb and its BOM,
# with the .deb's own filename as the common prefix.
#
local deb
for deb in "${debs[@]}"; do
local sbom_file deb_version
sbom_file="$WORKDIR/artifacts/$(basename "$deb").cdx.json"
#
# Read the version back out of the .deb itself, rather than
# relying on $PACKAGE_VERSION: by this point in the build,
# $PACKAGE_VERSION may no longer hold the final,
# revision-suffixed version set_changelog() wrote into the
# package (e.g. it's empty for packages that don't set it
# explicitly themselves, unlike syft/cyclonedx-cli's own
# config.sh). dpkg-deb reads the actual, authoritative
# version of the artifact being scanned.
#
deb_version="$(dpkg-deb -f "$deb" Version)"
#
# SYFT_FILE_METADATA_SELECTION=none suppresses Syft's default
# per-file "file" component (with SHA-1/SHA-256 hashes and the
# absolute build-workspace path baked in) -- noise that doesn't
# belong in a per-deb sidecar. Same reasoning as
# appliance-build's 95-generate-sbom.binary hook.
#
SYFT_FILE_METADATA_SELECTION=none logmust syft scan "$deb" \
--source-name "$PACKAGE" \
--source-version "$deb_version" \
-o "cyclonedx-json@1.6=$sbom_file"

logmust cyclonedx-cli validate \
--input-file "$sbom_file" \
--input-format json \
--input-version v1_6 \
--fail-on-errors
done
}

function set_secret_build_args() {
_SECRET_BUILD_ARGS=()

Expand Down
1 change: 1 addition & 0 deletions packages/bcc/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/bcc.git"
SBOM_DEEP_SCAN="false"

UPSTREAM_GIT_URL=https://github.com/iovisor/bcc.git
UPSTREAM_GIT_BRANCH=master
Expand Down
1 change: 1 addition & 0 deletions packages/challenge-response/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/challenge-response.git"
SBOM_DEEP_SCAN="false"

function prepare() {
install_build_deps_from_control_file
Expand Down
1 change: 1 addition & 0 deletions packages/cloud-init/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/cloud-init.git"
SBOM_DEEP_SCAN="false"

UPSTREAM_SOURCE_PACKAGE=cloud-init

Expand Down
1 change: 1 addition & 0 deletions packages/connstat/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/connstat.git"
SBOM_DEEP_SCAN="false"
PACKAGE_DEPENDENCIES="@linux-kernel dwarves"

function prepare() {
Expand Down
2 changes: 2 additions & 0 deletions packages/containerized-masking/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/dms-core-gate.git"
#
PACKAGE_NEEDS_DOCKER="true"
MEND_SCAN_APPLICABLE="true"
SBOM_DEEP_SCAN="true"

MEND_SCAN_IMAGES="'delphix-masking-proxy', 'delphix-masking-database', 'delphix-masking-app'"

SKIP_COPYRIGHTS_CHECK=true
Expand Down
1 change: 1 addition & 0 deletions packages/crash-python/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# shellcheck disable=SC2034
DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/crash-python.git"
SBOM_DEEP_SCAN="false"

function prepare() {
logmust install_build_deps_from_control_file
Expand Down
2 changes: 2 additions & 0 deletions packages/crypt-blowfish/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/crypt-blowfish.git"
SBOM_DEEP_SCAN="false"

SKIP_COPYRIGHTS_CHECK=true

function build() {
Expand Down
1 change: 1 addition & 0 deletions packages/cyclonedx-cli/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/cyclonedx-cli.git"
SBOM_DEEP_SCAN="false"

function build() {
logmust mkdir -p "$WORKDIR/repo"
Expand Down
1 change: 1 addition & 0 deletions packages/delphix-go/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/delphix-go.git"
SBOM_DEEP_SCAN="false"

function build() {
logmust mkdir -p "$WORKDIR/repo"
Expand Down
1 change: 1 addition & 0 deletions packages/delphix-kernel/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/delphix-kernel.git"
SBOM_DEEP_SCAN="false"
PACKAGE_DEPENDENCIES="@linux-kernel"

function prepare() {
Expand Down
1 change: 1 addition & 0 deletions packages/delphix-platform/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/delphix-platform.git"
SBOM_DEEP_SCAN="false"

function prepare() {
logmust cd "$WORKDIR/repo"
Expand Down
1 change: 1 addition & 0 deletions packages/delphix-rust/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/delphix-rust.git"
SBOM_DEEP_SCAN="true"

function build() {
logmust mkdir -p "$WORKDIR/repo"
Expand Down
1 change: 1 addition & 0 deletions packages/delphix-sso-app/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/saml-app.git"
MEND_SCAN_APPLICABLE="true"
MEND_SCAN_USING_SUDO="true"
SBOM_DEEP_SCAN="true"

function prepare() {
logmust install_pkgs openjdk-17-jdk-headless:
Expand Down
2 changes: 2 additions & 0 deletions packages/docker-python-image/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/docker-python-image.git"
SBOM_DEEP_SCAN="false"

#
# debian/rules' override_dh_install runs 'docker pull' to fetch the python
# image it repackages, so the build needs a docker daemon. The build container
Expand Down
1 change: 1 addition & 0 deletions packages/drgn/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# shellcheck disable=SC2034
DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/drgn.git"
SBOM_DEEP_SCAN="false"
PACKAGE_DEPENDENCIES="libkdumpfile"

UPSTREAM_GIT_URL="https://github.com/osandov/drgn.git"
Expand Down
1 change: 1 addition & 0 deletions packages/dwarves/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/dwarves.git"
SBOM_DEEP_SCAN="false"

UPSTREAM_GIT_URL="https://github.com/acmel/dwarves.git"
UPSTREAM_GIT_BRANCH="master"
Expand Down
1 change: 1 addition & 0 deletions packages/fluentd-gems/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/fluentd-gems.git"
SBOM_DEEP_SCAN="false"

function build() {
logmust mkdir -p "$WORKDIR/repo"
Expand Down
1 change: 1 addition & 0 deletions packages/gdb-python/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# shellcheck disable=SC2034
DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/gdb-python.git"
SBOM_DEEP_SCAN="false"
PACKAGE_DEPENDENCIES="libkdumpfile"

function prepare() {
Expand Down
1 change: 1 addition & 0 deletions packages/grub2/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL=none
SBOM_DEEP_SCAN="false"
SKIP_COPYRIGHTS_CHECK=true

URI="s3://release-de-images/internal-artifacts/2025.3.0.1/1.0.53/input-artifacts/combined-packages/packages/grub2"
Expand Down
1 change: 1 addition & 0 deletions packages/host-jdks/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# shellcheck disable=SC2034

DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/host-jdks.git"
SBOM_DEEP_SCAN="false"

function build() {
logmust mkdir -p "$WORKDIR/repo"
Expand Down
1 change: 1 addition & 0 deletions packages/libkdumpfile/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# shellcheck disable=SC2034
DEFAULT_PACKAGE_GIT_URL="https://github.com/delphix/libkdumpfile.git"
SBOM_DEEP_SCAN="false"

UPSTREAM_GIT_URL="https://codeberg.org/ptesarik/libkdumpfile.git"
UPSTREAM_GIT_BRANCH="tip"
Expand Down
3 changes: 3 additions & 0 deletions packages/linux-kernel-aws/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ default)
die "invalid linux-kernel package source '$linux_package_source'"
;;
esac

# shellcheck disable=SC2034
SBOM_DEEP_SCAN="false"
3 changes: 3 additions & 0 deletions packages/linux-kernel-azure/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ default)
die "invalid linux-kernel package source '$linux_package_source'"
;;
esac

# shellcheck disable=SC2034
SBOM_DEEP_SCAN="false"
Loading