//go:build integration 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" ) func TestOnARealBusTheVaultsAccountIsAnsweredByTheDeskAndTheProviderAndNothingElse(t *testing.T) { users, err := Users(vaultRecords()) if err != nil { t.Fatal(err) } hash, err := bcrypt.GenerateFromPassword([]byte("pw"), bcrypt.MinCost) if err != nil { t.Fatal(err) } for i := range users { users[i].PasswordHash = string(hash) } accounts, err := ComposeAccounts(users) 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() // anchor's runtime serves the desk's prompt and plex's issuing tool, on every subject it is granted, and says // which subject it heard. holder, err := nats.Connect(s.ClientURL(), nats.UserInfo("anchor.node-tools", "pw")) if err != nil { t.Fatal(err) } defer holder.Close() heard := make(chan string, 8) answer := func(m *nats.Msg) { heard <- m.Subject _ = m.Respond([]byte(`{"result":{"sealed":"c2VhbGVkLXRvLXRoZS1jYWxs"}}`)) } for _, subject := range []string{"mesh.seat.node-launcher.call.secret.anchor.*", "mesh.seat.node-launcher.tool.secret.anchor", "mesh.mod.plex.call.plex_issued_credential.anchor.*", "mesh.mod.other.call.tool.anchor.*", "mesh.seat.node-launcher.call.menu.anchor.*"} { sub, err := holder.Subscribe(subject, answer) if err != nil { t.Fatal(err) } _ = holder.Flush() if !sub.IsValid() { t.Fatalf("the desk's runtime may not hear %s", subject) } } refusals := make(chan error, 16) vault, err := nats.Connect(s.ClientURL(), nats.UserInfo("novox.mesh-vault", "pw"), nats.CustomInboxPrefix("_INBOX.novox.mesh-vault"), nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) { refusals <- err })) if err != nil { t.Fatalf("the vault's own account cannot connect: %v", err) } defer vault.Close() token := CallerToken("novox.mesh-vault") for _, subject := range []string{"mesh.seat.node-launcher.call.secret.anchor." + token, "mesh.mod.plex.call.plex_issued_credential.anchor." + token} { if _, err := vault.Request(subject, []byte(`{"seal_to":"a2V5LW9mLXRoZS1jYWxs"}`), 3*time.Second); err != nil { t.Fatalf("the vault's call %s was not answered: %v", subject, err) } if got := <-heard; got != subject { t.Fatalf("the holder heard %s, want %s", got, subject) } } for _, subject := range []string{"mesh.seat.node-launcher.tool.secret.anchor", "mesh.seat.node-launcher.call.secret.anchor.controller", "mesh.seat.node-launcher.call.menu.anchor." + token, "mesh.mod.other.call.tool.anchor." + token} { if err := vault.Publish(subject, []byte(`{}`)); err != nil { t.Fatal(err) } _ = vault.Flush() select { case err := <-refusals: if !errors.Is(err, nats.ErrPermissionViolation) { t.Errorf("%s: the server said %v, want a permissions violation", subject, err) } case <-time.After(3 * time.Second): t.Errorf("the server did not refuse the vault %s", subject) } } select { case got := <-heard: t.Errorf("a call the vault may not make reached the holder: %s", got) case <-time.After(200 * time.Millisecond): } // The controller is still answered on today's path, for the modules filled the old way (until R1). controller, err := nats.Connect(s.ClientURL(), nats.UserInfo("controller", "pw"), nats.CustomInboxPrefix("_INBOX.controller")) if err != nil { t.Fatal(err) } defer controller.Close() if _, err := controller.Request("mesh.seat.node-launcher.tool.secret.anchor", []byte(`{}`), 3*time.Second); err != nil { t.Errorf("the controller's call to the desk on today's path: %v", err) } <-heard }