Publish signed provider releases - #98
Conversation
01ac93e to
a62a99d
Compare
a53f1fb to
0ba61eb
Compare
a62a99d to
b85cf78
Compare
0ba61eb to
3755aea
Compare
3755aea to
f96c137
Compare
18cfeea to
a89c140
Compare
f96c137 to
2107314
Compare
a89c140 to
ef813b3
Compare
fa24611 to
fc3f751
Compare
094fc64 to
efa2797
Compare
fc3f751 to
af23b48
Compare
efa2797 to
17d0c8a
Compare
49e9ba3 to
9e33a53
Compare
17d0c8a to
6a40866
Compare
9e33a53 to
cec3e82
Compare
Sayan-
left a comment
There was a problem hiding this comment.
The protection model is thorough. Validating the environment's required-reviewer and self-review settings plus the tag ruleset at runtime, re-checking that the tag still resolves to the workflow commit at three separate points, re-verifying checksums after download, and inspecting the key with --import-options show-only before trusting it are all the right calls. Secrets reach gpg through env: and stdin rather than argv, so nothing lands in the process list. No notes on the security design.
Two operational things, both downstream of the fact that none of this has executed yet.
First, this supersedes the dry-run suggestion I left on #97. The semver gate here requires major ≥ 1 and rejects prereleases, so there's no throwaway tag available — the first tag pushed is the first time prepare ever runs, and the ruleset this job requires blocks tag deletion. If prepare fails on that attempt, the version number is burned and the retry has to be v1.0.1. Either go in accepting that the first tag may be sacrificial, or add a prepare-only workflow_dispatch path so the gates can be exercised before any tag exists. Given how much of this path is un-run, I'd lean toward the latter.
Second, worth confirming before release day: GET /repos/{owner}/{repo}/rulesets/{id} documents a requirement of Administration (read), and administration isn't a grantable scope in a workflow permissions: block, so GITHUB_TOKEN cannot hold it. If that call 403s, the publish job dies on its first step — after a human has already approved the deployment. It fails closed, so nothing unsafe happens, but that's an expensive place to discover a permissions problem. Cheapest check is to run those three gh api calls once from a scratch workflow here and confirm the token reads all of them; if the ruleset call is the odd one out, a read-only PAT scoped to the release environment covers it.
cec3e82 to
f7597f9
Compare
f7597f9 to
90c3170
Compare
Sayan-
left a comment
There was a problem hiding this comment.
The preflight job is a better answer than what I suggested. Running the real protection checks and a real signing operation under contents: read exercises the whole path with no structural ability to publish, and it settles the ruleset-permission question empirically instead of on release day. Extracting the shell into check-release-protections.sh and sign-release-checksum.sh so CI can exercise them against a faked gh and a disposable key is the right instinct — that logic was previously unreachable by any test.
The docs being explicit about what cannot be verified at runtime is also the right call: administrator bypass, ruleset bypass actors, and the immutable-releases setting are all genuinely outside a least-privilege token's reach, and saying so beats implying the workflow checks more than it does. Same for the note that repository write access is effectively release authorization — that's the actual threat model, stated plainly.
One gap worth closing in test-release-scripts.sh. It covers the happy path plus a single negative case (reviewer mismatch), but the assertions doing the real security work have no negative fixture: enforcement == "active", the creation/update/deletion rule set, prevent_self_review == true, and the exact deployment-policy match. Those jq filters are the control. If one silently went vacuous — a typo in a field name, a ? in the wrong place — the script would pass against a repository with no meaningful protection, and nothing in CI would notice. The harness already exists, so this is a handful of fixtures: an inactive ruleset, one missing the deletion rule, prevent_self_review: false, and a policy list with an extra entry. Each should exit non-zero with its own message, same shape as the reviewer-mismatch assertion you already have.
90c3170 to
9054963
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9054963. Configure here.
9054963 to
b1bf2ee
Compare
b1bf2ee to
c334d17
Compare
c334d17 to
29407cd
Compare
Require protected release approval before importing the Registry key, signing the prepared checksum, and publishing retry-safe GitHub Release assets.
29407cd to
371cda6
Compare

Summary
This PR completes the first provider release path with two modes:
workflow_dispatchpreflight for an entered stable versionThe manual preflight creates only a runner-local tag, builds and validates the
release artifacts, and proves the configured GPG key can sign the checksum. It
runs with
contents: read, so it cannot create a remote tag or GitHub Release.The tag-triggered path receives the prepared artifacts through
actions/download-artifact, verifies their checksums, requires the importedprivate key to match the Terraform Registry fingerprint, signs the checksum,
creates a draft release, and publishes it only after every asset upload
succeeds.
Any Kernel engineer authorized by the protected
v*tag ruleset can release acommit already merged into
main. This deliberately avoids a separate GitHubrelease environment, second-person approval, and long-lived administration
PAT.
Why
The first public provider release will be
v0.0.1. The artifact and signingpath must be testable before the first immutable remote tag exists. The manual
preflight exercises the build and signing path without permission to publish.
This PR does not change provider configuration, resources, data sources,
imports, API behavior, SDK behavior, or Terraform state. Runtime/session,
deployment, app, and API-key provider surfaces remain intentionally absent.
Security model
main.vMAJOR.MINOR.PATCHsyntax.contents: write.publication; lightweight and annotated tags are supported.
v*tag ruleset is administrator-owned configuration and must limitbypass to the Kernel engineering team.
Verification
gofmt -l cmd internalgo test -short -timeout=2m ./...go vet ./...terraform fmt -check -recursive examplesbash scripts/check-docs.shbash scripts/check-markdown-links.shbash scripts/check-examples.shbash scripts/test-release-scripts.shbash scripts/check-registry-manifest.shgoreleaser check.github/workflows/release.ymlThe release-script suite creates a real temporary Git remote and covers
lightweight, annotated, missing, and moved tags. It also generates a disposable
passphrase-protected GPG key and verifies fingerprint mismatch rejection,
import, signing, and detached-signature verification.
Live provider acceptance tests were not rerun because provider behavior is
unchanged. The complete selected-surface acceptance matrix remains a required
pre-tag gate.
Remaining release setup
Before the first public tag:
v*tag ruleset, with bypass limited to theKernel engineering team
v0.0.1preflight frommainNo release environment, deployment approval, release PAT, or runtime ruleset
API check is required.
Issues #24 and #25 remain outside this PR.