Files
mesh-host/internal/link/access_act_test.go
T
jschoubben aad33dfe5c Give the controller the contract it can fill (hq ADR 0325, issue 567)
The controller cannot know the operator's account's uid, names an access by its
declared id (which holds a slash), and should not parse words. A tool runner's
account may now come by name alone and is read here (refused when absent or root's,
its uid said in the access's listing); the take act matches the declared id exactly;
an access wait says its reason (keep or disk) and, for a filesystem that refuses
ACLs, whether the take act can enable them.
2026-10-12 19:51:09 +02:00

88 lines
4.0 KiB
Go

package link
import (
"crypto/ed25519"
"encoding/json"
"strings"
"testing"
"time"
)
// The take act's ask (novox/hq ADR 0325 §8): a hand-over's fields and the act's own in one signed object, under a
// signing context of its own. The controller's side holds the same names and words.
func TestTheTakeActsAskKeepsItsFieldNamesAndContext(t *testing.T) {
body, _ := json.Marshal(AccessActAsk{Act: ActTake, Access: "radarr.movies", Warrant: "w"})
if got := keysOf(t, body); got != "access act warrant" {
t.Fatalf("the act's fields are %q", got)
}
if AskAccessActSubject("ace") != "mesh.node.ace.ask.access-act" {
t.Fatalf("the subject is %q", AskAccessActSubject("ace"))
}
if AccessActContext != "novox-mesh access-act v2\n" {
t.Fatalf("the signing context is %q", AccessActContext)
}
}
// signedAct is an act's ask as the controller makes it: the hand-over's fields beside the act's, signed over the
// act's context and its bytes.
func signedAct(t *testing.T, key ed25519.PrivateKey, ask HandOverAsk, act AccessActAsk) []byte {
t.Helper()
raw, _ := json.Marshal(struct {
HandOverAsk
AccessActAsk
}{ask, act})
body, _ := json.Marshal(SignedHandOver{Ask: raw, Signature: ed25519.Sign(key, append([]byte(AccessActContext), raw...))})
return body
}
// **Nothing is done without a warrant**, nor for any act but take, nor signed as a hand-over (ADR 0325 §8).
func TestTheTakeActRunsOnlyOnAWarrant(t *testing.T) {
m, key := aMember(t)
now := time.Now()
var given []AccessAct
handle := func(a AccessAct) (string, error) { given = append(given, a); return "started", nil }
check := HandOverCheck{Signer: m.Signer, Node: m.Node, Nonces: &Nonces{}}
ask := func(nonce string) HandOverAsk {
return HandOverAsk{Node: m.Node, Path: "/srv/media/movies", By: "the operator", Expires: now.Add(HandOverGood),
Nonce: nonce}
}
for why, body := range map[string][]byte{
"no warrant": signedAct(t, key, ask(aNonce+"01"), AccessActAsk{Act: ActTake, Access: "radarr.movies"}),
"another act": signedAct(t, key, ask(aNonce+"02"), AccessActAsk{Act: "share", Access: "radarr.movies", Warrant: "w"}),
"no access": signedAct(t, key, ask(aNonce+"03"), AccessActAsk{Act: ActTake, Warrant: "w"}),
"a hand-over's": signedAsk(t, key, ask(aNonce+"04")),
"a slash in the warrant": signedAct(t, key, ask(aNonce+"05"), AccessActAsk{Act: ActTake, Access: "radarr.movies",
Warrant: "w/1"}),
"two lines as access": signedAct(t, key, ask(aNonce+"07"), AccessActAsk{Act: ActTake, Access: "radarr.\nmovies",
Warrant: "w"}),
} {
if a := AnswerAccessAct(check, handle, body); a.Refused == "" || !strings.HasSuffix(a.Refused, "nothing was changed") {
t.Errorf("%s: not refused, or refused without saying nothing was changed: %+v", why, a)
}
}
if len(given) != 0 {
t.Fatalf("a refused ask reached the act: %+v", given)
}
a := AnswerAccessAct(check, handle, signedAct(t, key, ask(aNonce+"06"), AccessActAsk{Act: ActTake,
Access: "radarr.movies", Warrant: "w-1"}))
if a.Said != "started" || len(given) != 1 || given[0].Warrant != "w-1" || given[0].Path != "/srv/media/movies" {
t.Fatalf("a warranted ask did not reach the act whole: %+v %+v", a, given)
}
}
// The controller names an access by its declared resource id exactly, which may hold a `/`: it reaches the act,
// which matches it against what was declared.
func TestATakeActNamesItsAccessByItsDeclaredId(t *testing.T) {
m, key := aMember(t)
var given []AccessAct
handle := func(a AccessAct) (string, error) { given = append(given, a); return "started", nil }
check := HandOverCheck{Signer: m.Signer, Node: m.Node, Nonces: &Nonces{}}
ask := HandOverAsk{Node: m.Node, Path: "/services/media/movies", By: "the operator",
Expires: time.Now().Add(HandOverGood), Nonce: aNonce + "08"}
id := "radarr.access-services/media/movies"
a := AnswerAccessAct(check, handle, signedAct(t, key, ask, AccessActAsk{Act: ActTake, Access: id, Warrant: "w-1"}))
if a.Refused != "" || len(given) != 1 || given[0].Access != id {
t.Fatalf("an access id with a slash did not reach the act as declared: %+v %+v", a, given)
}
}