Skip to content

Bump cosign / sign.Bundle retry budget to absorb transient Rekor flakes #1249

Description

@mchmarny

Summary

The AICR-side Sigstore signing in pkg/bundler/attestation/signing.go
performs a single-pass call to sign.Bundle() with no retry-with-backoff
on transient Rekor/Fulcio errors. The goreleaser cosign attest-blob
post-hook in .goreleaser.yaml similarly omits --retry. Rekor's public
instance occasionally has slow responses, and a single-attempt budget
turns ordinary upstream latency into a CI failure.

Bump the retry budget at both call sites so transient Rekor flakes
don't gate merges.

Background — observed failures

Two PRs hit this flake within 24h with the identical failure
fingerprint:

PR #1244 (feat/1223-registry-lint-guard)aicr recipe sign-catalog post-hook in goreleaser:

[cli] command failed: error=[TIMEOUT] sigstore signing timed out:
  Post "https://rekor.sigstore.dev/api/v1/log/entries":
  giving up after 1 attempt(s): context deadline exceeded exitCode=5

PR #1245 (feat/1222-enhance-existing-checks)
cli-bundle-attestation-ci chainsaw test invoking aicr bundle:

[cli] command failed: error=[INTERNAL] bundle attestation failed:
  [TIMEOUT] sigstore signing timed out:
  Post "https://rekor.sigstore.dev/api/v1/log/entries":
  giving up after 1 attempt(s): context deadline exceeded exitCode=8
--- FAIL: chainsaw/cli-bundle-attestation-ci (51.97s)

Both surface the same giving up after 1 attempt(s) shape — that's the
AICR-side sign.Bundle() wrapper at
pkg/bundler/attestation/signing.go:141-157. The 1 attempt budget is
not configurable today.

Affected Surfaces

  1. pkg/bundler/attestation/signing.go (lines ~130-157) — the
    sign.Bundle() invocation that backs aicr bundle --sign,
    aicr recipe sign-catalog, and aicr evidence. Single-pass under
    defaults.SigstoreSignTimeout. The classification logic distinguishes
    ErrCodeTimeout (context.DeadlineExceeded) from ErrCodeUnavailable
    (other Rekor/Fulcio errors) but doesn't retry either class.
  2. .goreleaser.yaml (lines ~36-46) — the binary-attestation
    post-hook calling cosign attest-blob. cosign's default retry budget
    is 1; no --retry flag is passed.

Proposed Approach

AICR-side (Go)

Add retry-with-backoff around sign.Bundle() for the transient class:

  • Retry on: ErrCodeUnavailable (Fulcio refused, Rekor 5xx,
    transient network) — these are the upstream-flake class.
  • Don't retry on: ErrCodeTimeout (caller's context deadline) —
    those are caller-intent failures and a retry would just chew through
    the budget. EXCEPT when the deadline is internal (i.e., we set
    SigstoreSignTimeout); then optionally retry up to a wall-clock cap.
  • Budget: 3 attempts total with exponential backoff (e.g., 1s, 5s,
    15s — Rekor's typical recovery window). Caps via
    defaults.SigstoreRetryBudget (new named constant).
  • The retry wrapper is in pkg/bundler/attestation/signing.go, behind
    the existing classification logic so callers still see structured
    errors with the same codes.

goreleaser-side

Add --retry 5 (cosign default backoff is reasonable) to the
cosign attest-blob invocation in .goreleaser.yaml. Costs nothing
when Rekor is healthy; saves an entire release run when it's slow.

Out of Scope

  • Switching off public Rekor for a private instance — out of scope.
  • Removing Rekor from the signing flow (--no-tlog) — out of scope;
    the supply-chain provenance contract requires the transparency log.
  • Per-call configurable retry budget exposed in the CLI flag set —
    not needed; the named constant in pkg/defaults is the single
    source of truth.

Acceptance Criteria

  • pkg/bundler/attestation/signing.go retries ErrCodeUnavailable
    failures from sign.Bundle() up to defaults.SigstoreRetryBudget
    attempts (initial proposal: 3) with exponential backoff.
  • The retry happens INSIDE the named SigstoreSignTimeout ceiling
    — a slow Rekor doesn't blow past the structured deadline contract.
  • ErrCodeTimeout (caller ctx deadline exceeded) is propagated
    as-is without retry.
  • .goreleaser.yaml cosign attest-blob calls pass --retry 5.
  • New defaults.SigstoreRetryBudget constant with a TestSigstoreRetryBudgetInvariant
    that gates against zero/negative values.
  • Unit test covers: success on first try, success on 2nd attempt
    after one ErrCodeUnavailable, full-budget exhaustion returns the
    last error, ErrCodeTimeout skips retry.
  • Documentation update in docs/contributor/ or the relevant
    attestation/signing reference doc mentioning the retry contract.

Related

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions