A module can be given a bucket: the provisioner that makes a secret true

Phase 1.1 of the work breakdown. The finding that shaped it came before
any code: **the control plane special-cases nothing.** provides,
requires, contributes and grants are entirely name-agnostic, so asking
for a bucket needed no change to the mesh at all — only a provider that
answers. What was missing was the last step, where something on the
machine turns a delivered secret into a key that works.

Named `s3-bucket` by ADR 0027's test: a consumer's code is written
against the S3 API, and swapping one store for another does not break
it, so the coupling is to the protocol rather than the product — which
is what the substrate design already said about AMQP, S3 and OCI.

Proven on a real store, 7 assertions: a generated secret becomes a
working key; rotation makes the new one work and the old one stop; a
consumer that goes away loses its key; a key nobody here made is left
alone; a manifest naming a credential that was never written is refused;
an unusable bucket name is refused naming the consumer that asked.

**And the one a database does not need.** One PostgreSQL server holds
separate databases and the product enforces the boundary; one object
store holds every bucket behind one endpoint, so a consumer being unable
to reach another's is a policy somebody wrote. A policy granting
arn:aws:s3:::* would pass every other test in the file, so the unit
tests assert what the policy does NOT say.

It drives the vendor's command line rather than an SDK: the admin API
encrypts its request bodies, which is why a separate admin library
exists, and pulling that in would add a system-metrics dependency tree
to a repository with none in order to create a user.
This commit is contained in:
2026-08-31 17:51:12 +02:00
parent 9f5d7a1a83
commit ed9a30f22d
3 changed files with 457 additions and 0 deletions
@@ -0,0 +1,68 @@
package main
import "testing"
// A bucket name is checked here so the refusal names the module that asked.
//
// The store would refuse most of these itself, as a REST error arriving inside a provisioner log,
// with nothing saying which consumer's manifest caused it.
func TestABucketNameThatWouldNotWorkIsRefusedHere(t *testing.T) {
for _, name := range []string{
"", // nothing asked for
"ab", // too short
"-lead",
"trail-",
"Photos", // upper case: arrives lower-cased, and works until somebody looks
"my_bucket", // underscore
"a.b", // dots are legal in S3 and break TLS host matching; not worth the surprise
} {
if err := usableBucketName(name); err == nil {
t.Errorf("%q was accepted", name)
}
}
}
func TestAnOrdinaryBucketNameIsAccepted(t *testing.T) {
for _, name := range []string{"photos", "a-b-c", "backups2026", "abc"} {
if err := usableBucketName(name); err != nil {
t.Errorf("%q was refused: %v", name, err)
}
}
}
// The policy a consumer gets names its own bucket and nothing else.
//
// **The half that matters is what it does not say.** A policy granting `arn:aws:s3:::*` would
// pass every test that checks a consumer can reach its own bucket, and would give every consumer
// the whole store.
func TestThePolicyGrantsOneBucketAndNoOther(t *testing.T) {
policy := onlyThatBucket("photos")
for _, want := range []string{`"arn:aws:s3:::photos"`, `"arn:aws:s3:::photos/*"`} {
if !contains(policy, want) {
t.Errorf("the policy does not carry %s:\n%s", want, policy)
}
}
for _, unwanted := range []string{`:::*`, `"*"`, `:::photos-other`} {
if contains(policy, unwanted) {
t.Errorf("the policy carries %s, which reaches beyond the bucket asked for:\n%s",
unwanted, policy)
}
}
}
// A policy is per consumer, so one asking for a second bucket cannot widen another's.
func TestTwoConsumersGetPoliciesThatDoNotOverlap(t *testing.T) {
if contains(onlyThatBucket("photos"), "invoices") ||
contains(onlyThatBucket("invoices"), "photos") {
t.Error("a consumer's policy names another consumer's bucket")
}
}
func contains(haystack, needle string) bool {
for i := 0; i+len(needle) <= len(haystack); i++ {
if haystack[i:i+len(needle)] == needle {
return true
}
}
return false
}