mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-25 06:42:52 +08:00
feat(build): publish OCI SBOM and provenance attestations (#2836)
* feat(build): embed auditable Rust dependency metadata Signed-off-by: Adrien Langou <alangou@nvidia.com> * feat(build): publish OCI SBOM and provenance attestations Signed-off-by: Adrien Langou <alangou@nvidia.com> --------- Signed-off-by: Adrien Langou <alangou@nvidia.com>
This commit is contained in:
@@ -13,13 +13,28 @@ The OpenShell SBOM tooling produces source-tree CycloneDX JSON SBOMs using Syft,
|
|||||||
|
|
||||||
SBOMs are **release artifacts only** -- they are generated on demand and not committed to the repository. Output lands in `deploy/sbom/output/` (gitignored).
|
SBOMs are **release artifacts only** -- they are generated on demand and not committed to the repository. Output lands in `deploy/sbom/output/` (gitignored).
|
||||||
|
|
||||||
Release Dev and Release Tag image builds separately embed cargo-auditable metadata in the staged gateway and supervisor binaries. This metadata describes the binary's Rust dependency graph and lets Syft discover Cargo packages from the binary itself. It is not a complete image SBOM and is not an OCI SBOM attestation; publishing such an attestation remains separate work.
|
Pushed gateway and supervisor images carry an SPDX SBOM and minimal SLSA provenance as OCI attestations. Branch E2E, Release Dev, and Release Tag image binaries embed cargo-auditable metadata, so their image SBOMs include linked Rust crates.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- `mise install` has been run (installs Syft and other tools)
|
- `mise install` has been run (installs Syft and other tools)
|
||||||
- The repository is checked out at the root
|
- The repository is checked out at the root
|
||||||
|
|
||||||
|
## Inspecting an Image SBOM
|
||||||
|
|
||||||
|
BuildKit uses its default Syft scanner and attaches one SPDX document per platform. Read one without pulling the image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker buildx imagetools inspect ghcr.io/nvidia/openshell/gateway:latest \
|
||||||
|
--format '{{ json (index .SBOM "linux/amd64").SPDX }}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Validate the final attestation, requiring a Cargo package for an auditable image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tasks/scripts/verify-image-sbom.sh ghcr.io/nvidia/openshell/gateway:latest --require-cargo
|
||||||
|
```
|
||||||
|
|
||||||
## Inspecting an Auditable Image Binary
|
## Inspecting an Auditable Image Binary
|
||||||
|
|
||||||
Opt into auditable metadata when staging a local image binary:
|
Opt into auditable metadata when staging a local image binary:
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
component: gateway
|
component: gateway
|
||||||
image-tag: ${{ github.sha }}
|
image-tag: ${{ github.sha }}
|
||||||
|
auditable: true
|
||||||
|
|
||||||
build-supervisor:
|
build-supervisor:
|
||||||
needs: [pr_metadata]
|
needs: [pr_metadata]
|
||||||
@@ -97,6 +98,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
component: supervisor
|
component: supervisor
|
||||||
image-tag: ${{ github.sha }}
|
image-tag: ${{ github.sha }}
|
||||||
|
auditable: true
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
needs: [pr_metadata]
|
needs: [pr_metadata]
|
||||||
|
|||||||
@@ -294,6 +294,10 @@ jobs:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs['checkout-ref'] || github.sha }}
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||||
|
|
||||||
@@ -309,3 +313,14 @@ jobs:
|
|||||||
--prefer-index=false \
|
--prefer-index=false \
|
||||||
-t "${image}:${{ needs.resolve.outputs.image_tag_base }}" \
|
-t "${image}:${{ needs.resolve.outputs.image_tag_base }}" \
|
||||||
"${refs[@]}"
|
"${refs[@]}"
|
||||||
|
|
||||||
|
- name: Verify merged manifest SBOM attestation
|
||||||
|
env:
|
||||||
|
IMAGE_REF: ghcr.io/nvidia/openshell/${{ inputs.component }}:${{ needs.resolve.outputs.image_tag_base }}
|
||||||
|
REQUIRE_CARGO: ${{ inputs.auditable }}
|
||||||
|
run: |
|
||||||
|
args=("${IMAGE_REF}")
|
||||||
|
if [[ "${REQUIRE_CARGO}" == "true" ]]; then
|
||||||
|
args+=(--require-cargo)
|
||||||
|
fi
|
||||||
|
tasks/scripts/verify-image-sbom.sh "${args[@]}"
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
|
# Branch E2E and release gateway/supervisor image builds opt in.
|
||||||
|
# CLI images and standalone glibc-static supervisor validation keep false.
|
||||||
auditable:
|
auditable:
|
||||||
description: "Embed cargo-auditable dependency metadata in the binary"
|
description: "Embed cargo-auditable dependency metadata in the binary"
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
+23
-11
@@ -131,27 +131,39 @@ step via the `rust-native-build.yml` workflow (per-architecture, per-component)
|
|||||||
and uploads the result as an artifact that the image build job downloads back
|
and uploads the result as an artifact that the image build job downloads back
|
||||||
into the staging directory before running Buildx.
|
into the staging directory before running Buildx.
|
||||||
|
|
||||||
Gateway and supervisor binaries staged into Release Dev and Release Tag images
|
Gateway and supervisor binaries staged into branch E2E, Release Dev, and Release
|
||||||
are compiled through `cargo auditable` (pinned in `mise.toml`), which embeds a
|
Tag images are compiled through `cargo auditable` (pinned in `mise.toml`), which
|
||||||
`.dep-v0` section describing the Rust dependencies actually compiled into the
|
embeds a `.dep-v0` section describing the Rust dependencies actually compiled
|
||||||
binary. That section holds data rather than symbols, so it survives the
|
into the binary. That section holds data rather than symbols, so it survives the
|
||||||
workspace's `strip = true` release profile, and Syft can catalog the crates
|
workspace's `strip = true` release profile, and Syft can catalog the crates
|
||||||
present in image binaries instead of inferring them from the source tree. This
|
present in image binaries instead of inferring them from the source tree. This
|
||||||
is a different artifact from the source SBOM produced by `syft dir:.` in
|
is a different artifact from the source SBOM produced by `syft dir:.` in
|
||||||
`tasks/sbom.toml`, which describes the checkout, and from an OCI SBOM
|
`tasks/sbom.toml`, which describes the checkout, and from the image SBOM
|
||||||
attestation, which remains out of scope.
|
attestation below, which describes a published image.
|
||||||
|
|
||||||
Only release image builds are auditable. `docker-build.yml` and
|
`docker-build.yml` and `rust-native-build.yml` take an `auditable` input that
|
||||||
`rust-native-build.yml` take an `auditable` input that defaults to false, so PR
|
defaults to false. The branch E2E gateway and supervisor image builds set it to
|
||||||
and E2E image builds and standalone release artifacts remain non-auditable. The
|
true so E2E exercises release-parity image binaries; other PR image builds and
|
||||||
CI image gains the pinned `cargo-auditable` tool through `mise install --locked`
|
standalone release artifacts stay non-auditable. The CI image gains the pinned
|
||||||
but ships no auditable OpenShell binary of its own. Local staging opts in with
|
`cargo-auditable` tool through `mise install --locked` but ships no auditable
|
||||||
|
OpenShell binary of its own. Local staging opts in with
|
||||||
`OPENSHELL_AUDITABLE=1`. sccache's `RUSTC_WRAPPER` is unset only around auditable
|
`OPENSHELL_AUDITABLE=1`. sccache's `RUSTC_WRAPPER` is unset only around auditable
|
||||||
builds, because it would otherwise wrap `cargo-auditable`'s workspace wrapper and
|
builds, because it would otherwise wrap `cargo-auditable`'s workspace wrapper and
|
||||||
be misidentified as `rustc`. Auditable builds are verified by scanning the built
|
be misidentified as `rustc`. Auditable builds are verified by scanning the built
|
||||||
binary with Syft and requiring at least one decoded Cargo package; the check runs
|
binary with Syft and requiring at least one decoded Cargo package; the check runs
|
||||||
only for those builds.
|
only for those builds.
|
||||||
|
|
||||||
|
Pushed Docker images carry minimal SLSA provenance and a per-platform SPDX SBOM
|
||||||
|
generated by BuildKit's default Syft scanner. The registry exporter uses OCI
|
||||||
|
media types and `oci-artifact=true`, so each attestation identifies its subject.
|
||||||
|
GHCR exposes these through the image index because it has no referrers API.
|
||||||
|
|
||||||
|
Attestations require a registry-backed image index. Local builds therefore keep
|
||||||
|
`--provenance=false`, and Podman builds carry neither attestation.
|
||||||
|
`tasks/scripts/verify-image-sbom.sh` verifies the merged multi-arch tag and runs
|
||||||
|
with `--require-cargo` for auditable builds, so those attestations must also
|
||||||
|
contain Cargo packages.
|
||||||
|
|
||||||
Runtime layout:
|
Runtime layout:
|
||||||
|
|
||||||
- **Gateway**: `gcr.io/distroless/cc-debian13:nonroot` base, GNU-linked binary at
|
- **Gateway**: `gcr.io/distroless/cc-debian13:nonroot` base, GNU-linked binary at
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
title: "Verify the Contents of Published OpenShell Images"
|
||||||
|
sidebar-title: "Verify Image Contents"
|
||||||
|
slug: "security/verify-image-contents"
|
||||||
|
description: "Read the SBOM attestation attached to published gateway and supervisor images to audit what each image contains."
|
||||||
|
keywords: "Generative AI, Cybersecurity, Supply Chain, SBOM, Container Images"
|
||||||
|
position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
Published gateway and supervisor images carry one SPDX SBOM per platform as OCI attestations.
|
||||||
|
|
||||||
|
## Inspect an Image
|
||||||
|
|
||||||
|
Read a platform's document without pulling the image:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker buildx imagetools inspect ghcr.io/nvidia/openshell/gateway:latest --format '{{ json (index .SBOM "linux/amd64").SPDX }}'
|
||||||
|
```
|
||||||
|
|
||||||
|
List the packages instead of the full document:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker buildx imagetools inspect ghcr.io/nvidia/openshell/gateway:latest --format '{{ range (index .SBOM "linux/amd64").SPDX.packages }}{{ .name }}@{{ .versionInfo }}{{ println }}{{ end }}'
|
||||||
|
```
|
||||||
|
|
||||||
|
The same commands work for `ghcr.io/nvidia/openshell/supervisor`.
|
||||||
|
|
||||||
|
## Coverage
|
||||||
|
|
||||||
|
Every SBOM lists the base-image packages. Release Dev and Release Tag images also list the Rust crates compiled into their OpenShell binary.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
OpenShell also publishes minimal SLSA provenance. It records how BuildKit produced the image, including its source revision, build platform, and base-image materials, without the extra build parameters included by full provenance.
|
||||||
|
</Note>
|
||||||
@@ -168,14 +168,19 @@ if [[ "${IS_FINAL_IMAGE}" == "1" ]]; then
|
|||||||
TAG_ARGS=(-t "${IMAGE_NAME}:${IMAGE_TAG}")
|
TAG_ARGS=(-t "${IMAGE_NAME}:${IMAGE_TAG}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ATTESTATION_ARGS=(--provenance=false)
|
||||||
OUTPUT_ARGS=()
|
OUTPUT_ARGS=()
|
||||||
if [[ -n "${DOCKER_OUTPUT:-}" ]]; then
|
if [[ -n "${DOCKER_OUTPUT:-}" ]]; then
|
||||||
OUTPUT_ARGS=(--output "${DOCKER_OUTPUT}")
|
OUTPUT_ARGS=(--output "${DOCKER_OUTPUT}")
|
||||||
elif [[ "${IS_FINAL_IMAGE}" == "1" ]]; then
|
elif [[ "${IS_FINAL_IMAGE}" == "1" ]]; then
|
||||||
if [[ "${DOCKER_PUSH:-}" == "1" ]]; then
|
if [[ "${DOCKER_PUSH:-}" == "1" || "${DOCKER_PLATFORM:-}" == *","* ]]; then
|
||||||
OUTPUT_ARGS=(--push)
|
if ce_is_docker; then
|
||||||
elif [[ "${DOCKER_PLATFORM:-}" == *","* ]]; then
|
# Attestations require a registry-backed image index.
|
||||||
OUTPUT_ARGS=(--push)
|
ATTESTATION_ARGS=(--provenance=mode=min --attest type=sbom)
|
||||||
|
OUTPUT_ARGS=(--output "type=image,push=true,oci-mediatypes=true,oci-artifact=true")
|
||||||
|
else
|
||||||
|
OUTPUT_ARGS=(--push)
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
OUTPUT_ARGS=(--load)
|
OUTPUT_ARGS=(--load)
|
||||||
fi
|
fi
|
||||||
@@ -191,7 +196,7 @@ ce_build \
|
|||||||
-f "${DOCKERFILE}" \
|
-f "${DOCKERFILE}" \
|
||||||
--target "${DOCKER_TARGET}" \
|
--target "${DOCKER_TARGET}" \
|
||||||
${TAG_ARGS[@]+"${TAG_ARGS[@]}"} \
|
${TAG_ARGS[@]+"${TAG_ARGS[@]}"} \
|
||||||
--provenance=false \
|
${ATTESTATION_ARGS[@]+"${ATTESTATION_ARGS[@]}"} \
|
||||||
"$@" \
|
"$@" \
|
||||||
${OUTPUT_ARGS[@]+"${OUTPUT_ARGS[@]}"} \
|
${OUTPUT_ARGS[@]+"${OUTPUT_ARGS[@]}"} \
|
||||||
.
|
.
|
||||||
|
|||||||
Executable
+53
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Validate the final image's SBOM attestation and, for auditable builds, require
|
||||||
|
# at least one Cargo package discovered from the binary metadata.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "${SCRIPT_DIR}/container-engine.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: verify-image-sbom.sh <image-ref> [--require-cargo]" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
IMAGE=${1:-}
|
||||||
|
REQUIRE_CARGO=${2:-}
|
||||||
|
if [[ -z "${IMAGE}" || $# -gt 2 || ( -n "${REQUIRE_CARGO}" && "${REQUIRE_CARGO}" != "--require-cargo" ) ]]; then
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ce_is_docker; then
|
||||||
|
echo "Error: SBOM attestations are produced on the Docker/buildx path; ${CONTAINER_ENGINE} has no imagetools equivalent" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Inspecting SBOM attestation of ${IMAGE}"
|
||||||
|
SBOM_JSON="$(ce buildx imagetools inspect "${IMAGE}" --format '{{ json .SBOM }}')"
|
||||||
|
COUNTS="$(
|
||||||
|
jq -r '
|
||||||
|
[.. | objects | .SPDX? | select(type == "object")] as $documents
|
||||||
|
| [
|
||||||
|
($documents | length),
|
||||||
|
([$documents[] | .. | strings | select(startswith("pkg:cargo/"))] | length)
|
||||||
|
]
|
||||||
|
| @tsv
|
||||||
|
' <<<"${SBOM_JSON}"
|
||||||
|
)"
|
||||||
|
read -r SPDX_COUNT CARGO_COUNT <<<"${COUNTS}"
|
||||||
|
|
||||||
|
if [[ "${SPDX_COUNT}" -eq 0 ]]; then
|
||||||
|
echo "Error: ${IMAGE} carries no SPDX SBOM" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "${REQUIRE_CARGO}" == "--require-cargo" && "${CARGO_COUNT}" -eq 0 ]]; then
|
||||||
|
echo "Error: ${IMAGE} SBOM contains no Cargo packages" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "SBOM attestation verified: SPDX=${SPDX_COUNT}, Cargo=${CARGO_COUNT}"
|
||||||
Reference in New Issue
Block a user