Skip to content

Commit 8864c42

Browse files
authored
Feature: Generate SBOM for both ko and docker built images (#224)
1 parent 78d1fb8 commit 8864c42

File tree

13 files changed

+850
-63
lines changed

13 files changed

+850
-63
lines changed

.github/actions/publish-container/action.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,9 @@ runs:
7474
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
7575
fi
7676
77-
- name: Attest
78-
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
79-
id: attest
77+
- name: Generate SBOM and Attest
78+
uses: ./.github/actions/sbom-and-attest
8079
with:
81-
subject-name: ${{ steps.image.outputs.name }}
82-
subject-digest: ${{ steps.image.outputs.digest }}
83-
push-to-registry: true
80+
image_name: ${{ steps.image.outputs.name }}
81+
image_digest: ${{ steps.image.outputs.digest }}
82+
registry_password: ${{ inputs.registry_password }}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: 'Generate SBOM and Attest'
16+
description: 'Generates SBOM and build provenance attestation for container images'
17+
18+
inputs:
19+
image_name:
20+
description: 'Full image name (without tag or digest)'
21+
required: true
22+
image_digest:
23+
description: 'Image digest (sha256:...)'
24+
required: true
25+
registry_password:
26+
description: 'Registry password for authentication'
27+
required: true
28+
crane_version:
29+
description: 'Version of crane to install (default: v0.20.2)'
30+
required: false
31+
default: 'v0.20.2'
32+
33+
runs:
34+
using: 'composite'
35+
steps:
36+
- name: Authenticate to GHCR
37+
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
38+
with:
39+
registry: ghcr.io
40+
username: ${{ github.actor }}
41+
password: ${{ inputs.registry_password }}
42+
43+
- name: Derive safe filename
44+
id: name
45+
shell: bash
46+
run: |
47+
IMAGE="${{ inputs.image_name }}"
48+
SAFE="$(basename "${IMAGE%%:*}")" # strip tag if present, then basename
49+
echo "safe=$SAFE" >> "$GITHUB_OUTPUT"
50+
51+
- name: Generate SBOM
52+
uses: anchore/sbom-action@8e94d75ddd33f69f691467e42275782e4bfefe84 # v0.20.9
53+
with:
54+
image: "${{ inputs.image_name }}@${{ inputs.image_digest }}"
55+
format: cyclonedx-json
56+
output-file: sbom-${{ steps.name.outputs.safe }}.cdx.json
57+
upload-artifact: true
58+
upload-release-assets: auto
59+
60+
- name: Verify SBOM file
61+
id: find-sbom
62+
shell: bash
63+
run: |
64+
SBOM_FILE="sbom-${{ steps.name.outputs.safe }}.cdx.json"
65+
if [ ! -f "$SBOM_FILE" ]; then
66+
echo "::error::SBOM file not found: $SBOM_FILE"
67+
exit 1
68+
fi
69+
echo "sbom_file=$SBOM_FILE" >> "$GITHUB_OUTPUT"
70+
71+
- name: Install Crane
72+
shell: bash
73+
run: |
74+
if ! command -v crane &> /dev/null; then
75+
echo "Installing crane ${{ inputs.crane_version }}..."
76+
curl -sL "https://github.com/google/go-containerregistry/releases/download/${{ inputs.crane_version }}/go-containerregistry_Linux_x86_64.tar.gz" | tar -xz crane
77+
sudo mv crane /usr/local/bin/crane
78+
sudo chmod +x /usr/local/bin/crane
79+
fi
80+
crane version
81+
82+
- name: Install Cosign
83+
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
84+
85+
- name: Cosign SBOM attestation
86+
shell: bash
87+
env:
88+
COSIGN_EXPERIMENTAL: "1"
89+
run: |
90+
set -euo pipefail
91+
92+
SBOM_FILE="${{ steps.find-sbom.outputs.sbom_file }}"
93+
IMAGE_REF="${{ inputs.image_name }}@${{ inputs.image_digest }}"
94+
95+
# Check if this is a multi-platform image (OCI index)
96+
MANIFEST_TYPE=$(crane manifest "$IMAGE_REF" | jq -r '.mediaType // "unknown"')
97+
98+
if [[ "$MANIFEST_TYPE" == "application/vnd.oci.image.index.v1+json" ]] || \
99+
[[ "$MANIFEST_TYPE" == "application/vnd.docker.distribution.manifest.list.v2+json" ]]; then
100+
# Multi-platform: attest each platform digest separately
101+
PLATFORM_DIGESTS=$(crane manifest "$IMAGE_REF" | \
102+
jq -r '.manifests[] | select((.annotations."vnd.docker.reference.type" // "") != "attestation-manifest") | .digest')
103+
104+
while IFS= read -r DIGEST; do
105+
echo "Attesting ${{ inputs.image_name }}@${DIGEST}"
106+
cosign attest \
107+
--yes \
108+
--predicate "$SBOM_FILE" \
109+
--type cyclonedx \
110+
"${{ inputs.image_name }}@${DIGEST}"
111+
done <<< "$PLATFORM_DIGESTS"
112+
else
113+
# Single-platform: attest directly
114+
echo "Attesting $IMAGE_REF"
115+
cosign attest \
116+
--yes \
117+
--predicate "$SBOM_FILE" \
118+
--type cyclonedx \
119+
"$IMAGE_REF"
120+
fi
121+
122+
- name: Attest build provenance
123+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
124+
with:
125+
subject-name: ${{ inputs.image_name }}
126+
subject-digest: ${{ inputs.image_digest }}
127+
push-to-registry: true

.github/workflows/container-build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ jobs:
123123
- name: Setup build environment
124124
uses: ./.github/actions/setup-ci-env
125125

126-
- name: Test ko build for ${{ matrix.module }}
126+
- name: Use ko to build ${{ matrix.module }}
127127
run: |
128128
cd ${{ matrix.module }}
129129
ko build --bare --platform=linux/amd64,linux/arm64 ${{ matrix.path }}

.github/workflows/publish.yml

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,14 @@ jobs:
8989
path: versions.txt
9090
retention-days: 90
9191

92-
container-publish:
92+
build-images-docker:
9393
runs-on: linux-amd64-cpu32
9494
timeout-minutes: 60
95+
permissions:
96+
contents: read
97+
packages: write
98+
id-token: write
99+
attestations: write
95100
strategy:
96101
matrix:
97102
include:
@@ -143,7 +148,7 @@ jobs:
143148
tag_suffix: ${{ matrix.tag_suffix }}
144149

145150
# Build images using ko and attest provenance
146-
build-images:
151+
build-images-ko:
147152
runs-on: linux-amd64-cpu32
148153
timeout-minutes: 60
149154
permissions:
@@ -154,6 +159,7 @@ jobs:
154159
env:
155160
KO_DOCKER_REPO: ghcr.io/${{ github.repository }}
156161
GIT_COMMIT: ${{ github.sha }}
162+
PLATFORMS: linux/amd64,linux/arm64
157163
outputs:
158164
images: ${{ steps.ko-build.outputs.images }}
159165
steps:
@@ -188,40 +194,38 @@ jobs:
188194
VERSION: ${{ steps.ref-name.outputs.value }}
189195
run: scripts/buildko.sh
190196

191-
attest:
192-
needs: build-images
197+
attest-and-sbom-ko:
198+
needs: build-images-ko
193199
runs-on: linux-amd64-cpu32
194200
permissions:
201+
contents: read
195202
packages: write
196203
id-token: write
197204
attestations: write
198205
strategy:
199206
matrix:
200-
image: ${{ fromJson(needs.build-images.outputs.images) }}
207+
image: ${{ fromJson(needs.build-images-ko.outputs.images) }}
201208
steps:
202-
- name: Authenticate to GHCR
203-
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
209+
- name: Checkout Code
210+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
204211
with:
205-
registry: ghcr.io
206-
username: ${{ github.actor }}
207-
password: ${{ secrets.GITHUB_TOKEN }}
212+
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
208213

209-
- name: Attest build provenance
210-
id: attest
211-
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
214+
- name: Generate SBOM and Attest
215+
uses: ./.github/actions/sbom-and-attest
212216
with:
213-
subject-name: ${{ matrix.image.name }}
214-
subject-digest: ${{ matrix.image.digest }}
215-
push-to-registry: true
217+
image_name: ${{ matrix.image.name }}
218+
image_digest: ${{ matrix.image.digest }}
219+
registry_password: ${{ secrets.GITHUB_TOKEN }}
216220

217221
e2e-test:
218222
name: "E2E Test Published Images"
219223
runs-on: linux-amd64-cpu32
220224
timeout-minutes: 60
221225
needs:
222-
- container-publish
223-
- build-images
224-
- attest
226+
- build-images-docker
227+
- build-images-ko
228+
- attest-and-sbom-ko
225229
env:
226230
CLUSTER_NAME: 'nvsentinel-uat'
227231
FAKE_GPU_NODE_COUNT: '10'

.ko.yaml

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,56 +15,141 @@
1515
# .ko.yaml in the root of the repository
1616
defaultBaseImage: cgr.dev/chainguard/static:latest
1717
platforms: [linux/amd64, linux/arm64]
18-
19-
ldflags:
20-
- "-s -w"
21-
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
22-
2318
env: [CGO_ENABLED=0]
2419

2520
builds:
2621

2722
- id: fault-quarantine-module
2823
dir: fault-quarantine-module
2924
main: .
25+
ldflags:
26+
- "-s -w"
27+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
28+
labels:
29+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
30+
org.opencontainers.image.licenses: "Apache-2.0"
31+
org.opencontainers.image.title: "NVSentinel Fault Quarantine Module"
32+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
33+
org.opencontainers.image.version: "{{.Env.VERSION}}"
34+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
35+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
3036

3137
- id: fault-remediation-module
3238
dir: fault-remediation-module
3339
main: .
40+
ldflags:
41+
- "-s -w"
42+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
43+
labels:
44+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
45+
org.opencontainers.image.licenses: "Apache-2.0"
46+
org.opencontainers.image.title: "NVSentinel Fault Remediation Module"
47+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
48+
org.opencontainers.image.version: "{{.Env.VERSION}}"
49+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
50+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
3451

3552
- id: health-events-analyzer
3653
dir: health-events-analyzer
3754
main: .
55+
ldflags:
56+
- "-s -w"
57+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
58+
labels:
59+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
60+
org.opencontainers.image.licenses: "Apache-2.0"
61+
org.opencontainers.image.title: "NVSentinel Health Events Analyzer"
62+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
63+
org.opencontainers.image.version: "{{.Env.VERSION}}"
64+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
65+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
3866

3967
- id: csp-health-monitor
4068
dir: health-monitors/csp-health-monitor
4169
main: ./cmd/csp-health-monitor
70+
ldflags:
71+
- "-s -w"
72+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
73+
labels:
74+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
75+
org.opencontainers.image.licenses: "Apache-2.0"
76+
org.opencontainers.image.title: "NVSentinel CSP Health Monitor"
77+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
78+
org.opencontainers.image.version: "{{.Env.VERSION}}"
79+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
80+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
4281

4382
- id: maintenance-notifier
4483
dir: health-monitors/csp-health-monitor
4584
main: ./cmd/maintenance-notifier
85+
ldflags:
86+
- "-s -w"
87+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
88+
labels:
89+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
90+
org.opencontainers.image.licenses: "Apache-2.0"
91+
org.opencontainers.image.title: "NVSentinel Maintenance Notifier"
92+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
93+
org.opencontainers.image.version: "{{.Env.VERSION}}"
94+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
95+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
4696

4797
- id: labeler-module
4898
dir: labeler-module
4999
main: .
100+
ldflags:
101+
- "-s -w"
102+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
103+
labels:
104+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
105+
org.opencontainers.image.licenses: "Apache-2.0"
106+
org.opencontainers.image.title: "NVSentinel Labeler Module"
107+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
108+
org.opencontainers.image.version: "{{.Env.VERSION}}"
109+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
110+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
50111

51112
- id: node-drainer-module
52113
dir: node-drainer-module
53114
main: .
115+
ldflags:
116+
- "-s -w"
117+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
118+
labels:
119+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
120+
org.opencontainers.image.licenses: "Apache-2.0"
121+
org.opencontainers.image.title: "NVSentinel Node Drainer Module"
122+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
123+
org.opencontainers.image.version: "{{.Env.VERSION}}"
124+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
125+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
54126

55127
- id: janitor
56128
dir: janitor
57129
main: .
130+
ldflags:
131+
- "-s -w"
132+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
133+
labels:
134+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
135+
org.opencontainers.image.licenses: "Apache-2.0"
136+
org.opencontainers.image.title: "NVSentinel Janitor"
137+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
138+
org.opencontainers.image.version: "{{.Env.VERSION}}"
139+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
140+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
58141

59142
- id: platform-connectors
60143
dir: platform-connectors
61144
main: .
62-
63-
labels:
64-
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
65-
org.opencontainers.image.licenses: "Apache-2.0"
66-
org.opencontainers.image.title: "NVSentinel"
67-
org.opencontainers.image.description: "Fault remediation service to help with rapidly node-level issues resolution in GPU-accelerated computing environments"
68-
org.opencontainers.image.version: "{{.Env.VERSION}}"
69-
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
70-
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"
145+
ldflags:
146+
- "-s -w"
147+
- "-X main.version={{.Env.VERSION}} -X main.commit={{.Env.GIT_COMMIT}} -X main.date={{.Env.BUILD_DATE}}"
148+
labels:
149+
org.opencontainers.image.source: "https://github.com/nvidia/nvsentinel"
150+
org.opencontainers.image.licenses: "Apache-2.0"
151+
org.opencontainers.image.title: "NVSentinel Platform Connectors"
152+
org.opencontainers.image.description: "Fault remediation service to help with rapid node-level issues resolution in GPU-accelerated computing environments"
153+
org.opencontainers.image.version: "{{.Env.VERSION}}"
154+
org.opencontainers.image.revision: "{{.Env.GIT_COMMIT}}"
155+
org.opencontainers.image.created: "{{.Env.BUILD_DATE}}"

0 commit comments

Comments
 (0)