Skip to content

Commit 7b94441

Browse files
committed
shows example content from the provenance blobs we push with indexes
1 parent f2ed3ee commit 7b94441

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

print-provenance.sh

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
cat >&2 <<EOF
6+
Usage: $0 <image[:tag]> [--raw] [--debug]
7+
--raw Print full attestation JSON (can repeat for multiple)
8+
--debug Show crane commands executed
9+
Defaults to :latest if no tag provided.
10+
EOF
11+
}
12+
13+
if ! command -v crane >/dev/null 2>&1; then
14+
echo "crane not found in PATH" >&2; exit 1; fi
15+
if ! command -v jq >/dev/null 2>&1; then
16+
echo "jq not found in PATH" >&2; exit 1; fi
17+
18+
[ $# -ge 1 ] || { usage; exit 1; }
19+
20+
IMAGE="$1"; shift || true
21+
RAW="false"; DEBUG="false"
22+
while [ $# -gt 0 ]; do
23+
case "$1" in
24+
--raw) RAW="true";;
25+
--debug) DEBUG="true";;
26+
-h|--help) usage; exit 0;;
27+
*) echo "Unknown arg: $1" >&2; usage; exit 1;;
28+
esac
29+
shift
30+
done
31+
32+
# Add :latest if no explicit tag or digest
33+
if [[ "$IMAGE" != *:@* && "$IMAGE" != *:*/*:* && "$IMAGE" != *:*:* && "$IMAGE" != *@sha256:* ]]; then
34+
# has no :tag part after last slash
35+
if [[ "$IMAGE" != *:* ]]; then
36+
IMAGE+=":latest"
37+
fi
38+
fi
39+
40+
echo "Inspecting provenance for $IMAGE" >&2
41+
42+
# Obtain manifest list (or single manifest) JSON
43+
if ! MANIFEST_JSON=$(crane manifest "$IMAGE" 2>/dev/null); then
44+
echo "Failed to fetch manifest for $IMAGE" >&2; exit 1;
45+
fi
46+
47+
# If it's a single-platform manifest, wrap to unify processing
48+
if ! echo "$MANIFEST_JSON" | jq -e '.manifests' >/dev/null 2>&1; then
49+
MANIFEST_LIST_JSON='{"manifests":[]}'
50+
else
51+
MANIFEST_LIST_JSON="$MANIFEST_JSON"
52+
fi
53+
54+
UNKNOWN_DIGESTS=$(echo "$MANIFEST_LIST_JSON" | jq -r '.manifests[]? | select(.platform.os=="unknown" and .platform.architecture=="unknown") | .digest')
55+
if [ -z "$UNKNOWN_DIGESTS" ]; then
56+
echo "No unknown/unknown platform manifests (attestations) found." >&2
57+
echo "Hint: ensure builds set provenance and sbom (buildkit) or attest step." >&2
58+
exit 2
59+
fi
60+
61+
FOUND=0
62+
REGISTRY="${IMAGE%%/*}" # crude but ok for ghcr.io/owner/name:tag
63+
REPO_TAG=${IMAGE#*/}
64+
# Split repo and tag/digest
65+
if [[ "$REPO_TAG" == *"@sha256:"* ]]; then
66+
REPO="${REPO_TAG%@sha256:*}"; REF="${REPO_TAG#*@}"; REF_TYPE=digest
67+
else
68+
REPO="${REPO_TAG%%:*}"; REF="${REPO_TAG##*:}"; REF_TYPE=tag
69+
fi
70+
71+
IMAGE_PATH=${IMAGE#*/} # remove registry
72+
REPO_PATH=${IMAGE_PATH%%@*} # drop @digest if any
73+
REPO_PATH=${REPO_PATH%%:*} # drop :tag
74+
75+
[ "$DEBUG" = "true" ] && echo "+ crane manifest $IMAGE # top-level" >&2
76+
77+
for DGST in $UNKNOWN_DIGESTS; do
78+
BASE_REF=${IMAGE%%@*}; BASE_REF=${BASE_REF%%:*} # registry/owner/name
79+
SUB_JSON=$(crane manifest "${BASE_REF}@${DGST}" 2>/dev/null) || continue
80+
[ "$DEBUG" = "true" ] && echo "+ crane manifest ${BASE_REF}@${DGST}" >&2
81+
LAYER_DIGESTS=$(echo "$SUB_JSON" | jq -r '.layers[]? | select(.mediaType | test("in-toto")) | .digest')
82+
[ -z "$LAYER_DIGESTS" ] && continue
83+
for LD in $LAYER_DIGESTS; do
84+
FOUND=1
85+
[ "$DEBUG" = "true" ] && echo "Sub-manifest digest: $DGST" >&2 && echo "In-toto layer digest: $LD" >&2
86+
# Retrieve attestation layer (handle crane versions expecting single arg)
87+
[ "$DEBUG" = "true" ] && echo "+ crane blob ${BASE_REF}@${LD}" >&2
88+
ATTESTATION=$(crane blob "${BASE_REF}@${LD}" 2>/dev/null || crane blob "${IMAGE%@*}@${LD}" 2>/dev/null || true)
89+
[ -z "$ATTESTATION" ] && continue
90+
if [ "$RAW" = "true" ]; then
91+
echo "$ATTESTATION" | jq '.'
92+
continue
93+
fi
94+
echo "--- Attestation layer $LD (sub-manifest $DGST) ---"
95+
JQ_SUMMARY='def dockerfiles: [ (.. | objects | to_entries[]? | select(.key|test("dockerfile";"i")) | .value) ] | flatten | map(tostring) | unique | .;
96+
def mats: (.materials // .predicate.materials // []);
97+
def norm(u; d):
98+
if (u|startswith("docker-image://")) then
99+
(u | sub("^docker-image://";"")) as $ref |
100+
if (d|length>0) and ($ref|test("@sha256:" )|not) then ($ref|split("@")|.[0]) + "@sha256:" + d else $ref end
101+
elif (u|startswith("pkg:docker/")) then
102+
(u | sub("^pkg:docker/";"") | split("?") | .[0]) as $ref |
103+
if (d|length>0) and ($ref|test("@sha256:" )|not) then ($ref|split("@")|.[0]) + "@sha256:" + d else $ref end
104+
else
105+
if (d|length>0) and (u|test("@sha256:" )|not) then (u + "@sha256:" + d) else u end
106+
end;
107+
def base_images: mats | map( ( .uri // .uri_ // empty ) as $u | ( .digest.sha256? // "" ) as $d | select($u != "") | norm($u; $d) ) | unique;
108+
def bkmeta: .predicate.metadata["https://mobyproject.org/buildkit@v1#metadata"].vcs? // {};
109+
def guess_source: (bkmeta.source // .predicate.invocation.environment.GIT_URL? // .predicate.buildConfig.sourceProvenance.resolvedRepoSource.repoUrl? // empty);
110+
def guess_revision: (bkmeta.revision // .predicate.invocation.environment.GITHUB_SHA? // .predicate.invocation.environment.GIT_COMMIT_SHA? // empty);
111+
["Dockerfiles:"] + (dockerfiles| if length==0 then ["(none found)"] else . end) +
112+
["Base images (materials):"] + (base_images | if length==0 then ["(none found)"] else . end) +
113+
["VCS source:", (guess_source // "(unknown)"),
114+
"VCS revision:", (guess_revision // "(unknown)"),
115+
"Build started:", (.predicate.metadata.buildStartedOn? // "(unknown)"),
116+
"Build finished:", (.predicate.metadata.buildFinishedOn? // "(unknown)")] | .[]'
117+
[ "$DEBUG" = "true" ] && echo "+ jq -r <summary_program>" >&2 && echo "$JQ_SUMMARY" | sed 's/^/| /' >&2
118+
SUMMARY=$(echo "$ATTESTATION" | jq -r "$JQ_SUMMARY")
119+
if [ -z "${PREV_LAST:-}" ]; then
120+
echo "$SUMMARY"
121+
else
122+
DIFF_PRINTED=false
123+
while IFS= read -r line; do
124+
if ! printf '%s\n' "$PREV_LAST" | grep -Fxq "$line"; then
125+
[ "$DIFF_PRINTED" = false ] && echo "(diff from previous attestation)" && DIFF_PRINTED=true
126+
echo "$line"
127+
fi
128+
done <<< "$SUMMARY"
129+
[ "$DIFF_PRINTED" = false ] && echo "(no diff from previous attestation)"
130+
fi
131+
PREV_LAST="$SUMMARY"
132+
done
133+
done
134+
135+
if [ $FOUND -eq 0 ]; then
136+
echo "No attestation (in-toto) layers found in unknown/unknown manifests." >&2
137+
exit 3
138+
fi

0 commit comments

Comments
 (0)