mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
245 lines
10 KiB
Go
245 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
|
)
|
|
|
|
// aWant is a wanted account of a module on ace, as the grants part writes it.
|
|
func aWant(module string) buscompose.WantedAccount {
|
|
return buscompose.WantedAccount{User: "ace." + module, Kind: buscompose.KindModule, Node: "ace", Module: module,
|
|
URL: "nats://novox.mesh:4222", Fingerprint: "ab:cd", Claims: []buscompose.ClaimedSeat{{Seat: "s", Scope: "node"}}}
|
|
}
|
|
|
|
func wantRaw(t *testing.T, w buscompose.WantedAccount) []byte {
|
|
t.Helper()
|
|
raw, err := json.Marshal(w)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
// mintingVault is a vault with a state of bus accounts, ace's sealing key stated, ace's modules assigned, and minting
|
|
// turned on or off as on says.
|
|
func mintingVault(t *testing.T, on bool) (testVault, *fakeAccounts, string) {
|
|
t.Helper()
|
|
plans := assignments{"ace.mod-x": `{"node":"ace","module":"mod-x"}`, "ace.mod-y": `{"node":"ace","module":"mod-y"}`}
|
|
v := newVault(t, nil)
|
|
v.Askers = Declared{Read: plans.read, Store: v.Store}
|
|
accounts := &fakeAccounts{}
|
|
v.Accounts = accounts
|
|
pub, priv := aSealingKey(t)
|
|
if err := v.recordNodeKey("ace", pub, "node.ace"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if on {
|
|
v.LeaseHeard([]byte(`{"handed-to":"the operator's hand-over"}`), true, nil)
|
|
} else {
|
|
v.LeaseHeard([]byte(`{"instance":"novox/1/2026-10-27","epoch":57}`), true, nil)
|
|
}
|
|
return v, accounts, priv
|
|
}
|
|
|
|
// 774/AC7 (novox/hq ADR 0561 §1, §2, §4): a want with no value, minting on, is minted once: kept whole in the controller's
|
|
// shape as the module's broker, its hash in bus-accounts, said account-issued before secret-changed, and answered by own
|
|
// sealed to the node; a value held or a hash adopted is never minted.
|
|
func TestTheVaultMintsAWantedAccountOnceAndNothingItHolds(t *testing.T) {
|
|
v, accounts, nodePriv := mintingVault(t, true)
|
|
w := aWant("mod-x")
|
|
before := len(v.bus.events)
|
|
v.WantHeard(w.User, wantRaw(t, w), false)
|
|
rec, held, err := v.Store.Read("own/ace/mod-x/broker")
|
|
if err != nil || !held || rec.Generation != 1 || rec.Origin != OriginMade {
|
|
t.Fatalf("the minted account is kept as %+v (held %v, %v)", rec, held, err)
|
|
}
|
|
value, err := seal.Open(rec.AtRest, v.Own.PrivateKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got struct{ Password string }
|
|
_ = json.Unmarshal([]byte(value), &got)
|
|
shape, _ := buscompose.HeldCredential(w, got.Password)
|
|
if len(got.Password) != 40 || value != string(shape) {
|
|
t.Errorf("the minted value is %q, not the controller's shape %q", value, shape)
|
|
}
|
|
if !accounts.hashOf(w.User, got.Password, 1) {
|
|
t.Errorf("%s's hash is not the minted password's: %+v", w.User, accounts.keys[w.User])
|
|
}
|
|
var order []string
|
|
for _, e := range v.bus.events[before:] {
|
|
order = append(order, e["event"].(string))
|
|
}
|
|
if strings.Join(order, ",") != AccountHashedEvent+","+AccountIssuedEvent+","+ChangedEvent {
|
|
t.Errorf("said %v, want the hash, account-issued, then secret-changed", order)
|
|
}
|
|
for _, raw := range v.bus.raw[before:] {
|
|
if strings.Contains(raw, got.Password) || strings.Contains(raw, "$2a$") {
|
|
t.Errorf("a value or a hash was said: %s", raw)
|
|
}
|
|
}
|
|
own, err := v.OwnOfNode("node.ace")
|
|
if err != nil || len(own.Own) != 1 {
|
|
t.Fatalf("own answered %+v, %v", own, err)
|
|
}
|
|
if opened, err := seal.Open(own.Own[0].Sealed, nodePriv); err != nil || opened != value {
|
|
t.Errorf("own's sealed value opens to %q, %v", opened, err)
|
|
}
|
|
|
|
// Heard again, or for a value held, or for a hash adopted: nothing is minted, nothing is held back.
|
|
v.WantHeard(w.User, wantRaw(t, w), false)
|
|
if rec, _, _ := v.Store.Read("own/ace/mod-x/broker"); rec.Generation != 1 {
|
|
t.Errorf("a want heard again minted generation %d", rec.Generation)
|
|
}
|
|
accounts.keys["ace.mod-y"] = AccountHash{User: "ace.mod-y", Hash: "$2a$04$adopted", Adopted: true}
|
|
y := aWant("mod-y")
|
|
v.WantHeard(y.User, wantRaw(t, y), false)
|
|
if _, held, _ := v.Store.Read("own/ace/mod-y/broker"); held || accounts.keys["ace.mod-y"].Hash != "$2a$04$adopted" {
|
|
t.Errorf("an adopted user was minted")
|
|
}
|
|
if st := v.MintState(); len(st.HeldBack) != 0 || len(st.Minted) != 1 {
|
|
t.Errorf("vault_status says %+v: an adopted user is not held back, one account minted", st)
|
|
}
|
|
}
|
|
|
|
// 774/AC8 (novox/hq ADR 0561 §3): while the controller holds the lease nothing is minted and vault_status says why and
|
|
// names each wanted account held back (never an adopted one); the write that hands the lease away mints them; a lease
|
|
// handed back, expired or unreadable turns minting off again, read afresh before every mint.
|
|
func TestNothingIsMintedUntilTheLeaseIsHandedAway(t *testing.T) {
|
|
v, accounts, _ := mintingVault(t, false)
|
|
accounts.keys = map[string]AccountHash{"ace.mod-y": {User: "ace.mod-y", Hash: "$2a$04$adopted", Adopted: true}}
|
|
for _, m := range []string{"mod-x", "mod-y"} {
|
|
w := aWant(m)
|
|
v.WantHeard(w.User, wantRaw(t, w), false)
|
|
}
|
|
st := v.MintState()
|
|
if st.On || !strings.Contains(st.Why, "controller holds the lease") || len(st.HeldBack) != 1 ||
|
|
!strings.Contains(st.HeldBack["ace.mod-x"], "controller holds the lease") {
|
|
t.Fatalf("under the controller's lease vault_status says %+v", st)
|
|
}
|
|
if _, held, _ := v.Store.Read("own/ace/mod-x/broker"); held {
|
|
t.Fatal("minted while the controller holds the lease")
|
|
}
|
|
// Handed away: the wanted account is minted at that write, the adopted one is not.
|
|
lease := []byte(`{"handed-to":"the operator's hand-over"}`)
|
|
v.LeaseRead = func() ([]byte, bool, error) { return lease, true, nil }
|
|
v.LeaseHeard(lease, true, nil)
|
|
if _, held, _ := v.Store.Read("own/ace/mod-x/broker"); !held {
|
|
t.Fatalf("not minted at the lease write: %+v", v.MintState())
|
|
}
|
|
if _, held, _ := v.Store.Read("own/ace/mod-y/broker"); held {
|
|
t.Error("an adopted account was minted at the lease write")
|
|
}
|
|
if st := v.MintState(); !st.On || len(st.HeldBack) != 0 {
|
|
t.Errorf("after the lease write vault_status says %+v", st)
|
|
}
|
|
// The key expired (no watch hears a key's age pass): read afresh before the next mint, nothing is minted.
|
|
v.LeaseRead = func() ([]byte, bool, error) { return nil, false, nil }
|
|
z := aWant("mod-z")
|
|
v.WantHeard(z.User, wantRaw(t, z), false)
|
|
if _, held, _ := v.Store.Read("own/ace/mod-z/broker"); held {
|
|
t.Error("minted after the lease's key expired")
|
|
}
|
|
if st := v.MintState(); st.On || !strings.Contains(st.HeldBack["ace.mod-z"], "no lease is written") {
|
|
t.Errorf("after the key expired vault_status says %+v", st)
|
|
}
|
|
}
|
|
|
|
// 774/AC10 issue-account (novox/hq ADR 0561 §9): the verb mints one wanted user now, read afresh; it is refused while
|
|
// minting is off, for a node's account and the controller's, and for a user nobody wants; a user held already is said so.
|
|
func TestIssueAccountMintsOneWantedUserNowAndRefusesTheRest(t *testing.T) {
|
|
v, _, _ := mintingVault(t, false)
|
|
w := aWant("mod-x")
|
|
raw := wantRaw(t, w)
|
|
v.WantRead = func(user string) ([]byte, bool, error) {
|
|
if user == w.User {
|
|
return raw, true, nil
|
|
}
|
|
return nil, false, nil
|
|
}
|
|
for user, says := range map[string]string{
|
|
"ace.mod-x": "controller holds the lease",
|
|
"node.ace": "never minted",
|
|
"controller": "never minted",
|
|
} {
|
|
if _, err := v.IssueAccount("person.jochen", user); err == nil || !strings.Contains(err.Error(), says) {
|
|
t.Errorf("%s: %v, want a refusal saying %q", user, err, says)
|
|
}
|
|
}
|
|
v.LeaseHeard([]byte(`{"handed-to":"the operator's hand-over"}`), true, nil)
|
|
if _, err := v.IssueAccount("person.jochen", "ace.nobody"); err == nil || !strings.Contains(err.Error(), "no wanted account") {
|
|
t.Errorf("a user nobody wants: %v", err)
|
|
}
|
|
got, err := v.IssueAccount("person.jochen", w.User)
|
|
if err != nil || got.(map[string]any)["minted"] != true {
|
|
t.Fatalf("issue-account: %v, %v", got, err)
|
|
}
|
|
if again, err := v.IssueAccount("person.jochen", w.User); err != nil || again.(map[string]any)["minted"] != false ||
|
|
!strings.Contains(again.(map[string]any)["why"].(string), "held already") {
|
|
t.Errorf("issue-account again: %v, %v", again, err)
|
|
}
|
|
}
|
|
|
|
// 774/AC10 the rotation's rule (novox/hq ADR 0561 §4): at most one rotation begins per revision of the loaded user list,
|
|
// over the second login `<user>--b`, never of a node's account, the controller's or an adopted hash, and `rotate` of a
|
|
// covered account stays refused, so nothing rotates in place.
|
|
func TestOneRotationBeginsPerRevisionOfTheLoadedList(t *testing.T) {
|
|
v, accounts, _ := mintingVault(t, true)
|
|
for _, m := range []string{"mod-x", "mod-y"} {
|
|
w := aWant(m)
|
|
v.WantHeard(w.User, wantRaw(t, w), false)
|
|
}
|
|
if _, err := v.BeginRotation("ace.mod-x"); err == nil || !strings.Contains(err.Error(), "not known yet") {
|
|
t.Errorf("began with no loaded revision known: %v", err)
|
|
}
|
|
v.LoadedListHeard(570)
|
|
r, err := v.BeginRotation("ace.mod-x")
|
|
if err != nil || r.Login != "ace.mod-x--b" || r.Revision != 570 || r.Generation != 2 || accounts.keys["ace.mod-x"].Rotating == nil {
|
|
t.Fatalf("the first rotation: %+v, %v", r, err)
|
|
}
|
|
if _, err := v.BeginRotation("ace.mod-y"); err == nil || !strings.Contains(err.Error(), "one rotation begins per revision") {
|
|
t.Errorf("a second rotation in one revision: %v", err)
|
|
}
|
|
v.LoadedListHeard(571)
|
|
if _, err := v.BeginRotation("ace.mod-y"); err != nil {
|
|
t.Errorf("the next revision's rotation: %v", err)
|
|
}
|
|
if _, err := v.BeginRotation("ace.mod-x"); err == nil || !strings.Contains(err.Error(), "rotating already") {
|
|
t.Errorf("an account rotating began again: %v", err)
|
|
}
|
|
accounts.keys["node.ace"] = AccountHash{User: "node.ace", Hash: "$2a$04$n", Adopted: true}
|
|
for _, u := range []string{"node.ace", "controller"} {
|
|
if _, err := v.BeginRotation(u); err == nil {
|
|
t.Errorf("%s began a rotation", u)
|
|
}
|
|
}
|
|
if _, err := v.RotateFor(ControllerCaller, "own/ace/mod-x/broker", Narrow{}, "the test", "a rotation in place"); err == nil {
|
|
t.Error("a covered account rotated in place")
|
|
}
|
|
}
|
|
|
|
// A mint that failed (here: no operator key yet, so nothing can be kept) is tried again at the pass, never lost.
|
|
func TestAFailedMintIsTriedAgainAtThePass(t *testing.T) {
|
|
v, accounts, _ := mintingVault(t, true)
|
|
key, _ := v.Store.OperatorKey()
|
|
if err := v.Store.setOperatorKey("", "the test", v.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w := aWant("mod-x")
|
|
v.WantHeard(w.User, wantRaw(t, w), false)
|
|
if _, ok := accounts.keys[w.User]; ok {
|
|
t.Fatal("minted with no operator key")
|
|
}
|
|
if err := v.Store.setOperatorKey(key, "the test", v.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
v.MintOwed()
|
|
if _, ok := accounts.keys[w.User]; !ok {
|
|
t.Errorf("not minted at the pass after the failure: %+v", v.MintState())
|
|
}
|
|
}
|