Skip to content

fix(ci): retry cosign transparency-log writes and upload SBOMs first - #10232

Merged
fatih-acar merged 1 commit into
stablefrom
pc-harden-cosign-transparency-log-retries
Aug 20, 2026
Merged

fix(ci): retry cosign transparency-log writes and upload SBOMs first#10232
fatih-acar merged 1 commit into
stablefrom
pc-harden-cosign-transparency-log-retries

Conversation

@petercrocker

@petercrocker petercrocker commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Problem

The v1.10.7 release failed in publish-docker-image / sbom (run 31521732131) at the Attest SBOM (SPDX) step:

Error: signing ...infrahub@sha256:fabec4d2...: signing bundle: error signing bundle:
Post "https://rekor.sigstore.dev/api/v1/log/entries": giving up after 2 attempt(s)

Rekor was only briefly unavailable. Evidence it was transient, not a config break:

  • The sign job succeeded at 18:21:53 against the same Sigstore infrastructure; sbom failed at 18:26–18:27.
  • The identical sbom job passed on the next run (31549394917) with no code change.

cosign retries a failed transparency-log write only twice before giving up, which is short enough that a blip of a few minutes fails an entire release.

Why it mattered more than it should have

Attest SBOM (CycloneDX) and Upload SBOM artifacts never ran, so:

  • v1.10.7 shipped with no SBOM artifacts — it is the only gap in the sbom-infrahub-v1.10.0v1.10.6 sequence (since re-run manually).
  • repository-dispatch was skipped. It declares needs: publish-docker-image (release.yml:137-141), so downstream repos were never notified of v1.10.7.

Changes

  1. Retry with exponential backoff (5 attempts: 15s/30s/60s/120s) around cosign sign and both cosign attest calls. Both talk to the same transparency log and are equally exposed; only attest happened to lose the coin flip this time.
  2. Upload SBOM artifacts before attesting them, so a transparency-log outage can no longer cost us the SBOMs. Attestation failure still fails the job — that is the correct outcome for a supply-chain step — but the artifacts survive.

The two Attest SBOM (*) steps are consolidated into one Attest SBOMs step so the retry helper is defined once.

Verification

  • yamllint -c .yamllint.yml passes on the modified workflow.
  • The retry logic was exercised under bash -e with a stubbed cosign for three cases: succeeds first try (exit 0), recovers after 3 failures (exit 0, the real-world scenario), and exhausts all 5 attempts (exit 1, step fails).
  • Confirmed against the pinned cosign v3.0.6 binary (what cosign-installer@v4.1.2 installs) that no flag semantics changed; the cosign invocations themselves are unchanged apart from moving the image reference into an IMAGE env var.

Deliberately not changed — needs a decision

cosign sign is pinned to the old signing path (--new-bundle-format=false --use-signing-config=false), added in 550d134 with the note "Use old bundle format and disable signing config until we upgrade Harbor to 2.15." The two cosign attest calls never received those flags, so attestations still run through the new sigstore-go bundle path — which is precisely the code path that emitted signing bundle: error signing bundle here.

I have left that asymmetry alone: flipping the attestation bundle format changes what is stored in the registry and how consumers verify it, which is a supply-chain behaviour change rather than a resilience fix. @fatih-acar — was excluding attest from those flags intentional, or should it match sign?

Review in cubic

@github-actions github-actions Bot added the group/ci Issue related to the CI pipeline label Aug 12, 2026

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 1 file

Confidence score: 5/5

  • In .github/workflows/ci-docker-image.yml, duplicating the exponential-backoff retry loop across Sign manifest and Attest SBOMs creates a maintenance-drift risk where one path may get updated while the other lags, leading to inconsistent CI reliability—extract the shared retry logic into a reusable step/composite action so both commands stay aligned.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/ci-docker-image.yml">

<violation number="1" location=".github/workflows/ci-docker-image.yml:217">
P3: The retry-with-exponential-backoff loop is duplicated almost verbatim between the `Sign manifest` step (5 attempts, 15s base) and the `Attest SBOMs` step — only the inner cosign subcommand differs. This is the exact duplication the PR elsewhere sets out to avoid ("share a single retry helper"), and it leaves the magic constants (attempt count `5`, base delay `15`) and the backoff formula in two places that can drift apart. Consider extracting a small reusable helper (e.g., a `retry_cosign` function or a shell snippet) so the attempt count/delays are defined once and the warning/error formatting stays consistent.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

Comment thread .github/workflows/ci-docker-image.yml
Comment thread .github/workflows/ci-docker-image.yml Outdated
if [ "${attempt}" -eq 5 ]; then
break
fi
delay=$((15 * 2 ** (attempt - 1)))

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.

P3: The retry-with-exponential-backoff loop is duplicated almost verbatim between the Sign manifest step (5 attempts, 15s base) and the Attest SBOMs step — only the inner cosign subcommand differs. This is the exact duplication the PR elsewhere sets out to avoid ("share a single retry helper"), and it leaves the magic constants (attempt count 5, base delay 15) and the backoff formula in two places that can drift apart. Consider extracting a small reusable helper (e.g., a retry_cosign function or a shell snippet) so the attempt count/delays are defined once and the warning/error formatting stays consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci-docker-image.yml, line 217:

<comment>The retry-with-exponential-backoff loop is duplicated almost verbatim between the `Sign manifest` step (5 attempts, 15s base) and the `Attest SBOMs` step — only the inner cosign subcommand differs. This is the exact duplication the PR elsewhere sets out to avoid ("share a single retry helper"), and it leaves the magic constants (attempt count `5`, base delay `15`) and the backoff formula in two places that can drift apart. Consider extracting a small reusable helper (e.g., a `retry_cosign` function or a shell snippet) so the attempt count/delays are defined once and the warning/error formatting stays consistent.</comment>

<file context>
@@ -199,9 +199,27 @@ jobs:
+            if [ "${attempt}" -eq 5 ]; then
+              break
+            fi
+            delay=$((15 * 2 ** (attempt - 1)))
+            echo "::warning::cosign sign failed (attempt ${attempt}/5), retrying in ${delay}s"
+            sleep "${delay}"
</file context>

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.

Addressed in 67af96b: both jobs now carry a byte-identical retry() function taking the command as arguments ("$@", no string/eval layer), replacing the inline loop / attest_with_retry pair. The jobs run on separate runners, so the copies can't be genuinely shared without a checkout or a third-party action in the signing path — the identical text is the drift guard, verified by parsing the workflow and asserting the two bodies equal. The exponential backoff and the COSIGN_RETRY_* env tunables are gone too: five attempts 60s apart cover the same ~4-minute window as the old 15/30/60/120 schedule.

@cubic-dev-ai cubic-dev-ai Bot 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.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

@petercrocker
petercrocker marked this pull request as ready for review August 12, 2026 12:21

@cubic-dev-ai cubic-dev-ai Bot 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.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot 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.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. Add retry with exponential backoff to cosign sign and attest commands in CI, and upload SBOM artifacts before attesting to prevent transient Sigstore outages from blocking releases.

Re-trigger cubic

The v1.10.7 release failed in publish-docker-image / sbom (run
31521732131) when `cosign attest` could not write to the Sigstore
transparency log:

    Post "https://rekor.sigstore.dev/api/v1/log/entries":
    giving up after 2 attempt(s)

Rekor was only briefly unavailable — the sign job had succeeded against
the same infrastructure five minutes earlier, and the identical sbom job
passed on the next nightly run with no code change. But cosign retries a
failed log write only twice internally and exposes no knob to raise
that, so a blip that short was enough to fail the release. The fallout
was disproportionate: the SBOM upload step never ran, so v1.10.7 is the
only release since v1.10.0 with no SBOM artifacts, and
repository-dispatch (which needs publish-docker-image) was skipped, so
downstream repos were never notified of the release.

Three changes:

- Wrap cosign sign and both cosign attest calls in a retry() helper:
  five attempts 60 seconds apart, covering a ~4-minute outage. The
  helper takes the command as arguments ("$@", no string/eval layer)
  and is pasted verbatim into the sign and sbom jobs — they run on
  separate runners, so it cannot be shared without a checkout or a
  third-party action in the signing path.

- Upload the SBOM artifacts before attesting them, so a
  transparency-log outage can no longer cost us the SBOMs themselves.
  Attestation failures still fail the job, which is the correct outcome
  for a supply-chain step.

- Set overwrite: true on the SBOM upload. Artifacts are scoped to the
  run rather than the attempt, so with the upload now preceding a step
  that can fail, a re-run would otherwise die at the upload step
  because the artifact name already exists from the earlier attempt.
  Every caller passes a version unique to its invocation, so the only
  artifact this can replace is the same SBOM from a previous attempt of
  the same run.

Verified by extracting both run scripts from the parsed workflow and
executing them against a stubbed cosign: immediate success (1 call, no
sleeps), recovery after 3 failures (exit 0, 3 sleeps), budget
exhaustion (5 calls, 4 sleeps, exit 1), and a terminally failing first
attest stops the script before the second attest runs. The two retry()
bodies are asserted byte-identical, and actionlint passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fatih-acar
fatih-acar force-pushed the pc-harden-cosign-transparency-log-retries branch from 67af96b to 461f485 Compare August 20, 2026 08:19
@fatih-acar
fatih-acar enabled auto-merge (rebase) August 20, 2026 08:27
@fatih-acar
fatih-acar merged commit 20fad94 into stable Aug 20, 2026
53 checks passed
@fatih-acar
fatih-acar deleted the pc-harden-cosign-transparency-log-retries branch August 20, 2026 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/ci Issue related to the CI pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants