feat(selfupdate): keyless cosign signing in release + verify-on-update - #94
Merged
Conversation
Wire keyless cosign (sigstore) signing into the release pipeline and add signature verification to the self-update flow (spec 14 + spec 25). Release pipeline: - release.yml gains `id-token: write` and a sigstore/cosign-installer step. - .goreleaser.yaml `signs:` block runs `cosign sign-blob --yes` (keyless, GitHub OIDC — no private key) over checksums.txt, publishing checksums.txt.sig + checksums.txt.pem as release assets. Verify-on-update (internal/selfupdate): - New Verifier interface (verify.go) shelling `cosign verify-blob`, pinning --certificate-identity-regexp to the release.yml workflow identity and --certificate-oidc-issuer to token.actions.githubusercontent.com. - downloadBinary verifies the checksums.txt signature (the trust root) BEFORE trusting any per-archive SHA-256 line; SHA-256 is always enforced. - --insecure-skip-verify escape hatch (default verify-on). cosign absent aborts with a clear remediation unless bypassed. - Keeps the existing IsDevBuild + package-manager CanSelfReplace refusals. Tests: happy-path verify, tamper-detection, verify-before-checksum ordering, cosign-unavailable abort, missing-signature abort, skip-verify bypass (SHA still enforced), and a release.yml YAML-parse assert that id-token: write is declared. No network required. Default: signature verification is ON; it requires the cosign binary. When cosign is absent the update refuses with a remediation rather than silently degrading — pass --insecure-skip-verify to proceed on checksum-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dry-run runs 'goreleaser release --snapshot', which now hits the new cosign signs: block and fails with 'cosign: executable file not found' — keyless OIDC signing can't run in a PR dry-run. Skip the sign pipe there; the real tagged release (release.yml) installs cosign and signs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Wires keyless cosign (sigstore) signing into the release pipeline and adds cosign signature verification to the
self updateflow — closing the "signature verification is mandatory" gap in spec 14, now that release signing exists (spec 25).Release pipeline (signing)
.github/workflows/release.yml: addsid-token: writepermission and asigstore/cosign-installer@v3step. Keyless signing uses the job's GitHub OIDC token — no long-lived private key..goreleaser.yaml: newsigns:block runscosign sign-blob --yesoverchecksums.txt(the root of the integrity chain), publishingchecksums.txt.sig(signature) +checksums.txt.pem(Fulcio certificate) as release assets.Verify-on-update (
internal/selfupdate)Verifierinterface (verify.go) shellingcosign verify-blob, pinning--certificate-identity-regexpto therelease.ymlworkflow identity and--certificate-oidc-issuertohttps://token.actions.githubusercontent.com.downloadBinarynow verifies thechecksums.txtsignature before trusting any per-archive SHA-256 line; SHA-256 remains enforced in all cases.--insecure-skip-verifyescape hatch, default verify-on. Keeps the existingIsDevBuild+ package-managerCanSelfReplacerefusals.Why
Auto-updating a binary that drives Docker, edits
/etc/hosts, and installs a root CA is a supply-chain target — an altered/unsigned release must be refused, not warned-and-installed (spec 14). The signing side (spec 25) had been deferred; this connects both ends.Default behavior (honest note)
Signature verification is ON by default and requires the
cosignbinary. When cosign is absent, the update aborts with a remediation (install cosign, or re-run with--insecure-skip-verify) rather than silently degrading.--insecure-skip-verifybypasses only the cosign check — SHA-256 is still enforced, so it never drops to "no integrity check".How tested
internal/selfupdatetable-driven tests (fake verifier, no network):checksums.txtbytes + published.sig/.pem)--insecure-skip-verifybypass (SHA-256 still fails a tampered archive)release.ymlYAML parses and declaresid-token: write+ installs cosignGates green:
CGO_ENABLED=0 go build ./...,CGO_ENABLED=1 go test ./internal/...,gofmt -lclean,go vet ./.... Nointernal/generate/templates/changes, so determinism is unaffected.🤖 Generated with Claude Code