Files
mesh-host/internal/link/judge_directories_test.go
T
jschoubben b92fb68116
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/358-judge-directories-read-only delivered: every member is delivered
Judge a node's directories read-only before the walk refuses any (issue 358)
Once a mesh directory's mode and owner go only through a walk that follows no
link another account could make, a directory behind such a link is refused
and its module gated: on the control-node that could stop the controller.
The operator is to be told which directories that walk would refuse on every
node before it lands. So the engine judges the declaration it keeps with the
same walk, making and changing nothing: on a signed ask from the controller
(mesh.node.<node>.ask.judge-directories, held to the hand-over's checks) and
as `nox-mesh-host judge-directories` at the machine.
2026-10-12 13:57:52 +02:00

53 lines
2.6 KiB
Go

package link
import (
"strings"
"testing"
"time"
)
// The subject and the signing context the controller holds the same words for (mesh-controller internal/link).
func TestTheDirectoryJudgementAskKeepsItsSubjectAndContext(t *testing.T) {
if AskJudgeDirectoriesSubject("laptop") != "mesh.node.laptop.ask.judge-directories" {
t.Fatalf("the subject is %q", AskJudgeDirectoriesSubject("laptop"))
}
if JudgeDirectoriesContext != "novox-mesh judge-directories v1\n" || JudgeDirectoriesContext == HandOverContext ||
JudgeDirectoriesContext == SetuidSearchContext {
t.Fatalf("the signing context is %q", JudgeDirectoriesContext)
}
}
// A judgement ask is believed as a hand-over is: signed by the mesh under its own context, for this node,
// unexpired, once, and naming no path.
func TestADirectoryJudgementAskIsBelievedOnlySignedForThisNodeAndOnce(t *testing.T) {
m, key := aMember(t)
now := time.Date(2026, 10, 12, 14, 0, 0, 0, time.UTC)
var asked []string
handle := func(by string) (string, error) { asked = append(asked, by); return "3 directories declared", nil }
check := HandOverCheck{Signer: m.Signer, Node: "n1", Now: func() time.Time { return now }, Nonces: &Nonces{}}
good := HandOverAsk{Node: "n1", By: "jo", Expires: now.Add(HandOverGood), Nonce: aNonce}
for name, body := range map[string][]byte{
"a setuid search's signature": signedSearch(t, key, SetuidSearchContext, good),
"another node": signedSearch(t, key, JudgeDirectoriesContext, HandOverAsk{Node: "n2", By: "jo", Expires: good.Expires, Nonce: aNonce}),
"a path": signedSearch(t, key, JudgeDirectoriesContext, HandOverAsk{Node: "n1", Path: "/x", By: "jo", Expires: good.Expires, Nonce: aNonce}),
"expired": signedSearch(t, key, JudgeDirectoriesContext, HandOverAsk{Node: "n1", By: "jo", Expires: now, Nonce: aNonce}),
} {
if a := AnswerJudgeDirectories(check, handle, body); !strings.HasSuffix(a.Refused, "nothing was judged") || a.Said != "" {
t.Errorf("%s: %+v", name, a)
}
}
if len(asked) != 0 {
t.Fatalf("a refused ask reached the handler: %v", asked)
}
body := signedSearch(t, key, JudgeDirectoriesContext, good)
if a := AnswerJudgeDirectories(check, handle, body); a.Said != "3 directories declared" || len(asked) != 1 {
t.Fatalf("the good ask: %+v, %v", a, asked)
}
if a := AnswerJudgeDirectories(check, handle, body); !strings.Contains(a.Refused, "taken already") {
t.Fatalf("the same ask again: %+v", a)
}
if _, answer := JudgeDirectoriesReply(check, handle, "", body); answer {
t.Fatal("a request with no reply subject was answered")
}
}