Every grant to call a tool is granted again under the call kind, with the credential's own bus user as the last token, and every grant to answer one is granted for calls naming any caller: the caller of a tool call becomes a fact the bus enforces, as ADR 0259 section 3 made the asker of an ask. A kind of its own because every existing tool grant is a wildcard that would match any caller appended. The old tool grants stay for one release so nothing loses its way to a tool (hq issue 464); the controller's own grants move with that issue, since they are also the installer's first user list.
101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
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):
|
|
}
|
|
}
|