tenant-iso-objstore
objstore: one store per bucket name and IAM user name (#218, fixes #191)
Problem. Bucket and IAM user names join account, app and environment with -, and app and environment names can both contain -. So app shop with environment eu and app shop-eu get the same bucket and user. Nothing refused it: the second grant adopted the bucket and user, reaped the first store's key, and a later teardown deleted the first store's bucket and user. An Instance-role token could reach it through private-storage instance create (environment = host's first label) and ?environment= on store provision. Operator adopt (carlos store grant -bucket) could name another app's bucket.
Fix. Names stay as they are (production buckets/users keep theirs). A name now belongs to one store:
internal/objstore/nameclaim.go: global create-only claim objects atobjstore/name-claims/{buckets,users}/<name>.json, same pattern asmail/domainclaim.go. The create-only Put is the uniqueness check, so two walks cannot both win, in one console or several, whichever store comes first.GrantInclaims both names before it writes the grant block and before EnsureUser / EnsureBucket / ReapKeys. A name held by another store returnsErrStoreNameTaken. Each run re-checks, so stores granted before this change pick up their claims on their next grant.- Stores granted before claims existed hold none, so two more checks cover them: the same-account stores whose names collide by construction (every split of
app[-env]at a hyphen) are read directly; and every first grant (no grant block yet) scans every store record (ListDirs, likemail.SweepLegacyRecords) for one already naming its bucket. That has to include minted grants, because an older operator adopt can hold a minted-shaped name in another account (#191 step 1). TeardownInleaves a bucket or user standing when another store's claim holds it, or when a colliding record (granted before this change) names it. When the user is shared, it still deletes this store's own keys on it (KeyID and PrevKeyID). It gives back its own claims just before deleting its record. That includes a declared-only record whose grant died after claiming: the user claim is found by computing the name, and the bucket claim by searching the claims for this store. This also fixes #191: a teardown that stopped after deleting its bucket keeps the bucket claim, so the name can't be granted elsewhere until the teardown finishes. A re-run never deletes a bucket another store has since claimed.console/storelock.go: the walk lock is keyed on the store's IAM user name, so colliding stores queue behind each other.- Console doors (operator grant, member provision, instance create) answer
ErrStoreNameTakenwith a 409 and a plain sentence instead of a 502.
Tests. internal/objstore/nameclaim_test.go covers refusal in both orders, a three-part split, the pre-claims case, the shared estate surviving a teardown, a shared user losing only the torn-down store's keys, claims released after teardown (including a declared-only teardown after a crashed grant), a minted first grant refused over an older adopt, adopting another store's bucket (claimed and pre-claims), and #191 (claimed and pre-claims). Also TestStoreWalkLocksSerialiseCollidingStores and TestAPIStoreGrantRefusesAnotherStoresBucket. Each failed before the fix. Everything uses the in-memory provisioner. The MinIO contract test was not run because there's no local MinIO, and the IAM policy is unchanged.
Deliberately not done.
- Ops note: a pair that already collides in production (both granted before this change) becomes mutually un-grantable. Every grant of either store is refused, including
regrantForScanning(turning scan enforcement on), which shows a generic 502. The way out is to tear one of the two down. Its teardown deletes its own keys but leaves the shared bucket and user standing, and after that the other store can be granted again. The survivor's delivered key may already be dead, because the pre-fix second grant reaped it.carlos store rotateon the survivor isn't gated by claims, and it mints and delivers a working key. - Pre-claims #191 across accounts (an adopted bucket's half-finished teardown, then that name minted elsewhere before this deploy): teardown checks claims and same-account colliders only, not a full scan.
- App/environment creation is not refused at the source. The grant-time claim holds whatever order things are created in.
Discussions 0 open
- No discussions on this branch yet.
Tasks
- No tasks yet.
Activity
- claude agent, operated by paul@keymail.dev replied to objstore: app shop + environment eu and app shop-eu share one bucket and IAM user
- claude agent, operated by paul@keymail.dev opened objstore: app shop + environment eu and app shop-eu share one bucket and IAM user