Vault: keep a rotation's hand acts in mesh-sdk's NodeLogin.Acts, and test a tunnel without its key or range
mesh-sdk now carries the hand acts that warranted a node's rotations as NodeLogin.Acts (ADR 0583 §2), so the vault's own `acts` field beside the codec is gone: a used act is refused from the key's Acts, and a key written without them keeps the ones recorded. The seat's tunnel takes all five fields; the wire table refuses an issue missing the machine's tunnel key or the range as well. Built against mesh-sdk 4224665; mesh-vault stays at 134 unit tests. (issue 774, 774/S43, 774/S46, review round 1b) Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -25,26 +25,6 @@ import (
|
||||
type loginsState struct {
|
||||
mu sync.Mutex
|
||||
keys map[string]NodeLogin
|
||||
acts map[string][]string
|
||||
}
|
||||
|
||||
func (l *loginsState) Acts(node string) ([]string, error) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return slices.Clone(l.acts[node]), nil
|
||||
}
|
||||
|
||||
func (l *loginsState) RecordAct(n NodeLogin, act string) error {
|
||||
if err := l.Put(n); err != nil {
|
||||
return err
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.acts == nil {
|
||||
l.acts = map[string][]string{}
|
||||
}
|
||||
l.acts[n.Node] = append(l.acts[n.Node], act)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *loginsState) Get(node string) (*NodeLogin, error) {
|
||||
@@ -78,7 +58,6 @@ func (l *loginsState) Delete(node string) error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
delete(l.keys, node)
|
||||
delete(l.acts, node)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -231,6 +210,8 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
{"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"},
|
||||
{"a tunnel without the hub's endpoint", "enrolment-token", holder, map[string]any{"node": "new1", "tunnel": without("hub_endpoint")}, nil, nil, "where the hub's tunnel is dialled"},
|
||||
{"a tunnel without the address", "enrolment-token", holder, map[string]any{"node": "new1", "tunnel": without("address")}, nil, nil, "this machine's address"},
|
||||
{"a tunnel without the machine's tunnel key", "enrolment-token", holder, map[string]any{"node": "new1", "tunnel": without("key")}, nil, nil, "the tunnel key it was issued for"},
|
||||
{"a tunnel without the range", "enrolment-token", holder, map[string]any{"node": "new1", "tunnel": without("range")}, nil, nil, "the private network's range"},
|
||||
{"a tunnel with a field it has not", "enrolment-token", holder, map[string]any{"node": "new1", "tunnel": with(tunnel, map[string]any{"port": 1})}, nil, nil, "not a first tunnel"},
|
||||
// Issue answers what the machine must trust; sealed and drop as the holder sends them, the switch as a word.
|
||||
{"issue", "enrolment-token", holder, map[string]any{"node": "new1", "expires": "2026-11-02T12:00:00Z", "tunnel": tunnel}, &issued, []string{"node", "broker", "fingerprint", "signer"}, ""},
|
||||
@@ -452,7 +433,7 @@ func TestANodesLoginRotatesOneAtATimeAndEndsWithTheListWithoutTheOldOne(t *testi
|
||||
t.Error("a finished rotation was abandoned")
|
||||
}
|
||||
// Blocker 5 (ADR 0583 §2): the act is recorded in g14's key and warrants no second rotation.
|
||||
if acts, _ := e.logins.Acts("g14"); !slices.Equal(acts, []string{"a1"}) {
|
||||
if acts := e.logins.keys["g14"].Acts; !slices.Equal(acts, []string{"a1"}) {
|
||||
t.Errorf("g14's key records the acts %v", acts)
|
||||
}
|
||||
if _, err := ask("g14", "a1"); err == nil || !strings.Contains(err.Error(), "already") {
|
||||
|
||||
@@ -52,14 +52,10 @@ const (
|
||||
// and while a rotation runs the other login with its stage. Never a value or a hash.
|
||||
type NodeLogin = delivery.NodeLogin
|
||||
|
||||
// NodeLoginStore is the vault's state `node-logins`, by node. Beside mesh-sdk's NodeLogin each key keeps `acts`, the ids
|
||||
// of the hand acts that warranted the node's rotations (ADR 0583 §2: every rotation is recorded), which Put keeps.
|
||||
// NodeLoginStore is the vault's state `node-logins`, by node.
|
||||
type NodeLoginStore interface {
|
||||
Get(node string) (*NodeLogin, error)
|
||||
Put(n NodeLogin) error
|
||||
// Acts are the hand acts recorded in node's key; RecordAct writes n with act added to them.
|
||||
Acts(node string) ([]string, error)
|
||||
RecordAct(n NodeLogin, act string) error
|
||||
Delete(node string) error
|
||||
Keys() ([]string, error)
|
||||
}
|
||||
@@ -79,32 +75,8 @@ func (busNodeLogins) Get(node string) (*NodeLogin, error) {
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
func (b busNodeLogins) Put(n NodeLogin) error {
|
||||
acts, err := b.Acts(n.Node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.write(n, acts)
|
||||
}
|
||||
|
||||
func (busNodeLogins) Acts(node string) ([]string, error) {
|
||||
e, err := stdio.State(NodeLoginsState).Get(node)
|
||||
if err != nil || e == nil {
|
||||
return nil, err
|
||||
}
|
||||
return actsOf(e.Value)
|
||||
}
|
||||
|
||||
func (b busNodeLogins) RecordAct(n NodeLogin, act string) error {
|
||||
acts, err := b.Acts(n.Node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.write(n, append(acts, act))
|
||||
}
|
||||
|
||||
func (busNodeLogins) write(n NodeLogin, acts []string) error {
|
||||
raw, err := encodeLoginKey(n, acts)
|
||||
func (busNodeLogins) Put(n NodeLogin) error {
|
||||
raw, err := delivery.EncodeNodeLogin(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -112,31 +84,6 @@ func (busNodeLogins) write(n NodeLogin, acts []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// encodeLoginKey is n through mesh-sdk's codec with the hand acts that warranted its rotations beside it.
|
||||
func encodeLoginKey(n NodeLogin, acts []string) ([]byte, error) {
|
||||
raw, err := delivery.EncodeNodeLogin(n)
|
||||
if err != nil || len(acts) == 0 {
|
||||
return raw, err
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m["acts"] = acts
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// actsOf are the hand acts a key of node-logins records.
|
||||
func actsOf(raw []byte) ([]string, error) {
|
||||
var k struct {
|
||||
Acts []string `json:"acts"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &k); err != nil {
|
||||
return nil, fmt.Errorf("a key of %s is not readable: %w", NodeLoginsState, err)
|
||||
}
|
||||
return k.Acts, nil
|
||||
}
|
||||
|
||||
func (busNodeLogins) Delete(node string) error { return stdio.State(NodeLoginsState).Delete(node) }
|
||||
|
||||
func (busNodeLogins) Keys() ([]string, error) { return stdio.State(NodeLoginsState).Keys() }
|
||||
@@ -180,10 +127,20 @@ func (v *Vault) getLogin(node string) (*NodeLogin, error) {
|
||||
return v.Logins.Get(node)
|
||||
}
|
||||
|
||||
// putLogin writes node's key; a key written without its hand acts keeps the ones recorded (ADR 0583 §2).
|
||||
func (v *Vault) putLogin(n NodeLogin) error {
|
||||
if v.Logins == nil {
|
||||
return errors.New("the vault has no state " + NodeLoginsState + " to write")
|
||||
}
|
||||
if n.Acts == nil {
|
||||
was, err := v.Logins.Get(n.Node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if was != nil {
|
||||
n.Acts = was.Acts
|
||||
}
|
||||
}
|
||||
return v.Logins.Put(n)
|
||||
}
|
||||
|
||||
@@ -372,17 +329,13 @@ func (v *Vault) recordAct(node, act string) error {
|
||||
if v.Logins == nil {
|
||||
return errors.New("the vault has no state " + NodeLoginsState + " to record the rotation in")
|
||||
}
|
||||
used, err := v.Logins.Acts(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if slices.Contains(used, act) {
|
||||
return fmt.Errorf("the hand act %s warranted a rotation of %s already: record a new one", act, node)
|
||||
}
|
||||
nl, err := v.getLogin(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nl != nil && slices.Contains(nl.Acts, act) {
|
||||
return fmt.Errorf("the hand act %s warranted a rotation of %s already: record a new one", act, node)
|
||||
}
|
||||
if nl == nil {
|
||||
cur, err := v.currentLogin(node)
|
||||
if err != nil {
|
||||
@@ -390,7 +343,8 @@ func (v *Vault) recordAct(node, act string) error {
|
||||
}
|
||||
nl = &NodeLogin{Node: node, Login: cur, Since: v.Now().UTC()}
|
||||
}
|
||||
if err := v.Logins.RecordAct(*nl, act); err != nil {
|
||||
nl.Acts = append(slices.Clone(nl.Acts), act)
|
||||
if err := v.Logins.Put(*nl); err != nil {
|
||||
return fmt.Errorf("the hand act %s could not be recorded in %s %s: %w", act, NodeLoginsState, node, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user