Node-engine: logins, signers, enrolment tokens and redeems, and the grants part's sealed user list come from the vault and node-enroller clients and the grants state package; no file imports go/delivery (ADR 0634 §1, §4; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -32,16 +32,15 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
)
|
||||
|
||||
// ResourceID is the id the composer gives the bus's user list (mesh-sdk catalogue.BusUsersID); in a composition it may
|
||||
@@ -66,7 +65,7 @@ type Env struct {
|
||||
Write func(ctx context.Context, module string, resource json.RawMessage, body []byte) (digest string, changed bool, err error)
|
||||
// Report says what was written, in the module's have.
|
||||
Report func(ctx context.Context, module string, w Written)
|
||||
// Grants watches the grants part's state (its key delivery.GrantsUserListKey); nil writes nothing.
|
||||
// Grants watches the grants part's state (its key grantsstate.GrantsUserListKey); nil writes nothing.
|
||||
Grants agent.Watcher
|
||||
// Fetch reads an object of the grants part's bucket by its name; Open opens a box sealed to this node's key; Key is
|
||||
// this node's sealing key's public half (SPKI PEM), said in the have so the grants part seals to it ("" unknown).
|
||||
@@ -103,7 +102,7 @@ type Follower struct {
|
||||
mod string
|
||||
said map[string]bool
|
||||
// grants is the grants part's key as last heard (nil when none).
|
||||
grants *delivery.GrantsUserList
|
||||
grants *grantsstate.GrantsUserList
|
||||
grantsRev uint64
|
||||
// unwatched is why the grants part's key is not watched now; empty while it is, or where it need not be.
|
||||
unwatched string
|
||||
@@ -124,7 +123,7 @@ func New(env Env) *Follower {
|
||||
said: map[string]bool{}}
|
||||
if env.Grants != nil && env.Kept != nil {
|
||||
if raw, rev := env.Kept(); raw != nil {
|
||||
if g, err := delivery.DecodeGrantsUserList(raw); err == nil {
|
||||
if g, err := grantsstate.DecodeGrantsUserList(raw); err == nil {
|
||||
f.grants, f.grantsRev = &g, rev
|
||||
}
|
||||
}
|
||||
@@ -180,7 +179,7 @@ func (f *Follower) Run(ctx context.Context) error {
|
||||
return
|
||||
}
|
||||
opened = true
|
||||
ch, err := f.env.Grants.Watch(ctx, delivery.GrantsUserListKey)
|
||||
ch, err := f.env.Grants.Watch(ctx, grantsstate.GrantsUserListKey)
|
||||
if err != nil {
|
||||
failed(err.Error())
|
||||
return
|
||||
@@ -268,7 +267,7 @@ func (f *Follower) takeGrants(u agent.Update) {
|
||||
f.sayOnceLocked("the grants part's user-list key is deleted, so the list loaded is kept")
|
||||
return
|
||||
}
|
||||
g, err := delivery.DecodeGrantsUserList(u.Value)
|
||||
g, err := grantsstate.DecodeGrantsUserList(u.Value)
|
||||
if err != nil {
|
||||
f.sayOnceLocked("the grants part's user-list key cannot be read, so the list loaded is kept: " + err.Error())
|
||||
return
|
||||
@@ -312,7 +311,7 @@ func reportable(prev, w Written) bool {
|
||||
// says why in the have.
|
||||
func (f *Follower) settleGrants(ctx context.Context, again bool) {
|
||||
f.mu.Lock()
|
||||
var g delivery.GrantsUserList
|
||||
var g grantsstate.GrantsUserList
|
||||
heard := f.grants != nil
|
||||
if heard {
|
||||
g = *f.grants
|
||||
@@ -343,8 +342,8 @@ func (f *Follower) settleGrants(ctx context.Context, again bool) {
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
w := Written{Source: delivery.GrantsUserListSource, Revision: int64(rev), Wanted: g.Digest, At: f.env.Now().UTC(),
|
||||
Follows: delivery.FollowGrants, Key: f.key(), Unwatched: f.unwatchedNow()}
|
||||
w := Written{Source: grantsstate.GrantsUserListSource, Revision: int64(rev), Wanted: g.Digest, At: f.env.Now().UTC(),
|
||||
Follows: grantsstate.FollowGrants, Key: f.key(), Unwatched: f.unwatchedNow()}
|
||||
var err error
|
||||
var changed bool
|
||||
resource, rerr := mode0600(file.resource)
|
||||
@@ -357,14 +356,14 @@ func (f *Follower) settleGrants(ctx context.Context, again bool) {
|
||||
err = fmt.Errorf("the grants part's key names no list yet; the list on the machine is kept")
|
||||
case f.env.Fetch == nil || f.env.Open == nil:
|
||||
err = fmt.Errorf("this engine cannot read or open the grants part's list")
|
||||
case g.SealedTo != "" && w.Key != "" && g.SealedTo != delivery.SealingKeyID(w.Key):
|
||||
case g.SealedTo != "" && w.Key != "" && g.SealedTo != grantsstate.SealingKeyID(w.Key):
|
||||
err = fmt.Errorf("the grants part's list is sealed to another key than this node's (%s); the list on the machine is kept until it seals to this one", g.SealedTo)
|
||||
}
|
||||
var body []byte
|
||||
if err == nil {
|
||||
var object []byte
|
||||
if object, err = f.env.Fetch(ctx, g.Object); err == nil {
|
||||
body, err = delivery.OpenUserList(object, g.Digest, f.env.Open)
|
||||
body, err = grantsstate.OpenUserList(object, g.Digest, f.env.Open)
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("the grants part's list %s could not be had: %w", short(g.Digest), err)
|
||||
@@ -395,7 +394,7 @@ func (f *Follower) settleGrants(ctx context.Context, again bool) {
|
||||
f.mod, f.state = file.module, w
|
||||
f.written = err == nil
|
||||
f.mu.Unlock()
|
||||
at := fmt.Sprintf("%s@%d", delivery.GrantsUserListSource, rev)
|
||||
at := fmt.Sprintf("%s@%d", grantsstate.GrantsUserListSource, rev)
|
||||
if err != nil {
|
||||
f.sayOnce(fmt.Sprintf("the bus's user list of %s could not be written: %v", at, err))
|
||||
} else if changed {
|
||||
|
||||
@@ -5,15 +5,14 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
)
|
||||
|
||||
const node = "novox"
|
||||
@@ -126,50 +125,50 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
|
||||
// this node runs declares; the list a composition carries is never written, whatever becomes of the grants key.
|
||||
// B17: the file's place is read from what runs, as the agent holds it, never from a composition bucket.
|
||||
{name: "the grants part's list is written into the file the bus module it runs declares",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: three}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
|
||||
{name: "a bus module it runs by digest still names the file",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two, byDigest: true}}, grants: []gput{{follow: delivery.FollowGrants, content: three}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two, byDigest: true}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
|
||||
{name: "a newer composition's list is not written",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}},
|
||||
after: []put{{module: "nats", fp: fpOf("b"), content: five}},
|
||||
grants: []gput{{follow: delivery.FollowGrants, content: three}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
|
||||
grants: []gput{{follow: grantsstate.FollowGrants, content: three}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
|
||||
{name: "a composition without the list is ignored", before: []put{{module: "web", fp: fpOf("a"), content: ""}},
|
||||
grants: []gput{{follow: delivery.FollowGrants, content: three}}},
|
||||
grants: []gput{{follow: grantsstate.FollowGrants, content: three}}},
|
||||
{name: "another node's composition declares no file",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two, asNode: "ace"}}, grants: []gput{{follow: delivery.FollowGrants, content: three}}},
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two, asNode: "ace"}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three}}},
|
||||
{name: "an unreadable grants key with none heard before writes nothing, never the composition's list",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{unreadable: true}},
|
||||
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: delivery.GrantsUserListSource},
|
||||
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
|
||||
{name: "a refused watch with no key kept writes nothing and says it is not watched",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grantsRefused: true, wantUnwatched: true,
|
||||
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: delivery.GrantsUserListSource},
|
||||
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
|
||||
{name: "a refused watch keeps the key last heard",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grantsRefused: true, wantUnwatched: true,
|
||||
kept: &gput{follow: delivery.FollowGrants, content: three},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
|
||||
kept: &gput{follow: grantsstate.FollowGrants, content: three},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
|
||||
{name: "a grants key that turns unreadable keeps the loaded list",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}},
|
||||
grants: []gput{{follow: delivery.FollowGrants, content: three}, {unreadable: true, after: true}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
|
||||
grants: []gput{{follow: grantsstate.FollowGrants, content: three}, {unreadable: true, after: true}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
|
||||
{name: "a grants key that is deleted keeps the loaded list",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}},
|
||||
grants: []gput{{follow: delivery.FollowGrants, content: three}, {deleted: true, after: true}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
|
||||
grants: []gput{{follow: grantsstate.FollowGrants, content: three}, {deleted: true, after: true}},
|
||||
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
|
||||
{name: "a grants list that drops more than half is refused and the file kept",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: one}},
|
||||
onDisk: five, wantUsers: 2, wantErr: "drops more than half", wantRefused: true, wantSource: delivery.GrantsUserListSource},
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: one}},
|
||||
onDisk: five, wantUsers: 2, wantErr: "drops more than half", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
|
||||
{name: "a failed write is said and kept", before: []put{{module: "nats", fp: fpOf("a"), content: two}},
|
||||
grants: []gput{{follow: delivery.FollowGrants, content: three}}, failWrite: true,
|
||||
wantWrites: []string{three}, wantUsers: 4, wantErr: "disk full", wantSource: delivery.GrantsUserListSource},
|
||||
grants: []gput{{follow: grantsstate.FollowGrants, content: three}}, failWrite: true,
|
||||
wantWrites: []string{three}, wantUsers: 4, wantErr: "disk full", wantSource: grantsstate.GrantsUserListSource},
|
||||
{name: "a grants list that does not open to its digest is refused and the file kept",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: three, badDigest: true}},
|
||||
onDisk: two, wantErr: "could not be had", wantRefused: true, wantSource: delivery.GrantsUserListSource},
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three, badDigest: true}},
|
||||
onDisk: two, wantErr: "could not be had", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
|
||||
{name: "a grants list sealed to another key is refused and the file kept",
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: three, otherKey: true}},
|
||||
onDisk: two, wantErr: "sealed to another key", wantRefused: true, wantSource: delivery.GrantsUserListSource},
|
||||
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three, otherKey: true}},
|
||||
onDisk: two, wantErr: "sealed to another key", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var mu sync.Mutex
|
||||
@@ -194,7 +193,7 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
|
||||
}
|
||||
for i, g := range gs {
|
||||
if g.unreadable || g.deleted {
|
||||
u := agent.Update{Key: delivery.GrantsUserListKey, Value: []byte("not a user-list key"), Revision: uint64(i + 1), Deleted: g.deleted}
|
||||
u := agent.Update{Key: grantsstate.GrantsUserListKey, Value: []byte("not a user-list key"), Revision: uint64(i + 1), Deleted: g.deleted}
|
||||
grantsAfter = append(grantsAfter, u)
|
||||
continue
|
||||
}
|
||||
@@ -203,21 +202,21 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
|
||||
other, _ := seal.GenerateKeyPair()
|
||||
to = other.PublicKey
|
||||
}
|
||||
digest := delivery.UserListDigest([]byte(g.content))
|
||||
digest := grantsstate.UserListDigest([]byte(g.content))
|
||||
if g.badDigest {
|
||||
digest = delivery.UserListDigest([]byte("another list"))
|
||||
digest = grantsstate.UserListDigest([]byte("another list"))
|
||||
}
|
||||
object, err := delivery.SealUserList([]byte(g.content), to)
|
||||
object, err := grantsstate.SealUserList([]byte(g.content), to)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sealed[delivery.UserListObjectName(digest)] = object
|
||||
raw, err := delivery.EncodeGrantsUserList(delivery.GrantsUserList{Follow: g.follow, Digest: digest,
|
||||
Object: delivery.UserListObjectName(digest), SealedTo: delivery.SealingKeyID(to), Node: node, Revision: uint64(i + 1)})
|
||||
sealed[grantsstate.UserListObjectName(digest)] = object
|
||||
raw, err := grantsstate.EncodeGrantsUserList(grantsstate.GrantsUserList{Follow: g.follow, Digest: digest,
|
||||
Object: grantsstate.UserListObjectName(digest), SealedTo: grantsstate.SealingKeyID(to), Node: node, Revision: uint64(i + 1)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u := agent.Update{Key: delivery.GrantsUserListKey, Value: raw, Revision: uint64(i + 1)}
|
||||
u := agent.Update{Key: grantsstate.GrantsUserListKey, Value: raw, Revision: uint64(i + 1)}
|
||||
switch {
|
||||
case c.kept != nil && i == len(gs)-1:
|
||||
keptRaw = raw
|
||||
@@ -296,7 +295,7 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
|
||||
}
|
||||
if len(grantsAfter) > 0 {
|
||||
for until := time.Now().Add(5 * time.Second); ; time.Sleep(5 * time.Millisecond) {
|
||||
if _, st := fl.State(); st.Source == delivery.GrantsUserListSource || time.Now().After(until) {
|
||||
if _, st := fl.State(); st.Source == grantsstate.GrantsUserListSource || time.Now().After(until) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -355,11 +354,11 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
|
||||
t.Fatalf("an apply while following %s holds the composition's list: %v", c.wantFollows, hasList)
|
||||
}
|
||||
// A list on the machine that cannot be read stops the apply; it is never left out to be removed.
|
||||
if c.wantFollows == delivery.FollowGrants {
|
||||
if c.wantFollows == grantsstate.FollowGrants {
|
||||
readErr := New(Env{Node: node, OnDisk: func(json.RawMessage) ([]byte, error) {
|
||||
return nil, errors.New("permission denied")
|
||||
}})
|
||||
readErr.grants = &delivery.GrantsUserList{Follow: delivery.FollowGrants}
|
||||
readErr.grants = &grantsstate.GrantsUserList{Follow: grantsstate.FollowGrants}
|
||||
if _, err := readErr.Hold(cmp); err == nil || !strings.Contains(err.Error(), "could not be read") {
|
||||
t.Fatalf("an unreadable list on the machine was held as %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user