package broker import ( "os" "path/filepath" "strings" "testing" "github.com/nats-io/nats-server/v2/server" ) // 1147/AC1: the composed accounts file declares $SYS with no user, exporting only the connect and disconnect events of // MESH and the request listing MESH's connections, and MESH imports exactly those three as mesh.bus.connect, // mesh.bus.disconnect and mesh.bus.connz (novox/hq ADR 0431 §1); the server's own grammar takes it with $SYS as its // system account; the golden file shows it (TestTheComposedConfigMatchesTheGolden). func TestTheComposedAccountsExportMeshsConnectionEventsFromTheSystemAccount(t *testing.T) { // 1147/AC1 text, err := ComposeAccounts([]Principal{{Kind: KindNode, Node: "one", PasswordHash: "x"}}) if err != nil { t.Fatal(err) } sys := between(text, `"$SYS" {`, "\n }\n") if sys == "" { t.Fatalf("no account $SYS in the composed accounts:\n%s", text) } if strings.Contains(sys, "users") || strings.Contains(sys, "user:") { t.Errorf("$SYS holds a user:\n%s", sys) } for _, want := range []string{ `{ stream: "$SYS.ACCOUNT.MESH.CONNECT", accounts: ["MESH"] }`, `{ stream: "$SYS.ACCOUNT.MESH.DISCONNECT", accounts: ["MESH"] }`, `{ service: "$SYS.REQ.ACCOUNT.MESH.CONNZ", accounts: ["MESH"] }`, } { if !strings.Contains(sys, want) { t.Errorf("$SYS does not export %s:\n%s", want, sys) } } if n := strings.Count(sys, "{ stream:") + strings.Count(sys, "{ service:"); n != 3 { t.Errorf("$SYS exports %d things, want exactly 3:\n%s", n, sys) } mesh := between(text, " MESH {", " users = [") for _, want := range []string{ `{ stream: { account: "$SYS", subject: "$SYS.ACCOUNT.MESH.CONNECT" }, to: "mesh.bus.connect" }`, `{ stream: { account: "$SYS", subject: "$SYS.ACCOUNT.MESH.DISCONNECT" }, to: "mesh.bus.disconnect" }`, `{ service: { account: "$SYS", subject: "$SYS.REQ.ACCOUNT.MESH.CONNZ" }, to: "mesh.bus.connz" }`, } { if !strings.Contains(mesh, want) { t.Errorf("MESH does not import %s:\n%s", want, mesh) } } if n := strings.Count(mesh, "{ stream:") + strings.Count(mesh, "{ service:"); n != 3 { t.Errorf("MESH imports %d things, want exactly 3:\n%s", n, mesh) } // The pinned server's grammar takes it, and makes $SYS its system account. path := filepath.Join(t.TempDir(), "accounts.conf") if err := os.WriteFile(path, []byte(text), 0o600); err != nil { t.Fatal(err) } opts, err := server.ProcessConfigFile(path) if err != nil { t.Fatalf("the server refuses the composed accounts: %v", err) } var found bool for _, a := range opts.Accounts { if a.Name != "$SYS" { continue } found = true if !a.IsExportService("$SYS.REQ.ACCOUNT.MESH.CONNZ") { t.Error("$SYS does not export the connections request as a service") } } if !found { t.Error("the server read no account $SYS") } for _, u := range opts.Users { if u.Account != nil && u.Account.Name == "$SYS" { t.Errorf("user %s is in $SYS", u.Username) } } } // between is the text from the first start to the first end after it, or empty. func between(s, start, end string) string { i := strings.Index(s, start) if i < 0 { return "" } rest := s[i:] j := strings.Index(rest[len(start):], end) if j < 0 { return rest } return rest[:len(start)+j] } // 1147/AC2: only mesh-delivery-watchman's principal may subscribe to mesh.bus.connect and mesh.bus.disconnect and // publish to mesh.bus.connz; no other principal's grant covers them by a wildcard (ADR 0431 §2). func TestOnlyTheWatchmanHearsTheBusConnectionEvents(t *testing.T) { // 1147/AC2 builder := Seat{Name: "node-build-agent", Scope: "node", Accepts: []string{"build"}, Emits: []string{"built"}} watchman := Principal{Kind: KindModule, Node: "one", Module: "mesh-delivery-watchman", PasswordHash: "x"} others := []Principal{ {Kind: KindController, PasswordHash: "x"}, {Kind: KindNode, Node: "one", PasswordHash: "x"}, {Kind: KindEnrolment, Node: "two", PasswordHash: "x"}, {Kind: KindPerson, Module: "jochen", Invokes: []string{"*"}, PasswordHash: "x"}, {Kind: KindProvisioner, Node: "one", PasswordHash: "x"}, {Kind: KindModule, Node: "one", Module: "gitea", Emits: []string{"pull.merged"}, PasswordHash: "x"}, {Kind: KindModule, Node: "one", Module: "mesh-delivery-view", PasswordHash: "x", Reads: []string{"node-engine.have"}, Consumes: []string{"mesh-delivery-watchman.presence"}}, {Kind: KindModule, Node: "one", Module: "build-agent", Holds: []Seat{builder}, PasswordHash: "x"}, {Kind: KindModule, Node: "one", Module: "nats", SnapshotsTheBus: true, PasswordHash: "x"}, {Kind: KindNodeTools, Node: "one", Module: RuntimeModule, PasswordHash: "x", Carries: []Declared{ {Module: "mesh-delivery-watchman"}, {Module: "build-agent", Holds: []Seat{builder}}}}, } heard := []string{"mesh.bus.connect", "mesh.bus.disconnect"} perms, err := PermissionsFor(watchman) if err != nil { t.Fatal(err) } for _, s := range heard { has(t, perms.Subscribe, s) } has(t, perms.Publish, "mesh.bus.connz") for _, s := range append(heard, "mesh.bus.connz") { for _, p := range perms.Subscribe { if p != s && SubjectsOverlap(p, s) && !strings.HasPrefix(p, "_INBOX.") { t.Errorf("the watchman's subscription %q reaches %s beyond naming it", p, s) } } } for _, p := range others { perms, err := PermissionsFor(p) if err != nil { t.Fatalf("%s: %v", p.Username(), err) } for _, s := range append(heard, "mesh.bus.connz") { for _, g := range append(append([]string(nil), perms.Publish...), perms.Subscribe...) { if SubjectsOverlap(g, s) { t.Errorf("%s is granted %q, which covers %s", p.Username(), g, s) } } } } }