The confirmation review asked who may answer root-free on the bus. Composed from the controller's own manifest, the module principal of the machine running the controller and that machine's runtime were granted the controller seat's tool subjects too: either could answer root-free, and the runtime's credential is one an agent on that machine may hold. The controller's seat is now served by the controller principal alone, in grants and memberships; TestOnlyTheServingControllerMayAnswerRootFree failed before (3 answerers) and passes. And a machine waiting for its first setuid search is not root-free, whatever ADR 0266's quiet window does to the self-check.
120 lines
4.8 KiB
Go
120 lines
4.8 KiB
Go
package inventory
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
}
|