Skip to content

Commit a07d701

Browse files
committed
chore: write script to validate attestations
1 parent 917174c commit a07d701

File tree

2 files changed

+307
-0
lines changed

2 files changed

+307
-0
lines changed

.github/actions/sbom-and-attest/action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,16 @@ runs:
5858

5959
- name: Cosign SBOM attestation
6060
shell: bash
61+
env:
62+
COSIGN_EXPERIMENTAL: "1"
6163
run: |
64+
echo "Attesting SBOM for ${{ inputs.image_name }}@${{ inputs.image_digest }}"
6265
cosign attest \
6366
--yes \
6467
--predicate sbom-${{ steps.name.outputs.safe }}.cdx.json \
6568
--type cyclonedx \
6669
"${{ inputs.image_name }}@${{ inputs.image_digest }}"
70+
echo "Cosign attestation completed"
6771
6872
- name: Attest build provenance
6973
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
# verify-image-attestations.sh
18+
#
19+
# Validates SBOM attestations for all NVSentinel container images built with a specific tag.
20+
# This script checks both Ko-built images (Go services) and Docker-built images (Python services).
21+
#
22+
# Usage:
23+
# ./scripts/verify-image-attestations.sh <tag>
24+
# ./scripts/verify-image-attestations.sh v1.2.3
25+
# ./scripts/verify-image-attestations.sh 3b37e68
26+
#
27+
# Requirements:
28+
# - crane (for manifest inspection)
29+
# - cosign (for attestation verification)
30+
# - jq (for JSON parsing)
31+
32+
set -euo pipefail
33+
34+
# Color codes for output
35+
RED='\033[0;31m'
36+
GREEN='\033[0;32m'
37+
YELLOW='\033[1;33m'
38+
BLUE='\033[0;34m'
39+
NC='\033[0m' # No Color
40+
41+
# Configuration
42+
REGISTRY="${REGISTRY:-ghcr.io}"
43+
ORG="${ORG:-nvidia}"
44+
REPO_OWNER="${REPO_OWNER:-nvidia}"
45+
REPO_NAME="${REPO_NAME:-nvsentinel}"
46+
47+
# Image lists
48+
KO_IMAGES=(
49+
"nvsentinel/fault-quarantine-module"
50+
"nvsentinel/fault-remediation-module"
51+
"nvsentinel/health-events-analyzer"
52+
"nvsentinel/csp-health-monitor"
53+
"nvsentinel/maintenance-notifier"
54+
"nvsentinel/labeler"
55+
"nvsentinel/node-drainer"
56+
"nvsentinel/janitor"
57+
"nvsentinel/platform-connectors"
58+
)
59+
60+
DOCKER_IMAGES=(
61+
"nvsentinel/gpu-health-monitor:dcgm-3.x"
62+
"nvsentinel/gpu-health-monitor:dcgm-4.x"
63+
"nvsentinel/syslog-health-monitor"
64+
"nvsentinel/log-collector"
65+
"nvsentinel/file-server-cleanup"
66+
)
67+
68+
# Counters
69+
TOTAL_IMAGES=0
70+
PASSED_IMAGES=0
71+
FAILED_IMAGES=0
72+
SKIPPED_IMAGES=0
73+
74+
# Usage
75+
usage() {
76+
cat <<EOF
77+
Usage: $0 <tag>
78+
79+
Validates SBOM attestations for all NVSentinel container images.
80+
81+
Arguments:
82+
tag Image tag to verify (e.g., v1.2.3, 3b37e68)
83+
84+
Environment Variables:
85+
REGISTRY Container registry (default: ghcr.io)
86+
ORG Organization name (default: nvidia)
87+
REPO_OWNER GitHub repo owner (default: nvidia)
88+
REPO_NAME GitHub repo name (default: nvsentinel)
89+
90+
Examples:
91+
$0 v1.2.3
92+
$0 3b37e68
93+
REGISTRY=my-registry.io ORG=myorg $0 main-abc1234
94+
95+
EOF
96+
exit 1
97+
}
98+
99+
# Check required tools
100+
check_requirements() {
101+
local missing_tools=()
102+
103+
for tool in crane cosign jq; do
104+
if ! command -v "$tool" &> /dev/null; then
105+
missing_tools+=("$tool")
106+
fi
107+
done
108+
109+
if [ ${#missing_tools[@]} -ne 0 ]; then
110+
echo -e "${RED}Error: Missing required tools: ${missing_tools[*]}${NC}"
111+
echo "Please install them before running this script."
112+
exit 1
113+
fi
114+
}
115+
116+
# Extract platform digests from multi-platform image
117+
get_platform_digests() {
118+
local image_ref="$1"
119+
local manifest
120+
121+
manifest=$(crane manifest "$image_ref" 2>/dev/null || echo "")
122+
123+
if [ -z "$manifest" ]; then
124+
return 1
125+
fi
126+
127+
# Check if it's a multi-platform index
128+
local media_type
129+
media_type=$(echo "$manifest" | jq -r '.mediaType')
130+
131+
if [[ "$media_type" == "application/vnd.oci.image.index.v1+json" ]] || \
132+
[[ "$media_type" == "application/vnd.docker.distribution.manifest.list.v2+json" ]]; then
133+
# Extract individual platform digests
134+
echo "$manifest" | jq -r '.manifests[] | select(.platform.architecture != "unknown") | .digest'
135+
else
136+
# Single platform image - get its digest
137+
crane digest "$image_ref" 2>/dev/null || echo ""
138+
fi
139+
}
140+
141+
# Verify GitHub attestation
142+
verify_github_attestation() {
143+
local image_ref="$1"
144+
145+
if gh attestation verify "oci://${image_ref}" --owner "$REPO_OWNER" &>/dev/null; then
146+
return 0
147+
else
148+
return 1
149+
fi
150+
}
151+
152+
# Verify Cosign SBOM attestation
153+
verify_cosign_attestation() {
154+
local image_ref="$1"
155+
156+
# Check if SBOM tag exists
157+
if cosign tree "$image_ref" 2>&1 | grep -q "SBOM"; then
158+
return 0
159+
else
160+
return 1
161+
fi
162+
}
163+
164+
# Verify attestations for a single image digest
165+
verify_image_digest() {
166+
local image_name="$1"
167+
local digest="$2"
168+
local image_ref="${REGISTRY}/${ORG}/${image_name}@${digest}"
169+
170+
echo -e "${BLUE} Platform: ${digest:7:12}...${NC}"
171+
172+
local github_ok=false
173+
local cosign_ok=false
174+
175+
# Verify GitHub attestation
176+
if verify_github_attestation "$image_ref"; then
177+
echo -e "${GREEN} ✓ GitHub build provenance attestation${NC}"
178+
github_ok=true
179+
else
180+
echo -e "${YELLOW} ⚠ GitHub build provenance attestation not found${NC}"
181+
fi
182+
183+
# Verify Cosign SBOM attestation
184+
if verify_cosign_attestation "$image_ref"; then
185+
echo -e "${GREEN} ✓ Cosign SBOM attestation${NC}"
186+
cosign_ok=true
187+
else
188+
echo -e "${RED} ✗ Cosign SBOM attestation not found${NC}"
189+
fi
190+
191+
if $github_ok && $cosign_ok; then
192+
return 0
193+
else
194+
return 1
195+
fi
196+
}
197+
198+
# Verify attestations for a single image
199+
verify_image() {
200+
local image_name="$1"
201+
local tag="$2"
202+
local image_ref="${REGISTRY}/${ORG}/${image_name}:${tag}"
203+
204+
echo -e "\n${BLUE}Verifying: ${image_name}:${tag}${NC}"
205+
TOTAL_IMAGES=$((TOTAL_IMAGES + 1))
206+
207+
# Check if image exists
208+
if ! crane manifest "$image_ref" &>/dev/null; then
209+
echo -e "${YELLOW} ⊘ Image not found, skipping${NC}"
210+
SKIPPED_IMAGES=$((SKIPPED_IMAGES + 1))
211+
return
212+
fi
213+
214+
# Get platform digests
215+
local digests
216+
digests=$(get_platform_digests "$image_ref")
217+
218+
if [ -z "$digests" ]; then
219+
echo -e "${RED} ✗ Failed to get image digests${NC}"
220+
FAILED_IMAGES=$((FAILED_IMAGES + 1))
221+
return
222+
fi
223+
224+
# Verify each platform
225+
local all_passed=true
226+
while IFS= read -r digest; do
227+
if ! verify_image_digest "$image_name" "$digest"; then
228+
all_passed=false
229+
fi
230+
done <<< "$digests"
231+
232+
if $all_passed; then
233+
echo -e "${GREEN} ✓ All attestations verified${NC}"
234+
PASSED_IMAGES=$((PASSED_IMAGES + 1))
235+
else
236+
echo -e "${RED} ✗ Some attestations missing${NC}"
237+
FAILED_IMAGES=$((FAILED_IMAGES + 1))
238+
fi
239+
}
240+
241+
# Main function
242+
main() {
243+
if [ $# -ne 1 ]; then
244+
usage
245+
fi
246+
247+
local tag="$1"
248+
249+
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
250+
echo -e "${BLUE} NVSentinel Image Attestation Verification${NC}"
251+
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
252+
echo -e "Registry: ${REGISTRY}"
253+
echo -e "Organization: ${ORG}"
254+
echo -e "Tag: ${tag}"
255+
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
256+
257+
# Check requirements
258+
check_requirements
259+
260+
# Verify Ko-built images
261+
echo -e "\n${BLUE}═══ Ko-built Images (Go services) ═══${NC}"
262+
for image in "${KO_IMAGES[@]}"; do
263+
verify_image "$image" "$tag"
264+
done
265+
266+
# Verify Docker-built images
267+
echo -e "\n${BLUE}═══ Docker-built Images ═══${NC}"
268+
for image_spec in "${DOCKER_IMAGES[@]}"; do
269+
# Handle images with tag suffixes (e.g., gpu-health-monitor:dcgm-3.x)
270+
if [[ "$image_spec" == *":"* ]]; then
271+
image_base="${image_spec%:*}"
272+
suffix="${image_spec#*:}"
273+
full_tag="${tag}-${suffix}"
274+
else
275+
image_base="$image_spec"
276+
full_tag="$tag"
277+
fi
278+
verify_image "$image_base" "$full_tag"
279+
done
280+
281+
# Print summary
282+
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
283+
echo -e "${BLUE} Verification Summary${NC}"
284+
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
285+
echo -e "Total images checked: ${TOTAL_IMAGES}"
286+
echo -e "${GREEN}Passed: ${PASSED_IMAGES}${NC}"
287+
echo -e "${RED}Failed: ${FAILED_IMAGES}${NC}"
288+
echo -e "${YELLOW}Skipped: ${SKIPPED_IMAGES}${NC}"
289+
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
290+
291+
if [ $FAILED_IMAGES -gt 0 ]; then
292+
echo -e "\n${RED}Some images are missing attestations!${NC}"
293+
exit 1
294+
elif [ $PASSED_IMAGES -eq 0 ]; then
295+
echo -e "\n${YELLOW}No images were successfully verified.${NC}"
296+
exit 1
297+
else
298+
echo -e "\n${GREEN}All images have valid attestations!${NC}"
299+
exit 0
300+
fi
301+
}
302+
303+
main "$@"

0 commit comments

Comments
 (0)