Files
mesh-controller/internal/broker/snapshot_test.go
T
jochen 48581af35c Grant the bus's own module the snapshot API and nothing else (hq ADR 0235)
The night's backup of the bus takes each stream through JetStream's snapshot
API, run by the nats module under its own account. The module holding
mesh-broker is composed that account: stream names and info, the snapshot
request, its flow-control acks, its own inbox — no write, which the writers
table checks. A bus module declaring anything else to say on the bus is
refused by module check rather than silently granted nothing. The genesis
user list is unchanged: the controller's grants are.
2026-10-06 18:20:57 +02:00

76 lines
2.7 KiB
Go

package broker
import (
"slices"
"testing"
)
// The bus's own module copies the bus and does nothing else on it (novox/hq ADR 0235): its whole
// authority is the snapshot API and its own inbox, whatever else it declared — its tools are the
// node's runtime's to serve.
func TestTheBussOwnModuleMaySnapshotAndNothingElse(t *testing.T) {
p := Principal{Kind: KindModule, Node: "anchor", Module: "nats", SnapshotsTheBus: true,
Serves: []string{"nats_streams"}, PasswordHash: "x"}
perms, err := PermissionsFor(p)
if err != nil {
t.Fatal(err)
}
want := []string{"$JS.API.STREAM.INFO.*", "$JS.API.STREAM.NAMES", "$JS.API.STREAM.SNAPSHOT.*", "$JS.SNAPSHOT.ACK.>"}
if !slices.Equal(perms.Publish, want) {
t.Errorf("it may publish %v, want exactly %v", perms.Publish, want)
}
if !slices.Equal(perms.Subscribe, []string{"_INBOX.anchor.nats.>"}) {
t.Errorf("it may subscribe %v, want its own inbox alone", perms.Subscribe)
}
if perms.AllowResponses {
t.Error("nothing is asked of it, and it may answer")
}
}
// Read-only, by the writers table: no grant of it overlaps a subject a write of the mesh's state is a
// publish to — a stream's definition, a bucket, a message.
func TestTheSnapshotGrantsWriteNothing(t *testing.T) {
p := Principal{Kind: KindModule, Node: "anchor", Module: "nats"}
if err := CheckWriters(p, BusSnapshotGrants.Publish); err != nil {
t.Fatal(err)
}
for _, grant := range BusSnapshotGrants.Publish {
for _, write := range []string{"$JS.API.STREAM.CREATE.X", "$JS.API.STREAM.UPDATE.X", "$JS.API.STREAM.DELETE.X",
"$JS.API.STREAM.PURGE.X", "$JS.API.STREAM.MSG.DELETE.X", "$JS.API.STREAM.RESTORE.X",
"$JS.API.CONSUMER.CREATE.X", "$JS.API.CONSUMER.DURABLE.CREATE.X.y", "$KV.b.k", "mesh.mod.m.event.e"} {
if SubjectsOverlap(grant, write) {
t.Errorf("%s would let the bus's own module publish %s", grant, write)
}
}
}
}
// A module that is not the bus gets nothing of it, and the flag travels from the records to the user.
func TestOnlyTheBussOwnModuleIsGrantedTheSnapshot(t *testing.T) {
users, err := Users(Records{Nodes: []string{"anchor"}, Assigned: map[string][]Declared{"anchor": {
{Module: "nats", SnapshotsTheBus: true},
{Module: "shop", Emits: []string{"order.placed"}},
}}})
if err != nil {
t.Fatal(err)
}
for _, u := range users {
perms, err := PermissionsFor(u)
if err != nil {
t.Fatal(err)
}
snapshots := slices.Contains(perms.Publish, "$JS.API.STREAM.SNAPSHOT.*")
switch u.Username() {
case "anchor.nats":
if !snapshots {
t.Error("the bus's own module is not granted the snapshot")
}
case "controller":
default:
if snapshots {
t.Errorf("%s may snapshot the bus", u.Username())
}
}
}
}