package broker import ( "errors" "os" "path/filepath" "testing" "time" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" "golang.org/x/crypto/bcrypt" ) // **On a real server, as composed** (novox/hq issue 365): a machine's runtime calls in its own name, and the // server refuses it a call naming another — the grant, not the runtime, is what makes the caller a fact. func TestAServerComposedFromTheGrantsRefusesACallNamingAnother(t *testing.T) { hash, err := bcrypt.GenerateFromPassword([]byte("pw"), bcrypt.MinCost) if err != nil { t.Fatal(err) } ledger := Seat{Name: "issue-tracker", Scope: "mesh", Serves: []string{"open"}} accounts, err := ComposeAccounts([]Principal{ {Kind: KindNodeTools, Node: "novox", Module: RuntimeModule, PasswordHash: string(hash), Carries: []Declared{{Module: "mesh-issues", Holds: []Seat{ledger}}}}, {Kind: KindNodeTools, Node: "shanks", Module: RuntimeModule, PasswordHash: string(hash)}, }) if err != nil { t.Fatal(err) } conf := filepath.Join(t.TempDir(), "bus.conf") if err := os.WriteFile(conf, []byte("listen: 127.0.0.1:-1\njetstream { store_dir: "+ `"`+t.TempDir()+`"`+" }\n"+accounts), 0o600); err != nil { t.Fatal(err) } opts, err := server.ProcessConfigFile(conf) if err != nil { t.Fatalf("the composed accounts do not parse: %v", err) } opts.NoLog, opts.NoSigs = true, true s, err := server.NewServer(opts) if err != nil { t.Fatal(err) } go s.Start() if !s.ReadyForConnections(10 * time.Second) { t.Fatal("the bus did not come up") } defer s.Shutdown() holder, err := nats.Connect(s.ClientURL(), nats.UserInfo("novox.node-tools", "pw")) if err != nil { t.Fatal(err) } defer holder.Close() heard := make(chan string, 4) sub, err := holder.Subscribe("mesh.seat.issue-tracker.call.open.*", func(m *nats.Msg) { heard <- m.Subject _ = m.Respond([]byte(`{"result":{}}`)) }) if err != nil { t.Fatal(err) } _ = holder.Flush() if !sub.IsValid() { t.Fatal("the holder may not hear the caller-named calls to its seat") } refusals := make(chan error, 4) caller, err := nats.Connect(s.ClientURL(), nats.UserInfo("shanks.node-tools", "pw"), nats.CustomInboxPrefix("_INBOX.shanks.node-tools"), nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) { refusals <- err })) if err != nil { t.Fatal(err) } defer caller.Close() if _, err := caller.Request("mesh.seat.issue-tracker.call.open.shanks~node-tools", []byte(`{}`), 3*time.Second); err != nil { t.Fatalf("a call in the caller's own name was not answered: %v", err) } if got := <-heard; got != "mesh.seat.issue-tracker.call.open.shanks~node-tools" { t.Fatalf("heard %s", got) } if err := caller.Publish("mesh.seat.issue-tracker.call.open.novox~node-tools", []byte(`{}`)); err != nil { t.Fatal(err) } _ = caller.Flush() select { case err := <-refusals: if !errors.Is(err, nats.ErrPermissionViolation) { t.Errorf("the server said %v, want a permissions violation", err) } case <-time.After(3 * time.Second): t.Error("the server did not refuse a call naming another caller") } select { case got := <-heard: t.Errorf("a call naming another reached the holder: %s", got) case <-time.After(200 * time.Millisecond): } }