[wrangler] Add opt-in OS keychain storage for OAuth credentials#14156
Open
petebacondarwin wants to merge 4 commits into
Open
[wrangler] Add opt-in OS keychain storage for OAuth credentials#14156petebacondarwin wants to merge 4 commits into
petebacondarwin wants to merge 4 commits into
Conversation
🦋 Changeset detectedLatest commit: 81bc660 The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
|
✅ All changesets look good |
Contributor
|
Review posted successfully to PR #14156. Summary of the review I submitted:
I also verified that the |
create-cloudflare
@cloudflare/deploy-helpers
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-auth
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
@cloudflare/wrangler-bundler
commit: |
373cdbf to
6be1153
Compare
Contributor
|
Codeowners approval required for this PR:
Show detailed file reviewers
|
4 tasks
The opt-out scrub for `wrangler login --no-use-keyring` went through `getCredentialStore()`, which resolves to `FileCredentialStore` whenever `CLOUDFLARE_AUTH_USE_KEYRING=false` is set in the environment (the resolver short-circuits on the env var before consulting the persistent preference). `FileCredentialStore.delete()` only removes the plaintext `.toml`, so the `.enc` file and the OS keyring entry were left on disk after opt-out. Resolve the encrypted store directly in the opt-out branch so the scrub always targets the backend the user is opting out of, regardless of the env-var state. When the keyring backend is unreachable on the current host, best-effort scrub the `.enc` file and warn that the keyring entry may still persist (the ciphertext is useless without the key, but leaving stale files around is confusing). Adds a regression test that stubs the env var and asserts both the `.enc` file and the keyring entry are scrubbed.
1075895 to
81bc660
Compare
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.
Fixes #14099.
Adds an opt-in path for storing wrangler's OAuth credentials in an AES-256-GCM-encrypted file under the wrangler config directory, with the encryption key held in the OS keyring (macOS Keychain, libsecret on Linux, or Windows Credential Manager). The default behavior is unchanged — existing users keep using the plaintext file. Opting in is explicit and persisted across invocations.
Why encrypted-file-with-keyring-key?
Storing credentials directly in the OS keyring runs into per-platform item-size limits (notably the ~2.5 KB macOS Keychain
kSecAttrGenericlimit), which would block adding richer auth state in the future. Storing only the encryption key (~44 bytes) sidesteps the limit while still pinning at-rest confidentiality to a hardware-/login-protected secret store.Usage
wrangler login --use-keyring— opt in; persisted to<wrangler-config>/preferences.json.wrangler login --no-use-keyring— opt back out. Deletes the encrypted file and the keyring entry; the subsequent login writes fresh credentials to the plaintext TOML. Opt-out never decrypts existing credentials onto disk (that would defeat the at-rest protection the user is choosing to disable).CLOUDFLARE_AUTH_USE_KEYRING=true|false— per-process override.wrangler whoami— reports where credentials are stored.Per-platform backends
Wrangler ships with zero native credential dependencies. Each platform uses whatever is already there:
/usr/bin/security(built-in)secret-toolfromlibsecret-tools@napi-rs/keyringlazy-installed vianpm installon first opt-inCLOUDFLARE_AUTH_USE_KEYRING=trueNon-opt-in users (the majority) pay zero install cost. macOS and Linux opt-in users pay zero install cost too. Only Windows opt-in users ever fetch a native binary, and only on first opt-in.
Encryption details
node:crypto— no third-party crypto deps.<wrangler-config>/config/<env>.enc, sibling of the legacy<env>.tomlso opt-in migration is non-destructive.{ v: 1, alg: "AES-256-GCM", iv, tag, ciphertext }(all binary fields base64). The GCM auth tag detects tampering; a mismatch is treated as "not logged in" and the next login re-encrypts.{ v: 1, key, created }holding only the 32-byte symmetric key — well below the macOS Keychain ~2.5 KB per-item limit.Migration
.encfile, delete the plaintext file. Logged asMigrated credentials from <toml> into <enc> (key in <keyProvider>)..--no-use-keyringopt-out: delete the encrypted file and the keyring entry without decrypting them. LogRemoved the encrypted credentials and the keyring entry. Run \wrangler login` to log in again.. The user then runswrangler login` (or the same opt-out command continues into login) which writes fresh credentials into the plaintext TOML.Internal architecture
All credential persistence — file backend, encrypted-file backend, key providers, and resolver — lives in
@cloudflare/workers-auth.createOAuthFlow(ctx)now requires acredentialStorageblock and returns a newgetCredentialStore()accessor forwhoami-style code:Future Cloudflare CLIs that reuse
@cloudflare/workers-authget OS keyring–encrypted credential storage by providing their ownserviceName.CLOUDFLARE_API_TOKENandCLOUDFLARE_API_KEY/CLOUDFLARE_EMAILcontinue to take priority over any stored OAuth credentials.Tests
credential-store/crypto.test.ts(19) — round-trip, tampered ciphertext, wrong key, IV non-repetition, envelope version checkscredential-store/file-store.test.ts(9) — plaintext TOML round-trip, missing/corrupted file handlingcredential-store/encrypted-file-store.test.ts(18) — encrypt/decrypt, missing key, file-tampered, legacy plaintext→encrypted migration on first readcredential-store/resolver.test.ts(21) — full platform matrix (darwin / linux-with-secret-tool / linux-missing / win32-installed / win32-missing-interactive / win32-missing-non-interactive / unsupported / forced)credential-store/key-providers/mac-security.test.ts(12),linux-secret-tool.test.ts(13),lazy-installer.test.ts(9) — argv construction, exit-code handling, stdin-based secret writes,WRANGLER_API_ENVIRONMENTscoping--use-keyring/--no-use-keyringflag tests rewritten to use the workers-authsetKeyProviderFactoryForTestingseam. The opt-out test now seeds encrypted credentials, runs--no-use-keyring, and asserts that the encrypted file + keyring entry are gone AND the stale credentials do not appear in the new plaintext file. Keyring-aware logout test updated. Full wrangler suite: 4300 passed, 4 skipped, 9 todo across 246 test files.pnpm check:lint,pnpm check:type,pnpm prettify,pnpm check, both packages'test:ci.Files
@cloudflare/workers-auth:src/credential-store/{interface,file-store,encrypted-file-store,crypto,resolver,state,index}.tssrc/credential-store/key-providers/{interface,shared,factory,mac-security,linux-secret-tool,napi-keyring,lazy-installer}.tstests/credential-store/@cloudflare/workers-auth:auth-config-file.ts(slimmed; now delegates),flow.ts(validates + populates state + addsgetCredentialStoreaccessor),context.ts(addscredentialStorageblock),env-vars.ts(addsCLOUDFLARE_AUTH_USE_KEYRING),index.ts(exports new APIs),AGENTS.md(documents credential storage layer)wrangler:src/user/{credential-store,mac-security-store,linux-secret-tool-store,lazy-keyring-installer,keyring-shared}.tsand their 4 test fileswrangler:src/user/preferences.ts(thekeyring_enableduser preference)wrangler:user.ts(addscredentialStorageblock tocreateOAuthFlow, exportsgetCredentialStore),whoami.ts(usesgetCredentialStore),commands.ts(--use-keyringopts in;--no-use-keyringscrubs encrypted credentials),user.test.ts+logout.test.ts(use workers-auth test seam)