The three tests of the controller's own module.json (process, claimed seat, addresses), the Dockerfile and Makefile pin test, the build-agent registry test and the manifest-in-use seat test move with their old package, as tests of mesh-sdk's exported API; a moved test is exempt from failing first.
120 lines
4.7 KiB
Go
120 lines
4.7 KiB
Go
package inventory
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
)
|
|
|
|
// grantMatches is a NATS subject pattern matching a subject: `*` one token, `>` the rest.
|
|
func grantMatches(pattern, subject string) bool {
|
|
p, s := strings.Split(pattern, "."), strings.Split(subject, ".")
|
|
for i, tok := range p {
|
|
if tok == ">" {
|
|
return len(s) > i
|
|
}
|
|
if i >= len(s) || (tok != "*" && tok != s[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return len(p) == len(s)
|
|
}
|
|
|
|
func grantsAny(patterns []string, subject string) bool {
|
|
for _, p := range patterns {
|
|
if grantMatches(p, subject) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// novox/hq ADR 0259 §8 (confirmation review of 2026-10-09): the router honours a verified sender only on the
|
|
// controller's `root-free` word, asked on the bus. So only the serving controller may answer that verb — be
|
|
// subscribed to its subject — and nobody may publish into the router's inbox but by answering a request it
|
|
// made (allow_responses). Composed here from the controller's own manifest, on a machine where the machine's
|
|
// runtime carries it beside an ordinary module, with the router, a channel, a person and an administrator: a
|
|
// runtime carrying the controller's module, a node-engine, a channel or anybody else answering `root-free` is
|
|
// an agent answering it.
|
|
func TestOnlyTheServingControllerMayAnswerRootFree(t *testing.T) {
|
|
raw, err := os.ReadFile("../../module.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
controller, err := catalogue.ParseManifest(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parse := func(s string) catalogue.Manifest {
|
|
m, err := catalogue.ParseManifest([]byte(s))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m
|
|
}
|
|
dir := `"resources": [{"id": "state", "type": "directory", "mode": "0700", "place": "."}]`
|
|
router := parse(`{"module": "messenger", "version": "1", "runs-as": "messenger",
|
|
"seats": [{"name": "operator-channel", "scope": "mesh", "serves": ["open"], "accepts": ["ask"],
|
|
"emits": ["decided"], "by-caller": ["ask", "decided"]}],
|
|
"claims": [{"name": "operator-channel", "scope": "mesh", "serves": ["open"]}],
|
|
"invokes": ["seat:mesh-controller.root-free", "seat:mesh-controller.conditions"],
|
|
"own-secrets": {"broker": "${dir:state}/broker"}, "secrets-owner": "messenger",
|
|
"resources": [{"id": "account", "type": "user", "name": "messenger", "shell": "/usr/bin/nologin", "home": "/var/lib/messenger"},
|
|
{"id": "state", "type": "directory", "mode": "0700", "place": ".", "owner": "messenger"}]}`)
|
|
ordinary := parse(`{"module": "lab-bystander", "version": "1", "own-secrets": {"broker": "${dir:state}/broker"}, ` + dir + `}`)
|
|
runtime := catalogue.Manifest{Module: broker.RuntimeModule}
|
|
|
|
manifests := []catalogue.Manifest{controller, router, ordinary, runtime}
|
|
seats := map[string]catalogue.SeatDeclaration{}
|
|
declarers := map[string]string{}
|
|
for _, m := range manifests {
|
|
for _, s := range m.DefinesSeats {
|
|
seats[s.Name], declarers[s.Name] = s, m.Module
|
|
}
|
|
}
|
|
for _, own := range catalogue.SeatsWithAProtocol() {
|
|
seats[own.Name] = catalogue.SeatDeclaration{Name: own.Name, Scope: own.Scope, Accepts: own.Accepts,
|
|
Emits: own.Emits, Serves: own.Serves}
|
|
}
|
|
records := broker.Records{Nodes: []string{"anchor", "laptop"}, Assigned: map[string][]broker.Declared{},
|
|
People: map[string][]string{"operator": {"*"}, "guest": {"mesh-controller.status"}},
|
|
Interchangeable: map[string]bool{}}
|
|
for _, m := range manifests {
|
|
records.Assigned["anchor"] = append(records.Assigned["anchor"], declaredFor(m, seats, declarers))
|
|
}
|
|
// And a second machine whose runtime carries an ordinary module: where agents run as the operator.
|
|
records.Assigned["laptop"] = []broker.Declared{declaredFor(ordinary, seats, declarers), declaredFor(runtime, seats, declarers)}
|
|
users, err := broker.Users(records)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const verb = "mesh.seat.mesh-controller.tool.root-free"
|
|
answerers := 0
|
|
for _, u := range users {
|
|
p, err := broker.PermissionsFor(u)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
answers := grantsAny(p.Subscribe, verb)
|
|
if answers != (u.Kind == broker.KindController) {
|
|
t.Errorf("%s (%s) %s subscribe to %s", u.Username(), u.Kind,
|
|
map[bool]string{true: "may", false: "may not"}[answers], verb)
|
|
}
|
|
if answers {
|
|
answerers++
|
|
}
|
|
// Nobody publishes into the router's inbox but as an answer to what it asked.
|
|
for _, inbox := range []string{"_INBOX.anchor.messenger.x1.y", "_INBOX.anchor.messenger.>"} {
|
|
if u.Username() != "anchor.messenger" && grantsAny(p.Publish, inbox) {
|
|
t.Errorf("%s may publish into the router's inbox (%s) without being asked", u.Username(), inbox)
|
|
}
|
|
}
|
|
}
|
|
if answerers != 1 {
|
|
t.Errorf("%d principals may answer root-free, want the controller alone", answerers)
|
|
}
|
|
}
|