[sbom] Emit container distro in per-container SBOM so grype scans deb/OS packages#28406
[sbom] Emit container distro in per-container SBOM so grype scans deb/OS packages#28406lunyue-ms wants to merge 3 commits into
Conversation
|
/azp run Azure.sonic-buildimage |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR updates the docker-sonic-mgmt Azure Pipeline definition to ensure the SBOM-based vulnerability scan job actually runs and that edits to the pipeline YAML itself will trigger the pipeline in PRs.
Changes:
- Extend the PR path filter to include
.azure-pipelines/docker-sonic-mgmt.yml, so pipeline YAML changes trigger PR validation. - Enable the
sbom_vuln_scanjob by removing thecondition: falsegate (leaving itcontinueOnError: true).
|
/azp run Azure.sonic-buildimage |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
5e8892a to
1cf1893
Compare
|
/azp run Azure.sonic-buildimage |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Comment out `condition: false` on the sbom_vuln_scan job so the docker-sonic-mgmt SBOM-based vulnerability scan (grype + OpenVEX) runs in the pipeline, and add .azure-pipelines/docker-sonic-mgmt.yml to the PR path filter (mirroring docker-ptf.yml) so a change to this pipeline definition actually triggers it. The SBOM sidecar is already produced by the Build stage (ENABLE_SBOM=y); only this post-build scan job was gated. Signed-off-by: Lun Yue <17232861+lunyue-ms@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1cf1893 to
4ac9266
Compare
|
/azp run Azure.sonic-buildimage |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
The per-container SBOM (target/<container>.gz.sbom.cdx.json) carried no OS/distro context: metadata.component was type=container and every deb component used a bare pkg:deb/debian/... PURL with no distro qualifier. grype needs the distro to pick an OS advisory feed, so it silently skipped ALL deb/OS packages and only reported language (npm) CVEs. e.g. docker-sonic-mgmt reported 22 npm findings and 0 deb, hiding the Debian/Ubuntu CVEs that image-based scanners catch. Detect the container distro from /etc/os-release in the image archive (overridable via SBOM_CONTAINER_DISTRO), emit a CycloneDX operating-system component with syft:distro:* properties, and namespace deb PURLs to the distro with a distro= qualifier. The aggregate .bin path is unchanged (distro defaults to None -> legacy behavior). Validated with grype 0.112.0 on the real docker-sonic-mgmt SBOM: before = 23 findings (all npm, 0 deb); after = 114 (23 npm + 91 deb). Signed-off-by: Lun Yue <17232861+lunyue-ms@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4ac9266 to
e69f2f8
Compare
|
/azp run Azure.sonic-buildimage |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Hi @bhouse-nexthop — this builds on your SBOM work (#27455), so your review would be appreciated.
Verified on the real SBOM: grype went 23 (npm-only) → 114 (23 npm + 91 deb). Thanks! |
The sbom_vuln_scan job installed python3-pip but never used it: scripts/sbom_vuln_scan.py is pure stdlib and grype is fetched via wget (scripts/install_sbom_tool.sh), not pip. Only jq is used. Remove python3-pip so it is not installed on the sonic-ubuntu-1c agent (security hardening). Signed-off-by: Lun Yue <17232861+lunyue-ms@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run Azure.sonic-buildimage |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
| if nm.endswith("etc/os-release") or nm.endswith("usr/lib/os-release"): | ||
| ex = tf.extractfile(member) | ||
| if ex is not None: | ||
| got = _parse_os_release( | ||
| ex.read().decode("utf-8", "replace") | ||
| ) | ||
| if got: | ||
| return got |
| if nm.endswith("etc/os-release") or nm.endswith( | ||
| "usr/lib/os-release" | ||
| ): | ||
| ex = outer.extractfile(m) | ||
| if ex is not None: | ||
| got = _parse_os_release( | ||
| ex.read().decode("utf-8", "replace") | ||
| ) | ||
| if got: | ||
| return got | ||
| continue |
| ex = outer.extractfile(m) | ||
| if ex is None: | ||
| continue | ||
| # Spill the layer to a temp file, then parse with random | ||
| # access. Opening a nested tar directly off the gzip outer | ||
| # stream (mode="r|*") silently finds no members, and | ||
| # buffering whole layers in memory risks OOM on large | ||
| # images, so stream to disk (bounded memory) and seek. | ||
| tmp = tempfile.NamedTemporaryFile( | ||
| prefix="sbom-distro-", suffix=".tar", | ||
| dir=os.path.dirname(os.path.abspath(gz_path)) or None, | ||
| delete=False, | ||
| ) | ||
| try: | ||
| shutil.copyfileobj(ex, tmp, 1024 * 1024) | ||
| tmp.close() | ||
| with tarfile.open(tmp.name, "r:*") as inner: | ||
| got = _search(inner) |
| def observation_components_for_scope( | ||
| target_path: str, scope: str, arch: str, supplier: str, | ||
| distro: Optional[tuple] = None, | ||
| ) -> list: |
Why I did it
The
docker-sonic-mgmtSBOM-based vulnerability scan (added opt-in in #27455)only ever reported npm CVEs — grype silently skipped every Debian/Ubuntu
(
deb) and OS package.Root cause: the per-container SBOM (
target/<container>.gz.sbom.cdx.json)carried no OS/distro context.
metadata.componentwastype: container, therewas no
operating-systemcomponent, and every deb component used a barepkg:deb/debian/<name>@<ver>PURL with nodistro=qualifier. grype needs thedistro to select an OS advisory feed, so it dropped all deb/OS packages and
matched only language (npm/py/…) packages.
For
docker-sonic-mgmt(anubuntu:24.04image) this meant the scan reported~22 npm findings and 0 deb, hiding exactly the Debian/Ubuntu CVEs that
image-based scanners (S360, trivy) surface — defeating the purpose of the scan.
Work item tracking
How I did it
scripts/build_sbom.py— per-container SBOM path (_container_main):/etc/os-releasefrom the imagearchive (
detect_container_distro). Handles both docker-archive(
<hash>/layer.tar) and OCI (blobs/sha256/*) layouts, and the common casewhere
/etc/os-releaseis a symlink to/usr/lib/os-release. Overridable viaSBOM_CONTAINER_DISTRO=<id>-<version>. Detection failure degrades gracefully(warns, keeps the legacy shape) and never fails the build.
operating-systemcomponent carryingsyft:distro:id/syft:distro:versionID, which grype reads to identify the distro.distro=qualifier, e.g.pkg:deb/ubuntu/adduser@3.137ubuntu1?arch=amd64&distro=ubuntu-24.04..binSBOM path is unchanged (distrodefaults toNone→ legacy
pkg:deb/debian/…), so this only affects the standaloneper-container test SBOMs (docker-ptf, docker-sonic-mgmt).
.azure-pipelines/docker-sonic-mgmt.yml:sbom_vuln_scanjob (previously gated withcondition: false)..azure-pipelines/docker-sonic-mgmt.ymlto the PRpathsfilter sochanges to this pipeline definition actually trigger it (mirroring
docker-ptf.yml).How to verify it
[build_sbom.py] Container distro: ubuntu 24.04, andtarget/docker-sonic-mgmt.gz.sbom.cdx.jsonnow contains anoperating-systemcomponent pluspkg:deb/ubuntu/…&distro=ubuntu-24.04debPURLs.
Measured on the real docker-sonic-mgmt SBOM, grype findings went from 23 (all
npm, 0 deb) to 114 (23 npm + 91 deb). The
sbom_vuln_scanCI job(mssonic build 1166089) confirmed 62 deb CVEs (medium+) in addition to the
npm findings, where before it reported npm only.
Which release branch to backport (provide reason below if selected)
Tested branch (Please provide the tested image version)
Description for the changelog
Link to config_db schema for YANG module changes
A picture of a cute animal (not mandatory but encouraged)