Skip to content

fix(build-push-docker-manifest): idempotent manifest + cosign reruns (RANE-4683)#1577

Open
HashWrangler wants to merge 20 commits into
mainfrom
fix/cosign-verify-retry
Open

fix(build-push-docker-manifest): idempotent manifest + cosign reruns (RANE-4683)#1577
HashWrangler wants to merge 20 commits into
mainfrom
fix/cosign-verify-retry

Conversation

@HashWrangler

@HashWrangler HashWrangler commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Hardens build-publish against manifest job flakes and non-idempotent reruns (RANE-4683).

Layer Problem Fix
Manifest create Rerun calls imagetools create again → new index digest (digest drift) Skip create when tag already points at the same platform digests; fail if tag exists with different digests
Cosign sign Always signs the digest produced/inspected by create Sign always in the action (safe with idempotent create); no in-action verify/skip-sign
Cosign verify First-run flake: no signatures found right after sign; job rerun used to re-create the index Isolated verify-manifest-signature job (needs: docker-manifest) with exponential backoff up to 10 minutes (job timeout-minutes: 12)
Manifest inspect Tag visible before multi-arch manifest fully propagated (ECR lag) Post-create inspect retries until platform digests match expected inputs

Parent epic: RANE-4695 private release improvements.

Design (final)

  • Composite action owns create + sign + create-idempotency + ECR propagation guard.
  • Reusable workflow owns post-sign verify as its own job, so a Sigstore propagation false-negative retries alone and never re-runs create/sign (the RANE-4683 digest-drift path).

Evidence

  • v2.43.0 / SECHD-30708 — non-idempotent reruns
  • chainlink + canary v2.990.5-beta.0 — cosign verify flake; rerun passed
  • v2.55.0-rc.0 — verify false-negative → manifest job rerun → tag moved onto unsigned index 1070bfbf… while signed digest was ff93d395… (identical platform children)

Out of scope (separate PR)

  • chainlink#22877 — Slack only posts when both core + ccip manifests succeed (RANE-4695 UX guard)

Test plan

  • Changesets publish minor build-push-docker-manifest + patch reusable-docker-build-publish
  • Confirm reusable-docker-build-publish @build-push-docker-manifest/v1 picks up release
  • Tag build on chainlink/canary: first run green
  • Re-run failed verify job only: digest unchanged, verify passes after backoff
  • Simulate digest mismatch on existing tag → create job fails with explicit error

@HashWrangler HashWrangler requested a review from a team as a code owner June 17, 2026 16:50
@github-actions

Copy link
Copy Markdown
Contributor

👋 HashWrangler, thanks for creating this pull request!

To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team.

Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks!

@HashWrangler HashWrangler marked this pull request as draft June 17, 2026 16:50
@HashWrangler HashWrangler changed the title fix(build-push-docker-manifest): retry cosign verify after sign fix(build-push-docker-manifest): idempotent manifest + cosign reruns (RANE-4683) Jun 17, 2026
…(RANE-4683)

- Skip imagetools create when the tag already references the expected
  platform digests; fail if the tag exists with different digests
- Skip cosign sign when verify already succeeds (idempotent rerun)
- Retry cosign verify after sign for Sigstore propagation flakes

Together these changes make build-publish manifest jobs safe to rerun without
digest drift or redundant signing, while still failing loudly on real conflicts.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the build-push-docker-manifest composite action to make build-publish reruns idempotent and reduce flakiness around Cosign verification/signature propagation.

Changes:

  • Adds an idempotency guard for manifest creation by comparing expected vs existing platform digests and skipping imagetools create when they match.
  • Skips cosign sign when an existing valid signature is already present, and adds retry logic to cosign verify to absorb Sigstore propagation lag.
  • Adds a changeset to release these hardening updates as a minor version bump.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
actions/build-push-docker-manifest/action.yml Adds digest comparison to avoid manifest digest drift on reruns; makes cosign signing/verification rerun-safe with skip + retry behavior.
.changeset/rane-4683-manifest-cosign-hardening.md Declares a minor release for the manifest + cosign hardening changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread actions/build-push-docker-manifest/action.yml
Comment thread actions/build-push-docker-manifest/action.yml
Comment thread actions/build-push-docker-manifest/action.yml Outdated
Comment thread actions/build-push-docker-manifest/action.yml Outdated
- Ensure jq is installed before digest comparison
- Validate docker-image-name-digests is non-empty
- Only skip cosign sign when OIDC identity constraints verify
- Fail fast on unexpected imagetools inspect errors (not just missing tag)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread actions/build-push-docker-manifest/action.yml Outdated
Comment thread actions/build-push-docker-manifest/action.yml Outdated
Keep MANIFEST_CREATE_SKIPPED expression on one line and only sleep between
cosign verify retries, not after the final failed attempt.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread actions/build-push-docker-manifest/action.yml Outdated
Comment thread actions/build-push-docker-manifest/action.yml Outdated
- Fail fast when jq is missing and apt-get/sudo are unavailable
- Shorten step/output ids so summary env expressions stay on one line
  under Prettier (avoids split ${{ }} expressions)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread actions/build-push-docker-manifest/action.yml Outdated
Comment thread actions/build-push-docker-manifest/action.yml Outdated
…empotency

Treat digest lists as sets: sort -u in normalization and existing-manifest
inspection, and reuse normalized digests when building imagetools create args.
- Only sleep between manifest digest inspect retries, not after the final fail
- Fail fast when an existing tag has no extractable platform digests instead
  of falling through to imagetools create (digest drift on rerun)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread actions/build-push-docker-manifest/action.yml
Reject non-sha256 tokens in normalize_digest_csv so bad
docker-image-name-digests input fails fast with a clear error instead
of deferring to imagetools create.
@HashWrangler HashWrangler requested a review from Copilot June 17, 2026 17:58
Comment thread actions/build-push-docker-manifest/action.yml Outdated
Comment thread actions/build-push-docker-manifest/action.yml Outdated
Comment thread workflows/reusable-docker-build-publish/reusable-docker-build-publish.yml Outdated
… (RANE-4683)

Per review (Erik/Chad): move cosign signature verification out of the
build-push-docker-manifest action into a standalone verify-manifest-signature
job in reusable-docker-build-publish, so a Sigstore propagation-lag flake can be
retried in isolation without re-running manifest create/sign. That rerun was the
root cause of index digest drift stranding the tag on an unsigned wrapper
(hit again on v2.55.0-rc.0 GA).

- reusable-docker-build-publish: new verify-manifest-signature job (needs
  docker-manifest); expose docker-manifest name-with-digest output; remove the
  inline verify step.
- build-push-docker-manifest: always cosign sign (safe now that verify is
  decoupled and imagetools create is idempotent); drop sign-skip verification
  shell; drop the jq availability guard (preinstalled on ubuntu-24.04); tidy
  summary.
- regenerate synced .github/workflows copy; update changeset.
@HashWrangler

Copy link
Copy Markdown
Author

Pushed 9c15a99 — verification is now its own job, per @erikburt / @chainchad.

reusable-docker-build-publish: new verify-manifest-signature job (needs: docker-manifest) that owns the cosign verify + 5×10s Sigstore-propagation retry. The docker-manifest job now only creates + signs (and exposes a name-with-digest output the verify job consumes). So a "no signatures found" propagation false-negative retries alone — manifest create/sign is never re-run, which removes the index digest-drift path entirely.

build-push-docker-manifest: back to create + sign only. Since verify is decoupled and imagetools create is already idempotent (skips when the tag points at the same platform digests), I dropped the sign-skip verification shell — it only existed to survive verify-triggered reruns. Also dropped the jq guard per your note, Erik.

@chainchad re: "is it ever unsigned, or just a delay to verify" — concrete answer from today: it was signed, then a rerun stranded the tag. This exact flake blocked v2.55.0 GA this morning. On Jul 7 the RC index sha256:ff93d395… was signed and announced, but the in-run verify hit the propagation lag → the manifest job was rerun → imagetools create produced a new index sha256:1070bfbf… and moved the tag onto it, unsigned. release-final then failed cosign verify on the current tag digest. Both indexes wrap identical per-arch images (amd64 7a3b55ba…, arm64 a048eb51…) — pure digest drift. A standalone, independently-retryable verify job prevents this by construction. (GA unblock itself needs ProdSec to re-point/re-sign the release-ECR tag — separate SECHD ticket.)

…ature action

Address "too much bash in the job" (Chad): move the standalone verify job's
inline cosign install + retry loop into a dedicated composite action
`verify-docker-signature`, with the retry logic in scripts/verify-with-retry.sh.
The verify-manifest-signature job in reusable-docker-build-publish is now
declarative (aws login -> ecr login -> uses: verify-docker-signature).

- new action actions/verify-docker-signature (action.yml, scripts, README,
  package.json, project.json); cosign verify with configurable retry for
  Sigstore propagation lag.
- reusable-docker-build-publish: verify job uses the new action (pinned @main
  pending first release; TODO to pin to /v1).
- changeset + regenerated .github/workflows copy.
Revert the verify-docker-signature composite action / external script; per
follow-up review we keep the logic directly in the workflow (matching the
existing inline style in reusable-docker-build-publish). Verification stays in
its own verify-manifest-signature job so a Sigstore propagation flake retries
without re-running create/sign, but the cosign install + retry loop live inline.
Per review (Chad: the retry loop isn't good; Erik: keep verify as its own
isolated step): drop the 5x10s hand-rolled retry loop and verify once. The
verify-manifest-signature job is already isolated and independently re-runnable,
so it is the retry unit for the Sigstore propagation-lag case — re-running it
never touches manifest create/sign, so no index digest drift (RANE-4683).
Per Chad: replace the fixed retry with an exponential-backoff retry (10s, 20s,
40s ... capped at 120s/wait) bounded to 10 minutes total, in the isolated
verify-manifest-signature job. Absorbs Sigstore propagation lag without ever
re-running manifest create/sign (RANE-4683).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread actions/build-push-docker-manifest/action.yml
Comment thread .github/workflows/reusable-docker-build-publish.yml Outdated
Comment thread workflows/reusable-docker-build-publish/reusable-docker-build-publish.yml Outdated
Job timeout was 5m while the in-step retry budget is 600s, so GitHub
could kill the job before Sigstore propagation retries finished.
@HashWrangler

Copy link
Copy Markdown
Author

Ready for re-review

All open review threads replied + resolved. Tip: 585dd63.

Final shape

  1. build-push-docker-manifest — idempotent imagetools create + always cosign sign + ECR propagation inspect (no in-action verify / sign-skip / jq guard)
  2. reusable-docker-build-publish — isolated verify-manifest-signature job with exponential backoff up to 10 min (timeout-minutes: 12)

Copilot today

  • ✅ timeout mismatch (5m job vs 600s retry) — fixed in 585dd63
  • ❌ jq preflight — won't fix (per Erik/Chad: assume available)

@erikburt @chainchad — please take another look when you can.

Comment thread actions/build-push-docker-manifest/action.yml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread .github/workflows/reusable-docker-build-publish.yml
Explain why we compare platform digests (not index digests), skip vs
fail-hard paths, and how create idempotency relates to the isolated
verify job (RANE-4683).
Makes auth/identity mismatches distinguishable from Sigstore
propagation lag when the 10-minute retry budget is exhausted.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread actions/build-push-docker-manifest/action.yml
Comment thread actions/build-push-docker-manifest/action.yml
Descriptors without .platform (e.g. SBOMs/attachments) no longer pass
the idempotency/propagation digest comparison.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@HashWrangler HashWrangler requested a review from chainchad July 13, 2026 20:36

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of complexity that we're adding to this action.

Since we're moving the verify step to its own job, what if we simplified this to make the manifest index idempotent by checking if the manifest index tag exists and then running successive steps only if it does not?

Example:

- name: Check if manifest already exists
  id: manifest-check
  shell: bash
  env:
    MANIFEST: ${{ steps.manifest-name.outputs.name }}:${{ inputs.docker-manifest-tag }}
  run: |
    if docker buildx imagetools inspect "${MANIFEST}" >/dev/null 2>&1; then
      echo "exists=true"  | tee -a "$GITHUB_OUTPUT"
    else
      echo "exists=false" | tee -a "$GITHUB_OUTPUT"
    fi

- name: Create and push Docker manifest
  if: steps.manifest-check.outputs.exists != 'true'
  ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants