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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user