Merge branch 'b5/registry' into feat/registry-milestone
This commit is contained in:
@@ -2,6 +2,11 @@ module checks/readable
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
require (
|
||||
git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-catalog/modules/restic/backupgood v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
require git.novox.be/novox/mesh-contracts/go v0.0.0-20261104174344-ae5266e77639 // indirect
|
||||
replace git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings => ../../modules/disk-load/diskreadings
|
||||
|
||||
replace git.novox.be/novox/mesh-catalog/modules/restic/backupgood => ../../modules/restic/backupgood
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// the owner's manifest went with clause 5, and announcetest holds a module to its own list, not to its readers.
|
||||
//
|
||||
// An owner announces from its code. A Go module announces when its code imports mesh-sdk's go/announce: every
|
||||
// announce.State literal there is read, its Name a string, a constant of the package, or a constant of mesh-sdk this
|
||||
// announce.State literal there is read, its Name a string, a constant of the package, or a constant of an owner's package this
|
||||
// check knows (Known). A TypeScript module announces when its code says the event `states` or calls the SDK's keeps:
|
||||
// every `{ name: "...", readable: true }` there is read. An owner that announces nothing from code is read as before
|
||||
// on the mesh, and is not judged; neither is an owner outside the catalogue (another repository's).
|
||||
@@ -29,13 +29,15 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/health"
|
||||
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
|
||||
"git.novox.be/novox/mesh-catalog/modules/restic/backupgood"
|
||||
)
|
||||
|
||||
// Known are the mesh-sdk constants an announcement may name a state by, by the selector the code writes.
|
||||
// Known are the constants of the owners' published packages an announcement may name a state by, by the selector the
|
||||
// code writes: disk-load's readings and restic's last good night (ADR 0634 §4: a reading's layout is its owner's).
|
||||
var Known = map[string]string{
|
||||
"health.GoodState": health.GoodState,
|
||||
"health.ReadingsState": health.ReadingsState,
|
||||
"backupgood.GoodState": backupgood.GoodState,
|
||||
"diskreadings.ReadingsState": diskreadings.ReadingsState,
|
||||
}
|
||||
|
||||
const announcePkg = `"git.novox.be/novox/mesh-sdk/go/announce"`
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestFindings(t *testing.T) {
|
||||
{"go, a known mesh-sdk name and readers", map[string]string{
|
||||
"modules/r/module.json": `{"module":"r","reads":["o.good","o.s"]}`,
|
||||
"modules/o/module.json": `{"module":"o"}`,
|
||||
"modules/o/cmd/o/main.go": goOwner + "var _ = []announce.State{{Name: health.GoodState, Readable: true}, {Name: \"s\", Readers: []string{\"r\"}}}\n"}, ""},
|
||||
"modules/o/cmd/o/main.go": goOwner + "var _ = []announce.State{{Name: backupgood.GoodState, Readable: true}, {Name: \"s\", Readers: []string{\"r\"}}}\n"}, ""},
|
||||
{"go, a name that cannot be read", map[string]string{
|
||||
"modules/r/module.json": `{"module":"r","reads":["o.s"]}`,
|
||||
"modules/o/module.json": `{"module":"o"}`,
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||
)
|
||||
|
||||
// The holders whose states this module reads, one constant each (a reader names its holder once; ADR 0634 §4).
|
||||
@@ -13,38 +14,26 @@ const (
|
||||
indexerModule = "mesh-delivery-indexer"
|
||||
)
|
||||
|
||||
// ArtifactsGoneEvent is the event the seat mesh-artifact-sweep says when the store does not hold an artifact a
|
||||
// builder's key names. The seat is a seat-list seat, which has no generated types, so its two bodies are the holder's
|
||||
// own: this module is the one that says the event and answers the verb held.
|
||||
const ArtifactsGoneEvent = "artifacts-gone"
|
||||
// The seat mesh-artifact-sweep types the answer of its verb held and the body of its event artifacts-gone; this module
|
||||
// holds the seat, so it answers and says them in those types.
|
||||
type (
|
||||
// HeldAnswer is the answer of the seat's verb held.
|
||||
HeldAnswer = sweep.HeldAnswer
|
||||
// ArtifactsGone is the body of the seat's event artifacts-gone.
|
||||
ArtifactsGone = sweep.ArtifactsGone
|
||||
)
|
||||
|
||||
// HeldAnswer is the answer of the seat's verb held: the references the store holds, and those it answered 404 for. A
|
||||
// reference the store could not be asked about is in neither, and named in Unknown with why.
|
||||
type HeldAnswer struct {
|
||||
Store string `json:"store,omitempty"`
|
||||
Held []string `json:"held"`
|
||||
Gone []string `json:"gone"`
|
||||
Unknown map[string]string `json:"unknown,omitempty"`
|
||||
}
|
||||
// GoneSubject is where the seat says artifacts-gone.
|
||||
const GoneSubject = sweep.SubjectArtifactsGone
|
||||
|
||||
// ArtifactsGone is the body of ArtifactsGoneEvent: the module whose builder's key names a gone artifact, the key and
|
||||
// its fingerprint, and every reference of it the store does not hold.
|
||||
type ArtifactsGone struct {
|
||||
Event string `json:"event"`
|
||||
Module string `json:"module"`
|
||||
Key string `json:"key"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Gone []string `json:"gone"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// EncodeArtifactsGone is the event's body, the references sorted.
|
||||
// EncodeArtifactsGone is the event's body, the references sorted; it refuses one naming no module, no fingerprint or no
|
||||
// gone reference.
|
||||
func EncodeArtifactsGone(e ArtifactsGone) ([]byte, error) {
|
||||
e.Event = ArtifactsGoneEvent
|
||||
e.Event = sweep.EventArtifactsGone
|
||||
e.Gone = append([]string(nil), e.Gone...)
|
||||
sort.Strings(e.Gone)
|
||||
if e.Module == "" || e.Fingerprint == "" || len(e.Gone) == 0 {
|
||||
return nil, fmt.Errorf("%s names a module, a fingerprint and at least one gone reference", ArtifactsGoneEvent)
|
||||
return nil, fmt.Errorf("%s names a module, a fingerprint and at least one gone reference", sweep.EventArtifactsGone)
|
||||
}
|
||||
return json.Marshal(e)
|
||||
}
|
||||
|
||||
@@ -49,8 +49,6 @@ const (
|
||||
FindingsState = meshconditions.StateFindings
|
||||
// Module is this module, the observer its findings name.
|
||||
Module = "artifact-store-tools"
|
||||
// GoneSubject is where the seat says artifacts-gone.
|
||||
GoneSubject = "mesh.seat." + catalogue.ArtifactSweepSeat + ".event." + ArtifactsGoneEvent
|
||||
)
|
||||
|
||||
// The sweep's bounds, the controller's (cmd/mesh-controller/collect.go mostPerSweep, sweepBudget).
|
||||
@@ -628,7 +626,8 @@ func (h *Holder) sayGone(j *judged, refs []string, how string) {
|
||||
for _, b := range ref.Builds {
|
||||
g := byKey[b.Key]
|
||||
if g == nil {
|
||||
g = &ArtifactsGone{Module: b.Module, Key: b.Key, Fingerprint: b.FP, At: h.now().UTC()}
|
||||
at := h.now().UTC()
|
||||
g = &ArtifactsGone{Module: b.Module, Key: b.Key, Fingerprint: b.FP, At: &at}
|
||||
byKey[b.Key] = g
|
||||
keys = append(keys, b.Key)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ module artifactstoretools
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
@@ -22,6 +23,7 @@ require (
|
||||
require git.novox.be/novox/mesh-contracts/go v0.0.0-20261104174344-ae5266e77639 // indirect
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep v0.0.0-00010101000000-000000000000
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/klauspost/compress v1.20.1 // indirect
|
||||
|
||||
@@ -3,7 +3,7 @@ package holder
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/window"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
@@ -29,11 +29,11 @@ const (
|
||||
)
|
||||
|
||||
// Window is one window's record, kept in distribution's state windows: what registry_old_name and
|
||||
// registry_direct_writes answer. Its open, holding, since, closed and last holding are mesh-sdk's delivery.WindowHeld,
|
||||
// the part every window keeper shares and steps by one rule (delivery.StepWindow).
|
||||
// registry_direct_writes answer. Its open, holding, since, closed and last holding are mesh-delivery's sdk/window.WindowHeld,
|
||||
// the part every window keeper shares and steps by one rule (window.StepWindow).
|
||||
type Window struct {
|
||||
Name string `json:"name"`
|
||||
delivery.WindowHeld
|
||||
window.WindowHeld
|
||||
Dropped []DroppedNode `json:"dropped,omitempty"`
|
||||
// Why says, of each node holding the window, why it holds it: what its read answered, a node-engine whose
|
||||
// manifest-reads did not answer (an away node, an older node-engine, a timeout) included, so a timeout taken as
|
||||
@@ -134,7 +134,7 @@ func isDropped(dropped []DroppedNode, node string) bool {
|
||||
return slices.ContainsFunc(dropped, func(d DroppedNode) bool { return d.Node == node })
|
||||
}
|
||||
|
||||
// Step moves the window by mesh-sdk's one rule (delivery.StepWindow): named is every node the read named, dropped ones
|
||||
// Step moves the window by mesh-sdk's one rule (window.StepWindow): named is every node the read named, dropped ones
|
||||
// included, holding those holding the window at now. A read naming no node judges nothing. It answers the next record,
|
||||
// the closing event when it closed in this step (said once), and the nodes holding it past WindowFindingAfter (the
|
||||
// finding; cleared when it closes). A window once closed stays closed: a node that holds it later is no reason to serve
|
||||
@@ -144,7 +144,7 @@ func (w Window) Step(named, holding []string, now time.Time) (next Window, close
|
||||
if !w.Open && !w.Closed.IsZero() {
|
||||
return next, nil, nil
|
||||
}
|
||||
held, shut, overdue := delivery.StepWindow(w.WindowHeld, named, holding, now, WindowFindingAfter)
|
||||
held, shut, overdue := window.StepWindow(w.WindowHeld, named, holding, now, WindowFindingAfter)
|
||||
next.WindowHeld = held
|
||||
if !held.Open {
|
||||
next.Why = nil
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-sdk/go/testbus"
|
||||
|
||||
"gitea/internal/packageseats"
|
||||
@@ -118,7 +118,7 @@ func (p *player) handle(m wire) {
|
||||
}
|
||||
_ = json.Unmarshal(m.Params, &a)
|
||||
switch {
|
||||
case m.Method == "mesh/ask" && a.Key == delivery.BuilderVersionsAddress:
|
||||
case m.Method == "mesh/ask" && a.Key == builds.BuilderVersionsAddress:
|
||||
var q struct{ Name, Version, Digest string }
|
||||
_ = json.Unmarshal(a.Body, &q)
|
||||
d, ok := p.numbered[q.Name+"@"+q.Version]
|
||||
|
||||
@@ -126,7 +126,7 @@ type Version struct {
|
||||
Published time.Time
|
||||
}
|
||||
|
||||
// Builder answers whether the builder numbered a version with a digest (mesh-sdk delivery.BuilderVersions).
|
||||
// Builder answers whether the builder numbered a version with a digest (the builder package's builds.BuilderVersions).
|
||||
type Builder interface {
|
||||
Recorded(name, version, digest string) (ok bool, why string, err error)
|
||||
}
|
||||
|
||||
@@ -580,7 +580,7 @@ func (c *Config) propose(call Call, action string) (Proposal, oc.Ask, []string,
|
||||
}
|
||||
trusted = false
|
||||
k, kept := s.Kept[keptKey(call.Pull, level.Node, level.Module, call.Key)]
|
||||
was, had = Value{Value: k.Value}, kept
|
||||
was, had = Value{Value: heldValue(k)}, kept
|
||||
} else {
|
||||
var derr error
|
||||
r, trusted, derr = c.declared(s, level, call.Key)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -61,32 +62,25 @@ type PullDecl struct {
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// KeptValue is an approved held value, kept apart from the values mesh-config resolves, in this module's own layout
|
||||
// (the owner's: its state `kept`), which the checker and the assigner's composition check read (novox/hq ADR 0565 §5).
|
||||
type KeptValue struct {
|
||||
Pull string `json:"pull"`
|
||||
Head string `json:"head"`
|
||||
Digest string `json:"declaration"`
|
||||
Node string `json:"node"`
|
||||
Module string `json:"module"`
|
||||
Key string `json:"key"`
|
||||
// Value is the value held, as the operator approved it.
|
||||
Value json.RawMessage `json:"value"`
|
||||
By string `json:"by"`
|
||||
Via string `json:"via"`
|
||||
Why string `json:"why"`
|
||||
// Approved is who approved it, as the proposal kept it.
|
||||
Approved string `json:"approved"`
|
||||
Proposal string `json:"proposal"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
// KeptValue is an approved held value, kept apart from the values mesh-config resolves, in the layout of the seat's state
|
||||
// `kept`, which the checker and the assigner's composition check read (novox/hq ADR 0565 §5, ADR 0634 §4).
|
||||
type KeptValue = meshconfigclient.Kept
|
||||
|
||||
// keptKey is a held value's key in `kept`: the pull request's key form (pullKey), the node, the module and the config
|
||||
// key, joined by dots.
|
||||
func keptKey(pull, node, module, key string) string {
|
||||
return pullKey(pull) + "." + node + "." + module + "." + key
|
||||
k, _ := meshconfigclient.HeldValueKey(pull, node, module, key) // a held value of no pull request has no key, and is found by none
|
||||
return k
|
||||
}
|
||||
|
||||
// heldValue is the value held, as the operator approved it, in its JSON form.
|
||||
func heldValue(k KeptValue) json.RawMessage {
|
||||
raw, _ := meshconfigclient.ValueOf(k) // a value that was read from JSON writes as JSON
|
||||
return raw
|
||||
}
|
||||
|
||||
func heldAt(t time.Time) *time.Time { return &t }
|
||||
|
||||
func keptStateKey(k KeptValue) string { return keptKey(k.Pull, k.Node, k.Module, k.Key) }
|
||||
|
||||
func keptLevel(k KeptValue) Level { return Level{Kind: LevelModule, Node: k.Node, Module: k.Module} }
|
||||
@@ -123,9 +117,9 @@ func (c *Config) readPulls(s *Sources) error {
|
||||
return fmt.Errorf("the pull requests' declarations cannot be read: %w", err)
|
||||
}
|
||||
if err := c.each(KeptState, func(key string, raw []byte) error {
|
||||
var k KeptValue
|
||||
if err := json.Unmarshal(raw, &k); err != nil || k.Pull == "" {
|
||||
return nil
|
||||
k, err := meshconfigclient.DecodeKept(key, raw)
|
||||
if err != nil {
|
||||
return nil // not a held value of the seat's layout under its own key
|
||||
}
|
||||
s.Kept[key] = k
|
||||
return nil
|
||||
@@ -176,11 +170,11 @@ func (c *Config) declarePull(pull, head string, declares map[string]map[string]D
|
||||
switch {
|
||||
case !declared:
|
||||
why = "the head no longer adds or changes its declaration"
|
||||
case declDigest(d) != kv.Digest:
|
||||
case declDigest(d) != kv.Declaration:
|
||||
why = "the head declares the key otherwise than the value was approved against"
|
||||
default:
|
||||
var v any
|
||||
_ = json.Unmarshal(kv.Value, &v)
|
||||
_ = json.Unmarshal(heldValue(kv), &v)
|
||||
if p := d.rule().check(v); len(p) > 0 {
|
||||
why = "the value no longer passes: " + strings.Join(p, "; ")
|
||||
}
|
||||
@@ -208,13 +202,13 @@ func (c *Config) declarePull(pull, head string, declares map[string]map[string]D
|
||||
// config-changed; what could not be done is answered, not dropped.
|
||||
func (c *Config) heldChange(kv KeptValue, action, why, approved string) []string {
|
||||
level := keptLevel(kv)
|
||||
ch := Change{Action: action, Level: &level, Key: kv.Key, Value: kv.Value, By: kv.By, Via: kv.Via, Why: why,
|
||||
ch := Change{Action: action, Level: &level, Key: kv.Key, Value: heldValue(kv), By: kv.By, Via: kv.Via, Why: why,
|
||||
Approved: approved, Pull: kv.Pull, Head: kv.Head, At: c.now(), Moved: []Moved{}}
|
||||
unsaid := c.record(level.ValueKey(kv.Key), ch)
|
||||
cause := "a value held for " + kv.Pull
|
||||
what := fmt.Sprintf("config %s %s of %s for %s, held for %s", action, kv.Key, kv.Module, kv.Node, kv.Pull)
|
||||
if action != ActionDrop {
|
||||
what += " (" + string(kv.Value) + ")"
|
||||
what += " (" + string(heldValue(kv)) + ")"
|
||||
}
|
||||
if err := c.Store.HandAct(what, why, cause); err != nil {
|
||||
unsaid = append(unsaid, "the hand-act log does not hold it: "+err.Error())
|
||||
@@ -260,8 +254,8 @@ type HeldSkipped struct {
|
||||
}
|
||||
|
||||
func heldValueOf(k KeptValue) HeldValue {
|
||||
return HeldValue{Pull: k.Pull, Head: k.Head, Node: k.Node, Module: k.Module, Key: k.Key, Value: compact(k.Value),
|
||||
Approved: k.Approved, By: k.By, Why: k.Why, At: k.At}
|
||||
return HeldValue{Pull: k.Pull, Head: k.Head, Node: k.Node, Module: k.Module, Key: k.Key, Value: compact(heldValue(k)),
|
||||
Approved: k.Approved, By: k.By, Why: k.Why, At: timeOf(k.At)}
|
||||
}
|
||||
|
||||
// Held answers the approved held values for the pull requests asked, at the heads asked, to the controller alone: a
|
||||
@@ -373,13 +367,13 @@ func (c *Config) keep(p Proposal) (string, error) {
|
||||
"propose it again", p.Pull, p.Key)
|
||||
}
|
||||
was, had := s.Kept[keptKey(p.Pull, p.Level.Node, p.Level.Module, p.Key)]
|
||||
if had != p.HadValue || valueDigest(was.Value, had) != p.WasDigest {
|
||||
if had != p.HadValue || valueDigest(heldValue(was), had) != p.WasDigest {
|
||||
return "", fmt.Errorf("the value held for %s changed since the operator was shown the change: propose it "+
|
||||
"again", p.Pull)
|
||||
}
|
||||
kv := KeptValue{Pull: p.Pull, Head: pd.Head, Digest: p.DeclDigest, Node: p.Level.Node, Module: p.Level.Module,
|
||||
kv := KeptValue{Pull: p.Pull, Head: pd.Head, Declaration: p.DeclDigest, Node: p.Level.Node, Module: p.Level.Module,
|
||||
Key: p.Key, Value: p.Value, By: "the operator", Via: p.From, Why: p.Why, Approved: p.Approved, Proposal: p.ID,
|
||||
At: c.now()}
|
||||
At: heldAt(c.now())}
|
||||
if err := c.Store.Put(KeptState, keptStateKey(kv), kv); err != nil {
|
||||
return "", fmt.Errorf("the value was not kept: %v", err)
|
||||
}
|
||||
@@ -434,7 +428,7 @@ func (c *Config) settlePull(pull string, merged bool, head string) []string {
|
||||
why = "the merged head is not known, so the value cannot be held to it"
|
||||
case !strings.EqualFold(head, kv.Head):
|
||||
why = fmt.Sprintf("it is held at head %s and the pull request merged at %s", short(kv.Head), short(head))
|
||||
case !known || !declared || declDigest(d) != kv.Digest:
|
||||
case !known || !declared || declDigest(d) != kv.Declaration:
|
||||
why = "the merged head does not declare the key as the operator was shown"
|
||||
}
|
||||
if why != "" {
|
||||
@@ -444,7 +438,7 @@ func (c *Config) settlePull(pull string, merged bool, head string) []string {
|
||||
continue
|
||||
}
|
||||
vk := keptLevel(kv).ValueKey(kv.Key)
|
||||
v := Value{Value: compact(kv.Value), By: kv.By, Via: kv.Via, Why: kv.Why + " (held for " + kv.Pull +
|
||||
v := Value{Value: compact(heldValue(kv)), By: kv.By, Via: kv.Via, Why: kv.Why + " (held for " + kv.Pull +
|
||||
", merged)", Approved: kv.Approved, At: c.now()}
|
||||
if err := c.Store.Put(ValuesState, vk, v); err != nil {
|
||||
said = append(said, fmt.Sprintf("%s of %s on %s held for %s is not written yet: %v; it is tried "+
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -317,8 +318,11 @@ func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *tes
|
||||
holdIt(t, c, m, r)
|
||||
approved := func() string {
|
||||
raw, _ := m.Get(KeptState, "novox.mesh-catalog.434.ace.n8n.media-gid")
|
||||
var k KeptValue
|
||||
_ = json.Unmarshal(raw.Value, &k)
|
||||
// the state kept is the seat's layout, under the seat's key: the checker and the assigner read it with this
|
||||
k, err := meshconfigclient.DecodeKept("novox.mesh-catalog.434.ace.n8n.media-gid", raw.Value)
|
||||
if err != nil || string(heldValue(k)) != "2000" || k.Declaration == "" || k.At == nil {
|
||||
t.Fatalf("the held value is not the seat's: %+v, %v", k, err)
|
||||
}
|
||||
return k.Approved
|
||||
}()
|
||||
m.events, m.handActs = nil, nil
|
||||
|
||||
@@ -3,6 +3,7 @@ module mesh-config
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
@@ -21,6 +22,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go v0.0.0-20261104174344-ae5266e77639 // indirect
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
@@ -76,10 +77,10 @@ func NewChecker() *Checker {
|
||||
// self-check's own (first-view-unanswered), written into its state findings.
|
||||
func newViews() *views.Reader {
|
||||
r := &views.Reader{Module: Module, Logf: logf, Findings: meshconditionsclient.FindingWriter{State: stdio.State("findings")}, Acts: meshhandactsclient.Recorder{Ask: stdio.Ask}}
|
||||
r.Follow(delivery.AssignerModule, delivery.PlacementState, PlacementTool)
|
||||
r.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool)
|
||||
r.Follow(delivery.IndexerModule, "modules", ModulesTool)
|
||||
r.Follow(delivery.IndexerModule, "manifests", ManifestsTool)
|
||||
r.Follow(assignerclient.Module, placing.StatePlacement, PlacementTool)
|
||||
r.Follow(assignerclient.Module, placing.StateSeatHolders, SeatHoldersTool)
|
||||
r.Follow(indexerModule, "modules", ModulesTool)
|
||||
r.Follow(indexerModule, "manifests", ManifestsTool)
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,14 @@ import (
|
||||
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
|
||||
"git.novox.be/novox/mesh-catalog/modules/restic/backupgood"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The states D13 reads (declared under `reads`).
|
||||
const (
|
||||
ReadingsRead = diskreadings.ReadingsRead
|
||||
GoodRead = backupgood.GoodRead
|
||||
ModulesRead = delivery.IndexerModule + ".modules"
|
||||
ManifestsRead = delivery.IndexerModule + ".manifests"
|
||||
ModulesRead = indexerModule + ".modules"
|
||||
ManifestsRead = indexerModule + ".manifests"
|
||||
)
|
||||
|
||||
// The kinds D13 raises.
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"net"
|
||||
"regexp"
|
||||
"slices"
|
||||
@@ -18,22 +21,21 @@ import (
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The states the machine probes read (declared under `reads`), and the verb D8 asks each node.
|
||||
const (
|
||||
PlacementRead = delivery.AssignerModule + "." + "placement"
|
||||
SeatHoldersRead = delivery.SeatHoldersRead
|
||||
PresenceRead = delivery.WatchmanModule + ".presence"
|
||||
PlacementRead = assignerclient.Module + "." + "placement"
|
||||
SeatHoldersRead = assignerclient.SeatHoldersRead
|
||||
PresenceRead = watchmanModule + ".presence"
|
||||
BannedSeat = "node-intrusion-prevention"
|
||||
BannedVerb = "seat:" + BannedSeat + ".banned"
|
||||
ScopeMachine = meshconditionsclient.ScopeMachine
|
||||
)
|
||||
|
||||
// placements is every node's placement, by node, from the state's keys in either form: a node key and its module keys,
|
||||
// or the form before, `placement.<node>` (delivery.PlacementsOf; research 072 row 37).
|
||||
func (c *Checker) placements() (map[string]delivery.Placement, error) {
|
||||
// or the form before, `placement.<node>` (assignerclient.PlacementsOf; research 072 row 37).
|
||||
func (c *Checker) placements() (map[string]placing.Placement, error) {
|
||||
keys, err := c.Store.Keys(PlacementRead)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the placement cannot be read: %w", err)
|
||||
@@ -48,7 +50,7 @@ func (c *Checker) placements() (map[string]delivery.Placement, error) {
|
||||
values[k] = raw
|
||||
}
|
||||
}
|
||||
out, err := delivery.PlacementsOf(values)
|
||||
out, err := assignerclient.PlacementsOf(values)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the placement is not readable: %w", err)
|
||||
}
|
||||
@@ -63,15 +65,15 @@ func (c *Checker) heard() (map[string]bool, error) {
|
||||
}
|
||||
out := map[string]bool{}
|
||||
for _, k := range keys {
|
||||
node, _, _, err := delivery.ParsePresenceKey(k)
|
||||
if err != nil || node == "" {
|
||||
node, ok := watchdogs.ParsePresenceByNodeKey(k)
|
||||
if !ok || node == "" {
|
||||
continue
|
||||
}
|
||||
raw, ok, err := c.Store.Get(PresenceRead, k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the presence of %s cannot be read: %w", node, err)
|
||||
}
|
||||
var p delivery.Presence
|
||||
var p watchdogs.PresenceByNodeValue
|
||||
if ok && json.Unmarshal(raw, &p) == nil && p.Online {
|
||||
out[node] = true
|
||||
}
|
||||
@@ -94,7 +96,7 @@ func (c *Checker) holdersOf(seat string) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the holders of %s cannot be read: %w", k, err)
|
||||
}
|
||||
var sh delivery.SeatHolders
|
||||
var sh placing.SeatHolders
|
||||
if !ok || json.Unmarshal(raw, &sh) != nil || sh.Seat != seat || sh.Node == "" || len(sh.Holders) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -214,3 +216,10 @@ func probeBans(ctx context.Context, c *Checker) ([]Observation, error) {
|
||||
}
|
||||
return sorted(out), nil
|
||||
}
|
||||
|
||||
// The holders whose states the probes read, one constant each (a reader names its holder once; ADR 0634 §4).
|
||||
const (
|
||||
watchmanModule = "mesh-delivery-watchman"
|
||||
indexerModule = "mesh-delivery-indexer"
|
||||
nodeEngineModule = "mesh-host"
|
||||
)
|
||||
|
||||
@@ -9,18 +9,19 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The engine's words and bounds the judgement reads (mesh-host rootsearch).
|
||||
const (
|
||||
// HealthRead is the node-engines' health reports.
|
||||
HealthRead = delivery.NodeHealthRead
|
||||
HealthRead = (nodeEngineModule + "." + nodeseat.StateHealth)
|
||||
// rootPending starts a verdict's reason while the engine's setuid search runs and no complete one judges.
|
||||
rootPending = "not judged yet (search running)"
|
||||
// rootQuiet is how long nothing is raised while an agent account waits for that search, counted from when this
|
||||
@@ -32,17 +33,17 @@ const (
|
||||
)
|
||||
|
||||
// report is a node's health report, and whether it has one.
|
||||
func (c *Checker) report(node string) (delivery.NodeHealth, bool, error) {
|
||||
func (c *Checker) report(node string) (nodeseat.HealthByNodeValue, bool, error) {
|
||||
raw, ok, err := c.Store.Get(HealthRead, node)
|
||||
if err != nil {
|
||||
return delivery.NodeHealth{}, false, fmt.Errorf("the health report of %s cannot be read: %w", node, err)
|
||||
return nodeseat.HealthByNodeValue{}, false, fmt.Errorf("the health report of %s cannot be read: %w", node, err)
|
||||
}
|
||||
if !ok {
|
||||
return delivery.NodeHealth{}, false, nil
|
||||
return nodeseat.HealthByNodeValue{}, false, nil
|
||||
}
|
||||
h, err := delivery.DecodeNodeHealth(raw)
|
||||
h, err := engineclient.DecodeHealth(raw)
|
||||
if err != nil {
|
||||
return delivery.NodeHealth{}, false, fmt.Errorf("the health report of %s: %w", node, err)
|
||||
return nodeseat.HealthByNodeValue{}, false, fmt.Errorf("the health report of %s: %w", node, err)
|
||||
}
|
||||
return h, true, nil
|
||||
}
|
||||
@@ -83,7 +84,7 @@ const (
|
||||
|
||||
// judgedConfined is the controller's judgement over one machine's report: confined only when the machine is heard
|
||||
// from, its engine judges root, and every verdict on the agent account is healthy. why is said either way.
|
||||
func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool, string) {
|
||||
func judgedConfined(agent string, h nodeseat.HealthByNodeValue, had, heard bool) (bool, string) {
|
||||
switch {
|
||||
case !had:
|
||||
return false, fmt.Sprintf("the agent account %s is not judged: the machine's node-engine has stated "+
|
||||
@@ -91,10 +92,10 @@ func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool,
|
||||
case !heard:
|
||||
return false, fmt.Sprintf("the agent account %s is not judged: the machine is not heard from, and a verdict "+
|
||||
"from before is not a verdict on now", agent)
|
||||
case h.Contract < delivery.RootContract:
|
||||
case h.Contract < engineclient.RootContract:
|
||||
return false, fmt.Sprintf("the agent account %s is not judged: the machine's node-engine is older than "+
|
||||
"the judging of an account's root (its statement's contract is %d, the judging is %d)",
|
||||
agent, h.Contract, delivery.RootContract)
|
||||
agent, h.Contract, engineclient.RootContract)
|
||||
}
|
||||
verdicts := verdictsOn(agent, h)
|
||||
if len(verdicts) == 0 {
|
||||
@@ -118,8 +119,8 @@ func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool,
|
||||
}
|
||||
|
||||
// verdictsOn is every verdict on the account, sorted by module and resource.
|
||||
func verdictsOn(agent string, h delivery.NodeHealth) []delivery.RootVerdict {
|
||||
var out []delivery.RootVerdict
|
||||
func verdictsOn(agent string, h nodeseat.HealthByNodeValue) []nodeseat.RootAccountVerdict {
|
||||
var out []nodeseat.RootAccountVerdict
|
||||
for _, v := range h.Root {
|
||||
if v.Account == agent {
|
||||
out = append(out, v)
|
||||
@@ -131,8 +132,8 @@ func verdictsOn(agent string, h delivery.NodeHealth) []delivery.RootVerdict {
|
||||
|
||||
// rootVerdictKind reads a report for the agent account: pending when every verdict on it is healthy or waits for the
|
||||
// setuid search, at least one of them that; complete when every verdict is healthy or one found a way to root.
|
||||
func rootVerdictKind(agent string, h delivery.NodeHealth, had, heard bool) int {
|
||||
if !had || !heard || h.Contract < delivery.RootContract {
|
||||
func rootVerdictKind(agent string, h nodeseat.HealthByNodeValue, had, heard bool) int {
|
||||
if !had || !heard || h.Contract < engineclient.RootContract {
|
||||
return verdictOther
|
||||
}
|
||||
pending, any := false, false
|
||||
@@ -159,7 +160,7 @@ func rootVerdictKind(agent string, h delivery.NodeHealth, had, heard bool) int {
|
||||
|
||||
// agentQuiet says the one thing keeping a machine's agent account unjudged is the engine's setuid search, first seen
|
||||
// waiting less than rootQuiet ago.
|
||||
func (c *Checker) agentQuiet(node, agent string, h delivery.NodeHealth, had, heard bool) bool {
|
||||
func (c *Checker) agentQuiet(node, agent string, h nodeseat.HealthByNodeValue, had, heard bool) bool {
|
||||
switch rootVerdictKind(agent, h, had, heard) {
|
||||
case verdictComplete:
|
||||
c.pending.judged(node)
|
||||
@@ -186,7 +187,7 @@ type confinement struct {
|
||||
}
|
||||
|
||||
// confined judges node's agent account from its placement, its report and its presence.
|
||||
func (c *Checker) confined(node string, place delivery.Placement, heard bool) (confinement, error) {
|
||||
func (c *Checker) confined(node string, place placing.Placement, heard bool) (confinement, error) {
|
||||
agent := place.Decided.AgentAccount
|
||||
if agent == "" {
|
||||
return confinement{Why: fmt.Sprintf("%s names no agent account: agents run as the operator account (%s)",
|
||||
|
||||
@@ -4,6 +4,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -13,7 +20,6 @@ import (
|
||||
"git.novox.be/novox/mesh-catalog/modules/restic/backupgood"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// bus is the bus module's answers as a probe asks them: every mesh stream and consumer as defined, unless changed.
|
||||
@@ -257,42 +263,42 @@ func machines() *memStore {
|
||||
node, address, endpoint string
|
||||
online bool
|
||||
}{{"novox", "10.10.0.1/16", "203.0.113.5:51820", true}, {"ace", "10.10.0.2/16", "", true}, {"g14", "10.10.0.3/16", "", false}} {
|
||||
pl := delivery.Placement{V: 1, Node: p.node,
|
||||
Decided: delivery.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}}
|
||||
pl := placing.Placement{V: 1, Node: p.node,
|
||||
Decided: placing.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}}
|
||||
if p.node == "ace" {
|
||||
pl.Modules = []delivery.PlacedModule{{Module: "sshd"}} // a module key beside the node key
|
||||
pl.Modules = []placing.PlacedModule{{Module: "sshd"}} // a module key beside the node key
|
||||
}
|
||||
place(s, pl)
|
||||
_ = s.Put(PresenceRead, p.node, delivery.Presence{V: 1, Online: p.online})
|
||||
key, _ := delivery.SeatHoldersKey(BannedSeat, p.node)
|
||||
_ = s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node,
|
||||
Holders: []delivery.SeatHolder{{Module: "fail2ban", Node: p.node}}})
|
||||
_ = s.Put(PresenceRead, p.node, watchdogs.PresenceByNodeValue{V: 1, Online: p.online})
|
||||
key, _ := assignerclient.SeatHoldersKey(BannedSeat, p.node)
|
||||
_ = s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node,
|
||||
Holders: []placing.SeatHoldersHoldersItem{{Module: "fail2ban", Node: p.node}}})
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// place writes a node's placement as the seat does, its node key and a key per module (research 072 row 37); g14's in
|
||||
// the form before, `placement.g14`, which the probes still read while the change is delivered.
|
||||
func place(s *memStore, p delivery.Placement) {
|
||||
func place(s *memStore, p placing.Placement) {
|
||||
if p.Node == "g14" {
|
||||
raw, _ := delivery.EncodePlacement(p)
|
||||
raw, _ := assignerclient.EncodePlacement(p)
|
||||
_ = s.Put(PlacementRead, "placement.g14", json.RawMessage(raw))
|
||||
return
|
||||
}
|
||||
values, _ := delivery.PlacementValues(p)
|
||||
values, _ := assignerclient.PlacementValues(p)
|
||||
for k, raw := range values {
|
||||
_ = s.Put(PlacementRead, k, json.RawMessage(raw))
|
||||
}
|
||||
}
|
||||
|
||||
// placed is every node's placement the store holds.
|
||||
func placed(s *memStore) map[string]delivery.Placement {
|
||||
func placed(s *memStore) map[string]placing.Placement {
|
||||
keys, _ := s.Keys(PlacementRead)
|
||||
values := map[string][]byte{}
|
||||
for _, k := range keys {
|
||||
values[k], _, _ = s.Get(PlacementRead, k)
|
||||
}
|
||||
out, _ := delivery.PlacementsOf(values)
|
||||
out, _ := assignerclient.PlacementsOf(values)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -314,20 +320,20 @@ func agents(verdicts map[string]string) *memStore {
|
||||
p := placed(s)[node]
|
||||
p.Decided.AgentAccount = "agent"
|
||||
place(s, p)
|
||||
h := delivery.NodeHealth{V: 1, Node: node, Contract: 13}
|
||||
h := nodeseat.HealthByNodeValue{V: 1, Node: node, Contract: 13}
|
||||
switch v {
|
||||
case "":
|
||||
case "old":
|
||||
h.Contract = 2
|
||||
h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: stateHealthy}}
|
||||
h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: stateHealthy}}
|
||||
case "pending":
|
||||
h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent",
|
||||
h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent",
|
||||
State: stateUnknown, Reason: rootPending + ": 3 of 9 trees"}}
|
||||
default:
|
||||
h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: v,
|
||||
h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: v,
|
||||
Reason: "can become root without a person: a sudo rule"}}
|
||||
}
|
||||
raw, _ := delivery.EncodeNodeHealth(h)
|
||||
raw, _ := engineclient.EncodeHealth(h)
|
||||
_ = s.Put(HealthRead, node, json.RawMessage(raw))
|
||||
}
|
||||
return s
|
||||
@@ -429,10 +435,10 @@ func (r *dataRig) firstSeen(node, module, item string, ago time.Duration) *dataR
|
||||
}
|
||||
|
||||
func (r *dataRig) holder(node string) {
|
||||
key, _ := delivery.SeatHoldersKey("node-backup", node)
|
||||
_ = r.s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node,
|
||||
Holders: []delivery.SeatHolder{{Module: "restic", Node: node}}})
|
||||
_ = r.s.Put(PresenceRead, node, delivery.Presence{V: 1, Online: true})
|
||||
key, _ := assignerclient.SeatHoldersKey("node-backup", node)
|
||||
_ = r.s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node,
|
||||
Holders: []placing.SeatHoldersHoldersItem{{Module: "restic", Node: node}}})
|
||||
_ = r.s.Put(PresenceRead, node, watchdogs.PresenceByNodeValue{V: 1, Online: true})
|
||||
}
|
||||
|
||||
// dataMesh builds the mesh: pg declares data (backed up within 48 h, written all the time within 24 h) and dumps; plex
|
||||
@@ -450,9 +456,9 @@ func dataMesh(change func(*dataRig)) *memStore {
|
||||
man.Data.Own = append(man.Data.Own, it)
|
||||
}
|
||||
raw, _ := json.Marshal(man)
|
||||
digest := delivery.ManifestDigest(raw)
|
||||
_ = r.s.Put(ManifestsRead, digest, delivery.Manifest{V: 1, Module: module, JSON: string(raw)})
|
||||
_ = r.s.Put(ModulesRead, module, delivery.Module{V: 1, Manifest: digest})
|
||||
digest := catalogclient.ManifestDigest(raw)
|
||||
_ = r.s.Put(ManifestsRead, digest, catalog.ManifestsByDigestValue{V: 1, Module: module, Manifest: string(raw)})
|
||||
_ = r.s.Put(ModulesRead, module, catalog.ModulesByModuleValue{V: 1, Manifest: digest})
|
||||
}
|
||||
declare("pg", map[string]catalogue.DataItem{
|
||||
"data": {Class: catalogue.ClassValuable, Within: "48h", Active: "24h"},
|
||||
|
||||
@@ -3,6 +3,13 @@ module mesh-self-check
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
|
||||
@@ -5,7 +5,7 @@ package main
|
||||
// The controller's own account, the node accounts and the enrolled devices were never handed to the vault, so it cannot
|
||||
// hash them, and the grants part's list would leave them out. The one part that has their hashes is the node-engine of
|
||||
// the node holding mesh-broker, in the list the bus loaded: it hands every loaded user's hash in the bus module's have,
|
||||
// sealed to this vault's key (mesh-sdk delivery.AccountsHanded), which it learns from the vault's answer to node-key.
|
||||
// sealed to this vault's key (mesh-sdk nodeseat.AccountsHanded), which it learns from the vault's answer to node-key.
|
||||
// The vault reads that have (its manifest `reads` node-engine.have), as a watch of the bus module's have and again by
|
||||
// hand with the tool `account_adopt`, and opens it with its own key.
|
||||
//
|
||||
@@ -19,11 +19,10 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// HaveRead is the node-engines' have bucket as the vault reads it (its manifest's `reads`).
|
||||
@@ -186,12 +185,12 @@ func (v *Vault) adopt(node string, raw []byte, by string, force bool) (Adoption,
|
||||
return Adoption{}, fmt.Errorf("%s does not hold the bus (the assignments name %s): its handing is not adopted",
|
||||
node, bus)
|
||||
}
|
||||
have, err := delivery.DecodeHave(raw)
|
||||
have, err := engineclient.DecodeHave(raw)
|
||||
if err != nil {
|
||||
return Adoption{}, fmt.Errorf("the have of %s on %s is not a have: %w", BusModule, node, err)
|
||||
}
|
||||
if have.UserList != nil {
|
||||
v.LoadedListHeard(have.UserList.Revision)
|
||||
v.LoadedListHeard(uint64(have.UserList.Revision))
|
||||
}
|
||||
if have.UserList == nil || have.UserList.Handed == nil {
|
||||
return Adoption{}, fmt.Errorf("the have of %s on %s hands no hashes yet (its node-engine hands them once it "+
|
||||
@@ -205,7 +204,7 @@ func (v *Vault) adopt(node string, raw []byte, by string, force bool) (Adoption,
|
||||
if seen && !force {
|
||||
return Adoption{}, nil
|
||||
}
|
||||
accounts, err := delivery.OpenHanded(h, v.Own.PublicKey, v.Own.PrivateKey)
|
||||
accounts, err := engineclient.OpenHanded(h, v.Own.PublicKey, v.Own.PrivateKey)
|
||||
if err != nil {
|
||||
return Adoption{}, err
|
||||
}
|
||||
@@ -232,7 +231,7 @@ func (v *Vault) adopt(node string, raw []byte, by string, force bool) (Adoption,
|
||||
a.Refused[user] = why
|
||||
}
|
||||
for _, acc := range accounts {
|
||||
if !delivery.IsBcrypt(acc.Hash) || !namePart.MatchString(acc.User) {
|
||||
if !engineclient.IsBcrypt(acc.Hash) || !namePart.MatchString(acc.User) {
|
||||
refuse(acc.User, "not a bus user with a bcrypt hash")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -21,25 +21,27 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// The names enrolment gives: the module that holds the seat mesh-node-enroller, the prefix of a token's bus user, and the
|
||||
// vault's state of each node's logins.
|
||||
const (
|
||||
EnrolmentModule = delivery.EnrolmentModule
|
||||
EnrolUserPrefix = delivery.EnrolmentUserPrefix
|
||||
NodeLoginsState = delivery.NodeLoginsState
|
||||
EnrolmentModule = "mesh-enrolment"
|
||||
EnrolUserPrefix = enrollerclient.UserPrefix
|
||||
NodeLoginsState = meshvault.StateNodeLogins
|
||||
// MeshHostModule is the module a node's own login secret is kept under (`own/<node>/mesh-host/<login>`, ADR 0583 §3).
|
||||
MeshHostModule = "mesh-host"
|
||||
// vaultModule is this module: the holder of the seat mesh-vault, whose states the reads and the bus names carry.
|
||||
vaultModule = "mesh-vault"
|
||||
)
|
||||
|
||||
// Events the vault says for enrolment's accounts are account-hashed (accounts.go), never carrying a hash or a value.
|
||||
@@ -144,7 +146,7 @@ type TokenBase struct {
|
||||
type EnrolmentTokenArgs struct {
|
||||
Node string
|
||||
Expires string
|
||||
Tunnel *delivery.TokenTunnel
|
||||
Tunnel *enrollerclient.TokenTunnel
|
||||
Sealed bool
|
||||
Drop bool
|
||||
}
|
||||
@@ -197,7 +199,7 @@ func (v *Vault) issueEnrolmentToken(caller string, a EnrolmentTokenArgs) (Enrolm
|
||||
return EnrolmentTokenAnswer{}, errors.New("enrolment-token at issue carries the machine's first tunnel (`tunnel`: key, address, range, " +
|
||||
"hub_key, hub_endpoint), so the machine reaches the bus over the private network (novox/hq ADR 0587 §3)")
|
||||
}
|
||||
if missing := (delivery.Token{Version: 1, Broker: base.Broker, Fingerprint: base.Fingerprint, Signer: signer,
|
||||
if missing := (enrollerclient.Token{Version: 1, Broker: base.Broker, Fingerprint: base.Fingerprint, Signer: signer,
|
||||
Secret: "-", Tunnel: a.Tunnel}).Missing(); len(missing) > 0 {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the token of %s would lack %s: nothing was issued", a.Node, strings.Join(missing, ", "))
|
||||
}
|
||||
@@ -264,7 +266,7 @@ func (v *Vault) showEnrolmentToken(caller, node string) (EnrolmentTokenAnswer, e
|
||||
if err != nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the token carries the vault's signing key, which is not held yet: %w", err)
|
||||
}
|
||||
var tunnel *delivery.TokenTunnel
|
||||
var tunnel *enrollerclient.TokenTunnel
|
||||
if trec, theld, err := v.Store.Read(enrolTunnel(node)); err != nil {
|
||||
return EnrolmentTokenAnswer{}, err
|
||||
} else if theld {
|
||||
@@ -272,13 +274,13 @@ func (v *Vault) showEnrolmentToken(caller, node string) (EnrolmentTokenAnswer, e
|
||||
if err != nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the first tunnel of %s does not open with the vault's key: %w", node, err)
|
||||
}
|
||||
tunnel = &delivery.TokenTunnel{}
|
||||
tunnel = &enrollerclient.TokenTunnel{}
|
||||
if err := json.Unmarshal([]byte(raw), tunnel); err != nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the first tunnel of %s is not readable: %w", node, err)
|
||||
}
|
||||
}
|
||||
signer := keys.key.Public().(ed25519.PublicKey)
|
||||
tok := delivery.Token{Version: 1, Node: node, Broker: base.Broker, Fingerprint: base.Fingerprint,
|
||||
tok := enrollerclient.Token{Version: 1, Node: node, Broker: base.Broker, Fingerprint: base.Fingerprint,
|
||||
Signer: []byte(signer), Secret: secret, Tunnel: tunnel}
|
||||
if tunnel == nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the vault keeps no first tunnel for %s's token: issue it again with its tunnel", node)
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -16,8 +20,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
@@ -41,7 +44,7 @@ func (l *loginsState) Get(node string) (*NodeLogin, error) {
|
||||
func (l *loginsState) Put(n NodeLogin) error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
raw, err := delivery.EncodeNodeLogin(n)
|
||||
raw, err := vaultclient.EncodeNodeLogin(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -127,10 +130,10 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
}
|
||||
// new1 is placed with its private-network address, as mesh-enrolment writes it before it asks for the certificate.
|
||||
values := map[string][]byte{}
|
||||
for node, p := range map[string]delivery.Placement{"novox": {Node: "novox", Modules: []delivery.PlacedModule{{Module: "nats"}}},
|
||||
"g14": {Node: "g14"}, "new1": {Node: "new1", Decided: delivery.NodeDecided{Address: "10.77.0.9"}}} {
|
||||
p.V = delivery.Version
|
||||
kv, err := delivery.PlacementValues(p)
|
||||
for node, p := range map[string]placing.Placement{"novox": {Node: "novox", Modules: []placing.PlacedModule{{Module: "nats"}}},
|
||||
"g14": {Node: "g14"}, "new1": {Node: "new1", Decided: placing.NodeDecided{Address: "10.77.0.9"}}} {
|
||||
p.V = vaultclient.Version
|
||||
kv, err := assignerclient.PlacementValues(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -175,7 +178,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
serving, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
keys := map[string]any{"public_key": base64.StdEncoding.EncodeToString(identity), "overlay_key": "overlay-pub",
|
||||
"sealing_key": sealingPub, "serving_key": base64.StdEncoding.EncodeToString(serving)}
|
||||
digest := delivery.KeysDigest(keys["public_key"].(string), "overlay-pub", sealingPub, keys["serving_key"].(string))
|
||||
digest := enrollerclient.KeysDigest(keys["public_key"].(string), "overlay-pub", sealingPub, keys["serving_key"].(string))
|
||||
with := func(base map[string]any, more map[string]any) map[string]any {
|
||||
out := map[string]any{}
|
||||
for k, x := range base {
|
||||
@@ -186,7 +189,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
}
|
||||
return out
|
||||
}
|
||||
reply := delivery.RedeemAnswer{Node: "new1", Login: "node.new1", Account: "sealed", Certificate: "pem", Signer: signerB64}
|
||||
reply := enrollerclient.RedeemAnswer{Node: "new1", Login: "node.new1", Account: "sealed", Certificate: "pem", Signer: signerB64}
|
||||
|
||||
var issued, sealed, dropped meshvault.EnrolmentTokenAnswer
|
||||
var account, accountAgain, retired meshvault.NodeAccountAnswer
|
||||
@@ -203,8 +206,8 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
{"a node asks a token", "enrolment-token", "node.g14", map[string]any{"node": "new1", "tunnel": tunnel}, nil, nil, "mesh-enrolment"},
|
||||
{"the controller asks a token", "enrolment-token", "controller", map[string]any{"node": "new1", "tunnel": tunnel}, nil, nil, "mesh-enrolment"},
|
||||
{"a person asks an account", "node-account", "person.jochen", map[string]any{"node": "new1"}, nil, nil, "mesh-enrolment"},
|
||||
{"unproven asks an enrolment signed", "sign", "", map[string]any{"kind": delivery.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "proved"},
|
||||
{"a grants part asks an enrolment signed", "sign", "novox.mesh-grants", map[string]any{"kind": delivery.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "mesh-enrolment"},
|
||||
{"unproven asks an enrolment signed", "sign", "", map[string]any{"kind": vaultclient.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "proved"},
|
||||
{"a grants part asks an enrolment signed", "sign", "novox.mesh-grants", map[string]any{"kind": vaultclient.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "mesh-enrolment"},
|
||||
// Insight 2026-11-02: the first tunnel is whole at issue, or nothing is issued.
|
||||
{"no tunnel", "enrolment-token", holder, map[string]any{"node": "new1"}, nil, nil, "first tunnel"},
|
||||
{"a tunnel without the hub's key", "enrolment-token", holder, map[string]any{"node": "new1", "tunnel": without("hub_key")}, nil, nil, "hub's tunnel key"},
|
||||
@@ -222,11 +225,11 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
{"node-account for other keys", "node-account", holder, map[string]any{"node": "new1", "sealing_key": sealingPub, "keys_digest": "sha256:other"}, nil, nil, "another set of keys"},
|
||||
{"node-account of an adopted node", "node-account", holder, map[string]any{"node": "g14", "sealing_key": sealingPub, "keys_digest": digest}, nil, nil, "never minted anew"},
|
||||
// The certificate for the serving key presented, checked against node-account's digest; the reply signed.
|
||||
{"a certificate without the keys", "sign", holder, map[string]any{"kind": delivery.KindNodeCertificate, "node": "new1", "serving_key": keys["serving_key"]}, nil, nil, "keys it presented"},
|
||||
{"a certificate for another serving key", "sign", holder, with(keys, map[string]any{"kind": delivery.KindNodeCertificate, "node": "new1", "serving_key": base64.StdEncoding.EncodeToString(identity)}), nil, nil, "not the ones its account was made for"},
|
||||
{"a certificate", "sign", holder, with(keys, map[string]any{"kind": delivery.KindNodeCertificate, "node": "new1"}), &cert, []string{"kind", "node", "certificate", "authority", "signer", "digest"}, ""},
|
||||
{"the reply signed", "sign", holder, map[string]any{"kind": delivery.KindEnrolment, "node": "new1", "reply": base64.StdEncoding.EncodeToString(reply.SignedBytes())}, &replySig, []string{"kind", "node", "signature", "signer", "digest"}, ""},
|
||||
{"a reply of a node with no token", "sign", holder, map[string]any{"kind": delivery.KindEnrolment, "node": "g14", "reply": "AAEC"}, nil, nil, "no enrolment token"},
|
||||
{"a certificate without the keys", "sign", holder, map[string]any{"kind": vaultclient.KindNodeCertificate, "node": "new1", "serving_key": keys["serving_key"]}, nil, nil, "keys it presented"},
|
||||
{"a certificate for another serving key", "sign", holder, with(keys, map[string]any{"kind": vaultclient.KindNodeCertificate, "node": "new1", "serving_key": base64.StdEncoding.EncodeToString(identity)}), nil, nil, "not the ones its account was made for"},
|
||||
{"a certificate", "sign", holder, with(keys, map[string]any{"kind": vaultclient.KindNodeCertificate, "node": "new1"}), &cert, []string{"kind", "node", "certificate", "authority", "signer", "digest"}, ""},
|
||||
{"the reply signed", "sign", holder, map[string]any{"kind": vaultclient.KindEnrolment, "node": "new1", "reply": base64.StdEncoding.EncodeToString(reply.SignedBytes())}, &replySig, []string{"kind", "node", "signature", "signer", "digest"}, ""},
|
||||
{"a reply of a node with no token", "sign", holder, map[string]any{"kind": vaultclient.KindEnrolment, "node": "g14", "reply": "AAEC"}, nil, nil, "no enrolment token"},
|
||||
// A revoke: the account retired, the token and its hash dropped.
|
||||
{"retire", "node-account", holder, map[string]any{"node": "new1", "retire": "true"}, &retired, []string{"node", "retired"}, ""},
|
||||
{"drop", "enrolment-token", holder, map[string]any{"node": "new1", "drop": "true"}, &dropped, []string{"node", "dropped"}, ""},
|
||||
@@ -276,7 +279,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
t.Errorf("issue answered %+v", issued)
|
||||
}
|
||||
// sealed issued nothing new: the token it seals is the one whose hash is enrol.new1, with its tunnel, and usable whole.
|
||||
tok, err := delivery.ParseToken(openOperator(t, e.operatorPublic, e.operatorPrivate, sealed.Sealed))
|
||||
tok, err := enrollerclient.ParseToken(openOperator(t, e.operatorPublic, e.operatorPrivate, sealed.Sealed))
|
||||
if err != nil || len(tok.Missing()) != 0 || tok.Node != "new1" || tok.Broker != issued.Broker || tok.Tunnel == nil ||
|
||||
tok.Tunnel.HubEndpoint != "203.0.113.1:51820" || base64.StdEncoding.EncodeToString(tok.Signer) != signerB64 ||
|
||||
tok.Secret != secret {
|
||||
@@ -304,7 +307,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
if err != nil || !ed25519.PublicKey(serving).Equal(parsed.PublicKey) {
|
||||
t.Errorf("the certificate is of another key: %v", err)
|
||||
}
|
||||
ctx, _ := delivery.SignContext(delivery.KindEnrolment)
|
||||
ctx, _ := vaultclient.SignContext(vaultclient.KindEnrolment)
|
||||
sig, _ := base64.StdEncoding.DecodeString(replySig.Signature)
|
||||
if !ed25519.Verify(signer.key.Public().(ed25519.PublicKey), append([]byte(ctx), reply.SignedBytes()...), sig) {
|
||||
t.Error("the redeem answer's signature does not verify under the enrolment context")
|
||||
|
||||
@@ -9,11 +9,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// EnrolmentHeld is one enrolment token the vault holds, as secret_holders lists it: never the token or its hash.
|
||||
@@ -175,7 +174,7 @@ func enrolmentTools(env *Env) []toolDef {
|
||||
|
||||
// tunnelArg reads enrolment-token's `tunnel`, an object or its JSON as a string, as mesh-sdk's TokenTunnel; nil when
|
||||
// absent.
|
||||
func tunnelArg(v any) (*delivery.TokenTunnel, error) {
|
||||
func tunnelArg(v any) (*enrollerclient.TokenTunnel, error) {
|
||||
var raw []byte
|
||||
switch t := v.(type) {
|
||||
case nil:
|
||||
@@ -192,7 +191,7 @@ func tunnelArg(v any) (*delivery.TokenTunnel, error) {
|
||||
}
|
||||
raw = b
|
||||
}
|
||||
var tt delivery.TokenTunnel
|
||||
var tt enrollerclient.TokenTunnel
|
||||
dec := json.NewDecoder(bytes.NewReader(raw))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(&tt); err != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"git.novox.be/novox/mesh-sdk/go/testbus"
|
||||
|
||||
@@ -10,11 +10,13 @@ package main
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
@@ -383,11 +385,11 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
|
||||
later -= time.Hour
|
||||
// own_drift reads it from the node's latest shadow answer, mesh-wide; the id as a node runs it, its module's name
|
||||
// first (captured from novox's shadow answer, 2026-10-28: only-office.needs-jwt).
|
||||
answer := delivery.CandidateAnswer{Proposal: "pro", Revision: 1, Verdict: delivery.CandidateDiffers, Differs: []delivery.ModuleDiffers{{
|
||||
Module: "postgres", Why: "the resource differs", Bodies: []delivery.ResourceBodies{{ID: "postgres.needs-superuser-password",
|
||||
Composed: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`}}}}}
|
||||
have, err := delivery.EncodeHaveNode(delivery.HaveNode{Candidates: map[string]delivery.CandidateAnswer{"pro": answer},
|
||||
Composes: delivery.ComposesOwn})
|
||||
answer := nodeseat.CandidateAnswer{Proposal: "pro", Revision: 1, Verdict: nodeseat.CandidateAnswerVerdictDiffers, Differs: []nodeseat.CandidateDiffers{{
|
||||
Module: "postgres", Why: "the resource differs", Bodies: []nodeseat.CandidateBodies{{ID: "postgres.needs-superuser-password",
|
||||
Composed: `{"sealed":"` + vaultclient.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + vaultclient.MaskedAnotherValue + `"}`}}}}}
|
||||
have, err := engineclient.EncodeHaveNode(nodeseat.HaveByNodeNodeValue{Candidates: map[string]nodeseat.CandidateAnswer{"pro": answer},
|
||||
Composes: engineclient.ComposesOwn})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -399,7 +401,7 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
|
||||
}
|
||||
if len(drift.Secrets) != 1 || drift.Secrets[0].Secret != "own/anchor/postgres/superuser-password" ||
|
||||
drift.Secrets[0].Generation != 2 || drift.Secrets[0].Previous != 1 || drift.Secrets[0].Takeable ||
|
||||
len(drift.Nodes) != 1 || drift.Nodes[0].ValuesDiffer["pro"] != 1 || drift.Nodes[0].Composes != delivery.ComposesOwn {
|
||||
len(drift.Nodes) != 1 || drift.Nodes[0].ValuesDiffer["pro"] != 1 || drift.Nodes[0].Composes != engineclient.ComposesOwn {
|
||||
t.Errorf("own_drift: %+v", drift)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -29,20 +31,19 @@ import (
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// The events a rotation says and the stages of a node's key are mesh-sdk's (delivery/node_logins.go); a rotation warranted
|
||||
// by a hand act is warranted for a day.
|
||||
const (
|
||||
RotationBegunEvent = delivery.RotationBegunEvent
|
||||
NodeAccountReadyEvent = delivery.NodeAccountReadyEvent
|
||||
RotationDoneEvent = delivery.RotationDoneEvent
|
||||
RotationBegunEvent = meshvault.HolderEventRotationBegun
|
||||
NodeAccountReadyEvent = meshvault.HolderEventNodeAccountReady
|
||||
RotationDoneEvent = meshvault.HolderEventRotationDone
|
||||
|
||||
StageBegun = delivery.RotationBegun
|
||||
StageReady = delivery.RotationReady
|
||||
StageConfirmed = delivery.RotationConfirmed
|
||||
StageBegun = vaultclient.RotationBegun
|
||||
StageReady = vaultclient.RotationReady
|
||||
StageConfirmed = vaultclient.RotationConfirmed
|
||||
|
||||
// HandActWithin is how recent a hand act is that warrants a rotation.
|
||||
HandActWithin = 24 * time.Hour
|
||||
@@ -50,7 +51,7 @@ const (
|
||||
|
||||
// NodeLogin is one key of the vault's state node-logins (mesh-sdk's contract): the login the node's node-engine dials as,
|
||||
// and while a rotation runs the other login with its stage. Never a value or a hash.
|
||||
type NodeLogin = delivery.NodeLogin
|
||||
type NodeLogin = meshvault.NodeLoginsByNodeValue
|
||||
|
||||
// NodeLoginStore is the vault's state `node-logins`, by node.
|
||||
type NodeLoginStore interface {
|
||||
@@ -68,7 +69,7 @@ func (busNodeLogins) Get(node string) (*NodeLogin, error) {
|
||||
if err != nil || e == nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := delivery.DecodeNodeLogin(e.Value)
|
||||
n, err := vaultclient.DecodeNodeLogin(e.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s is not a node's login: %w", NodeLoginsState, node, err)
|
||||
}
|
||||
@@ -76,7 +77,7 @@ func (busNodeLogins) Get(node string) (*NodeLogin, error) {
|
||||
}
|
||||
|
||||
func (busNodeLogins) Put(n NodeLogin) error {
|
||||
raw, err := delivery.EncodeNodeLogin(n)
|
||||
raw, err := vaultclient.EncodeNodeLogin(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,8 +202,8 @@ func (v *Vault) checkAct(caller, node, id string) error {
|
||||
return fmt.Errorf("the hand act %s was recorded by %s, not by %s who asks: a rotation is warranted by its own caller's act", id, act.By, caller)
|
||||
case v.Now().Sub(act.At) > HandActWithin:
|
||||
return fmt.Errorf("the hand act %s is older than %s: record a new one", id, HandActWithin)
|
||||
case act.Verb != delivery.RotateNodeVerb && !strings.HasSuffix(act.Verb, "."+delivery.RotateNodeVerb):
|
||||
return fmt.Errorf("the hand act %s is %q, not %s: it warrants no rotation", id, act.Verb, delivery.RotateNodeVerb)
|
||||
case act.Verb != meshvault.VerbRotateNode && !strings.HasSuffix(act.Verb, "."+meshvault.VerbRotateNode):
|
||||
return fmt.Errorf("the hand act %s is %q, not %s: it warrants no rotation", id, act.Verb, meshvault.VerbRotateNode)
|
||||
case !slices.Contains(act.Args, node):
|
||||
return fmt.Errorf("the hand act %s is a rotation of %s, not of %s", id, strings.Join(act.Args, " "), node)
|
||||
}
|
||||
@@ -385,7 +386,7 @@ func (v *Vault) beginNodeRotation(by, node, why, act string, revision uint64) (N
|
||||
if err := v.Accounts.Put(marked); err != nil {
|
||||
return NodeLogin{}, fmt.Errorf("%s could not be marked rotating: %w", cur, err)
|
||||
}
|
||||
nl := NodeLogin{Node: node, Login: cur, Rotating: second, Stage: StageBegun, Since: at, ListRevision: revision}
|
||||
nl := NodeLogin{Node: node, Login: cur, Rotating: second, Stage: StageBegun, Since: at, ListRevision: int64(revision)}
|
||||
if err := v.putLogin(nl); err != nil {
|
||||
return NodeLogin{}, fmt.Errorf("%s %s could not be written: %w", NodeLoginsState, node, err)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -33,7 +36,6 @@ import (
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// ReplaceRunningArg is the argument of `move-in` that makes it take a running own secret over the one the vault holds.
|
||||
@@ -253,7 +255,7 @@ type Drift struct {
|
||||
}
|
||||
|
||||
// OwnDrift reads every node's own have key (`<node>.node`) and lists, from its latest answers to the placement
|
||||
// candidates, every own secret whose shown bodies say another value (delivery.OwnValuesDiffer), with what the vault holds
|
||||
// candidates, every own secret whose shown bodies say another value (vaultclient.OwnValuesDiffer), with what the vault holds
|
||||
// of it and whether move-in with replace_running would take the running value now.
|
||||
func (v *Vault) OwnDrift() (Drift, error) {
|
||||
if v.Haves == nil || v.HaveKeys == nil {
|
||||
@@ -268,7 +270,7 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
out := Drift{Secrets: []DriftRow{}, Nodes: []DriftNode{},
|
||||
Note: "from each node's latest shadow answers; a value is never shown: move-in with replace_running from that node replaces it"}
|
||||
for _, key := range keys {
|
||||
node, isNode := strings.CutSuffix(key, "."+delivery.HaveNodeWord)
|
||||
node, isNode := strings.CutSuffix(key, "."+engineclient.NodeWord)
|
||||
if !isNode || !namePart.MatchString(node) {
|
||||
continue
|
||||
}
|
||||
@@ -279,7 +281,7 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
out.Nodes = append(out.Nodes, dn)
|
||||
continue
|
||||
}
|
||||
have, err := delivery.DecodeHaveNode(raw)
|
||||
have, err := engineclient.DecodeHaveNode(raw)
|
||||
if err != nil {
|
||||
dn.Unread = err.Error()
|
||||
out.Nodes = append(out.Nodes, dn)
|
||||
@@ -287,13 +289,13 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
}
|
||||
dn.Composes = have.Composes
|
||||
for group, a := range have.Candidates {
|
||||
if n := delivery.ValuesDiffer(a); n > 0 {
|
||||
if n := vaultclient.ValuesDiffer(bodiesOf(a)); n > 0 {
|
||||
if dn.ValuesDiffer == nil {
|
||||
dn.ValuesDiffer = map[string]int{}
|
||||
}
|
||||
dn.ValuesDiffer[group] = n
|
||||
}
|
||||
for _, o := range delivery.OwnValuesDiffer(a) {
|
||||
for _, o := range vaultclient.OwnValuesDiffer(bodiesOf(a)) {
|
||||
secret, err := OwnSecret(node, o.Module, o.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -399,3 +401,17 @@ func (v *Vault) ownTakeOff() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// bodiesOf is the shown bodies of the modules a node's answer to a candidate says differ, in the form the vault client
|
||||
// reads them (the node-engine's answer is the seat's type, the reading of masked values the vault's).
|
||||
func bodiesOf(a nodeseat.CandidateAnswer) []vaultclient.ModuleBodies {
|
||||
out := make([]vaultclient.ModuleBodies, 0, len(a.Differs))
|
||||
for _, d := range a.Differs {
|
||||
m := vaultclient.ModuleBodies{Module: d.Module}
|
||||
for _, b := range d.Bodies {
|
||||
m.Bodies = append(m.Bodies, vaultclient.Body{ID: b.ID, Composed: b.Composed, Running: b.Running})
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// PlacementRead is the seat mesh-module-assigner's placement, as the vault's manifest reads it (mesh-sdk's contract).
|
||||
const PlacementRead = delivery.PlacementRead
|
||||
const PlacementRead = assignerclient.PlacementRead
|
||||
|
||||
// PlacementReader reads the seat's placement: every key, and one key's value (nil when there is none).
|
||||
type PlacementReader struct {
|
||||
@@ -25,10 +25,10 @@ type PlacementReader struct {
|
||||
Read func(key string) ([]byte, error)
|
||||
}
|
||||
|
||||
// placements is every node's placement, by node, from the state's keys in either form (delivery.PlacementsOf; research
|
||||
// placements is every node's placement, by node, from the state's keys in either form (assignerclient.PlacementsOf; research
|
||||
// 072 row 37); none when the vault reads no placement. A node whose keys do not read whole is read as they stand.
|
||||
func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
||||
out := map[string]delivery.Placement{}
|
||||
func (v *Vault) placements() (map[string]placing.Placement, error) {
|
||||
out := map[string]placing.Placement{}
|
||||
if v.Placement.Keys == nil || v.Placement.Read == nil {
|
||||
return out, nil
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
||||
}
|
||||
values := map[string][]byte{}
|
||||
for _, k := range keys {
|
||||
if _, _, _, err := delivery.ParsePlacementKey(k); err != nil {
|
||||
if _, _, _, err := assignerclient.ParseKey(k); err != nil {
|
||||
continue
|
||||
}
|
||||
raw, err := v.Placement.Read(k)
|
||||
@@ -49,15 +49,15 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
||||
values[k] = raw
|
||||
}
|
||||
}
|
||||
out, _ = delivery.PlacementsOf(values)
|
||||
out, _ = assignerclient.PlacementsOf(values)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// nodePlaced is what node's own key of the placement says: the modules placed there and what a person decided of it,
|
||||
// read from its node key `<node>.node` alone (which names every module placed, research 072 row 37), or, for a node not
|
||||
// converted yet, from its key in the form before, `placement.<node>`. found is false when it has neither.
|
||||
func (v *Vault) nodePlaced(node string) (modules []string, decided delivery.NodeDecided, found bool, err error) {
|
||||
key, err := delivery.PlacementNodeKey(node)
|
||||
func (v *Vault) nodePlaced(node string) (modules []string, decided placing.NodeDecided, found bool, err error) {
|
||||
key, err := assignerclient.NodeKey(node)
|
||||
if err != nil {
|
||||
return nil, decided, false, err
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func (v *Vault) nodePlaced(node string) (modules []string, decided delivery.Node
|
||||
return nil, decided, false, err
|
||||
}
|
||||
if raw != nil {
|
||||
n, err := delivery.DecodePlacementNode(raw)
|
||||
n, err := assignerclient.DecodeNode(raw)
|
||||
if err != nil {
|
||||
return nil, decided, false, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
||||
}
|
||||
@@ -75,18 +75,18 @@ func (v *Vault) nodePlaced(node string) (modules []string, decided delivery.Node
|
||||
}
|
||||
return modules, n.Decided, true, nil
|
||||
}
|
||||
legacy, err := delivery.LegacyPlacementKey(node)
|
||||
legacy, err := assignerclient.LegacyKey(node)
|
||||
if err != nil {
|
||||
return nil, decided, false, err
|
||||
}
|
||||
if raw, err = v.Placement.Read(legacy); err != nil || raw == nil {
|
||||
return nil, decided, false, err
|
||||
}
|
||||
p, err := delivery.DecodePlacement(raw)
|
||||
p, err := assignerclient.DecodePlacement(raw)
|
||||
if err != nil {
|
||||
return nil, decided, false, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
||||
}
|
||||
return p.Assigned(), p.Decided, true, nil
|
||||
return assignerclient.Assigned(p), p.Decided, true, nil
|
||||
}
|
||||
|
||||
// placedOn says module is placed on node by the catalogue.
|
||||
@@ -132,7 +132,7 @@ func (v *Vault) busNode() (string, error) {
|
||||
}
|
||||
var nodes []string
|
||||
for node, p := range placed {
|
||||
if slices.Contains(p.Assigned(), BusModule) {
|
||||
if slices.Contains(assignerclient.Assigned(p), BusModule) {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// placementsOf is the seat mesh-module-assigner's placement of each node, as the vault reads it.
|
||||
@@ -12,12 +13,12 @@ func placementsOf(t *testing.T, nodes map[string][]string) PlacementReader {
|
||||
t.Helper()
|
||||
values := map[string][]byte{}
|
||||
for node, modules := range nodes {
|
||||
p := delivery.Placement{V: delivery.Version, Node: node}
|
||||
p := placing.Placement{V: vaultclient.Version, Node: node}
|
||||
for _, m := range modules {
|
||||
p.Modules = append(p.Modules, delivery.PlacedModule{Module: m})
|
||||
p.Modules = append(p.Modules, placing.PlacedModule{Module: m})
|
||||
}
|
||||
// Research 072 row 37: the node key and a key per module.
|
||||
kv, err := delivery.PlacementValues(p)
|
||||
kv, err := assignerclient.PlacementValues(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -10,20 +10,20 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// PlacementTool is the assigner's ordinary read tool of its placement; PlacementViewsEvent the event of its views family.
|
||||
const (
|
||||
PlacementTool = delivery.AssignerModule + ".assigner_read_placement"
|
||||
PlacementViewsEvent = delivery.AssignerModule + ".views.placement"
|
||||
PlacementTool = assignerclient.Module + ".assigner_read_placement"
|
||||
PlacementViewsEvent = assignerclient.Module + ".views.placement"
|
||||
FirstViewsTool = "vault_first_views"
|
||||
FirstViewAgainTool = "vault_first_view_again"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ var placementView *views.Follower
|
||||
// it is held (a first view's keys too).
|
||||
func newPlacementView(ask func(key string, body any) (json.RawMessage, error), gone func(key string), changed func(),
|
||||
findings views.FindingWriter) *views.Follower {
|
||||
return &views.Follower{Owner: delivery.AssignerModule, Tool: PlacementTool, State: "placement", Logf: say,
|
||||
return &views.Follower{Owner: assignerclient.Module, Tool: PlacementTool, State: "placement", Logf: say,
|
||||
Ask: func(_ context.Context, after string) (views.Answer, error) {
|
||||
body := map[string]any{"values": true}
|
||||
if after != "" {
|
||||
|
||||
@@ -15,6 +15,11 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -28,7 +33,6 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"git.novox.be/novox/mesh-sdk/go/testbus"
|
||||
)
|
||||
@@ -113,24 +117,24 @@ func TestTheVaultWholeRotatesANodesLoginWithAStandInEngine(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var accounts []delivery.HandedAccount
|
||||
var accounts []engineclient.HandedAccount
|
||||
loaded = map[string]string{}
|
||||
for _, k := range keys {
|
||||
a := p.hashOf(t, k)
|
||||
if a == nil || !delivery.IsBcrypt(a.Hash) {
|
||||
if a == nil || !engineclient.IsBcrypt(a.Hash) {
|
||||
continue
|
||||
}
|
||||
accounts = append(accounts, delivery.HandedAccount{User: k, Hash: a.Hash})
|
||||
accounts = append(accounts, engineclient.HandedAccount{User: k, Hash: a.Hash})
|
||||
loaded[k] = a.Hash
|
||||
}
|
||||
revision++
|
||||
list, _ := json.Marshal(accounts)
|
||||
handed, err := delivery.HandAccounts(accounts, vaultKey.PublicKey, Fingerprint(string(list)), time.Now())
|
||||
handed, err := engineclient.HandAccounts(accounts, vaultKey.PublicKey, Fingerprint(string(list)), time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, err := delivery.EncodeHave(delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
|
||||
UserList: &delivery.UserListWritten{Source: "k", Revision: revision, Users: len(accounts), Handed: &handed}})
|
||||
raw, err := engineclient.EncodeHave(nodeseat.HaveByNodeModuleValue{Phase: engineclient.PhaseRunning, Health: engineclient.HealthOK,
|
||||
UserList: &nodeseat.UserListWritten{Source: "k", Revision: int64(revision), Users: int64(len(accounts)), Handed: &handed}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -161,11 +165,11 @@ func TestTheVaultWholeRotatesANodesLoginWithAStandInEngine(t *testing.T) {
|
||||
// held the bus, and adopts from it (learning the loaded list's revision) when the placement names novox.
|
||||
p.firstViewAnswered()
|
||||
for node, modules := range map[string][]string{"novox": {"nats"}, "g14": {"web"}} {
|
||||
pl := delivery.Placement{V: delivery.Version, Node: node}
|
||||
pl := placing.Placement{V: vaultclient.Version, Node: node}
|
||||
for _, m := range modules {
|
||||
pl.Modules = append(pl.Modules, delivery.PlacedModule{Module: m})
|
||||
pl.Modules = append(pl.Modules, placing.PlacedModule{Module: m})
|
||||
}
|
||||
values, err := delivery.PlacementValues(pl)
|
||||
values, err := assignerclient.PlacementValues(pl)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
)
|
||||
|
||||
@@ -37,7 +39,7 @@ const (
|
||||
// signingSecret says name is one of the vault's signing secrets, which no asker is ever handed.
|
||||
func signingSecret(name string) bool { return name == SigningKeySecret || name == AuthorityKeySecret }
|
||||
|
||||
// SignersStore is the vault's state `signers` (delivery.SignersBucket): a key's value, or nil; and a write.
|
||||
// SignersStore is the vault's state `signers` ((vaultModule + "_" + meshvault.StateSigners)): a key's value, or nil; and a write.
|
||||
type SignersStore interface {
|
||||
Get(key string) ([]byte, error)
|
||||
Put(key string, value []byte) error
|
||||
@@ -47,7 +49,7 @@ type SignersStore interface {
|
||||
type busSigners struct{}
|
||||
|
||||
func (busSigners) Get(key string) ([]byte, error) {
|
||||
e, err := stdio.State(delivery.SignersState).Get(key)
|
||||
e, err := stdio.State(meshvault.StateSigners).Get(key)
|
||||
if err != nil || e == nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -55,7 +57,7 @@ func (busSigners) Get(key string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (busSigners) Put(key string, value []byte) error {
|
||||
_, err := stdio.State(delivery.SignersState).Put(key, json.RawMessage(value))
|
||||
_, err := stdio.State(meshvault.StateSigners).Put(key, json.RawMessage(value))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -94,11 +96,11 @@ func (v *Vault) EnsureSigning() (bool, error) {
|
||||
"would be trusted by no node; put it back from its operator copy (novox/hq ADR 0113 §21, ADR 0550 §2)", missing)
|
||||
}
|
||||
if v.Signers != nil {
|
||||
if raw, err := v.Signers.Get(delivery.SignersKeyKey); err != nil {
|
||||
return false, fmt.Errorf("the vault's state %s could not be read, so no signing key is made: %w", delivery.SignersRead, err)
|
||||
if raw, err := v.Signers.Get(meshvault.SignersByKeyKey()); err != nil {
|
||||
return false, fmt.Errorf("the vault's state %s could not be read, so no signing key is made: %w", (vaultModule + "." + meshvault.StateSigners), err)
|
||||
} else if raw != nil {
|
||||
return false, fmt.Errorf("THE VAULT'S SIGNING KEYS ARE MISSING WHILE %s PUBLISHES ONE: nothing is made, since every "+
|
||||
"node trusts the published key; put them back from their operator copies (novox/hq ADR 0113 §21)", delivery.SignersRead)
|
||||
"node trusts the published key; put them back from their operator copies (novox/hq ADR 0113 §21)", (vaultModule + "." + meshvault.StateSigners))
|
||||
}
|
||||
}
|
||||
for _, name := range []string{SigningKeySecret, AuthorityKeySecret} {
|
||||
@@ -147,7 +149,7 @@ func (v *Vault) keys() (signingKeys, error) {
|
||||
return signingKeys{}, err
|
||||
}
|
||||
pub := auth.Public().(ed25519.PublicKey)
|
||||
tmpl := delivery.AuthorityTemplate(pub, authMade)
|
||||
tmpl := vaultclient.AuthorityTemplate(pub, authMade)
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, auth)
|
||||
if err != nil {
|
||||
return signingKeys{}, fmt.Errorf("the vault's authority certificate could not be made: %w", err)
|
||||
@@ -161,31 +163,31 @@ func (v *Vault) keys() (signingKeys, error) {
|
||||
}
|
||||
|
||||
// SignersValues are the two keys of `signers` as the vault holds them now.
|
||||
func (v *Vault) SignersValues() (delivery.SignerKey, delivery.SignerAuthority, error) {
|
||||
func (v *Vault) SignersValues() (meshvault.SignersByKeyValue, meshvault.SignersByAuthorityValue, error) {
|
||||
k, err := v.keys()
|
||||
if err != nil {
|
||||
return delivery.SignerKey{}, delivery.SignerAuthority{}, err
|
||||
return meshvault.SignersByKeyValue{}, meshvault.SignersByAuthorityValue{}, err
|
||||
}
|
||||
fp, err := delivery.AuthorityFingerprint(k.certificate)
|
||||
fp, err := vaultclient.AuthorityFingerprint(k.certificate)
|
||||
if err != nil {
|
||||
return delivery.SignerKey{}, delivery.SignerAuthority{}, err
|
||||
return meshvault.SignersByKeyValue{}, meshvault.SignersByAuthorityValue{}, err
|
||||
}
|
||||
return delivery.NewSignerKey(k.key.Public().(ed25519.PublicKey), k.keyMade),
|
||||
delivery.SignerAuthority{Certificate: k.certificate, Fingerprint: fp, Made: k.authorityMade.UTC()}, nil
|
||||
return vaultclient.NewSignerKey(k.key.Public().(ed25519.PublicKey), k.keyMade),
|
||||
meshvault.SignersByAuthorityValue{Certificate: k.certificate, Fingerprint: fp, Made: k.authorityMade.UTC()}, nil
|
||||
}
|
||||
|
||||
// PublishSigners writes `signers` from the keys the vault holds, each key only when its value differs (or always with
|
||||
// force, signers_reassert), and answers which keys it wrote.
|
||||
func (v *Vault) PublishSigners(force bool) ([]string, error) {
|
||||
if v.Signers == nil {
|
||||
return nil, errors.New("the vault has no state " + delivery.SignersRead + " to write")
|
||||
return nil, errors.New("the vault has no state " + (vaultModule + "." + meshvault.StateSigners) + " to write")
|
||||
}
|
||||
key, auth, err := v.SignersValues()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var written []string
|
||||
for name, value := range map[string]any{delivery.SignersKeyKey: key, delivery.SignersAuthorityKey: auth} {
|
||||
for name, value := range map[string]any{meshvault.SignersByKeyKey(): key, meshvault.SignersByAuthorityKey(): auth} {
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return written, err
|
||||
@@ -193,19 +195,19 @@ func (v *Vault) PublishSigners(force bool) ([]string, error) {
|
||||
if !force {
|
||||
held, err := v.Signers.Get(name)
|
||||
if err != nil {
|
||||
return written, fmt.Errorf("%s %s could not be read: %w", delivery.SignersRead, name, err)
|
||||
return written, fmt.Errorf("%s %s could not be read: %w", (vaultModule + "." + meshvault.StateSigners), name, err)
|
||||
}
|
||||
if same(held, raw) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := v.Signers.Put(name, raw); err != nil {
|
||||
return written, fmt.Errorf("%s %s could not be written: %w", delivery.SignersRead, name, err)
|
||||
return written, fmt.Errorf("%s %s could not be written: %w", (vaultModule + "." + meshvault.StateSigners), name, err)
|
||||
}
|
||||
written = append(written, name)
|
||||
}
|
||||
if len(written) > 0 {
|
||||
v.log("wrote %s %s (ADR 0550 §2)", delivery.SignersRead, strings.Join(written, " and "))
|
||||
v.log("wrote %s %s (ADR 0550 §2)", (vaultModule + "." + meshvault.StateSigners), strings.Join(written, " and "))
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
@@ -242,12 +244,12 @@ type SignArgs struct {
|
||||
Node string
|
||||
Reply string
|
||||
// Keys are, for a joining node's certificate asked by mesh-enrolment, the public keys the machine presented at its
|
||||
// redeem (delivery.Redeem's): the serving key is certified, the four are checked against the digest node-account
|
||||
// redeem (enrollerclient.Redeem's): the serving key is certified, the four are checked against the digest node-account
|
||||
// recorded.
|
||||
Keys PresentedKeys
|
||||
}
|
||||
|
||||
// PresentedKeys are the public keys a joining machine presented at its redeem, as delivery.Redeem names them.
|
||||
// PresentedKeys are the public keys a joining machine presented at its redeem, as enrollerclient.Redeem names them.
|
||||
type PresentedKeys struct {
|
||||
Public, Overlay, Sealing, Serving string
|
||||
}
|
||||
@@ -260,21 +262,21 @@ func (v *Vault) Sign(caller string, a SignArgs) (meshvault.SignAnswer, error) {
|
||||
return meshvault.SignAnswer{}, errors.New("the vault signs only for a caller the runtime proved")
|
||||
}
|
||||
switch a.Kind {
|
||||
case delivery.KindNodeCertificate:
|
||||
case vaultclient.KindNodeCertificate:
|
||||
if v.fromEnrolment(caller, "sign") == nil {
|
||||
return v.signRedeemingCertificate(caller, a.Node, a.Keys)
|
||||
}
|
||||
return v.signNodeCertificate(caller, a.Node)
|
||||
case delivery.KindEnrolment:
|
||||
case vaultclient.KindEnrolment:
|
||||
if err := v.fromEnrolment(caller, "sign an enrolment"); err != nil {
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("%s may not ask for an enrolment's signature: only the seat %s, which "+
|
||||
"decides who joins, is named for it (novox/hq ADR 0561 §5, ADR 0587 §6)", caller, EnrolmentModule)
|
||||
}
|
||||
return v.signEnrolment(caller, a.Node, a.Reply)
|
||||
case delivery.KindAuthority:
|
||||
case vaultclient.KindAuthority:
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("the authority is the vault's own, signed for itself; %s may not ask it", caller)
|
||||
}
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("%q is not a kind the vault signs (%s)", a.Kind, strings.Join(delivery.SignKinds, ", "))
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("%q is not a kind the vault signs (%s)", a.Kind, strings.Join(vaultclient.SignKinds, ", "))
|
||||
}
|
||||
|
||||
// redeeming says node has an enrolment token held: a machine joining, which the seat mesh-node-enroller redeems.
|
||||
@@ -312,7 +314,7 @@ func (v *Vault) signRedeemingCertificate(caller, node string, keys PresentedKeys
|
||||
return meshvault.SignAnswer{}, errors.New("a joining node's certificate is asked with the keys it presented at the redeem " +
|
||||
"(`public_key`, `overlay_key`, `sealing_key`, `serving_key`), which are checked against node-account's digest")
|
||||
}
|
||||
if got := delivery.KeysDigest(keys.Public, keys.Overlay, keys.Sealing, keys.Serving); got != acct.KeysDigest {
|
||||
if got := enrollerclient.KeysDigest(keys.Public, keys.Overlay, keys.Sealing, keys.Serving); got != acct.KeysDigest {
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("the keys presented for %s are not the ones its account was made for: nothing is signed", node)
|
||||
}
|
||||
pub, err := base64.StdEncoding.DecodeString(keys.Serving)
|
||||
@@ -336,7 +338,7 @@ func (v *Vault) signEnrolment(caller, node, reply string) (meshvault.SignAnswer,
|
||||
if err != nil || len(msg) == 0 {
|
||||
return meshvault.SignAnswer{}, errors.New("an enrolment's signature is asked for the reply's bytes in base64 (`reply`)")
|
||||
}
|
||||
context, err := delivery.SignContext(delivery.KindEnrolment)
|
||||
context, err := vaultclient.SignContext(vaultclient.KindEnrolment)
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
@@ -344,9 +346,9 @@ func (v *Vault) signEnrolment(caller, node, reply string) (meshvault.SignAnswer,
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
answer := meshvault.SignAnswer{Kind: delivery.KindEnrolment, Node: node,
|
||||
Signature: base64.StdEncoding.EncodeToString(delivery.SignMessage(k.key, context, msg)),
|
||||
Signer: delivery.SignerFingerprint(k.key.Public().(ed25519.PublicKey)), Digest: delivery.DigestOf(msg)}
|
||||
answer := meshvault.SignAnswer{Kind: vaultclient.KindEnrolment, Node: node,
|
||||
Signature: base64.StdEncoding.EncodeToString(vaultclient.SignMessage(k.key, context, msg)),
|
||||
Signer: vaultclient.SignerFingerprint(k.key.Public().(ed25519.PublicKey)), Digest: vaultclient.DigestOf(msg)}
|
||||
if err := v.saySigned(caller, SigningKeySecret, answer); err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
@@ -385,14 +387,14 @@ func (v *Vault) signCertificateWith(caller, node, name string, serving ed25519.P
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, delivery.NodeCertificateTemplate(name, serving, k.authorityMade),
|
||||
der, err := x509.CreateCertificate(rand.Reader, vaultclient.NodeCertificateTemplate(name, serving, k.authorityMade),
|
||||
k.parsed, serving, k.authority)
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("the certificate of %s could not be signed: %w", name, err)
|
||||
}
|
||||
fp, _ := delivery.AuthorityFingerprint(k.certificate)
|
||||
answer := meshvault.SignAnswer{Kind: delivery.KindNodeCertificate, Node: node, Authority: k.certificate,
|
||||
Certificate: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), Signer: fp, Digest: delivery.DigestOf(der)}
|
||||
fp, _ := vaultclient.AuthorityFingerprint(k.certificate)
|
||||
answer := meshvault.SignAnswer{Kind: vaultclient.KindNodeCertificate, Node: node, Authority: k.certificate,
|
||||
Certificate: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), Signer: fp, Digest: vaultclient.DigestOf(der)}
|
||||
if err := v.saySigned(caller, AuthorityKeySecret, answer); err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
@@ -423,7 +425,7 @@ func (v *Vault) servingKey(node string) (ed25519.PublicKey, error) {
|
||||
if v.Haves == nil {
|
||||
return nil, errors.New("the vault cannot read the have bucket (" + HaveRead + "), so it cannot read a node's record")
|
||||
}
|
||||
key, err := delivery.HaveNodeKey(node)
|
||||
key, err := engineclient.HaveNodeKey(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -434,7 +436,7 @@ func (v *Vault) servingKey(node string) (ed25519.PublicKey, error) {
|
||||
if raw == nil {
|
||||
return nil, fmt.Errorf("%s has written no record (%s %s), so it states no serving key", node, HaveRead, key)
|
||||
}
|
||||
h, err := delivery.DecodeHaveNode(raw)
|
||||
h, err := engineclient.DecodeHaveNode(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the record of %s is not readable: %w", node, err)
|
||||
}
|
||||
@@ -525,7 +527,7 @@ func (v *Vault) SigningState() map[string]any {
|
||||
}
|
||||
published := map[string]any{}
|
||||
match := true
|
||||
for name, want := range map[string]string{delivery.SignersKeyKey: key.Fingerprint, delivery.SignersAuthorityKey: auth.Fingerprint} {
|
||||
for name, want := range map[string]string{meshvault.SignersByKeyKey(): key.Fingerprint, meshvault.SignersByAuthorityKey(): auth.Fingerprint} {
|
||||
raw, err := v.Signers.Get(name)
|
||||
var got struct {
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
|
||||
@@ -5,10 +5,15 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
)
|
||||
@@ -35,25 +40,25 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
state := &signersState{keys: map[string][]byte{}}
|
||||
v.Signers = state
|
||||
servingPub, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
placements := map[string]delivery.Placement{
|
||||
"ace": {Node: "ace", Decided: delivery.NodeDecided{Address: "10.77.0.3"}},
|
||||
"g14": {Node: "g14", Decided: delivery.NodeDecided{Address: "10.77.0.4"}},
|
||||
placements := map[string]placing.Placement{
|
||||
"ace": {Node: "ace", Decided: placing.NodeDecided{Address: "10.77.0.3"}},
|
||||
"g14": {Node: "g14", Decided: placing.NodeDecided{Address: "10.77.0.4"}},
|
||||
"plain": {Node: "plain"},
|
||||
}
|
||||
// Research 072 row 37: ace and plain in their keys per module and node, g14 still in the form before.
|
||||
v.Placement = PlacementReader{Read: func(key string) ([]byte, error) {
|
||||
node, _, kind, err := delivery.ParsePlacementKey(key)
|
||||
node, _, kind, err := assignerclient.ParseKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, ok := placements[node]
|
||||
if !ok || (node == "g14") != (kind == delivery.PlacementLegacyKind) {
|
||||
if !ok || (node == "g14") != (kind == assignerclient.LegacyKind) {
|
||||
return nil, nil
|
||||
}
|
||||
if kind == delivery.PlacementLegacyKind {
|
||||
return delivery.EncodePlacement(p)
|
||||
if kind == assignerclient.LegacyKind {
|
||||
return assignerclient.EncodePlacement(p)
|
||||
}
|
||||
values, err := delivery.PlacementValues(p)
|
||||
values, err := assignerclient.PlacementValues(p)
|
||||
return values[key], err
|
||||
}}
|
||||
records := map[string]string{"ace": base64.StdEncoding.EncodeToString(servingPub), "plain": base64.StdEncoding.EncodeToString(servingPub)}
|
||||
@@ -63,7 +68,7 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return delivery.EncodeHaveNode(delivery.HaveNode{Record: &delivery.NodeRecord{Node: node, ServingKey: serving}})
|
||||
return engineclient.EncodeHaveNode(nodeseat.HaveByNodeNodeValue{Record: &nodeseat.NodeRecord{Node: node, ServingKey: serving}})
|
||||
}
|
||||
|
||||
// s2: made once, kept with their operator copies; a second pass makes nothing new and writes nothing again (s5).
|
||||
@@ -97,10 +102,10 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
if key2 != key1 || auth2 != auth1 || state.writes != writes {
|
||||
t.Errorf("a second pass changed the keys or wrote signers again (%d writes)", state.writes)
|
||||
}
|
||||
if _, err := delivery.DecodeSignerKey(state.keys[delivery.SignersKeyKey]); err != nil {
|
||||
if _, err := vaultclient.DecodeSignerKey(state.keys[meshvault.SignersByKeyKey()]); err != nil {
|
||||
t.Errorf("signers' key: %v", err)
|
||||
}
|
||||
authority, err := delivery.DecodeSignerAuthority(state.keys[delivery.SignersAuthorityKey])
|
||||
authority, err := vaultclient.DecodeSignerAuthority(state.keys[meshvault.SignersByAuthorityKey()])
|
||||
if err != nil {
|
||||
t.Fatalf("signers' authority: %v", err)
|
||||
}
|
||||
@@ -121,14 +126,14 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
for _, row := range []struct {
|
||||
name, caller, kind, node, refused string
|
||||
}{
|
||||
{"a module is not a node-engine", "ace.mesh-grants", delivery.KindNodeCertificate, "", "not a node-engine"},
|
||||
{"another node's certificate", "node.g14", delivery.KindNodeCertificate, "ace", "its own alone"},
|
||||
{"a node not on the private network", "node.plain", delivery.KindNodeCertificate, "", "not on the private network"},
|
||||
{"a node that states no serving key", "node.g14", delivery.KindNodeCertificate, "", "no record"},
|
||||
{"an enrolment has no caller yet", "node.ace", delivery.KindEnrolment, "", "ADR 0561 §5"},
|
||||
{"the authority is the vault's own", "node.ace", delivery.KindAuthority, "", "the vault's own"},
|
||||
{"an unproven caller", "", delivery.KindNodeCertificate, "", "proved"},
|
||||
{"the node's own certificate", "node.ace", delivery.KindNodeCertificate, "ace", ""},
|
||||
{"a module is not a node-engine", "ace.mesh-grants", vaultclient.KindNodeCertificate, "", "not a node-engine"},
|
||||
{"another node's certificate", "node.g14", vaultclient.KindNodeCertificate, "ace", "its own alone"},
|
||||
{"a node not on the private network", "node.plain", vaultclient.KindNodeCertificate, "", "not on the private network"},
|
||||
{"a node that states no serving key", "node.g14", vaultclient.KindNodeCertificate, "", "no record"},
|
||||
{"an enrolment has no caller yet", "node.ace", vaultclient.KindEnrolment, "", "ADR 0561 §5"},
|
||||
{"the authority is the vault's own", "node.ace", vaultclient.KindAuthority, "", "the vault's own"},
|
||||
{"an unproven caller", "", vaultclient.KindNodeCertificate, "", "proved"},
|
||||
{"the node's own certificate", "node.ace", vaultclient.KindNodeCertificate, "ace", ""},
|
||||
} {
|
||||
v.bus.raw = nil
|
||||
got, err := v.Sign(row.caller, SignArgs{Kind: row.kind, Node: row.node})
|
||||
@@ -144,7 +149,7 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", row.name, err)
|
||||
}
|
||||
if err := delivery.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
if err := vaultclient.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
servingPub, authority.Made.Add(1)); err != nil {
|
||||
t.Errorf("%s: the certificate does not check against signers' authority: %v", row.name, err)
|
||||
}
|
||||
@@ -158,15 +163,15 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
t.Errorf("%s: signed not said: %s", row.name, said)
|
||||
}
|
||||
rows, _ := v.Signatures(10)
|
||||
if len(rows) != 1 || rows[0].Caller != "node.ace" || rows[0].Kind != delivery.KindNodeCertificate || rows[0].Digest != got.Digest {
|
||||
if len(rows) != 1 || rows[0].Caller != "node.ace" || rows[0].Kind != vaultclient.KindNodeCertificate || rows[0].Digest != got.Digest {
|
||||
t.Errorf("%s: vault_signed lists %+v", row.name, rows)
|
||||
}
|
||||
status, _ := json.Marshal(v.SigningState())
|
||||
for _, seed := range seeds {
|
||||
raw, _ := base64.StdEncoding.DecodeString(seed)
|
||||
priv := ed25519.NewKeyFromSeed(raw)
|
||||
for _, text := range []string{string(answer), said, string(status), string(state.keys[delivery.SignersKeyKey]),
|
||||
string(state.keys[delivery.SignersAuthorityKey])} {
|
||||
for _, text := range []string{string(answer), said, string(status), string(state.keys[meshvault.SignersByKeyKey()]),
|
||||
string(state.keys[meshvault.SignersByAuthorityKey()])} {
|
||||
if strings.Contains(text, seed) || strings.Contains(text, base64.StdEncoding.EncodeToString(priv)) {
|
||||
t.Errorf("%s: a signing key's private half is in what the vault says: %s", row.name, text)
|
||||
}
|
||||
@@ -176,15 +181,15 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
// A serving key changed in the record is the one certified next, never the one before.
|
||||
other, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
records["ace"] = base64.StdEncoding.EncodeToString(other)
|
||||
got, err := v.Sign("node.ace", SignArgs{Kind: delivery.KindNodeCertificate})
|
||||
if err != nil || delivery.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
got, err := v.Sign("node.ace", SignArgs{Kind: vaultclient.KindNodeCertificate})
|
||||
if err != nil || vaultclient.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
servingPub, authority.Made.Add(1)) == nil {
|
||||
t.Errorf("a certificate for a serving key the record no longer states: %v", err)
|
||||
}
|
||||
|
||||
// s2: one secret lost beside the other, or both lost while signers publishes a key, makes nothing.
|
||||
lost := newVault(t, nil)
|
||||
lost.Signers = &signersState{keys: map[string][]byte{delivery.SignersKeyKey: state.keys[delivery.SignersKeyKey]}}
|
||||
lost.Signers = &signersState{keys: map[string][]byte{meshvault.SignersByKeyKey(): state.keys[meshvault.SignersByKeyKey()]}}
|
||||
if made, err := lost.EnsureSigning(); made || err == nil || !strings.Contains(err.Error(), "MISSING") {
|
||||
t.Errorf("keys made while signers publishes one: %v, %v", made, err)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ package main
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
)
|
||||
|
||||
@@ -234,20 +235,20 @@ func TestSecretOwnListsFingerprintsOfOwnSecretsOnly(t *testing.T) {
|
||||
handing := map[string]string{"controller": h("c"), "node.ace": h("n"), "g14.dunst": h("d")}
|
||||
sealedTo := v.Own.PublicKey
|
||||
have := func() []byte {
|
||||
var accounts []delivery.HandedAccount
|
||||
var accounts []engineclient.HandedAccount
|
||||
for _, u := range []string{"controller", "node.ace", "g14.dunst", "bad.user"} {
|
||||
if hash, ok := handing[u]; ok {
|
||||
accounts = append(accounts, delivery.HandedAccount{User: u, Hash: hash})
|
||||
accounts = append(accounts, engineclient.HandedAccount{User: u, Hash: hash})
|
||||
}
|
||||
}
|
||||
// The list's digest, as the node says it: another list (another hash or user) is another digest.
|
||||
list, _ := json.Marshal(accounts)
|
||||
handed, err := delivery.HandAccounts(accounts, sealedTo, Fingerprint(string(list)), time.Now())
|
||||
handed, err := engineclient.HandAccounts(accounts, sealedTo, Fingerprint(string(list)), time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := delivery.EncodeHave(delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
|
||||
UserList: &delivery.UserListWritten{Source: "k", Users: 3, Handed: &handed}})
|
||||
raw, _ := engineclient.EncodeHave(nodeseat.HaveByNodeModuleValue{Phase: engineclient.PhaseRunning, Health: engineclient.HealthOK,
|
||||
UserList: &nodeseat.UserListWritten{Source: "k", Users: 3, Handed: &handed}})
|
||||
return raw
|
||||
}
|
||||
v.Assignments = Mirror{Keys: func() ([]string, error) { return []string{"novox.nats", "novox.mesh-vault", "ace.dunst"}, nil }}
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
"grants part's wanted accounts alone (the placement is the assigner's, through its tool and events, ADR 0615), never the controller's lease (issue 1126)", r)
|
||||
}
|
||||
sources, _ := filepath.Glob("*.go")
|
||||
bucket := regexp.MustCompile(`stdio\.State\(|nats-io|jetstream|KeyValue`)
|
||||
bucket := regexp.MustCompile(`stdio\.State\(|nats-io|jetstream|\bKeyValue\b`)
|
||||
for _, f := range sources {
|
||||
if strings.HasSuffix(f, "_test.go") {
|
||||
continue
|
||||
@@ -182,8 +182,8 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
}
|
||||
// The signing keys' public halves, in signing.go alone: one read and one write of its own bucket (ADR 0550 §2).
|
||||
if f == "signing.go" {
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(delivery.SignersState).Get(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(delivery.SignersState).Put(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(meshvault.StateSigners).Get(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(meshvault.StateSigners).Put(", "", 1))
|
||||
}
|
||||
// The haves, in main.go alone: one read, one watch and their keys (own_drift), never a write; one watch of the
|
||||
// assignments, which the vault's rules read from memory once it caught up (issue 1126); and one watch of the wanted
|
||||
|
||||
@@ -3,8 +3,14 @@ module mesh-vault
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshvault v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
|
||||
@@ -36,24 +37,28 @@ import (
|
||||
// each verdict asks for, and acts on the persons' requests its tools put in its own state requests (requests.go) as they
|
||||
// are written or answered. It serves nothing (ADR 0436 §3).
|
||||
|
||||
// AcceptedOf reads a manifest-accepted body: mesh-sdk's delivery.ManifestAccepted, with the indexer's revision of the
|
||||
// verdict when the event carries it (a field mesh-sdk is asked to add; 0 until then).
|
||||
// AcceptedOf reads a manifest-accepted body: the seat mesh-catalog's typed event, with the indexer's revision of the
|
||||
// verdict when the event carries it (0 from an indexer that does not say it).
|
||||
func AcceptedOf(body []byte) (keeper.Accepted, error) {
|
||||
var a struct {
|
||||
delivery.ManifestAccepted
|
||||
Revision uint64 `json:"revision"`
|
||||
}
|
||||
var a catalog.ManifestAcceptedEvent
|
||||
if err := json.Unmarshal(body, &a); err != nil {
|
||||
return keeper.Accepted{}, err
|
||||
}
|
||||
if a.Module == "" {
|
||||
return keeper.Accepted{}, fmt.Errorf("a manifest-accepted names no module")
|
||||
}
|
||||
return keeper.Accepted{Module: a.Module, Digest: a.Digest, Revision: a.Revision, BusState: a.BusState}, nil
|
||||
var state []catalogue.BusStateEntry // the seat types an entry as the manifest wrote it; the keeper reads the catalogue's form
|
||||
if raw, err := json.Marshal(a.BusState); err != nil || json.Unmarshal(raw, &state) != nil {
|
||||
return keeper.Accepted{}, fmt.Errorf("the bus-state of the manifest-accepted of %s is not readable", a.Module)
|
||||
}
|
||||
if len(a.BusState) == 0 {
|
||||
state = nil
|
||||
}
|
||||
return keeper.Accepted{Module: a.Module, Digest: a.Digest, Revision: uint64(max(a.Revision, 0)), BusState: state}, nil
|
||||
}
|
||||
|
||||
// AcceptedSubject is the seat mesh-catalog's manifest-accepted on the shared events stream.
|
||||
var AcceptedSubject = "mesh.seat.mesh-catalog.event." + delivery.ManifestAcceptedEvent
|
||||
var AcceptedSubject = catalog.SubjectManifestAccepted
|
||||
|
||||
// The router's records of asks, read under this module's own name (ADR 0259 §3), as the mail sender reads them.
|
||||
const asksBucket = "messenger_asks"
|
||||
@@ -324,7 +329,7 @@ func Keep(ctx context.Context, k *keeper.Keeper, hear func(context.Context, func
|
||||
wake(asked)()
|
||||
return nil
|
||||
}
|
||||
if subject != AcceptedSubject && !strings.HasSuffix(subject, ".event."+delivery.ManifestAcceptedEvent) {
|
||||
if subject != AcceptedSubject && !strings.HasSuffix(subject, ".event."+catalog.EventManifestAccepted) {
|
||||
return nil
|
||||
}
|
||||
a, err := AcceptedOf(body)
|
||||
|
||||
@@ -3,6 +3,7 @@ module nats-tools
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel v0.0.0-00010101000000-000000000000
|
||||
@@ -14,6 +15,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go v0.0.0-20261104174344-ae5266e77639 // indirect
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
|
||||
Reference in New Issue
Block a user