Cloudflare Access: CLI auth without Access-app configuration#12
Open
koomen wants to merge 12 commits into
Open
Cloudflare Access: CLI auth without Access-app configuration#12koomen wants to merge 12 commits into
koomen wants to merge 12 commits into
Conversation
SCRATCHWORK_AUTH=cloudflare-access delegates authentication to a Cloudflare Access application: the server verifies the edge-injected Cf-Access-Jwt-Assertion (RS256 vs team JWKS, issuer, AUD tag, expiry) and uses the asserted email as the identity. /auth/login converts the assertion into the CLI bearer token and relays the verified Access JWT to the CLI loopback as cf_token; API requests also accept the relayed JWT via a cf-access-token header, verified identically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Store the cf_token relayed by a cloudflare-access server at login in auth.json and send it back as a cf-access-token header on every API request, so publish/share/etc. pass the edge with no Access bypass policy. SCRATCHWORK_CF_ACCESS_CLIENT_ID/SECRET attach Access service-token headers for CI. Requests the edge blocks anyway (403 + cf-mitigated, or an Access login page where JSON was expected) fail with a clear re-login prompt instead of dumping HTML. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The /api/* Access bypass policy is now an optional fallback for older CLIs; document the token relay, service-token env vars, and the Access session-duration recommendation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In cloudflare-access mode, requireApiUser propagated a bearer-signature failure (e.g. a token signed with a rotated session secret) instead of trying the request's Access assertion, locking the CLI out until manual re-login even though it carried a valid credential. Treat an unverifiable bearer as absent, matching currentUser. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the wrangler CLI shell-outs in deployServer with the official cloudflare SDK: R2 bucket and D1 database provisioning, the multipart worker upload with R2/D1/plain-text bindings, zone routes and custom domains, the workers.dev subdomain, and secrets all go through the REST API. Deploys now authenticate with CLOUDFLARE_API_TOKEN (plus CLOUDFLARE_ACCOUNT_ID when the token sees several accounts) instead of wrangler credentials; the local runtime still runs through wrangler dev, which remains the only way to run workerd locally. Rename deploy/sndbx.sh to deploy/cloudflare-vanilla to match the other deploy projects' flavor naming (compare deploy/cf-access), and document the exact token permissions in its .env.example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
Pushed 7596985, which adds two related changes to this branch:
🤖 Generated with Claude Code |
"Visibility" meant two different things: a full access-group ceiling in server config (maxVisibility) and a binary public/private toggle everywhere else. Make every setting say exactly what it does: - Server: allowPublicProjects (bool), allowedShareDomains, publicByDefault (bool) replace maxVisibility, shareAllowedDomains, defaultVisibility. Retired SCRATCHWORK_* variables fail startup with a pointer to their replacement so policy is never silently dropped. - Project: a boolean isPublic replaces the visibility string in the stored record (now version 5; v4 records migrate at load), the publish wire format, API responses, and .scratchwork.json. - CLI: --public/--private flags replace --visibility. allowedShareDomains is now also enforced at role-resolution time, so tightening it locks down existing grants the way the maxVisibility subset check used to. Setting share domains no longer implicitly forbids public publishes; that is allowPublicProjects' job. accessGroupIsSubset had no remaining callers and is removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename deploy/cf-access to deploy/cloudflare-access and give it the same shape as cloudflare-vanilla: shared server-config.ts/cloudflare-config.ts consumed by both deploy.ts and local.ts, plus an .env.example. The remote deploy serves access.sndbx.sh (app) and access-pages.sndbx.sh (content) behind a Cloudflare Access application, with the Worker scratchwork-access and its own R2 bucket and D1 database. Everything is private (allowPublicProjects: false) since Access blocks anonymous visitors at the edge; allowedUsers stays "public" so the Access policy alone decides who gets in. The team domain and AUD tag live in .env because they only exist once the Access application is created. The local run keeps its previous behavior — simulated Access edge, no .env or Cloudflare account needed — now driven by the shared config. The root scripts become local:cloudflare-access / deploy:cloudflare-access, and stale cf-access references in the READMEs are updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generic sign-in page swallowed the reason auth failed, so a Cloudflare Access misconfiguration (wrong AUD tag, missing assertion header) rendered the same page as an ordinary signed-out visit and was undiagnosable from the browser. Surface the caught message as the note line under the friendly copy on 401 and 403 pages. This exposes nothing new: the JSON error path already sends these exact messages to API clients for the same statuses. 404 and 5xx pages are unchanged. Co-Authored-By: Claude Fable 5 <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.
Summary
Implements
notes/cloudflare-access.md.Server: Cloudflare Access auth mode
SCRATCHWORK_AUTH=cloudflare-accessdelegates auth to a Cloudflare Access application: the server verifies the edge-injectedCf-Access-Jwt-Assertion(RS256 signature against the team JWKS, issuer, AUD tag, expiry) and uses the asserted email as the identity. Shared RS256/JWKS machinery extracted tojwt-rs256.ts(also used by the Google ID-token path)./auth/loginconverts the verified assertion into the CLI bearer token and relays the Access JWT to the CLI loopback ascf_token(loopback-validated redirects only, same exposure class as the bearer token).cf-access-tokenheader, verified identically to the assertion header; the edge-injected header wins when both are present. The AUD check is enforced in every acceptance path.CLI: pass the Access-protected edge
auth.jsonrecords gain an optionalcfToken(file stays version 1; old files and old CLIs unaffected).apiRequestresolves it per origin and sends it ascf-access-token, which Cloudflare's edge accepts as an Access credential — sopublish/share/etc. work with zero Access-application configuration.SCRATCHWORK_CF_ACCESS_CLIENT_ID/SECRETattach Access service-token headers for CI/headless use (identity still comes from the bearer token).cf-mitigated, or an Access login page where JSON was expected) fail with a clear "runscratchwork loginagain" prompt instead of dumping HTML; ordinary 403s and non-Access HTML error pages still flow through to callers unchanged.Docs
/api/*Access bypass policy is now an optional fallback for older CLIs; documents the relay, service tokens, and the session-duration recommendation. spec.md "Authenticating" updated.Test coverage
cf_tokenin CF mode and never in OAuth mode (including a full mocked Google login→callback round trip);cf-access-tokenacceptance, assertion-header precedence, invalid tokens rejected; JWKS/RS256 and config-validation suites.apiRequestagainst real loopback servers for header attachment, service tokens, both edge-block shapes, and both false-positive guards.Test plan
cd cli && bun run check— typecheck + 82 tests passcd server && bun run check— typecheck + 143 tests pass across core/scripts/deploy suites🤖 Generated with Claude Code