package main import ( "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" ) // A fresh assignment is pushed before its credential exists (novox/hq issue 203): `assign` recorded // the module, `push` sealed a random own secret where the bus credential belongs, and the process // crash-looped until a person ran `module issue` and pushed again. Now assigning a module that speaks // on the bus issues its credential in the same act — or, when the bus cannot be reached from here, // says which verb to run — and a push never seals a placeholder in a credential's place. func aTalker() catalogue.Manifest { return catalogue.Manifest{Module: "talker", Version: "1", OwnSecrets: catalogue.OwnSecrets{"broker": {Path: "/var/lib/mesh/talker/broker"}}, Resources: []map[string]any{ {"id": "state", "type": "directory", "path": "/var/lib/mesh/talker", "mode": "0700"}, }} } func TestAssigningAModuleThatSpeaksOnTheBusNamesItsCredential(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, aTalker()) // No bus is known to this process, so the credential cannot be issued here: the assignment // stands and says exactly what must happen before a push — never silently. said, err := assign(ctx, open, "laptop", "talker") if err != nil { t.Fatal(err) } if !strings.Contains(said, "module issue talker --node laptop") { t.Fatalf("an assignment whose credential could not be issued does not name the verb:\n%s", said) } // And the push refuses to send it, naming the same verb, rather than sealing a placeholder. plan, settings, err := planFor(ctx, open, "laptop") if err != nil { t.Fatal(err) } _, err = declarationFor(ctx, open, "laptop", plan, settings) if err == nil { t.Fatal("a push sealed a placeholder where talker's bus credential belongs") } if !strings.Contains(err.Error(), "module issue talker --node laptop") || !strings.Contains(err.Error(), "issue 203") { t.Fatalf("the refusal does not say what to run: %v", err) } // Once the user is minted, the push goes on to the credential the mesh sealed, and re-assigning // does not mint again: a credential rotates on purpose, never by habit. if _, err := open.inventory.MintBusPassword(ctx, inventory.BusUser{ Username: "laptop.talker", Kind: inventory.BusModule, Node: "laptop", Module: "talker"}); err != nil { t.Fatal(err) } hash, _, err := open.inventory.BusUserHash(ctx, "laptop.talker") if err != nil { t.Fatal(err) } said, err = assign(ctx, open, "laptop", "talker") if err != nil { t.Fatal(err) } if strings.Contains(said, "module issue") { t.Fatalf("a module with a minted credential was told to issue one:\n%s", said) } again, _, err := open.inventory.BusUserHash(ctx, "laptop.talker") if err != nil { t.Fatal(err) } if again != hash { t.Fatal("re-assigning rotated the credential") } } // A module that declares no broker secret is left alone: nothing to issue, nothing said. func TestAssigningAModuleThatDoesNotSpeakSaysNothingOfCredentials(t *testing.T) { open := aMesh(t) register(t, open, helloWeb()) said, err := assign(t.Context(), open, "laptop", "hello-web") if err != nil { t.Fatal(err) } if strings.Contains(said, "credential") { t.Fatalf("a module without a broker secret was told about credentials:\n%s", said) } }