feat(gate): activate, mint and revoke the preview access gate (switchboard#26 Part B) - #31
Merged
Merged
Conversation
…board#26 Part B) ePHPm ships the enforcement and verification for the GitHub-OAuth preview access gate (ephpm#487/#491); this is switchboard's control-plane half. - Gating policy (preview_auth.rs): a private repo's preview is always gated, a public one only with --gate-public-previews. Repo visibility comes from repository.private, plumbed through the job and webhook paths and defaulting to private when absent (fail closed). - Activation (site_override.rs): a gated preview gets a [preview_auth] section written into the same per-site override file, carrying a session_secret REFERENCE (env:/file:, never the key) and the issuer's login_url. - Fail closed (deployer.rs): resolve_preview_gate resolves the session secret (>=32 bytes) before serving; a gated preview whose secret does not resolve, or that has nowhere to write the gate (no --site-overrides-dir), FAILS the deploy rather than coming up world-readable. - Share links (preview_auth.rs + github.rs): opt-in --share-link mints a wire-compatible via:"share" HS256 capability token bound to the preview's site key and posts ...?ephpm_share=<token> in the PR comment with the bearer-capability warning. Only the token travels; the secret never does. - Revocation (kv.rs + teardown.rs): teardown bumps the per-site epoch (preview:share:epoch) in the preview's KV keyspace, killing every outstanding share link cluster-wide. Best-effort — an unreachable KV warns, never fails the teardown; skipped without --kv-secret-file. - Config: --gate-public-previews, --preview-session-secret-ref, --share-link, --share-link-ttl-secs, --kv-secret-file, --kv-addr. - Docs: rewrote docs/preview-access-gate.md and the README to match the shipped OAuth + share-token design (superseding the old Basic-auth plan).
|
ePHPm Preview — removed Preview deployment has been torn down. |
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
Switchboard's control-plane half of the GitHub-OAuth preview access gate. ePHPm ships the enforcement and verification (
ephpm/ephpm#487/#491, per-sitepreview-gatemiddleware +Hs256Policy+ KV revocation); this PR turns the gate on per preview, posts access info, mints share links, and drives revocation. Security-critical: a private repo's preview must never deploy world-readable — every path where gating could silently not happen fails the deploy instead.Do not merge — for review. Please pay attention to the fail-closed gating of private previews and that the session secret never leaks.
Visibility detection + gating policy
repository.private, plumbed through both producers:JobRepository::private(queue path) and webhookRepository::private, intoPreviewRequest::private. Absent ⇒ private (fail closed) in both.preview_auth::should_gate): private repo → always gated; public repo → ungated unless--gate-public-previews.The override written
For a gated preview,
[preview_auth]is written into the same<site-key>.tomlthat already carriesdocument_root/auto_prepend_file:session_secretis written as a reference (env:/file:/literal), matching the contract — the file is tenant-adjacent, so the key never lands in it or the served tree. Both strings are TOML-injection-validated.Temp-URL mint + revocation
preview_auth::mint_share_token): wire-compatible withephpm_middleware_builtins::preview_gate::mint_share_token—{site, via:"share", jti, iat, exp}, header{"alg":"HS256","typ":"JWT"}, base64url-unpadded, HMAC-SHA256 overh.p.site= canonical site key;jtifrom the OS CSPRNG. Switchboard mints directly (it resolves the shared secret); only the token travels in the URL, never the secret. Opt-in via--share-link, short default TTL (1 day).kv.rs+ teardown): on teardown, bumppreview:share:epoch = nowin the preview's own KV keyspace (RESPAUTH <site> <HMAC-SHA256([kv] secret, site)>, thenSET), killing all outstanding share links cluster-wide. Best-effort — an unreachable KV warns, never fails the teardown; skipped without--kv-secret-file. Wired into the teardown path (env: never reaches PHP for docroot: "." — the generated prepend is not auto-loaded #4/fix(teardown): keep the applied/ marker that records our own teardown #24 reconcile).Fail-closed (the whole point) + tests
session_secretwon't resolve to ≥32 bytes → deploy fails (resolve_preview_gate).--site-overrides-dir(nowhere to write the gate) → deploy fails (apply_site_override); an ungated preview only warns.[preview_auth]written + deploy proceeds; private job with no secret → fails; public ungated by default, gated under--gate-public-previews; PR comment for a gated preview has login guidance; a minted token is a validvia:"share"token for the site (shape + signature verified) and the secret never appears; teardown bumps the epoch (mock RESP server); unreachable KV doesn't fail teardown. All existing deployer/teardown/comment/config tests stay green. 265 tests pass; clippy-D warningsclean;cargo fmtclean; MSRV 1.85cargo checkclean.PR-comment format
A gated preview's sticky comment gains an Access block ("Sign in with GitHub — you'll be authorised automatically if you have read access"); with
--share-linkit also shows the shareable URL and states plainly it is a bearer capability (anyone with it is in until expiry/revocation). Ungated previews are unchanged.Operator config: one-time fleet vs per-preview
One-time, per fleet (ePHPm node config — NOT switchboard code): register one GitHub OAuth App, mount
github-authglobally inephpm.toml(client id/secret, per-repo access target,session_secret = "env:EPHPM_PREVIEW_SESSION_SECRET", and for a wildcard fleet the apexredirect_uri+cookie_domain), and setEPHPM_PREVIEW_SESSION_SECRET(≥32 bytes) in both the ePHPm and switchboard process environments (both must resolve the reference to identical bytes for a switchboard-minted share token to verify).Per preview (switchboard, this PR): decide gating from visibility, write
[preview_auth], mint/post the optional share link, bump the revocation epoch on teardown. New flags:--gate-public-previews,--preview-session-secret-ref,--share-link,--share-link-ttl-secs,--kv-secret-file,--kv-addr.Rollout ordering: only write
[preview_auth]once an ePHPm that enforces it is deployed (an older ePHPm ignores the unknown section and would serve ungated) — same disciplinedocument_rootand theephpm execgate follow.Docs (
docs/preview-access-gate.md, README) rewritten to match the shipped OAuth + share-token design, superseding the earlier Basic-auth plan.🤖 Generated with Claude Code