From d8b4bb5487d23e4370aebde88f460a6e4e9e8ac0 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 16:07:00 +0100 Subject: [PATCH] =?UTF-8?q?mesh-vault=20reads=20signers,=20node=20logins,?= =?UTF-8?q?=20tokens,=20placement,=20haves=20and=20the=20handing=20through?= =?UTF-8?q?=20the=20seats'=20types=20and=20clients;=20its=20holder=20names?= =?UTF-8?q?=20are=20its=20own=20constants=20(B5g;=20ADR=200634=20=C2=A71,?= =?UTF-8?q?=20=C2=A74;=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- modules/mesh-vault/cmd/mesh-vault/adopt.go | 13 ++-- .../mesh-vault/cmd/mesh-vault/enrolment.go | 22 +++--- .../cmd/mesh-vault/enrolment_test.go | 39 +++++----- .../cmd/mesh-vault/enrolment_tools.go | 7 +- .../cmd/mesh-vault/mint_integration_test.go | 2 +- .../mesh-vault/cmd/mesh-vault/move_test.go | 16 ++-- .../mesh-vault/cmd/mesh-vault/noderotation.go | 27 +++---- modules/mesh-vault/cmd/mesh-vault/owntake.go | 28 +++++-- modules/mesh-vault/cmd/mesh-vault/placed.go | 30 +++---- .../mesh-vault/cmd/mesh-vault/placed_test.go | 11 +-- .../cmd/mesh-vault/placementview.go | 8 +- .../mesh-vault/rotation_integration_test.go | 24 +++--- modules/mesh-vault/cmd/mesh-vault/signing.go | 78 ++++++++++--------- .../mesh-vault/cmd/mesh-vault/signing_test.go | 59 +++++++------- .../mesh-vault/cmd/mesh-vault/tools_test.go | 13 ++-- .../mesh-vault/cmd/mesh-vault/vault_test.go | 6 +- modules/mesh-vault/go.mod | 8 +- 17 files changed, 216 insertions(+), 175 deletions(-) diff --git a/modules/mesh-vault/cmd/mesh-vault/adopt.go b/modules/mesh-vault/cmd/mesh-vault/adopt.go index 60129cf92..1367aee81 100644 --- a/modules/mesh-vault/cmd/mesh-vault/adopt.go +++ b/modules/mesh-vault/cmd/mesh-vault/adopt.go @@ -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 } diff --git a/modules/mesh-vault/cmd/mesh-vault/enrolment.go b/modules/mesh-vault/cmd/mesh-vault/enrolment.go index 04c48b236..a562add1a 100644 --- a/modules/mesh-vault/cmd/mesh-vault/enrolment.go +++ b/modules/mesh-vault/cmd/mesh-vault/enrolment.go @@ -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//mesh-host/`, 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) diff --git a/modules/mesh-vault/cmd/mesh-vault/enrolment_test.go b/modules/mesh-vault/cmd/mesh-vault/enrolment_test.go index e25511f69..080e731a4 100644 --- a/modules/mesh-vault/cmd/mesh-vault/enrolment_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/enrolment_test.go @@ -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") diff --git a/modules/mesh-vault/cmd/mesh-vault/enrolment_tools.go b/modules/mesh-vault/cmd/mesh-vault/enrolment_tools.go index efeaa308e..59160165f 100644 --- a/modules/mesh-vault/cmd/mesh-vault/enrolment_tools.go +++ b/modules/mesh-vault/cmd/mesh-vault/enrolment_tools.go @@ -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 { diff --git a/modules/mesh-vault/cmd/mesh-vault/mint_integration_test.go b/modules/mesh-vault/cmd/mesh-vault/mint_integration_test.go index 8e42369c9..a7b4549ad 100644 --- a/modules/mesh-vault/cmd/mesh-vault/mint_integration_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/mint_integration_test.go @@ -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" diff --git a/modules/mesh-vault/cmd/mesh-vault/move_test.go b/modules/mesh-vault/cmd/mesh-vault/move_test.go index c40028456..2f55ed3bc 100644 --- a/modules/mesh-vault/cmd/mesh-vault/move_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/move_test.go @@ -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) } } diff --git a/modules/mesh-vault/cmd/mesh-vault/noderotation.go b/modules/mesh-vault/cmd/mesh-vault/noderotation.go index 8409482d8..22468b75a 100644 --- a/modules/mesh-vault/cmd/mesh-vault/noderotation.go +++ b/modules/mesh-vault/cmd/mesh-vault/noderotation.go @@ -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) } diff --git a/modules/mesh-vault/cmd/mesh-vault/owntake.go b/modules/mesh-vault/cmd/mesh-vault/owntake.go index 1e4f6f2f4..3f65249e7 100644 --- a/modules/mesh-vault/cmd/mesh-vault/owntake.go +++ b/modules/mesh-vault/cmd/mesh-vault/owntake.go @@ -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`) 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 +} diff --git a/modules/mesh-vault/cmd/mesh-vault/placed.go b/modules/mesh-vault/cmd/mesh-vault/placed.go index 4c072fa9b..febd082e1 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placed.go +++ b/modules/mesh-vault/cmd/mesh-vault/placed.go @@ -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` 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.`. 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) } } diff --git a/modules/mesh-vault/cmd/mesh-vault/placed_test.go b/modules/mesh-vault/cmd/mesh-vault/placed_test.go index 122d9f247..36c0aeef9 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placed_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/placed_test.go @@ -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) } diff --git a/modules/mesh-vault/cmd/mesh-vault/placementview.go b/modules/mesh-vault/cmd/mesh-vault/placementview.go index 81003eb5c..d4d224d08 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placementview.go +++ b/modules/mesh-vault/cmd/mesh-vault/placementview.go @@ -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 != "" { diff --git a/modules/mesh-vault/cmd/mesh-vault/rotation_integration_test.go b/modules/mesh-vault/cmd/mesh-vault/rotation_integration_test.go index 6972ba798..cc185eba1 100644 --- a/modules/mesh-vault/cmd/mesh-vault/rotation_integration_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/rotation_integration_test.go @@ -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) } diff --git a/modules/mesh-vault/cmd/mesh-vault/signing.go b/modules/mesh-vault/cmd/mesh-vault/signing.go index 8500f12c2..8ed4b0ff8 100644 --- a/modules/mesh-vault/cmd/mesh-vault/signing.go +++ b/modules/mesh-vault/cmd/mesh-vault/signing.go @@ -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"` diff --git a/modules/mesh-vault/cmd/mesh-vault/signing_test.go b/modules/mesh-vault/cmd/mesh-vault/signing_test.go index 815cf5bc2..8a3d27930 100644 --- a/modules/mesh-vault/cmd/mesh-vault/signing_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/signing_test.go @@ -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) } diff --git a/modules/mesh-vault/cmd/mesh-vault/tools_test.go b/modules/mesh-vault/cmd/mesh-vault/tools_test.go index 4d48b4ed2..9ff081e36 100644 --- a/modules/mesh-vault/cmd/mesh-vault/tools_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/tools_test.go @@ -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 }} diff --git a/modules/mesh-vault/cmd/mesh-vault/vault_test.go b/modules/mesh-vault/cmd/mesh-vault/vault_test.go index 1eabe93d5..30c44b300 100644 --- a/modules/mesh-vault/cmd/mesh-vault/vault_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/vault_test.go @@ -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 diff --git a/modules/mesh-vault/go.mod b/modules/mesh-vault/go.mod index 41e94d087..0e2eb7cae 100644 --- a/modules/mesh-vault/go.mod +++ b/modules/mesh-vault/go.mod @@ -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