Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .azure-pipelines/azure-pipelines-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ jobs:
if echo $(GROUP_NAME) | grep mellanox; then
BUILD_OPTIONS="$BUILD_OPTIONS INCLUDE_DHCP_SERVER=y"
fi
# ENABLE_SBOM=y emits CycloneDX SBOMs alongside each .bin /
# .img / .swi / .tar and standalone SBOMs for docker-ptf /
# docker-ptf-sai / docker-sonic-mgmt when those are built.
BUILD_OPTIONS="$BUILD_OPTIONS ENABLE_SBOM=y"
if [ $(GROUP_NAME) == pensando ]; then
make $BUILD_OPTIONS target/sonic-pensando.tar
elif [ $(GROUP_NAME) == vs ]; then
Expand Down
88 changes: 77 additions & 11 deletions .azure-pipelines/build-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,54 +83,120 @@ jobs:
sudo apt-get install -y acl
export DOCKER_DATA_ROOT_FOR_MULTIARCH=/data/march/docker
CACHE_OPTIONS="SONIC_DPKG_CACHE_METHOD=${{ parameters.cache_mode }} SONIC_DPKG_CACHE_SOURCE=/nfs/dpkg_cache/${{ parameters.platform }}"
ENABLE_DOCKER_BASE_PULL=y make configure PLATFORM=${{ parameters.platform }} PLATFORM_ARCH=${{ parameters.platform_arch }}
# ENABLE_SBOM=y emits CycloneDX SBOMs alongside every .bin and
# standalone SBOMs for docker-ptf / docker-ptf-sai.
ENABLE_DOCKER_BASE_PULL=y ENABLE_SBOM=y make configure PLATFORM=${{ parameters.platform }} PLATFORM_ARCH=${{ parameters.platform_arch }}
trap "sudo rm -rf fsroot" EXIT

if [ ${{ parameters.platform }} == vs ]; then
if [ ${{ parameters.dbg_image }} == true ]; then
make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) INSTALL_DEBUG_TOOLS=y target/sonic-vs.img.gz && \
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) INSTALL_DEBUG_TOOLS=y target/sonic-vs.img.gz && \
mv target/sonic-vs.img.gz target/sonic-vs-dbg.img.gz
fi

make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) target/docker-sonic-vs.gz target/sonic-vs.img.gz target/docker-ptf.gz
make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) target/docker-ptf-sai.gz
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) target/docker-sonic-vs.gz target/sonic-vs.img.gz target/docker-ptf.gz
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) target/docker-ptf-sai.gz
elif [ ${{ parameters.platform }} == alpinevs ]; then
make USERNAME=admin SONIC_BUILD_JOB=2 $CACHE_OPTIONS target/sonic-alpinevs.img.gz
make USERNAME=admin SONIC_BUILD_JOB=2 $CACHE_OPTIONS ENABLE_SBOM=y target/sonic-alpinevs.img.gz
else
if [ ${{ parameters.dbg_image }} == true ]; then
make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) INSTALL_DEBUG_TOOLS=y target/sonic-${{ parameters.platform }}.bin && \
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) INSTALL_DEBUG_TOOLS=y target/sonic-${{ parameters.platform }}.bin && \
mv target/sonic-${{ parameters.platform }}.bin target/sonic-${{ parameters.platform }}-dbg.bin
fi
if [ ${{ parameters.swi_image }} == true ]; then
make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) ENABLE_IMAGE_SIGNATURE=y target/sonic-aboot-${{ parameters.platform }}.swi
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) ENABLE_IMAGE_SIGNATURE=y target/sonic-aboot-${{ parameters.platform }}.swi
fi
if [ ${{ parameters.sync_rpc_image }} == true ]; then
make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) ENABLE_SYNCD_RPC=y target/docker-syncd-${{ parameters.platform_short }}-rpc.gz
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) ENABLE_SYNCD_RPC=y target/docker-syncd-${{ parameters.platform_short }}-rpc.gz
# workaround for issue in rules/sairedis.dep, git ls-files will list un-exist files for cache
pushd ./src/sonic-sairedis/SAI
git stash
popd
if [ ${{ parameters.platform }} == broadcom ]; then
make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) ENABLE_SYNCD_RPC=y SAITHRIFT_V2=y target/docker-saiserverv2-brcm.gz
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) ENABLE_SYNCD_RPC=y SAITHRIFT_V2=y target/docker-saiserverv2-brcm.gz
pushd ./src/sonic-sairedis/SAI
git stash
popd
fi
if [ ${{ parameters.platform }} == barefoot ]; then
make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) SAITHRIFT_V2=y ENABLE_SYNCD_RPC=y target/docker-saiserverv2-bfn.gz
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) SAITHRIFT_V2=y ENABLE_SYNCD_RPC=y target/docker-saiserverv2-bfn.gz
pushd ./src/sonic-sairedis/SAI
git stash
popd
fi
fi

make USERNAME=admin $CACHE_OPTIONS SONIC_BUILD_JOBS=$(nproc) target/sonic-${{ parameters.platform }}.bin
make USERNAME=admin $CACHE_OPTIONS ENABLE_SBOM=y SONIC_BUILD_JOBS=$(nproc) target/sonic-${{ parameters.platform }}.bin
fi
displayName: 'Build sonic image'
- script: |
set +e
# Run an SBOM-based vulnerability scan against each SBOM the
# build produced in this pipeline: the platform .bin/.img/.swi
# plus the docker-ptf / docker-ptf-sai test containers when
# the VS group built them. docker-sonic-mgmt is scanned in
# its own pipeline (docker-sonic-mgmt.yml) and is not built
# here. VEX statements under vex/ suppress CVEs that SONiC
# patches fix. Mirrors the legacy trivy policy: MEDIUM+
# severity, fixed-only, fail on remaining unsuppressed
# findings.
OVERALL_RC=0
mkdir -p $(Build.ArtifactStagingDirectory)/sbom-vuln
shopt -s nullglob
# Aggregate SBOMs the build emitted. SONiC produces several
# installer formats — .bin (ONIE), .swi (Arista aboot),
# .img.gz (VS/VPP) — plus standalone test-container SBOMs
# (note the .sbom. infix on the latter, which distinguishes
# them from per-container recipe fragments at
# target/docker-*.gz.cdx.json).
for sbom in \
target/sonic-*.bin.cdx.json \
target/sonic-*.swi.cdx.json \
target/sonic-*.img.gz.cdx.json \
target/docker-*.gz.sbom.cdx.json
do
# Derive a short name for this scan's outputs. Test-container
# SBOMs include a `.sbom` infix on disk (e.g. docker-ptf.gz.sbom.cdx.json)
# to distinguish them from per-container recipe fragments
# (docker-ptf.gz.cdx.json); strip it so the per-artifact
# output names match the underlying artifact extension:
# sonic-broadcom.bin.cdx.json -> sonic-broadcom.bin{.txt,.cdx.json}
# docker-ptf.gz.sbom.cdx.json -> docker-ptf.gz{.txt,.cdx.json}
name=$(basename "$sbom" .cdx.json)
name="${name%.sbom}"
# Azure Pipelines log-folding directive — wrap each SBOM's
# output so the log view shows a collapsible section per
# scan rather than one giant concatenated stream.
echo "##[group]SBOM vuln scan: $name"
python3 scripts/sbom_vuln_scan.py \
--vex vex \
--min-severity medium \
--fail-on medium \
--format both \
--output "$(Build.ArtifactStagingDirectory)/sbom-vuln/${name}.cdx.json" \
"$sbom" | tee "$(Build.ArtifactStagingDirectory)/sbom-vuln/${name}.txt"
RC=${PIPESTATUS[0]}
if [ "$RC" != "0" ]; then OVERALL_RC=$RC; fi
echo "##[endgroup]"
done
exit $OVERALL_RC
# Disabled for now; change to `succeededOrFailed()` to re-enable
# the SBOM-based vulnerability scan step. SBOM emission itself is
# unaffected — the .cdx.json sidecars are still produced by the
# `Build sonic image` step above. Only the post-build scan is
# gated.
condition: false
continueOnError: true
displayName: 'SBOM vulnerability scan'
- template: cleanup.yml
- publish: $(System.DefaultWorkingDirectory)/
artifact: sonic-buildimage.${{ parameters.platform }}
displayName: "Archive sonic image"
- publish: $(Build.ArtifactStagingDirectory)/sbom-vuln
artifact: sbom-vuln-scan-results.${{ parameters.platform }}
displayName: "Archive SBOM vuln-scan results"
condition: always()
continueOnError: true
- script: |
set -x
find target -name "*.log" | xargs -I{} cp {} $(Build.ArtifactStagingDirectory)/
Expand Down
72 changes: 45 additions & 27 deletions .azure-pipelines/docker-sonic-mgmt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ stages:
set -xe
git submodule update --init --recursive -- src/sonic-platform-daemons src/sonic-genl-packet src/sonic-sairedis src/ptf src/sonic-device-data src/sonic-dash-api

make NOBUSTER=1 NOBULLSEYE=1 SONIC_BUILD_JOBS=$(nproc) DEFAULT_CONTAINER_REGISTRY=publicmirror.azurecr.io ENABLE_DOCKER_BASE_PULL=y configure PLATFORM=generic DOCKER_BUILDKIT=0
make -f Makefile.work BLDENV=bookworm SONIC_BUILD_JOBS=$(nproc) DEFAULT_CONTAINER_REGISTRY=publicmirror.azurecr.io ENABLE_DOCKER_BASE_PULL=y target/docker-sonic-mgmt.gz
make NOBUSTER=1 NOBULLSEYE=1 SONIC_BUILD_JOBS=$(nproc) DEFAULT_CONTAINER_REGISTRY=publicmirror.azurecr.io ENABLE_DOCKER_BASE_PULL=y ENABLE_SBOM=y configure PLATFORM=generic DOCKER_BUILDKIT=0
make -f Makefile.work BLDENV=bookworm SONIC_BUILD_JOBS=$(nproc) DEFAULT_CONTAINER_REGISTRY=publicmirror.azurecr.io ENABLE_DOCKER_BASE_PULL=y ENABLE_SBOM=y target/docker-sonic-mgmt.gz
cp -r target $(Build.ArtifactStagingDirectory)/target
env:
REGISTRY_SERVER: ${{ parameters.registry_url }}
Expand Down Expand Up @@ -92,8 +92,14 @@ stages:
- name: BUILD_BRANCH
value: $(Build.SourceBranchName)
jobs:
- job: trivy_scan
displayName: "[OPTIONAL] Trivy vulnerability scan (docker-sonic-mgmt)"
- job: sbom_vuln_scan
displayName: "[OPTIONAL] SBOM-based vulnerability scan (docker-sonic-mgmt)"
# Disabled for now; remove this `condition: false` (or change to
# `and(succeeded(), in(dependencies.Build.result, 'Succeeded'))`)
# to re-enable the docker-sonic-mgmt SBOM vulnerability scan.
# The SBOM itself is still produced by the Build stage above
# when ENABLE_SBOM=y; only the post-build scan is gated.
condition: false
pool: sonic-ubuntu-1c
continueOnError: true
timeoutInMinutes: 30
Expand All @@ -103,34 +109,46 @@ stages:
fetchDepth: 1
- download: current
artifact: docker-sonic-mgmt
displayName: "Download docker-sonic-mgmt artifact"
displayName: "Download docker-sonic-mgmt artifact (includes SBOM)"
- script: |
set -ex
TRIVY_VERSION="0.70.0"
curl -fLO https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.deb
sudo apt install -y ./trivy_${TRIVY_VERSION}_Linux-64bit.deb
trivy --version
displayName: "Install Trivy"
sudo apt-get install -y python3-pip jq
displayName: "Install dependencies"
- script: |
set -x
trivy image \
--input $(Pipeline.Workspace)/docker-sonic-mgmt/target/docker-sonic-mgmt.gz \
--severity MEDIUM,HIGH,CRITICAL \
--ignore-unfixed \
--exit-code 1 \
--format table \
--no-progress \
--timeout 15m \
--output $(Build.ArtifactStagingDirectory)/trivy-sonic-mgmt.txt
TRIVY_EXIT=$?
SBOM=$(Pipeline.Workspace)/docker-sonic-mgmt/target/docker-sonic-mgmt.gz.sbom.cdx.json
if [ ! -f "$SBOM" ]; then
echo "::error::SBOM not found at $SBOM — was the build invoked with ENABLE_SBOM=y?"
ls -la $(Pipeline.Workspace)/docker-sonic-mgmt/target/ | head -30
exit 1
fi
echo "SBOM: $SBOM"
echo "Component count: $(jq '.components | length' "$SBOM")"
displayName: "Verify SBOM artifact is present"
- script: |
set -x
# Mirror the prior trivy policy: report MEDIUM/HIGH/CRITICAL,
# filter to fixed CVEs only, fail the job on any unsuppressed
# finding. VEX statements under vex/ are applied first.
REPORT=$(Build.ArtifactStagingDirectory)/sbom-vuln-sonic-mgmt
python3 scripts/sbom_vuln_scan.py \
--vex vex \
--min-severity medium \
--fail-on medium \
--format both \
--output "${REPORT}.cdx.json" \
$(Pipeline.Workspace)/docker-sonic-mgmt/target/docker-sonic-mgmt.gz.sbom.cdx.json \
| tee "${REPORT}.txt"
EXIT=${PIPESTATUS[0]}
echo ""
echo "=== Trivy Scan Results (docker-sonic-mgmt) ==="
cat $(Build.ArtifactStagingDirectory)/trivy-sonic-mgmt.txt
exit $TRIVY_EXIT
displayName: "Trivy scan docker-sonic-mgmt"
- publish: $(Build.ArtifactStagingDirectory)/trivy-sonic-mgmt.txt
artifact: trivy-scan-results
displayName: "Publish Trivy scan results"
echo "=== SBOM vulnerability scan results (docker-sonic-mgmt) ==="
echo "Full CycloneDX VEX report: ${REPORT}.cdx.json"
echo "Human-readable table: ${REPORT}.txt"
exit $EXIT
displayName: "SBOM vulnerability scan (docker-sonic-mgmt)"
- publish: $(Build.ArtifactStagingDirectory)
artifact: sbom-vuln-scan-results
displayName: "Publish SBOM vuln-scan results"
condition: always()
continueOnError: true

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ fsroot/
fsroot-*/
fs.*
target/

# Auto-extracted OpenVEX statements (regenerated from src/*/patches/
# during the build by scripts/sbom_extract_vex_from_patches.py).
# Manually-authored VEX statements live in vex/manual/ and ARE tracked.
vex/auto/
*.deb
*.udeb
*.changes
Expand Down
19 changes: 19 additions & 0 deletions Makefile.work
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ PREPARE_DOCKER=BUILD_SLAVE=y \
MIRROR_SNAPSHOT=$(MIRROR_SNAPSHOT) \
BUILD_PUBLIC_URL=$(BUILD_PUBLIC_URL) \
BUILD_SNAPSHOT_URL=$(BUILD_SNAPSHOT_URL) \
ENABLE_SBOM=$(ENABLE_SBOM) \
scripts/prepare_docker_buildinfo.sh \
$(SLAVE_BASE_IMAGE) \
$(SLAVE_DIR)/Dockerfile \
Expand Down Expand Up @@ -293,6 +294,17 @@ OVERLAY_MODULE_CHECK := \

BUILD_TIMESTAMP := $(shell date +%Y%m%d\.%H%M%S)

# SOURCE_DATE_EPOCH is the reproducible-build convention for stamping
# generated artifacts (SBOMs, in-toto attestations, etc.) with a
# deterministic time. Default to the timestamp of the HEAD git commit
# so two builds of the same source produce byte-identical SBOMs; can
# be overridden by passing SOURCE_DATE_EPOCH= explicitly from the
# environment, which is what reproducible-build CI typically does.
ifeq ($(SOURCE_DATE_EPOCH),)
SOURCE_DATE_EPOCH := $(shell git -C $(SOURCE_TREE) log -1 --format=%ct HEAD 2>/dev/null || date +%s)
endif
export SOURCE_DATE_EPOCH

# Create separate Docker lockfiles for saving vs. loading an image.
ifeq ($(DOCKER_LOCKDIR),)
override DOCKER_LOCKDIR := /tmp/docklock
Expand Down Expand Up @@ -480,6 +492,7 @@ DOCKER_SLAVE_BASE_BUILD = docker build $(DOCKER_NO_CACHE_FLAG) \
--build-arg no_proxy=$(no_proxy) \
--build-arg SONIC_VERSION_CACHE=$(SONIC_VERSION_CACHE) \
--build-arg SONIC_VERSION_CONTROL_COMPONENTS=$(SONIC_VERSION_CONTROL_COMPONENTS) \
--build-arg ENABLE_SBOM=$(ENABLE_SBOM) \
$(SLAVE_DIR) \
$(SPLIT_LOG) $(DOCKER_BASE_LOG)

Expand Down Expand Up @@ -653,6 +666,12 @@ SONIC_BUILD_INSTRUCTION := $(MAKE) \
ENABLE_MULTIDB=$(ENABLE_MULTIDB) \
ENABLE_VRF_STRICT=$(ENABLE_VRF_STRICT) \
ENABLE_FRR_TCMALLOC=$(ENABLE_FRR_TCMALLOC) \
ENABLE_SBOM=$(ENABLE_SBOM) \
SBOM_FORMAT=$(SBOM_FORMAT) \
SBOM_SCAN_TOOL=$(SBOM_SCAN_TOOL) \
SBOM_INCLUDE_LICENSES=$(SBOM_INCLUDE_LICENSES) \
SBOM_STRICT=$(SBOM_STRICT) \
SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) \
$(SONIC_OVERRIDE_BUILD_VARS)

.PHONY: sonic-slave-build sonic-slave-bash init reset
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,18 @@ For details refer to [SONiC Buildimage Guide](https://github.com/sonic-net/sonic
Please refer to [SONiC roadmap](https://github.com/sonic-net/SONiC/wiki/Sonic-Roadmap-Planning)
on the SAI version for each SONiC release.

## Software Bill of Materials (SBOM) and vulnerability scanning

Opt-in SBOM generation and SBOM-based vulnerability scanning are
supported via `ENABLE_SBOM=y` at build time. The default build path
is unchanged; enabling SBOM adds CycloneDX 1.6 + SPDX 2.3 + SLSA
v1.0 in-toto provenance sidecars per built artifact, along with
standalone CycloneDX scanner output for vulnerability reports.

See [README.sbom.md](README.sbom.md) for the full design, build
flag reference, vulnerability-report quick start, VEX workflow,
reproducibility notes, and known limitations.

## Notes

* If you are running make for the first time, a sonic-slave-${USER} docker image
Expand Down
Loading
Loading