Files
mesh-host/internal/link/setuid_search_test.go
T
jochen 8e79403c21
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 fail: its merge-check.sh failed: --- FAIL: TestNatsAnEnrolmentWithNoReplyAddressIsNotAnswered (0.00s)
mesh/delivery superseded: a newer head of the same pull request
Show how far the setuid search got, and start a fresh one when the terminal asks (hq issue 361)
The walk kept its progress every minute and nobody could read it: root-free said only that a search ran.
The account verdict now carries the progress (counts, times, mount points; never a path) as fields and in
its reason, outside what makes a verdict changed. A signed ask from the controller's terminal throws the last
search away and starts a full one, after the operator removed by hand what it found.
2026-10-10 01:55:58 +02:00

61 lines
2.7 KiB
Go

package link
import (
"crypto/ed25519"
"encoding/json"
"strings"
"testing"
"time"
)
// The subject and the signing context the controller holds the same words for (mesh-controller internal/link).
func TestTheSetuidSearchAskKeepsItsSubjectAndContext(t *testing.T) {
if AskSetuidSearchSubject("laptop") != "mesh.node.laptop.ask.setuid-search" {
t.Fatalf("the subject is %q", AskSetuidSearchSubject("laptop"))
}
if SetuidSearchContext != "novox-mesh setuid-search v1\n" || SetuidSearchContext == HandOverContext {
t.Fatalf("the signing context is %q", SetuidSearchContext)
}
}
func signedSearch(t *testing.T, key ed25519.PrivateKey, context string, ask HandOverAsk) []byte {
t.Helper()
raw, _ := json.Marshal(ask)
body, _ := json.Marshal(SignedHandOver{Ask: raw, Signature: ed25519.Sign(key, append([]byte(context), raw...))})
return body
}
// A search ask is believed as a hand-over is: signed by the mesh under its own context (a hand-over's signature
// starts no search), for this node, unexpired, once, and naming no path.
func TestASetuidSearchAskIsBelievedOnlySignedForThisNodeAndOnce(t *testing.T) {
m, key := aMember(t)
now := time.Date(2026, 10, 10, 2, 0, 0, 0, time.UTC)
var asked []string
handle := func(by string) (string, error) { asked = append(asked, by); return "a new search starts", 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 hand-over's signature": signedSearch(t, key, HandOverContext, good),
"another node": signedSearch(t, key, SetuidSearchContext, HandOverAsk{Node: "n2", By: "jo", Expires: good.Expires, Nonce: aNonce}),
"a path": signedSearch(t, key, SetuidSearchContext, HandOverAsk{Node: "n1", Path: "/x", By: "jo", Expires: good.Expires, Nonce: aNonce}),
"expired": signedSearch(t, key, SetuidSearchContext, HandOverAsk{Node: "n1", By: "jo", Expires: now, Nonce: aNonce}),
} {
if a := AnswerSetuidSearch(check, handle, body); !strings.HasSuffix(a.Refused, "no search was started") || 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, SetuidSearchContext, good)
if a := AnswerSetuidSearch(check, handle, body); a.Said != "a new search starts" || len(asked) != 1 || asked[0] != "jo" {
t.Fatalf("the good ask: %+v, %v", a, asked)
}
if a := AnswerSetuidSearch(check, handle, body); !strings.Contains(a.Refused, "taken already") {
t.Fatalf("the same ask again: %+v", a)
}
if a := AnswerSetuidSearch(check, nil, body); a.Refused == "" {
t.Fatalf("no handler: %+v", a)
}
}