store-backends-beyond-aws
store: grant on Linode, Backblaze and Hetzner, not only AWS
carlos store could only grant against AWS. CARLOS_STORE_BACKEND fatalled on any value but "aws", and the one Provisioner was S3 + IAM — so on Hetzner, Linode or Backblaze the bucket half would have worked and the key half had nothing to call. (The control plane has been provider-agnostic since internal/s3compat; only the per-app store was stuck.)
## What changed
Bucket work is plain S3 and identical on every provider, so it moves to one shared s3Buckets each backend embeds. Only the credential half varies, and it is now the only half a new backend writes. Two AWS spellings that had leaked into the walk — the arn:aws:s3::: prefix and the IAM user path — went with it, and the seam passes a Target (principal + bucket), because providers key on different halves.
| backend | who issues the app's key | what confines it | | --- | --- | --- | | aws | CARLOS, IAM user per app | the credential | | linode | CARLOS, bucket_access at creation | the credential | | b2 | CARLOS, app key scoped to one bucket id | the credential | | s3 | the operator, by hand | the bucket's own policy |
Why Hetzner is a different arrangement, not a fourth copy
Hetzner's S3 keys exist only in its web console and are valid for every bucket in the project. CARLOS cannot mint one, so it declares that (Capabilities().MintsKeys) and grant takes the pair the operator made. Since CARLOS did not issue the key, it can't confine it by issuing it narrowly — confinement comes from the other direction: every bucket CARLOS provisions denies every principal but its own app's key and the console's.
A policy nobody checks is a promise nobody keeps, so AdoptKey proves it: it writes the allow-list without the new key, requires the store to deny it, then adds it and requires the store to allow it. A wrong principal spelling fails the grant instead of quietly handing an app a project-wide key while status claims isolation.
Honesty rules extended
store status reports mints_keys and isolation, so a member learns rotation needs an operator before the 2am they need it. rotate --finish says "revoked" rather than "deleted" where the key survives in the operator's account.
Two ordering bugs fixed in Grant
- The credential gate belongs after the idempotence short-circuit, or re-running grant over a healthy store — the documented drift repair — demands a key pair for a no-op.
- The grant block belongs after every refusal that costs nothing, or a refused grant leaves the mark
release.PurgeAppreads as "still has backend estate". It stays beforeEnsureBucketeither way (§5).
State
Green locally: build, vet, gofmt, go test ./... -count=1. Pushed to amadan at 2642034; run result not yet read.
Caveats: Linode and B2 are implemented against published APIs and stub-tested, never against a live account. The local gate ran with -gcflags=all='-N -l' (the ec2 SDK compile OOMs on the dev box), so amadan's optimised build is the first real one.
Discussions
- No discussions on this branch yet.
Tasks
- No tasks yet.
Activity
- james@keymail.dev pushed 26420341d store: grant on Linode, Backblaze and Hetzner, not only AWS