Repository navigation
fix(credentials): isolate Android app credentials - #480
YosefHayim merged 7 commits into
Conversation
|
This is an important missing feature for anyone who has multiple Android credentials. The rest of my PRs are strictly related to security. |
|
@YosefHayim any feedback here or my other PRs? I'd like to get these merged so that may local copy is not out of sync with your upstream. |
YosefHayim
left a comment
There was a problem hiding this comment.
Thanks for this, and sorry for the slow reply. Isolating Play keys and keystores per app is the right fix, and the two-app tests help. The blocker is the upgrade path: today a current Android user's build fails after upgrading, and the message does not say how to recover. Keys Launch generated cannot be re-imported either, because their passwords were never shown. Before we merge, please:
- Add an explicit one-time command, for example
launch creds adopt --platform android --app <name>. It copies the existing unscoped Play key and upload keystore, with their keychain passwords, into one app's slot. It copies and never deletes, checks that the keystore opens and the Play key can read that package first, and refuses to overwrite an existing app key without--yes. - Print that exact command in the build, store, release and reports errors when a legacy key exists, and add
--appto the other "import one with" messages (src/providers/credentials/local.ts:69,metadataCommand.ts:220,googleStoreCommand.ts:48,playReportsCommand.ts:132,releaseTrain/builder.ts:184). - Make
packageNamerequired in the credential store functions, and movestatsCredentials.ts:47to per-app keys, so no unscoped read or write is left. - Let
launch creds statuslist every configured Android app instead of failing without--app(command.ts:729). - Never offer to generate a new upload keystore when an older one exists for that app, and ask before an explicit
--importreplaces an existing app keystore (androidKeystore.ts:264,369-383). - Add
--appto the CI snippet insrc/core/config/ciCommand.ts:115-116. - Add tests for the legacy-only build message, keystore non-fallback, status with two apps, and a wrong keystore password.
- Small: the two identical branches in
local.ts:132-134and the cache check instoreClients.ts:42-44.
Main moved since you opened this; the conflicts will be in the generated README and llms files, so pnpm docs:gen after a rebase covers them.
d1caa82 to
1be9da5
Compare
YosefHayim
left a comment
There was a problem hiding this comment.
Thanks, this is a big step forward. creds adopt checks the keystore and Play access before copying, never deletes, and needs --yes to overwrite; status lists every app; the keystore is never regenerated next to an old one; and the CI snippet has --app. A few things left before we merge:
Blocker
- Snapshot capture: one Android app without a Play key fails the whole Play products and subscriptions source (
snapshot/sources/playProducts.ts:146-150,playSubscriptions.ts:184-188), so a user with two apps and one key loses both apps in the snapshot. Skip that app with a reason instead, like restore already does, and drop the presence-onlyEffect.forEachatplayProducts.ts:129-134.
Should fix
2. missingPlayServiceAccountMessage (androidKeystore.ts:270-277) prints a literal <name> and says "legacy Play service account" even when only the keystore passwords exist. Pass the app name in so the exact command prints, and word the message by what was actually found.
3. verifyKeystore (androidKeystore.ts:280-318) writes a PKCS12 copy of the private key protected by a fixed, public password. Use a random password per run (for example randomBytes(32).toString('hex')).
4. The internal helpers still take an optional packageName (androidKeystore.ts:22, 98, 109, 131, 208, 221, 418, 511, 567; paths.ts:83-90), so leaving it out silently reads or writes the unscoped slot. Please make it required, and give adoption and legacy detection their own explicit legacy reads.
5. Missing test: an app with no keystore while a legacy keystore exists must neither use the legacy one nor generate a new one (ensureUploadKeystore). androidIsolation.test.ts:53-66 covers only the Play key.
6. creds status (command.ts:759-762) turns any config error into "no apps". A missing config can mean no apps, but a broken one should fail with its error.
Small: the let keystoreEffect at command.ts:698-701 and let androidApps at command.ts:763 can be consts with early returns.
Please also say whether you ran launch creds adopt once on a real install that had the old unscoped key, followed by a signed build.
Main moved again (#519, #484). The rebase only conflicts in the generated README and llms files, so pnpm docs:gen after it covers them.
1be9da5 to
6e12343
Compare
|
Thanks, 591cc54 covers everything I asked. I am pushing one small fixup commit on top (an unreadable Android index or config now fails instead of reading as empty, the stats config uses the working directory, and a few style bits), then I will merge. Please hold further pushes to this branch until then. |
f4c07d4 to
c3afe8e
Compare
YosefHayim
left a comment
There was a problem hiding this comment.
Thanks for working through all of this. I pushed one fixup commit: an unreadable Android index, config, saved portfolio or stored package list now fails or shows as a Play discovery failure instead of reading as empty, the stats config gets the working directory, missingPlayServiceAccountMessage is now describeMissingPlayServiceAccount, and a few lets became consts. I also rebased on main after #520 merged.
Android builds could reuse another app's Play account or upload keystore because both were stored globally. Scope Play keys, upload-keystore backups, and their keychain passwords by package name throughout build, store, release, reporting, and readiness workflows.
Existing users can run
launch creds adopt --platform android --app <name>to copy legacy credentials into the selected app. It checks Play access and both keystore passwords before copying, retains the originals, and requires--yesto overwrite an existing app slot. Recovery messages name the selected app. An existing but unreadable keystore blocks new-key generation; replacing one through import requires confirmation or--yes.Credential status lists every configured Android app and surfaces config errors. Snapshot capture retains accessible apps while recording per-app skips, and snapshot diff does not report unavailable apps as deleted. The Android CI example specifies an app. Play and GA4 statistics use scoped keys; discovery uses configured apps, cached portfolio apps, and package identifiers recorded alongside imported keys.
Validation:
pnpm checkandpnpm test:e2epassed. Tests cover a real temporary JKS with distinct passwords, invalid passwords and Play access, random verification passwords, legacy-keystore non-fallback, replacement protection, partial snapshot capture and persistence, app-specific recovery messages, and status for two apps and a broken config.I have not run
launch creds adopton a real installation containing legacy unscoped credentials, nor followed it with a real signed Android build. Play access in the adoption tests uses a fake; no production credentials or store APIs were used.