* HH-462: bind release attestations to source and digest * HH-462: exercise release attestation production paths --------- Co-authored-by: Rogee <rogee@ipao.vip>
205 lines
7.0 KiB
Bash
Executable File
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
|