Validate signed release sidecar assets

This commit is contained in:
rcourtman 2026-04-22 16:30:01 +01:00
parent a442eb6bda
commit 21dde76c6f
3 changed files with 109 additions and 4 deletions

View file

@ -95,6 +95,64 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
}
}
func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
localValidatorBytes, err := os.ReadFile(repoFile("scripts", "validate-release.sh"))
if err != nil {
t.Fatalf("read validate-release.sh: %v", err)
}
localValidator := string(localValidatorBytes)
localRequired := []string{
`info "Validating SSH signature sidecars..."`,
`if [ ! -s "checksums.txt.sshsig" ]; then`,
`error "Missing or empty checksums.txt.sshsig"`,
`if [ ! -s "${filename}.sshsig" ]; then`,
`error "Missing or empty ${filename}.sshsig"`,
`success "SSH signature sidecars validated"`,
}
for _, needle := range localRequired {
if !strings.Contains(localValidator, needle) {
t.Fatalf("validate-release.sh missing signed sidecar validation: %s", needle)
}
}
publishedValidatorBytes, err := os.ReadFile(repoFile("scripts", "validate-published-release.sh"))
if err != nil {
t.Fatalf("read validate-published-release.sh: %v", err)
}
publishedValidator := string(publishedValidatorBytes)
publishedRequired := []string{
`CHECKSUMS_SIG_PATH="${TMP_DIR}/checksums.txt.sshsig"`,
`"${BASE_URL}/checksums.txt.sshsig"`,
`echo "Failed to download checksums.txt.sshsig for ${TAG}" >&2`,
`sshsig_path="${TMP_DIR}/${filename}.sshsig"`,
`"${artifact_url}.sshsig"`,
`echo "Failed to download ${filename}.sshsig" >&2`,
`Published release assets for ${TAG} match checksums.txt, *.sha256 files, and required *.sshsig sidecars.`,
}
for _, needle := range publishedRequired {
if !strings.Contains(publishedValidator, needle) {
t.Fatalf("validate-published-release.sh missing signed sidecar validation: %s", needle)
}
}
contractBytes, err := os.ReadFile(repoFile("docs", "release-control", "v6", "internal", "subsystems", "deployment-installability.md"))
if err != nil {
t.Fatalf("read deployment-installability contract: %v", err)
}
contract := string(contractBytes)
contractRequired := []string{
"`scripts/validate-release.sh`, and",
"`scripts/validate-published-release.sh` must derive the embedded update trust",
"fail validation if any",
"published artifact or `checksums.txt` is missing its `.sshsig` sidecar",
}
for _, needle := range contractRequired {
if !strings.Contains(contract, needle) {
t.Fatalf("deployment-installability contract missing signed sidecar validation requirement: %s", needle)
}
}
}
func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) {
dockerfileBytes, err := os.ReadFile(repoFile("Dockerfile"))
if err != nil {

View file

@ -2,9 +2,10 @@
# Remote release validator.
# Downloads the published (or draft) assets straight from GitHub Releases,
# recalculates their SHA256 sums, and ensures checksums.txt and the *.sha256
# helper files match what is actually live. This prevents broken updates when
# artifacts are re-uploaded without regenerating checksums (see issue #698).
# recalculates their SHA256 sums, and ensures checksums.txt, the *.sha256
# helper files, and the required *.sshsig sidecars match the live release
# packet. This prevents broken updates when artifacts are re-uploaded without
# regenerating checksums or their pinned signature sidecars (see issue #698).
set -euo pipefail
@ -29,6 +30,17 @@ if ! "${curl_args[@]}" "${BASE_URL}/checksums.txt" >"$CHECKSUMS_PATH"; then
exit 1
fi
CHECKSUMS_SIG_PATH="${TMP_DIR}/checksums.txt.sshsig"
echo "Downloading ${BASE_URL}/checksums.txt.sshsig"
if ! "${curl_args[@]}" "${BASE_URL}/checksums.txt.sshsig" >"$CHECKSUMS_SIG_PATH"; then
echo "Failed to download checksums.txt.sshsig for ${TAG}" >&2
exit 1
fi
if [[ ! -s "$CHECKSUMS_SIG_PATH" ]]; then
echo "checksums.txt.sshsig is empty for ${TAG}" >&2
exit 1
fi
status=0
while read -r checksum filename _; do
@ -66,6 +78,17 @@ while read -r checksum filename _; do
echo "${filename}.sha256 content mismatch (expected '${expected_line}', got '${sha_content}')" >&2
status=$((status + 1))
fi
sshsig_path="${TMP_DIR}/${filename}.sshsig"
if ! "${curl_args[@]}" "${artifact_url}.sshsig" >"$sshsig_path"; then
echo "Failed to download ${filename}.sshsig" >&2
status=$((status + 1))
continue
fi
if [[ ! -s "$sshsig_path" ]]; then
echo "${filename}.sshsig is empty" >&2
status=$((status + 1))
fi
done < "$CHECKSUMS_PATH"
if [[ "$status" -ne 0 ]]; then
@ -73,4 +96,4 @@ if [[ "$status" -ne 0 ]]; then
exit 1
fi
echo "Published release assets for ${TAG} match checksums.txt and *.sha256 files."
echo "Published release assets for ${TAG} match checksums.txt, *.sha256 files, and required *.sshsig sidecars."

View file

@ -364,12 +364,36 @@ info "Validating checksums..."
sha256sum -c checksums.txt >/dev/null 2>&1 || { error "checksums.txt validation failed"; exit 1; }
success "checksums.txt validated"
# Validate release signature sidecars
info "Validating SSH signature sidecars..."
if [ ! -s "checksums.txt.sshsig" ]; then
error "Missing or empty checksums.txt.sshsig"
exit 1
fi
while IFS= read -r line; do
checksum=$(echo "$line" | awk '{print $1}')
filename=$(echo "$line" | awk '{print $2}')
[ -n "$checksum" ] || continue
[ -n "$filename" ] || continue
if [ ! -s "${filename}.sshsig" ]; then
error "Missing or empty ${filename}.sshsig"
exit 1
fi
done < checksums.txt
success "SSH signature sidecars validated"
# Validate individual .sha256 files exist and match checksums.txt
info "Validating individual .sha256 files..."
while IFS= read -r line; do
checksum=$(echo "$line" | awk '{print $1}')
filename=$(echo "$line" | awk '{print $2}')
[ -n "$checksum" ] || continue
[ -n "$filename" ] || continue
# Check .sha256 file exists
if [ ! -f "${filename}.sha256" ]; then
error "Missing ${filename}.sha256"