Files
mesh-controller/internal/broker/provisioner_test.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
2026-10-15 21:27:33 +00:00

230 lines
10 KiB
Go

package broker
import (
"slices"
"testing"
)
// The provisioner client's own bus account (novox/hq ADR 0349 clauses 6 and 11, design 57 package P3, issue
// 669): one per machine running it, apart from the machine's tool runner, granted to read its own machine's keys
// in each domain's bucket, to say what it applied on that machine, and to ask the vault in its own name; nothing
// else.
func provisionerRecords() Records {
client := Declared{Module: "mesh-provisioner", ProvisionsNode: true}
return Records{
Nodes: []string{"g14", "shanks", "ace"},
Assigned: map[string][]Declared{
"g14": {client, {Module: RuntimeModule}},
"shanks": {client},
"ace": {{Module: "dunst"}},
},
}
}
// One account per machine that runs the client, named for the machine and apart from its tool runner's: the
// vault tells the client from an agent on that machine by the name the bus proves.
func TestEachMachineRunningTheProvisionerClientHasAnAccountOfItsOwn(t *testing.T) {
users, err := Users(provisionerRecords())
if err != nil {
t.Fatal(err)
}
var got []string
for _, u := range users {
if u.Kind == KindProvisioner {
got = append(got, u.Username())
if u.Module != "mesh-provisioner" {
t.Errorf("%s is delivered to %q, want the module that runs it", u.Username(), u.Module)
}
}
}
if want := []string{"provisioner.g14", "provisioner.shanks"}; !slices.Equal(got, want) {
t.Fatalf("the provisioner clients' accounts are %v, want %v (none for a machine that does not run it)", got, want)
}
for _, u := range users {
if u.Kind == KindNodeTools && u.Username() == "provisioner.g14" {
t.Fatal("the client shares the tool runner's account")
}
}
}
// Its whole authority, subject by subject: a read of its own machine's keys in each domain's bucket, a read and a
// write of its own machine's keys in the provisioner server's needs, applied and collections, a read of the
// server's registry (design 57 P4), a publish of what it applied on its own machine, the vault's `get` in its own
// name, its own inbox and the vault's secret-changed.
func TestTheProvisionerClientIsGrantedItsMachinesKeysAndNothingElse(t *testing.T) {
perms, err := PermissionsFor(Principal{Kind: KindProvisioner, Node: "g14", Module: "mesh-provisioner",
BoundBuckets: []string{"postgres_bound"}})
if err != nil {
t.Fatal(err)
}
var wantPub []string
for _, bucket := range []string{"mesh-config_resolved", "postgres_bound"} {
stream := "KV_" + bucket
wantPub = append(wantPub,
"$JS.API.STREAM.INFO."+stream,
"$JS.API.DIRECT.GET."+stream+".$KV."+bucket+".g14.>",
"$JS.API.CONSUMER.CREATE."+stream+".*.$KV."+bucket+".g14.>",
"$JS.API.CONSUMER.DELETE."+stream+".>",
"$JS.FC."+stream+".>")
}
// The assignments mirror whole, read and never written: a provider's consumers are bound to it in their own
// machines' plans, which its machine's client reads to fill ${consumers:<provision>} (novox/hq ADR 0373, issue
// 679). The mirror holds placement and plans, never a value of a secret.
wantPub = append(wantPub,
"$JS.API.STREAM.INFO.KV_mesh-controller_assignments",
"$JS.API.DIRECT.GET.KV_mesh-controller_assignments.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-controller_assignments.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-controller_assignments.>",
"$JS.FC.KV_mesh-controller_assignments.>")
for _, bucket := range []string{"mesh-provisioner_needs", "mesh-provisioner_applied", "mesh-provisioner_collect"} {
stream := "KV_" + bucket
wantPub = append(wantPub,
"$JS.API.STREAM.INFO."+stream,
"$JS.API.DIRECT.GET."+stream+".$KV."+bucket+".g14.>",
"$JS.API.CONSUMER.CREATE."+stream+".*.$KV."+bucket+".g14.>",
"$JS.API.CONSUMER.DELETE."+stream+".>",
"$JS.FC."+stream+".>",
"$KV."+bucket+".g14.>")
}
wantPub = append(wantPub,
"$JS.API.STREAM.INFO.KV_mesh-provisioner_registry",
"$JS.API.DIRECT.GET.KV_mesh-provisioner_registry.>",
"$JS.API.CONSUMER.CREATE.KV_mesh-provisioner_registry.>",
"$JS.API.CONSUMER.DELETE.KV_mesh-provisioner_registry.>",
"$JS.FC.KV_mesh-provisioner_registry.>")
wantPub = append(wantPub, "prov.applied.g14.>", "mesh.seat.mesh-vault.call.get.provisioner~g14",
"prov.changed.g14.>")
slices.Sort(wantPub)
if !slices.Equal(perms.Publish, wantPub) {
t.Errorf("publish:\n have %q\n want %q", perms.Publish, wantPub)
}
// Its own inbox, what the vault says changed (`secret-changed`: which, by whom, never a value), so a rotated
// secret reaches its file without a new declaration (design 57 V4), and its own machine's tool runner asking
// it for a carried module's config (ADR 0370 §3), which it answers on the ask's reply.
if want := []string{"_INBOX.provisioner.g14.>", "mesh.mod.mesh-vault.event.secret-changed", "prov.ask.g14"}; !slices.Equal(perms.Subscribe, want) {
t.Errorf("subscribe:\n have %q\n want %q", perms.Subscribe, want)
}
if !perms.AllowResponses || len(perms.PublishDeny) > 0 {
t.Errorf("it answers what it is asked and needs no deny: %+v", perms)
}
// No write of any bucket but its own machine's keys of the server's needs, applied and collections: a domain
// writes its own (ADR 0349 clause 3, research 051), and the registry is the server's alone.
for _, p := range perms.Publish {
if !SubjectsOverlap(p, "$KV.>") {
continue
}
if !slices.Contains([]string{"$KV.mesh-provisioner_needs.g14.>", "$KV.mesh-provisioner_applied.g14.>",
"$KV.mesh-provisioner_collect.g14.>"}, p) {
t.Errorf("it may write a bucket: %s", p)
}
}
}
// Each provider's bucket of entries, `<module>_bound` (mesh-sdk go/provider, issue 678), is read by every machine's
// client for that machine's consumers' keys alone (`<node>.>`), to fill ${bound:<provision>:<key>} (issue 679): the
// buckets of every module the mesh places that keeps state named bound, wherever it runs, and no other.
func TestTheProvisionerClientReadsItsMachinesKeysOfEachProvidersBoundBucket(t *testing.T) {
records := provisionerRecords()
records.Assigned["ace"] = append(records.Assigned["ace"],
Declared{Module: "postgres", State: []Bucket{{Module: "postgres", Name: "bound"}, {Module: "postgres", Name: "kept"}}},
Declared{Module: "keycloak", State: []Bucket{{Module: "keycloak", Name: "bound"}}})
records.Assigned["shanks"] = append(records.Assigned["shanks"],
Declared{Module: "postgres", State: []Bucket{{Module: "postgres", Name: "bound"}}})
users, err := Users(records)
if err != nil {
t.Fatal(err)
}
for _, u := range users {
if u.Kind != KindProvisioner {
continue
}
if want := []string{"keycloak_bound", "postgres_bound"}; !slices.Equal(u.BoundBuckets, want) {
t.Errorf("%s reads the bound buckets %v, want %v", u.Username(), u.BoundBuckets, want)
}
perms, err := PermissionsFor(u)
if err != nil {
t.Fatal(err)
}
mine := "$JS.API.DIRECT.GET.KV_postgres_bound.$KV.postgres_bound." + u.Node + ".>"
if !slices.Contains(perms.Publish, mine) {
t.Errorf("%s may not read its machine's keys of postgres_bound", u.Username())
}
for _, p := range perms.Publish {
if SubjectsOverlap(p, "$JS.API.DIRECT.GET.KV_postgres_kept.>") || SubjectsOverlap(p, "$KV.postgres_bound.>") ||
SubjectsOverlap(p, "$JS.API.DIRECT.GET.KV_postgres_bound.$KV.postgres_bound.ace.>") {
t.Errorf("%s is granted %s: another bucket, a write, or another machine's consumers", u.Username(), p)
}
}
}
if _, err := PermissionsFor(Principal{Kind: KindProvisioner, Node: "g14", Module: "mesh-provisioner",
BoundBuckets: []string{"postgres_bound.>"}}); err == nil {
t.Error("a bound bucket that is a pattern composed")
}
}
// A machine with no usable name reaches nothing: its grants would otherwise be a pattern over every machine.
func TestAProvisionerClientNamingNoMachineIsRefused(t *testing.T) {
for _, node := range []string{"", "g14.>", "*"} {
if _, err := PermissionsFor(Principal{Kind: KindProvisioner, Node: node, Module: "mesh-provisioner"}); err == nil {
t.Errorf("a provisioner client on %q composed", node)
}
}
}
// A whole mesh composes with the clients beside every other kind: nobody is granted a second writer's subject.
func TestAMeshWithProvisionerClientsComposes(t *testing.T) {
users, err := Users(provisionerRecords())
if err != nil {
t.Fatal(err)
}
for i := range users {
users[i].PasswordHash = "x"
}
if _, err := ComposeAccounts(users); err != nil {
t.Fatal(err)
}
}
// The client is asked on a subject its own machine's tool runner alone may publish (novox/hq ADR 0370 §3, issue
// 685): across a whole mesh, no principal but g14's tool runner may ask g14's client, none but g14's client hears
// it, and the client's word of what moved is heard by g14's tool runner alone.
func TestTheProvisionerClientIsAskedByItsOwnMachinesToolRunnerAlone(t *testing.T) {
records := provisionerRecords()
records.Assigned["shanks"] = append(records.Assigned["shanks"], Declared{Module: RuntimeModule},
Declared{Module: "dunst"})
records.Assigned["g14"] = append(records.Assigned["g14"], Declared{Module: "claude-code"})
users, err := Users(records)
if err != nil {
t.Fatal(err)
}
users = append(users, Principal{Kind: KindPerson, Module: "jochen", Invokes: []string{"*"}})
var asks, hears, told []string
for _, u := range users {
perms, err := PermissionsFor(u)
if err != nil {
t.Fatalf("%s: %v", u.Username(), err)
}
if slices.ContainsFunc(perms.Publish, func(p string) bool { return SubjectsOverlap(p, ProvisionerAskSubject("g14")) }) {
asks = append(asks, u.Username())
}
if slices.ContainsFunc(perms.Subscribe, func(p string) bool { return SubjectsOverlap(p, ProvisionerAskSubject("g14")) }) {
hears = append(hears, u.Username())
}
if slices.ContainsFunc(perms.Subscribe, func(p string) bool {
return SubjectsOverlap(p, ProvisionerChangedSubject("g14", "claude-code"))
}) {
told = append(told, u.Username())
}
}
if want := []string{"g14.node-tools"}; !slices.Equal(asks, want) {
t.Errorf("g14's client may be asked by %v, want %v alone", asks, want)
}
if want := []string{"provisioner.g14"}; !slices.Equal(hears, want) {
t.Errorf("g14's client's asks are heard by %v, want %v alone", hears, want)
}
if want := []string{"g14.node-tools"}; !slices.Equal(told, want) {
t.Errorf("what g14's client says moved is heard by %v, want %v alone", told, want)
}
}