feat(wizard): auto-register OIDC trusted publisher during apply#242
Merged
vpetersson merged 5 commits intoJun 2, 2026
Conversation
The wizard emits an OIDC workflow but the trust binding still had to be created by hand in the sbomify UI — the sbomify#1 reason a first OIDC publish 403s. Register it automatically during apply instead, using the new binding-management API. * SbomifyApiClient.create_oidc_binding(component_id, repository) — POSTs to /api/v1/auth/oidc/github/bindings. Idempotent: 409 (already bound) is success; 400/404/5xx raise APIError. * apply_plan: new best-effort step (oidc mode only) that registers a binding per applied component. Never fatal — per-component failures are warnings; skipped with a clear note for private repos (backend can't resolve private GitHub IDs yet) and when no owner/repo slug is known. * Done screen: shows a '✓ trusted publishing is set up' panel on success, or falls back to the manual instructions (prefixed with the reason) when auto-registration was skipped/failed. 12 new tests (client 201/409/error, apply per-component/token-skip/ 409-counts/failure-is-warning/private-skip/no-slug, done rendering). Full suite: 2329 passed. Depends on the main-app binding API (sbomify/sbomify#988).
Auto-registration previously skipped private repos: sbomify can't read a
private repo's metadata anonymously, so the binding create 400'd. For a
private repo the wizard now resolves the immutable IDs itself, using the
operator's own local GitHub auth, and passes them to the binding API
(which accepts explicit IDs). The token is used to call GitHub directly
from the machine and is NEVER sent to sbomify — only the two integers are.
* repo_facts.github_token() — GH_TOKEN / GITHUB_TOKEN / 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.
* repo_facts.resolve_repo_ids(slug, token) — authenticated GET /repos/{slug}
-> (repository_id, repository_owner_id); None on any failure.
* SbomifyApiClient.create_oidc_binding gains optional repository_id /
repository_owner_id (sent only when both present).
* apply._register_oidc_bindings: private repo -> resolve IDs + register with
them; no token or resolve failure -> graceful skip + note (unchanged for
public repos, which still let the backend resolve the slug).
Verified live end-to-end against a real private repo: the wizard resolved
its IDs and created bindings carrying the correct immutable values, which a
GitHub Actions OIDC token from that repo would match at exchange time.
New tests for github_token, resolve_repo_ids, the client body, and the
apply paths (private+token, private+no-token, private+resolve-fail, public).
Full suite: 2342 passed.
…not /components/{id}/settings)
The Done screen's OIDC manual-fallback links and the 'copy URL' action
pointed at `/components/{id}/settings`, which 404s — the real component
page (carrying the Trusted Publishing section) is `/component/{id}/`
(singular, no /settings). Surfaced by the private-repo E2E. Updated the
done test to assert the correct path and guard against the old one.
…en logic Per the converged design (backend now pins private-repo IDs on first OIDC publish), the wizard no longer needs to resolve repository IDs itself. It just sends 'org/repo' — same call for public and private repos, no GitHub token. * Remove repo_facts.github_token() + resolve_repo_ids() (and the os/shutil imports + their tests). * create_oidc_binding: name only (drop repository_id/repository_owner_id). * apply._register_oidc_bindings: register by name for every component regardless of visibility; only skip when the git remote yields no 'owner/repo' slug. No token, no per-visibility branching. Keeps the Done-screen component-URL fix. Full suite: 2329 passed. Depends on sbomify/sbomify#989 (deferred pinning).
Commit 5de2281 swept in untracked local scratch (docs/plans/, .agent/, audit_trail.txt, and assorted *.cdx.json/*.spdx.json SBOM outputs) that don't belong in the repo. Untrack them (left on disk locally). The OIDC code/test changes from that commit are unaffected.
vpetersson
approved these changes
Jun 2, 2026
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.
Summary
The onboarding wizard emits an OIDC workflow but the trust binding still had to be created by hand in the sbomify UI — the #1 reason a first OIDC publish 403s. This registers the binding automatically during apply, for public and private repos, sending only
org/repo— no GitHub token.What changed
SbomifyApiClient.create_oidc_binding(component_id, repository)— thinPOST /api/v1/auth/oidc/github/bindings, name only. Idempotent (409 = already bound → success); raises on other non-2xx.apply_plan— new best-effort step (OIDC mode): register a binding per applied component, by name, regardless of repo visibility. Never fatal — per-component failures are warnings; the only skip is when the git remote yields noowner/reposlug.✓ OIDC trusted publishing is set upon success, else the manual instructions (with the reason). Also fixes the component link:/component/{id}/(the old/components/{id}/settings404'd).WizardStatefields carry the outcome to the Done screen.Testing
History
Earlier revisions of this PR had the wizard resolve private-repo IDs via a local GitHub token; that was dropped in favour of backend deferred pinning (#989) so setup needs nothing but the repo name — identical to adding a public repo in the UI.