Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
398 changes: 398 additions & 0 deletions docs/specs/2026-09-08-sbom-per-package-sidecar-design.md

Large diffs are not rendered by default.

169 changes: 169 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,115 @@ 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 extract_dir
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)"

#
# Scan the .deb's extracted payload rather than the .deb file.
# "syft scan <file>.deb" only identifies the archive: its
# deb-archive-cataloger reads the control metadata and emits a
# single pkg:deb component, never descending into data.tar.*,
# so none of the bundled jars/wheels/modules this sidecar
# exists to capture are found. That produced valid but empty
# documents -- one component for a 1.2GB application -- which
# is no more than appliance-build's image-level dpkg scan
# already gives for free. Extracting first is what the design
# spec prescribed for exactly this case.
#
extract_dir="$(logmust mktemp -d)"
logmust dpkg-deb -x "$deb" "$extract_dir"

#
# Full catalogers here, deliberately unlike appliance-build's
# 95-generate-sbom.binary hook, which restricts to dpkg. That
# restriction exists because a jar sitting somewhere on a whole
# rootfs cannot be attributed to the package that placed it;
# within a single package's own extracted payload everything
# found belongs to that package by construction, which is the
# entire point of scanning here rather than at image level.
#
# SYFT_FILE_METADATA_SELECTION=none suppresses Syft's default
# per-file "file" component (with SHA-1/SHA-256 hashes and the
# scanned path baked in) -- with a whole extracted payload to
# walk that would otherwise emit an entry per file.
#
SYFT_FILE_METADATA_SELECTION=none logmust syft scan "dir:$extract_dir" \
--source-name "$PACKAGE" \
--source-version "$deb_version" \
-o "cyclonedx-json@1.6=$sbom_file"

logmust rm -rf "$extract_dir"

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
Loading