Files
gochat/.github/scripts/release_attestation.sh
T
Rogeeandrogee b6b1597e90 HH-462: harden release attestation semantics (#94)
* HH-462: bind release attestations to source and digest

* HH-462: exercise release attestation production paths

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-22 04:07:32 +08:00

205 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
readonly statement_type=https://in-toto.io/Statement/v0.1
readonly provenance_type=https://slsa.dev/provenance/v1
readonly spdx_type=https://spdx.dev/Document
image_subject() {
local image_ref=$1
[[ $image_ref =~ ^(.+)@sha256:([0-9a-f]{64})$ ]] || {
echo "Expected immutable sha256 image reference: $image_ref" >&2
return 1
}
printf '%s\n%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
}
create_provenance() {
local output=$1 source_uri=$2 source_ref=$3 commit=$4 builder=$5 invocation=$6
jq -n \
--arg source_uri "$source_uri" \
--arg source_ref "$source_ref" \
--arg commit "$commit" \
--arg builder "$builder" \
--arg invocation "$invocation" \
'{
buildDefinition: {
buildType: "https://github.com/Attestations/GitHubActionsWorkflow@v1",
externalParameters: {source: {uri: $source_uri, ref: $source_ref}},
internalParameters: {},
resolvedDependencies: [{uri: $source_uri, digest: {gitCommit: $commit}}]
},
runDetails: {
builder: {id: $builder},
metadata: {invocationId: $invocation}
}
}' > "$output"
}
extract_sbom() (
local output=$1 image_ref=$2 wrapper
wrapper=$(mktemp)
trap 'rm -f "$wrapper"' EXIT
docker buildx imagetools inspect "$image_ref" --format '{{ json .SBOM }}' > "$wrapper"
jq -e '
.SPDX
| .SPDXID == "SPDXRef-DOCUMENT"
and (.spdxVersion | startswith("SPDX-"))
and (.packages | type == "array")
' "$wrapper" > /dev/null
jq '.SPDX' "$wrapper" > "$output"
)
verify_subject() {
local statement=$1 subject_ref=$2 expected_name expected_digest
mapfile -t subject < <(image_subject "$subject_ref")
expected_name=${subject[0]}
expected_digest=${subject[1]}
jq -e \
--arg statement_type "$statement_type" \
--arg expected_name "$expected_name" \
--arg expected_digest "$expected_digest" '
._type == $statement_type
and (.subject | length == 1)
and .subject[0].name == $expected_name
and .subject[0].digest.sha256 == $expected_digest
' "$statement" > /dev/null
}
verify_statement() {
local kind=$1 statement=$2 subject_ref=$3
verify_subject "$statement" "$subject_ref"
shift 3
case $kind in
provenance)
local source_uri=$1 source_ref=$2 commit=$3 builder=$4 invocation=$5
jq -e \
--arg predicate_type "$provenance_type" \
--arg source_uri "$source_uri" \
--arg source_ref "$source_ref" \
--arg commit "$commit" \
--arg builder "$builder" \
--arg invocation "$invocation" '
.predicateType == $predicate_type
and .predicate.buildDefinition.buildType == "https://github.com/Attestations/GitHubActionsWorkflow@v1"
and .predicate.buildDefinition.externalParameters.source.uri == $source_uri
and .predicate.buildDefinition.externalParameters.source.ref == $source_ref
and (.predicate.buildDefinition.resolvedDependencies | length == 1)
and .predicate.buildDefinition.resolvedDependencies[0].uri == $source_uri
and .predicate.buildDefinition.resolvedDependencies[0].digest.gitCommit == $commit
and .predicate.runDetails.builder.id == $builder
and .predicate.runDetails.metadata.invocationId == $invocation
' "$statement" > /dev/null
;;
sbom)
jq -e \
--arg predicate_type "$spdx_type" '
.predicateType == $predicate_type
and .predicate.SPDXID == "SPDXRef-DOCUMENT"
and (.predicate.spdxVersion | startswith("SPDX-"))
and (.predicate.packages | type == "array")
' "$statement" > /dev/null
;;
*)
echo "Unknown attestation kind: $kind" >&2
return 1
;;
esac
}
verify_attestation() (
local kind=$1 lookup_ref=$2 subject_ref=$3 identity=$4 predicate_alias envelope statement payload
shift 4
case $kind in
provenance) predicate_alias=slsaprovenance1 ;;
sbom) predicate_alias=spdxjson ;;
*) echo "Unknown attestation kind: $kind" >&2; return 1 ;;
esac
envelope=$(mktemp)
statement=$(mktemp)
trap 'rm -f "$envelope" "$statement"' EXIT
cosign verify-attestation \
--type "$predicate_alias" \
--certificate-identity "$identity" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"$lookup_ref" > "$envelope"
payload=$(jq -sr -e 'map(select(.payload?))[0].payload' "$envelope")
printf '%s' "$payload" | base64 --decode > "$statement"
verify_statement "$kind" "$statement" "$subject_ref" "$@"
)
attest_images() (
local source_uri=$1 source_ref=$2 commit=$3 builder=$4 invocation=$5
local image_ref sbom provenance temp_dir
shift 5
(($# > 0))
temp_dir=$(mktemp -d)
trap 'rm -rf "$temp_dir"' EXIT
for image_ref in "$@"; do
sbom="$temp_dir/sbom.json"
provenance="$temp_dir/provenance.json"
extract_sbom "$sbom" "$image_ref"
create_provenance "$provenance" "$source_uri" "$source_ref" "$commit" "$builder" "$invocation"
cosign attest --yes --type spdxjson --predicate "$sbom" "$image_ref"
cosign attest --yes --type slsaprovenance1 --predicate "$provenance" "$image_ref"
done
)
verify_image() {
local lookup_ref=$1 subject_ref=$2 identity=$3 source_uri=$4 source_ref=$5 commit=$6 builder=$7 invocation=$8
cosign verify \
--certificate-identity "$identity" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"$lookup_ref"
verify_attestation sbom "$lookup_ref" "$subject_ref" "$identity"
verify_attestation provenance "$lookup_ref" "$subject_ref" "$identity" \
"$source_uri" "$source_ref" "$commit" "$builder" "$invocation"
}
verify_images() {
local identity=$1 source_uri=$2 source_ref=$3 commit=$4 builder=$5 invocation=$6 image_ref
shift 6
(($# > 0))
for image_ref in "$@"; do
verify_image "$image_ref" "$image_ref" "$identity" \
"$source_uri" "$source_ref" "$commit" "$builder" "$invocation"
done
}
verify_promoted() {
local identity=$1 source_uri=$2 source_ref=$3 commit=$4 builder=$5 invocation=$6
local entry subject_ref target expected actual
shift 6
(($# > 0))
for entry in "$@"; do
subject_ref=${entry%%|*}
target=${entry#*|}
expected=${subject_ref##*@}
actual=$(crane digest "$target")
[[ $actual == "$expected" ]] || {
echo "Digest mismatch: $target resolved to $actual, expected $expected" >&2
return 1
}
echo "$target -> $actual"
verify_image "$target" "$subject_ref" "$identity" \
"$source_uri" "$source_ref" "$commit" "$builder" "$invocation"
done
}
case ${1:-} in
create-provenance) shift; create_provenance "$@" ;;
extract-sbom) shift; extract_sbom "$@" ;;
attest-images) shift; attest_images "$@" ;;
verify-statement) shift; verify_statement "$@" ;;
verify) shift; verify_attestation "$@" ;;
verify-images) shift; verify_images "$@" ;;
verify-promoted) shift; verify_promoted "$@" ;;
*)
echo "Usage: $0 {create-provenance|extract-sbom|attest-images|verify-statement|verify|verify-images|verify-promoted} ..." >&2
exit 2
;;
esac