fix(cd): signed-artifact-gate får retry på "no signatures found" (sign-racet mätt live) #60

Merged
admin merged 1 commits from fix/cd-gate-verify-retry into main 2026-09-15 02:55:42 +00:00
+41 -17
View File
@@ -112,31 +112,55 @@ jobs:
# the artifact release.yml produces instead. Bounded: a genuinely
# broken/skipped release run fails this job loudly rather than
# hanging forever.
#
# The loop retries TWO distinct not-ready states (measured live on
# run 2265dbb 2026-09-15: deploy pulled the image at 02:36:45 and
# failed verify at 02:37:03 with "no signatures found" while the
# release run attached the signature at 02:37:11 — an 8-second race
# that the pull-only retry missed):
# 1. tag not pushed yet (release still building)
# 2. tag pushed but signature not attached yet (release between
# push and cosign sign)
attempt=0
max_attempts=40
until docker pull "$IMAGE_REF" >/tmp/pull.log 2>&1; do
digest=""
while true; do
attempt=$((attempt + 1))
if [ "$attempt" -ge "$max_attempts" ]; then
echo "::error::no signed image found at $IMAGE_REF after $max_attempts attempts (10 min) — release.yml has not produced an artifact for sha-$SHORT. Refusing to deploy an unverified rebuild."
cat /tmp/pull.log
echo "::error::no signed image found at $IMAGE_REF after $max_attempts attempts (10 min) — release.yml has not produced a signed artifact for sha-$SHORT. Refusing to deploy an unverified rebuild."
cat /tmp/pull.log 2>/dev/null || true
cat /tmp/verify.log 2>/dev/null || true
exit 1
fi
echo "image not yet available at $IMAGE_REF (attempt $attempt/$max_attempts) — waiting for release.yml..."
sleep 15
if ! docker pull "$IMAGE_REF" >/tmp/pull.log 2>&1; then
echo "image not yet available at $IMAGE_REF (attempt $attempt/$max_attempts) — waiting for release.yml..."
sleep 15
continue
fi
# Read back the digest the registry actually stored for this tag —
# same filter release.yml itself uses (RepoDigests can hold multiple
# entries; never trust index 0) — and verify THAT digest, never the
# mutable tag alone, so a tag reassigned after signing can't slip past.
repo_digest="$(docker inspect --format='{{json .RepoDigests}}' "$IMAGE_REF" \
| node -e "const rd=JSON.parse(require('fs').readFileSync(0,'utf8')); const prefix=process.env.IMAGE_REPO+'@'; const m=rd.find(r=>r.startsWith(prefix)); if(!m){console.error('no RepoDigests entry starting with '+prefix+' among: '+JSON.stringify(rd));process.exit(1);} console.log(m);")"
digest="${repo_digest##*@}"
echo "$digest" | grep -Eq '^sha256:[0-9a-f]{64}$' || { echo "::error::unexpected digest shape: $digest"; exit 1; }
echo "verifying cosign signature on ${IMAGE_REPO}@${digest} ..."
if cosign verify --key cosign.pub "${IMAGE_REPO}@${digest}" >/tmp/verify.log 2>&1; then
break
fi
if grep -q "no signatures found" /tmp/verify.log; then
echo "image present but signature not attached yet (attempt $attempt/$max_attempts) — waiting for release.yml's cosign sign..."
sleep 15
continue
fi
echo "::error::cosign verify FAILED for ${IMAGE_REPO}@${digest} (not a not-ready state — refusing to deploy)"
cat /tmp/verify.log
exit 1
done
# Read back the digest the registry actually stored for this tag —
# same filter release.yml itself uses (RepoDigests can hold multiple
# entries; never trust index 0) — and verify THAT digest, never the
# mutable tag alone, so a tag reassigned after signing can't slip past.
repo_digest="$(docker inspect --format='{{json .RepoDigests}}' "$IMAGE_REF" \
| node -e "const rd=JSON.parse(require('fs').readFileSync(0,'utf8')); const prefix=process.env.IMAGE_REPO+'@'; const m=rd.find(r=>r.startsWith(prefix)); if(!m){console.error('no RepoDigests entry starting with '+prefix+' among: '+JSON.stringify(rd));process.exit(1);} console.log(m);")"
digest="${repo_digest##*@}"
echo "$digest" | grep -Eq '^sha256:[0-9a-f]{64}$' || { echo "::error::unexpected digest shape: $digest"; exit 1; }
echo "verifying cosign signature on ${IMAGE_REPO}@${digest} ..."
cosign verify --key cosign.pub "${IMAGE_REPO}@${digest}"
echo "digest=$digest" >> "$GITHUB_OUTPUT"
echo "SIGNED ARTIFACT VERIFIED: ${IMAGE_REPO}@${digest} — authorizing Coolify redeploy of sha-$SHORT"