package broker import ( "slices" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/nats-io/nats.go/jetstream" ) // 1136/AC1: the controller's start asserts node-engine_have with the history delivery keeps, and the bucket is one of // the controller's own, so raising the buckets does not name it undeclared (ADR 0429 §1). func TestTheHaveBucketIsTheControllersAndKeepsTwentyRevisions(t *testing.T) { // 1136/AC1 cfg := HaveBucketConfig() if cfg.Bucket != delivery.HaveBucket || cfg.History != delivery.HaveHistory || delivery.HaveHistory != 20 { t.Fatalf("the have bucket is asserted as %q with history %d, want %q with 20", cfg.Bucket, cfg.History, delivery.HaveBucket) } if cfg.Storage != jetstream.FileStorage || cfg.MaxBytes <= 0 || cfg.MaxValueSize <= 0 { t.Fatalf("the have bucket is not bounded on disk: %+v", cfg) } if !IsControllerBucket(delivery.HaveBucket) || !slices.Contains(ControllerBuckets(), delivery.HaveBucket) { t.Fatalf("%s is not among the controller's buckets", delivery.HaveBucket) } undeclared, err := RaiseBuckets(fakeBuckets{names: []string{delivery.HaveBucket}}, nil) if err != nil { t.Fatal(err) } if len(undeclared) != 0 { t.Fatalf("%v are raised as undeclared", undeclared) } } // 1136/AC2: a node may write and read the keys of its own node of the have bucket, with what a get, create, update and // watch of them need, and no other key of it; the controller does not write it; an unsafe node name is refused. func TestANodeWritesAndReadsOnlyItsOwnHaveKeys(t *testing.T) { // 1136/AC2 perms, err := PermissionsFor(Principal{Kind: KindNode, Node: "one", PasswordHash: "x"}) if err != nil { t.Fatal(err) } b, stream := delivery.HaveBucket, "KV_"+delivery.HaveBucket for _, want := range []string{ "$KV." + b + ".one.>", "$JS.API.STREAM.INFO." + stream, "$JS.API.DIRECT.GET." + stream + ".$KV." + b + ".one.>", "$JS.API.CONSUMER.CREATE." + stream + ".*.$KV." + b + ".one.>", "$JS.API.CONSUMER.DELETE." + stream + ".>", "$JS.FC." + stream + ".>", } { has(t, perms.Publish, want) } for _, p := range perms.Publish { if !strings.Contains(p, delivery.HaveBucket) { continue } if strings.Contains(p, "KV."+b+".>") || strings.Contains(p, "KV."+b+".two") || strings.HasSuffix(p, "."+b+".*") || strings.Contains(p, "DIRECT.GET."+stream+".>") || strings.Contains(p, "CONSUMER.CREATE."+stream+".>") { t.Errorf("the node is granted %q, which reaches keys of other nodes", p) } } if err := CheckWriters(Principal{Kind: KindNode, Node: "one"}, perms.Publish); err != nil { t.Fatalf("the node's own have keys are refused as a second writer: %v", err) } controller, err := PermissionsFor(Principal{Kind: KindController, PasswordHash: "x"}) if err != nil { t.Fatal(err) } for _, p := range controller.Publish { if SubjectsOverlap(p, "$KV."+b+".one.zsh") { t.Errorf("the controller may write the have keys through %q", p) } } for _, bad := range []string{"on.e", "one.>", "*", "o>ne"} { if _, err := PermissionsFor(Principal{Kind: KindNode, Node: bad, PasswordHash: "x"}); err == nil { t.Errorf("node %q was accepted as part of a have subject", bad) } } // A principal that is not a node writes no have key: the writers table refuses it. if err := CheckWriters(Principal{Kind: KindModule, Module: "billing", Node: "one"}, []string{"$KV." + b + ".one.>"}); err == nil { t.Error("a module granted the have keys of a node is not refused as a second writer") } if err := CheckWriters(Principal{Kind: KindNode, Node: "two"}, []string{"$KV." + b + ".one.>"}); err == nil { t.Error("a node granted another node's have keys is not refused as a second writer") } } // 1136/AC3: a module whose manifest reads node-engine.have, mesh-controller.assignments and mesh-controller.builds may // watch and get every key of the three and write none. func TestAModuleReadingHaveAssignmentsAndBuildsReadsAndWritesNone(t *testing.T) { // 1136/AC3 perms, err := PermissionsFor(Principal{Kind: KindModule, Node: "one", Module: "mesh-delivery-view", PasswordHash: "x", Reads: []string{"node-engine.have", "mesh-controller.assignments", "mesh-controller.builds"}}) if err != nil { t.Fatal(err) } for _, b := range []string{delivery.HaveBucket, AssignmentsBucket, BuildsBucket} { stream := "KV_" + b has(t, perms.Publish, "$JS.API.STREAM.INFO."+stream) has(t, perms.Publish, "$JS.API.DIRECT.GET."+stream+".>") has(t, perms.Publish, "$JS.API.CONSUMER.CREATE."+stream+".>") for _, p := range perms.Publish { if SubjectsOverlap(p, "$KV."+b+".any.key") { t.Errorf("the reading module may write %s through %q", b, p) } } } } // A node's own key carries its whole facts (its profile among them), several kilobytes on a desktop: the have // bucket takes any value the chain's buckets take, since a value is never cut (novox/hq ADR 0408 §6, issue 1162). // 1162/AC1 func TestTheHaveBucketTakesAValueAsLargeAsTheChains(t *testing.T) { if got := HaveBucketConfig().MaxValueSize; got != ChainValueBytes { t.Fatalf("the have bucket takes values up to %d bytes, the chain's up to %d", got, ChainValueBytes) } }