mesh-vault reads signers, node logins, tokens, placement, haves and the handing through the seats' types and clients; its holder names are its own constants (B5g; ADR 0634 §1, §4; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -5,7 +5,7 @@ package main
|
||||
// The controller's own account, the node accounts and the enrolled devices were never handed to the vault, so it cannot
|
||||
// hash them, and the grants part's list would leave them out. The one part that has their hashes is the node-engine of
|
||||
// the node holding mesh-broker, in the list the bus loaded: it hands every loaded user's hash in the bus module's have,
|
||||
// sealed to this vault's key (mesh-sdk delivery.AccountsHanded), which it learns from the vault's answer to node-key.
|
||||
// sealed to this vault's key (mesh-sdk nodeseat.AccountsHanded), which it learns from the vault's answer to node-key.
|
||||
// The vault reads that have (its manifest `reads` node-engine.have), as a watch of the bus module's have and again by
|
||||
// hand with the tool `account_adopt`, and opens it with its own key.
|
||||
//
|
||||
@@ -19,11 +19,10 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// HaveRead is the node-engines' have bucket as the vault reads it (its manifest's `reads`).
|
||||
@@ -186,12 +185,12 @@ func (v *Vault) adopt(node string, raw []byte, by string, force bool) (Adoption,
|
||||
return Adoption{}, fmt.Errorf("%s does not hold the bus (the assignments name %s): its handing is not adopted",
|
||||
node, bus)
|
||||
}
|
||||
have, err := delivery.DecodeHave(raw)
|
||||
have, err := engineclient.DecodeHave(raw)
|
||||
if err != nil {
|
||||
return Adoption{}, fmt.Errorf("the have of %s on %s is not a have: %w", BusModule, node, err)
|
||||
}
|
||||
if have.UserList != nil {
|
||||
v.LoadedListHeard(have.UserList.Revision)
|
||||
v.LoadedListHeard(uint64(have.UserList.Revision))
|
||||
}
|
||||
if have.UserList == nil || have.UserList.Handed == nil {
|
||||
return Adoption{}, fmt.Errorf("the have of %s on %s hands no hashes yet (its node-engine hands them once it "+
|
||||
@@ -205,7 +204,7 @@ func (v *Vault) adopt(node string, raw []byte, by string, force bool) (Adoption,
|
||||
if seen && !force {
|
||||
return Adoption{}, nil
|
||||
}
|
||||
accounts, err := delivery.OpenHanded(h, v.Own.PublicKey, v.Own.PrivateKey)
|
||||
accounts, err := engineclient.OpenHanded(h, v.Own.PublicKey, v.Own.PrivateKey)
|
||||
if err != nil {
|
||||
return Adoption{}, err
|
||||
}
|
||||
@@ -232,7 +231,7 @@ func (v *Vault) adopt(node string, raw []byte, by string, force bool) (Adoption,
|
||||
a.Refused[user] = why
|
||||
}
|
||||
for _, acc := range accounts {
|
||||
if !delivery.IsBcrypt(acc.Hash) || !namePart.MatchString(acc.User) {
|
||||
if !engineclient.IsBcrypt(acc.Hash) || !namePart.MatchString(acc.User) {
|
||||
refuse(acc.User, "not a bus user with a bcrypt hash")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -21,25 +21,27 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// The names enrolment gives: the module that holds the seat mesh-node-enroller, the prefix of a token's bus user, and the
|
||||
// vault's state of each node's logins.
|
||||
const (
|
||||
EnrolmentModule = delivery.EnrolmentModule
|
||||
EnrolUserPrefix = delivery.EnrolmentUserPrefix
|
||||
NodeLoginsState = delivery.NodeLoginsState
|
||||
EnrolmentModule = "mesh-enrolment"
|
||||
EnrolUserPrefix = enrollerclient.UserPrefix
|
||||
NodeLoginsState = meshvault.StateNodeLogins
|
||||
// MeshHostModule is the module a node's own login secret is kept under (`own/<node>/mesh-host/<login>`, ADR 0583 §3).
|
||||
MeshHostModule = "mesh-host"
|
||||
// vaultModule is this module: the holder of the seat mesh-vault, whose states the reads and the bus names carry.
|
||||
vaultModule = "mesh-vault"
|
||||
)
|
||||
|
||||
// Events the vault says for enrolment's accounts are account-hashed (accounts.go), never carrying a hash or a value.
|
||||
@@ -144,7 +146,7 @@ type TokenBase struct {
|
||||
type EnrolmentTokenArgs struct {
|
||||
Node string
|
||||
Expires string
|
||||
Tunnel *delivery.TokenTunnel
|
||||
Tunnel *enrollerclient.TokenTunnel
|
||||
Sealed bool
|
||||
Drop bool
|
||||
}
|
||||
@@ -197,7 +199,7 @@ func (v *Vault) issueEnrolmentToken(caller string, a EnrolmentTokenArgs) (Enrolm
|
||||
return EnrolmentTokenAnswer{}, errors.New("enrolment-token at issue carries the machine's first tunnel (`tunnel`: key, address, range, " +
|
||||
"hub_key, hub_endpoint), so the machine reaches the bus over the private network (novox/hq ADR 0587 §3)")
|
||||
}
|
||||
if missing := (delivery.Token{Version: 1, Broker: base.Broker, Fingerprint: base.Fingerprint, Signer: signer,
|
||||
if missing := (enrollerclient.Token{Version: 1, Broker: base.Broker, Fingerprint: base.Fingerprint, Signer: signer,
|
||||
Secret: "-", Tunnel: a.Tunnel}).Missing(); len(missing) > 0 {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the token of %s would lack %s: nothing was issued", a.Node, strings.Join(missing, ", "))
|
||||
}
|
||||
@@ -264,7 +266,7 @@ func (v *Vault) showEnrolmentToken(caller, node string) (EnrolmentTokenAnswer, e
|
||||
if err != nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the token carries the vault's signing key, which is not held yet: %w", err)
|
||||
}
|
||||
var tunnel *delivery.TokenTunnel
|
||||
var tunnel *enrollerclient.TokenTunnel
|
||||
if trec, theld, err := v.Store.Read(enrolTunnel(node)); err != nil {
|
||||
return EnrolmentTokenAnswer{}, err
|
||||
} else if theld {
|
||||
@@ -272,13 +274,13 @@ func (v *Vault) showEnrolmentToken(caller, node string) (EnrolmentTokenAnswer, e
|
||||
if err != nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the first tunnel of %s does not open with the vault's key: %w", node, err)
|
||||
}
|
||||
tunnel = &delivery.TokenTunnel{}
|
||||
tunnel = &enrollerclient.TokenTunnel{}
|
||||
if err := json.Unmarshal([]byte(raw), tunnel); err != nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the first tunnel of %s is not readable: %w", node, err)
|
||||
}
|
||||
}
|
||||
signer := keys.key.Public().(ed25519.PublicKey)
|
||||
tok := delivery.Token{Version: 1, Node: node, Broker: base.Broker, Fingerprint: base.Fingerprint,
|
||||
tok := enrollerclient.Token{Version: 1, Node: node, Broker: base.Broker, Fingerprint: base.Fingerprint,
|
||||
Signer: []byte(signer), Secret: secret, Tunnel: tunnel}
|
||||
if tunnel == nil {
|
||||
return EnrolmentTokenAnswer{}, fmt.Errorf("the vault keeps no first tunnel for %s's token: issue it again with its tunnel", node)
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -16,8 +20,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
@@ -41,7 +44,7 @@ func (l *loginsState) Get(node string) (*NodeLogin, error) {
|
||||
func (l *loginsState) Put(n NodeLogin) error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
raw, err := delivery.EncodeNodeLogin(n)
|
||||
raw, err := vaultclient.EncodeNodeLogin(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -127,10 +130,10 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
}
|
||||
// new1 is placed with its private-network address, as mesh-enrolment writes it before it asks for the certificate.
|
||||
values := map[string][]byte{}
|
||||
for node, p := range map[string]delivery.Placement{"novox": {Node: "novox", Modules: []delivery.PlacedModule{{Module: "nats"}}},
|
||||
"g14": {Node: "g14"}, "new1": {Node: "new1", Decided: delivery.NodeDecided{Address: "10.77.0.9"}}} {
|
||||
p.V = delivery.Version
|
||||
kv, err := delivery.PlacementValues(p)
|
||||
for node, p := range map[string]placing.Placement{"novox": {Node: "novox", Modules: []placing.PlacedModule{{Module: "nats"}}},
|
||||
"g14": {Node: "g14"}, "new1": {Node: "new1", Decided: placing.NodeDecided{Address: "10.77.0.9"}}} {
|
||||
p.V = vaultclient.Version
|
||||
kv, err := assignerclient.PlacementValues(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -175,7 +178,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
serving, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
keys := map[string]any{"public_key": base64.StdEncoding.EncodeToString(identity), "overlay_key": "overlay-pub",
|
||||
"sealing_key": sealingPub, "serving_key": base64.StdEncoding.EncodeToString(serving)}
|
||||
digest := delivery.KeysDigest(keys["public_key"].(string), "overlay-pub", sealingPub, keys["serving_key"].(string))
|
||||
digest := enrollerclient.KeysDigest(keys["public_key"].(string), "overlay-pub", sealingPub, keys["serving_key"].(string))
|
||||
with := func(base map[string]any, more map[string]any) map[string]any {
|
||||
out := map[string]any{}
|
||||
for k, x := range base {
|
||||
@@ -186,7 +189,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
}
|
||||
return out
|
||||
}
|
||||
reply := delivery.RedeemAnswer{Node: "new1", Login: "node.new1", Account: "sealed", Certificate: "pem", Signer: signerB64}
|
||||
reply := enrollerclient.RedeemAnswer{Node: "new1", Login: "node.new1", Account: "sealed", Certificate: "pem", Signer: signerB64}
|
||||
|
||||
var issued, sealed, dropped meshvault.EnrolmentTokenAnswer
|
||||
var account, accountAgain, retired meshvault.NodeAccountAnswer
|
||||
@@ -203,8 +206,8 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
{"a node asks a token", "enrolment-token", "node.g14", map[string]any{"node": "new1", "tunnel": tunnel}, nil, nil, "mesh-enrolment"},
|
||||
{"the controller asks a token", "enrolment-token", "controller", map[string]any{"node": "new1", "tunnel": tunnel}, nil, nil, "mesh-enrolment"},
|
||||
{"a person asks an account", "node-account", "person.jochen", map[string]any{"node": "new1"}, nil, nil, "mesh-enrolment"},
|
||||
{"unproven asks an enrolment signed", "sign", "", map[string]any{"kind": delivery.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "proved"},
|
||||
{"a grants part asks an enrolment signed", "sign", "novox.mesh-grants", map[string]any{"kind": delivery.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "mesh-enrolment"},
|
||||
{"unproven asks an enrolment signed", "sign", "", map[string]any{"kind": vaultclient.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "proved"},
|
||||
{"a grants part asks an enrolment signed", "sign", "novox.mesh-grants", map[string]any{"kind": vaultclient.KindEnrolment, "node": "new1", "reply": "AAEC"}, nil, nil, "mesh-enrolment"},
|
||||
// Insight 2026-11-02: the first tunnel is whole at issue, or nothing is issued.
|
||||
{"no tunnel", "enrolment-token", holder, map[string]any{"node": "new1"}, nil, nil, "first tunnel"},
|
||||
{"a tunnel without the hub's key", "enrolment-token", holder, map[string]any{"node": "new1", "tunnel": without("hub_key")}, nil, nil, "hub's tunnel key"},
|
||||
@@ -222,11 +225,11 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
{"node-account for other keys", "node-account", holder, map[string]any{"node": "new1", "sealing_key": sealingPub, "keys_digest": "sha256:other"}, nil, nil, "another set of keys"},
|
||||
{"node-account of an adopted node", "node-account", holder, map[string]any{"node": "g14", "sealing_key": sealingPub, "keys_digest": digest}, nil, nil, "never minted anew"},
|
||||
// The certificate for the serving key presented, checked against node-account's digest; the reply signed.
|
||||
{"a certificate without the keys", "sign", holder, map[string]any{"kind": delivery.KindNodeCertificate, "node": "new1", "serving_key": keys["serving_key"]}, nil, nil, "keys it presented"},
|
||||
{"a certificate for another serving key", "sign", holder, with(keys, map[string]any{"kind": delivery.KindNodeCertificate, "node": "new1", "serving_key": base64.StdEncoding.EncodeToString(identity)}), nil, nil, "not the ones its account was made for"},
|
||||
{"a certificate", "sign", holder, with(keys, map[string]any{"kind": delivery.KindNodeCertificate, "node": "new1"}), &cert, []string{"kind", "node", "certificate", "authority", "signer", "digest"}, ""},
|
||||
{"the reply signed", "sign", holder, map[string]any{"kind": delivery.KindEnrolment, "node": "new1", "reply": base64.StdEncoding.EncodeToString(reply.SignedBytes())}, &replySig, []string{"kind", "node", "signature", "signer", "digest"}, ""},
|
||||
{"a reply of a node with no token", "sign", holder, map[string]any{"kind": delivery.KindEnrolment, "node": "g14", "reply": "AAEC"}, nil, nil, "no enrolment token"},
|
||||
{"a certificate without the keys", "sign", holder, map[string]any{"kind": vaultclient.KindNodeCertificate, "node": "new1", "serving_key": keys["serving_key"]}, nil, nil, "keys it presented"},
|
||||
{"a certificate for another serving key", "sign", holder, with(keys, map[string]any{"kind": vaultclient.KindNodeCertificate, "node": "new1", "serving_key": base64.StdEncoding.EncodeToString(identity)}), nil, nil, "not the ones its account was made for"},
|
||||
{"a certificate", "sign", holder, with(keys, map[string]any{"kind": vaultclient.KindNodeCertificate, "node": "new1"}), &cert, []string{"kind", "node", "certificate", "authority", "signer", "digest"}, ""},
|
||||
{"the reply signed", "sign", holder, map[string]any{"kind": vaultclient.KindEnrolment, "node": "new1", "reply": base64.StdEncoding.EncodeToString(reply.SignedBytes())}, &replySig, []string{"kind", "node", "signature", "signer", "digest"}, ""},
|
||||
{"a reply of a node with no token", "sign", holder, map[string]any{"kind": vaultclient.KindEnrolment, "node": "g14", "reply": "AAEC"}, nil, nil, "no enrolment token"},
|
||||
// A revoke: the account retired, the token and its hash dropped.
|
||||
{"retire", "node-account", holder, map[string]any{"node": "new1", "retire": "true"}, &retired, []string{"node", "retired"}, ""},
|
||||
{"drop", "enrolment-token", holder, map[string]any{"node": "new1", "drop": "true"}, &dropped, []string{"node", "dropped"}, ""},
|
||||
@@ -276,7 +279,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
t.Errorf("issue answered %+v", issued)
|
||||
}
|
||||
// sealed issued nothing new: the token it seals is the one whose hash is enrol.new1, with its tunnel, and usable whole.
|
||||
tok, err := delivery.ParseToken(openOperator(t, e.operatorPublic, e.operatorPrivate, sealed.Sealed))
|
||||
tok, err := enrollerclient.ParseToken(openOperator(t, e.operatorPublic, e.operatorPrivate, sealed.Sealed))
|
||||
if err != nil || len(tok.Missing()) != 0 || tok.Node != "new1" || tok.Broker != issued.Broker || tok.Tunnel == nil ||
|
||||
tok.Tunnel.HubEndpoint != "203.0.113.1:51820" || base64.StdEncoding.EncodeToString(tok.Signer) != signerB64 ||
|
||||
tok.Secret != secret {
|
||||
@@ -304,7 +307,7 @@ func TestTheVaultsEnrolmentAnswersAreMeshSDKsWireShapes(t *testing.T) {
|
||||
if err != nil || !ed25519.PublicKey(serving).Equal(parsed.PublicKey) {
|
||||
t.Errorf("the certificate is of another key: %v", err)
|
||||
}
|
||||
ctx, _ := delivery.SignContext(delivery.KindEnrolment)
|
||||
ctx, _ := vaultclient.SignContext(vaultclient.KindEnrolment)
|
||||
sig, _ := base64.StdEncoding.DecodeString(replySig.Signature)
|
||||
if !ed25519.Verify(signer.key.Public().(ed25519.PublicKey), append([]byte(ctx), reply.SignedBytes()...), sig) {
|
||||
t.Error("the redeem answer's signature does not verify under the enrolment context")
|
||||
|
||||
@@ -9,11 +9,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// EnrolmentHeld is one enrolment token the vault holds, as secret_holders lists it: never the token or its hash.
|
||||
@@ -175,7 +174,7 @@ func enrolmentTools(env *Env) []toolDef {
|
||||
|
||||
// tunnelArg reads enrolment-token's `tunnel`, an object or its JSON as a string, as mesh-sdk's TokenTunnel; nil when
|
||||
// absent.
|
||||
func tunnelArg(v any) (*delivery.TokenTunnel, error) {
|
||||
func tunnelArg(v any) (*enrollerclient.TokenTunnel, error) {
|
||||
var raw []byte
|
||||
switch t := v.(type) {
|
||||
case nil:
|
||||
@@ -192,7 +191,7 @@ func tunnelArg(v any) (*delivery.TokenTunnel, error) {
|
||||
}
|
||||
raw = b
|
||||
}
|
||||
var tt delivery.TokenTunnel
|
||||
var tt enrollerclient.TokenTunnel
|
||||
dec := json.NewDecoder(bytes.NewReader(raw))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(&tt); err != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"git.novox.be/novox/mesh-sdk/go/testbus"
|
||||
|
||||
@@ -10,11 +10,13 @@ package main
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
@@ -383,11 +385,11 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
|
||||
later -= time.Hour
|
||||
// own_drift reads it from the node's latest shadow answer, mesh-wide; the id as a node runs it, its module's name
|
||||
// first (captured from novox's shadow answer, 2026-10-28: only-office.needs-jwt).
|
||||
answer := delivery.CandidateAnswer{Proposal: "pro", Revision: 1, Verdict: delivery.CandidateDiffers, Differs: []delivery.ModuleDiffers{{
|
||||
Module: "postgres", Why: "the resource differs", Bodies: []delivery.ResourceBodies{{ID: "postgres.needs-superuser-password",
|
||||
Composed: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`}}}}}
|
||||
have, err := delivery.EncodeHaveNode(delivery.HaveNode{Candidates: map[string]delivery.CandidateAnswer{"pro": answer},
|
||||
Composes: delivery.ComposesOwn})
|
||||
answer := nodeseat.CandidateAnswer{Proposal: "pro", Revision: 1, Verdict: nodeseat.CandidateAnswerVerdictDiffers, Differs: []nodeseat.CandidateDiffers{{
|
||||
Module: "postgres", Why: "the resource differs", Bodies: []nodeseat.CandidateBodies{{ID: "postgres.needs-superuser-password",
|
||||
Composed: `{"sealed":"` + vaultclient.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + vaultclient.MaskedAnotherValue + `"}`}}}}}
|
||||
have, err := engineclient.EncodeHaveNode(nodeseat.HaveByNodeNodeValue{Candidates: map[string]nodeseat.CandidateAnswer{"pro": answer},
|
||||
Composes: engineclient.ComposesOwn})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -399,7 +401,7 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
|
||||
}
|
||||
if len(drift.Secrets) != 1 || drift.Secrets[0].Secret != "own/anchor/postgres/superuser-password" ||
|
||||
drift.Secrets[0].Generation != 2 || drift.Secrets[0].Previous != 1 || drift.Secrets[0].Takeable ||
|
||||
len(drift.Nodes) != 1 || drift.Nodes[0].ValuesDiffer["pro"] != 1 || drift.Nodes[0].Composes != delivery.ComposesOwn {
|
||||
len(drift.Nodes) != 1 || drift.Nodes[0].ValuesDiffer["pro"] != 1 || drift.Nodes[0].Composes != engineclient.ComposesOwn {
|
||||
t.Errorf("own_drift: %+v", drift)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -29,20 +31,19 @@ import (
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// The events a rotation says and the stages of a node's key are mesh-sdk's (delivery/node_logins.go); a rotation warranted
|
||||
// by a hand act is warranted for a day.
|
||||
const (
|
||||
RotationBegunEvent = delivery.RotationBegunEvent
|
||||
NodeAccountReadyEvent = delivery.NodeAccountReadyEvent
|
||||
RotationDoneEvent = delivery.RotationDoneEvent
|
||||
RotationBegunEvent = meshvault.HolderEventRotationBegun
|
||||
NodeAccountReadyEvent = meshvault.HolderEventNodeAccountReady
|
||||
RotationDoneEvent = meshvault.HolderEventRotationDone
|
||||
|
||||
StageBegun = delivery.RotationBegun
|
||||
StageReady = delivery.RotationReady
|
||||
StageConfirmed = delivery.RotationConfirmed
|
||||
StageBegun = vaultclient.RotationBegun
|
||||
StageReady = vaultclient.RotationReady
|
||||
StageConfirmed = vaultclient.RotationConfirmed
|
||||
|
||||
// HandActWithin is how recent a hand act is that warrants a rotation.
|
||||
HandActWithin = 24 * time.Hour
|
||||
@@ -50,7 +51,7 @@ const (
|
||||
|
||||
// NodeLogin is one key of the vault's state node-logins (mesh-sdk's contract): the login the node's node-engine dials as,
|
||||
// and while a rotation runs the other login with its stage. Never a value or a hash.
|
||||
type NodeLogin = delivery.NodeLogin
|
||||
type NodeLogin = meshvault.NodeLoginsByNodeValue
|
||||
|
||||
// NodeLoginStore is the vault's state `node-logins`, by node.
|
||||
type NodeLoginStore interface {
|
||||
@@ -68,7 +69,7 @@ func (busNodeLogins) Get(node string) (*NodeLogin, error) {
|
||||
if err != nil || e == nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := delivery.DecodeNodeLogin(e.Value)
|
||||
n, err := vaultclient.DecodeNodeLogin(e.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s is not a node's login: %w", NodeLoginsState, node, err)
|
||||
}
|
||||
@@ -76,7 +77,7 @@ func (busNodeLogins) Get(node string) (*NodeLogin, error) {
|
||||
}
|
||||
|
||||
func (busNodeLogins) Put(n NodeLogin) error {
|
||||
raw, err := delivery.EncodeNodeLogin(n)
|
||||
raw, err := vaultclient.EncodeNodeLogin(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,8 +202,8 @@ func (v *Vault) checkAct(caller, node, id string) error {
|
||||
return fmt.Errorf("the hand act %s was recorded by %s, not by %s who asks: a rotation is warranted by its own caller's act", id, act.By, caller)
|
||||
case v.Now().Sub(act.At) > HandActWithin:
|
||||
return fmt.Errorf("the hand act %s is older than %s: record a new one", id, HandActWithin)
|
||||
case act.Verb != delivery.RotateNodeVerb && !strings.HasSuffix(act.Verb, "."+delivery.RotateNodeVerb):
|
||||
return fmt.Errorf("the hand act %s is %q, not %s: it warrants no rotation", id, act.Verb, delivery.RotateNodeVerb)
|
||||
case act.Verb != meshvault.VerbRotateNode && !strings.HasSuffix(act.Verb, "."+meshvault.VerbRotateNode):
|
||||
return fmt.Errorf("the hand act %s is %q, not %s: it warrants no rotation", id, act.Verb, meshvault.VerbRotateNode)
|
||||
case !slices.Contains(act.Args, node):
|
||||
return fmt.Errorf("the hand act %s is a rotation of %s, not of %s", id, strings.Join(act.Args, " "), node)
|
||||
}
|
||||
@@ -385,7 +386,7 @@ func (v *Vault) beginNodeRotation(by, node, why, act string, revision uint64) (N
|
||||
if err := v.Accounts.Put(marked); err != nil {
|
||||
return NodeLogin{}, fmt.Errorf("%s could not be marked rotating: %w", cur, err)
|
||||
}
|
||||
nl := NodeLogin{Node: node, Login: cur, Rotating: second, Stage: StageBegun, Since: at, ListRevision: revision}
|
||||
nl := NodeLogin{Node: node, Login: cur, Rotating: second, Stage: StageBegun, Since: at, ListRevision: int64(revision)}
|
||||
if err := v.putLogin(nl); err != nil {
|
||||
return NodeLogin{}, fmt.Errorf("%s %s could not be written: %w", NodeLoginsState, node, err)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -33,7 +36,6 @@ import (
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// ReplaceRunningArg is the argument of `move-in` that makes it take a running own secret over the one the vault holds.
|
||||
@@ -253,7 +255,7 @@ type Drift struct {
|
||||
}
|
||||
|
||||
// OwnDrift reads every node's own have key (`<node>.node`) and lists, from its latest answers to the placement
|
||||
// candidates, every own secret whose shown bodies say another value (delivery.OwnValuesDiffer), with what the vault holds
|
||||
// candidates, every own secret whose shown bodies say another value (vaultclient.OwnValuesDiffer), with what the vault holds
|
||||
// of it and whether move-in with replace_running would take the running value now.
|
||||
func (v *Vault) OwnDrift() (Drift, error) {
|
||||
if v.Haves == nil || v.HaveKeys == nil {
|
||||
@@ -268,7 +270,7 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
out := Drift{Secrets: []DriftRow{}, Nodes: []DriftNode{},
|
||||
Note: "from each node's latest shadow answers; a value is never shown: move-in with replace_running from that node replaces it"}
|
||||
for _, key := range keys {
|
||||
node, isNode := strings.CutSuffix(key, "."+delivery.HaveNodeWord)
|
||||
node, isNode := strings.CutSuffix(key, "."+engineclient.NodeWord)
|
||||
if !isNode || !namePart.MatchString(node) {
|
||||
continue
|
||||
}
|
||||
@@ -279,7 +281,7 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
out.Nodes = append(out.Nodes, dn)
|
||||
continue
|
||||
}
|
||||
have, err := delivery.DecodeHaveNode(raw)
|
||||
have, err := engineclient.DecodeHaveNode(raw)
|
||||
if err != nil {
|
||||
dn.Unread = err.Error()
|
||||
out.Nodes = append(out.Nodes, dn)
|
||||
@@ -287,13 +289,13 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
}
|
||||
dn.Composes = have.Composes
|
||||
for group, a := range have.Candidates {
|
||||
if n := delivery.ValuesDiffer(a); n > 0 {
|
||||
if n := vaultclient.ValuesDiffer(bodiesOf(a)); n > 0 {
|
||||
if dn.ValuesDiffer == nil {
|
||||
dn.ValuesDiffer = map[string]int{}
|
||||
}
|
||||
dn.ValuesDiffer[group] = n
|
||||
}
|
||||
for _, o := range delivery.OwnValuesDiffer(a) {
|
||||
for _, o := range vaultclient.OwnValuesDiffer(bodiesOf(a)) {
|
||||
secret, err := OwnSecret(node, o.Module, o.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -399,3 +401,17 @@ func (v *Vault) ownTakeOff() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// bodiesOf is the shown bodies of the modules a node's answer to a candidate says differ, in the form the vault client
|
||||
// reads them (the node-engine's answer is the seat's type, the reading of masked values the vault's).
|
||||
func bodiesOf(a nodeseat.CandidateAnswer) []vaultclient.ModuleBodies {
|
||||
out := make([]vaultclient.ModuleBodies, 0, len(a.Differs))
|
||||
for _, d := range a.Differs {
|
||||
m := vaultclient.ModuleBodies{Module: d.Module}
|
||||
for _, b := range d.Bodies {
|
||||
m.Bodies = append(m.Bodies, vaultclient.Body{ID: b.ID, Composed: b.Composed, Running: b.Running})
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// PlacementRead is the seat mesh-module-assigner's placement, as the vault's manifest reads it (mesh-sdk's contract).
|
||||
const PlacementRead = delivery.PlacementRead
|
||||
const PlacementRead = assignerclient.PlacementRead
|
||||
|
||||
// PlacementReader reads the seat's placement: every key, and one key's value (nil when there is none).
|
||||
type PlacementReader struct {
|
||||
@@ -25,10 +25,10 @@ type PlacementReader struct {
|
||||
Read func(key string) ([]byte, error)
|
||||
}
|
||||
|
||||
// placements is every node's placement, by node, from the state's keys in either form (delivery.PlacementsOf; research
|
||||
// placements is every node's placement, by node, from the state's keys in either form (assignerclient.PlacementsOf; research
|
||||
// 072 row 37); none when the vault reads no placement. A node whose keys do not read whole is read as they stand.
|
||||
func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
||||
out := map[string]delivery.Placement{}
|
||||
func (v *Vault) placements() (map[string]placing.Placement, error) {
|
||||
out := map[string]placing.Placement{}
|
||||
if v.Placement.Keys == nil || v.Placement.Read == nil {
|
||||
return out, nil
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
||||
}
|
||||
values := map[string][]byte{}
|
||||
for _, k := range keys {
|
||||
if _, _, _, err := delivery.ParsePlacementKey(k); err != nil {
|
||||
if _, _, _, err := assignerclient.ParseKey(k); err != nil {
|
||||
continue
|
||||
}
|
||||
raw, err := v.Placement.Read(k)
|
||||
@@ -49,15 +49,15 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
||||
values[k] = raw
|
||||
}
|
||||
}
|
||||
out, _ = delivery.PlacementsOf(values)
|
||||
out, _ = assignerclient.PlacementsOf(values)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// nodePlaced is what node's own key of the placement says: the modules placed there and what a person decided of it,
|
||||
// read from its node key `<node>.node` alone (which names every module placed, research 072 row 37), or, for a node not
|
||||
// converted yet, from its key in the form before, `placement.<node>`. found is false when it has neither.
|
||||
func (v *Vault) nodePlaced(node string) (modules []string, decided delivery.NodeDecided, found bool, err error) {
|
||||
key, err := delivery.PlacementNodeKey(node)
|
||||
func (v *Vault) nodePlaced(node string) (modules []string, decided placing.NodeDecided, found bool, err error) {
|
||||
key, err := assignerclient.NodeKey(node)
|
||||
if err != nil {
|
||||
return nil, decided, false, err
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func (v *Vault) nodePlaced(node string) (modules []string, decided delivery.Node
|
||||
return nil, decided, false, err
|
||||
}
|
||||
if raw != nil {
|
||||
n, err := delivery.DecodePlacementNode(raw)
|
||||
n, err := assignerclient.DecodeNode(raw)
|
||||
if err != nil {
|
||||
return nil, decided, false, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
||||
}
|
||||
@@ -75,18 +75,18 @@ func (v *Vault) nodePlaced(node string) (modules []string, decided delivery.Node
|
||||
}
|
||||
return modules, n.Decided, true, nil
|
||||
}
|
||||
legacy, err := delivery.LegacyPlacementKey(node)
|
||||
legacy, err := assignerclient.LegacyKey(node)
|
||||
if err != nil {
|
||||
return nil, decided, false, err
|
||||
}
|
||||
if raw, err = v.Placement.Read(legacy); err != nil || raw == nil {
|
||||
return nil, decided, false, err
|
||||
}
|
||||
p, err := delivery.DecodePlacement(raw)
|
||||
p, err := assignerclient.DecodePlacement(raw)
|
||||
if err != nil {
|
||||
return nil, decided, false, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
||||
}
|
||||
return p.Assigned(), p.Decided, true, nil
|
||||
return assignerclient.Assigned(p), p.Decided, true, nil
|
||||
}
|
||||
|
||||
// placedOn says module is placed on node by the catalogue.
|
||||
@@ -132,7 +132,7 @@ func (v *Vault) busNode() (string, error) {
|
||||
}
|
||||
var nodes []string
|
||||
for node, p := range placed {
|
||||
if slices.Contains(p.Assigned(), BusModule) {
|
||||
if slices.Contains(assignerclient.Assigned(p), BusModule) {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// placementsOf is the seat mesh-module-assigner's placement of each node, as the vault reads it.
|
||||
@@ -12,12 +13,12 @@ func placementsOf(t *testing.T, nodes map[string][]string) PlacementReader {
|
||||
t.Helper()
|
||||
values := map[string][]byte{}
|
||||
for node, modules := range nodes {
|
||||
p := delivery.Placement{V: delivery.Version, Node: node}
|
||||
p := placing.Placement{V: vaultclient.Version, Node: node}
|
||||
for _, m := range modules {
|
||||
p.Modules = append(p.Modules, delivery.PlacedModule{Module: m})
|
||||
p.Modules = append(p.Modules, placing.PlacedModule{Module: m})
|
||||
}
|
||||
// Research 072 row 37: the node key and a key per module.
|
||||
kv, err := delivery.PlacementValues(p)
|
||||
kv, err := assignerclient.PlacementValues(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -10,20 +10,20 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// PlacementTool is the assigner's ordinary read tool of its placement; PlacementViewsEvent the event of its views family.
|
||||
const (
|
||||
PlacementTool = delivery.AssignerModule + ".assigner_read_placement"
|
||||
PlacementViewsEvent = delivery.AssignerModule + ".views.placement"
|
||||
PlacementTool = assignerclient.Module + ".assigner_read_placement"
|
||||
PlacementViewsEvent = assignerclient.Module + ".views.placement"
|
||||
FirstViewsTool = "vault_first_views"
|
||||
FirstViewAgainTool = "vault_first_view_again"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ var placementView *views.Follower
|
||||
// it is held (a first view's keys too).
|
||||
func newPlacementView(ask func(key string, body any) (json.RawMessage, error), gone func(key string), changed func(),
|
||||
findings views.FindingWriter) *views.Follower {
|
||||
return &views.Follower{Owner: delivery.AssignerModule, Tool: PlacementTool, State: "placement", Logf: say,
|
||||
return &views.Follower{Owner: assignerclient.Module, Tool: PlacementTool, State: "placement", Logf: say,
|
||||
Ask: func(_ context.Context, after string) (views.Answer, error) {
|
||||
body := map[string]any{"values": true}
|
||||
if after != "" {
|
||||
|
||||
@@ -15,6 +15,11 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -28,7 +33,6 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"git.novox.be/novox/mesh-sdk/go/testbus"
|
||||
)
|
||||
@@ -113,24 +117,24 @@ func TestTheVaultWholeRotatesANodesLoginWithAStandInEngine(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var accounts []delivery.HandedAccount
|
||||
var accounts []engineclient.HandedAccount
|
||||
loaded = map[string]string{}
|
||||
for _, k := range keys {
|
||||
a := p.hashOf(t, k)
|
||||
if a == nil || !delivery.IsBcrypt(a.Hash) {
|
||||
if a == nil || !engineclient.IsBcrypt(a.Hash) {
|
||||
continue
|
||||
}
|
||||
accounts = append(accounts, delivery.HandedAccount{User: k, Hash: a.Hash})
|
||||
accounts = append(accounts, engineclient.HandedAccount{User: k, Hash: a.Hash})
|
||||
loaded[k] = a.Hash
|
||||
}
|
||||
revision++
|
||||
list, _ := json.Marshal(accounts)
|
||||
handed, err := delivery.HandAccounts(accounts, vaultKey.PublicKey, Fingerprint(string(list)), time.Now())
|
||||
handed, err := engineclient.HandAccounts(accounts, vaultKey.PublicKey, Fingerprint(string(list)), time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, err := delivery.EncodeHave(delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
|
||||
UserList: &delivery.UserListWritten{Source: "k", Revision: revision, Users: len(accounts), Handed: &handed}})
|
||||
raw, err := engineclient.EncodeHave(nodeseat.HaveByNodeModuleValue{Phase: engineclient.PhaseRunning, Health: engineclient.HealthOK,
|
||||
UserList: &nodeseat.UserListWritten{Source: "k", Revision: int64(revision), Users: int64(len(accounts)), Handed: &handed}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -161,11 +165,11 @@ func TestTheVaultWholeRotatesANodesLoginWithAStandInEngine(t *testing.T) {
|
||||
// held the bus, and adopts from it (learning the loaded list's revision) when the placement names novox.
|
||||
p.firstViewAnswered()
|
||||
for node, modules := range map[string][]string{"novox": {"nats"}, "g14": {"web"}} {
|
||||
pl := delivery.Placement{V: delivery.Version, Node: node}
|
||||
pl := placing.Placement{V: vaultclient.Version, Node: node}
|
||||
for _, m := range modules {
|
||||
pl.Modules = append(pl.Modules, delivery.PlacedModule{Module: m})
|
||||
pl.Modules = append(pl.Modules, placing.PlacedModule{Module: m})
|
||||
}
|
||||
values, err := delivery.PlacementValues(pl)
|
||||
values, err := assignerclient.PlacementValues(pl)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
enrollerclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshvault"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
)
|
||||
|
||||
@@ -37,7 +39,7 @@ const (
|
||||
// signingSecret says name is one of the vault's signing secrets, which no asker is ever handed.
|
||||
func signingSecret(name string) bool { return name == SigningKeySecret || name == AuthorityKeySecret }
|
||||
|
||||
// SignersStore is the vault's state `signers` (delivery.SignersBucket): a key's value, or nil; and a write.
|
||||
// SignersStore is the vault's state `signers` ((vaultModule + "_" + meshvault.StateSigners)): a key's value, or nil; and a write.
|
||||
type SignersStore interface {
|
||||
Get(key string) ([]byte, error)
|
||||
Put(key string, value []byte) error
|
||||
@@ -47,7 +49,7 @@ type SignersStore interface {
|
||||
type busSigners struct{}
|
||||
|
||||
func (busSigners) Get(key string) ([]byte, error) {
|
||||
e, err := stdio.State(delivery.SignersState).Get(key)
|
||||
e, err := stdio.State(meshvault.StateSigners).Get(key)
|
||||
if err != nil || e == nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -55,7 +57,7 @@ func (busSigners) Get(key string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (busSigners) Put(key string, value []byte) error {
|
||||
_, err := stdio.State(delivery.SignersState).Put(key, json.RawMessage(value))
|
||||
_, err := stdio.State(meshvault.StateSigners).Put(key, json.RawMessage(value))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -94,11 +96,11 @@ func (v *Vault) EnsureSigning() (bool, error) {
|
||||
"would be trusted by no node; put it back from its operator copy (novox/hq ADR 0113 §21, ADR 0550 §2)", missing)
|
||||
}
|
||||
if v.Signers != nil {
|
||||
if raw, err := v.Signers.Get(delivery.SignersKeyKey); err != nil {
|
||||
return false, fmt.Errorf("the vault's state %s could not be read, so no signing key is made: %w", delivery.SignersRead, err)
|
||||
if raw, err := v.Signers.Get(meshvault.SignersByKeyKey()); err != nil {
|
||||
return false, fmt.Errorf("the vault's state %s could not be read, so no signing key is made: %w", (vaultModule + "." + meshvault.StateSigners), err)
|
||||
} else if raw != nil {
|
||||
return false, fmt.Errorf("THE VAULT'S SIGNING KEYS ARE MISSING WHILE %s PUBLISHES ONE: nothing is made, since every "+
|
||||
"node trusts the published key; put them back from their operator copies (novox/hq ADR 0113 §21)", delivery.SignersRead)
|
||||
"node trusts the published key; put them back from their operator copies (novox/hq ADR 0113 §21)", (vaultModule + "." + meshvault.StateSigners))
|
||||
}
|
||||
}
|
||||
for _, name := range []string{SigningKeySecret, AuthorityKeySecret} {
|
||||
@@ -147,7 +149,7 @@ func (v *Vault) keys() (signingKeys, error) {
|
||||
return signingKeys{}, err
|
||||
}
|
||||
pub := auth.Public().(ed25519.PublicKey)
|
||||
tmpl := delivery.AuthorityTemplate(pub, authMade)
|
||||
tmpl := vaultclient.AuthorityTemplate(pub, authMade)
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, auth)
|
||||
if err != nil {
|
||||
return signingKeys{}, fmt.Errorf("the vault's authority certificate could not be made: %w", err)
|
||||
@@ -161,31 +163,31 @@ func (v *Vault) keys() (signingKeys, error) {
|
||||
}
|
||||
|
||||
// SignersValues are the two keys of `signers` as the vault holds them now.
|
||||
func (v *Vault) SignersValues() (delivery.SignerKey, delivery.SignerAuthority, error) {
|
||||
func (v *Vault) SignersValues() (meshvault.SignersByKeyValue, meshvault.SignersByAuthorityValue, error) {
|
||||
k, err := v.keys()
|
||||
if err != nil {
|
||||
return delivery.SignerKey{}, delivery.SignerAuthority{}, err
|
||||
return meshvault.SignersByKeyValue{}, meshvault.SignersByAuthorityValue{}, err
|
||||
}
|
||||
fp, err := delivery.AuthorityFingerprint(k.certificate)
|
||||
fp, err := vaultclient.AuthorityFingerprint(k.certificate)
|
||||
if err != nil {
|
||||
return delivery.SignerKey{}, delivery.SignerAuthority{}, err
|
||||
return meshvault.SignersByKeyValue{}, meshvault.SignersByAuthorityValue{}, err
|
||||
}
|
||||
return delivery.NewSignerKey(k.key.Public().(ed25519.PublicKey), k.keyMade),
|
||||
delivery.SignerAuthority{Certificate: k.certificate, Fingerprint: fp, Made: k.authorityMade.UTC()}, nil
|
||||
return vaultclient.NewSignerKey(k.key.Public().(ed25519.PublicKey), k.keyMade),
|
||||
meshvault.SignersByAuthorityValue{Certificate: k.certificate, Fingerprint: fp, Made: k.authorityMade.UTC()}, nil
|
||||
}
|
||||
|
||||
// PublishSigners writes `signers` from the keys the vault holds, each key only when its value differs (or always with
|
||||
// force, signers_reassert), and answers which keys it wrote.
|
||||
func (v *Vault) PublishSigners(force bool) ([]string, error) {
|
||||
if v.Signers == nil {
|
||||
return nil, errors.New("the vault has no state " + delivery.SignersRead + " to write")
|
||||
return nil, errors.New("the vault has no state " + (vaultModule + "." + meshvault.StateSigners) + " to write")
|
||||
}
|
||||
key, auth, err := v.SignersValues()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var written []string
|
||||
for name, value := range map[string]any{delivery.SignersKeyKey: key, delivery.SignersAuthorityKey: auth} {
|
||||
for name, value := range map[string]any{meshvault.SignersByKeyKey(): key, meshvault.SignersByAuthorityKey(): auth} {
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return written, err
|
||||
@@ -193,19 +195,19 @@ func (v *Vault) PublishSigners(force bool) ([]string, error) {
|
||||
if !force {
|
||||
held, err := v.Signers.Get(name)
|
||||
if err != nil {
|
||||
return written, fmt.Errorf("%s %s could not be read: %w", delivery.SignersRead, name, err)
|
||||
return written, fmt.Errorf("%s %s could not be read: %w", (vaultModule + "." + meshvault.StateSigners), name, err)
|
||||
}
|
||||
if same(held, raw) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := v.Signers.Put(name, raw); err != nil {
|
||||
return written, fmt.Errorf("%s %s could not be written: %w", delivery.SignersRead, name, err)
|
||||
return written, fmt.Errorf("%s %s could not be written: %w", (vaultModule + "." + meshvault.StateSigners), name, err)
|
||||
}
|
||||
written = append(written, name)
|
||||
}
|
||||
if len(written) > 0 {
|
||||
v.log("wrote %s %s (ADR 0550 §2)", delivery.SignersRead, strings.Join(written, " and "))
|
||||
v.log("wrote %s %s (ADR 0550 §2)", (vaultModule + "." + meshvault.StateSigners), strings.Join(written, " and "))
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
@@ -242,12 +244,12 @@ type SignArgs struct {
|
||||
Node string
|
||||
Reply string
|
||||
// Keys are, for a joining node's certificate asked by mesh-enrolment, the public keys the machine presented at its
|
||||
// redeem (delivery.Redeem's): the serving key is certified, the four are checked against the digest node-account
|
||||
// redeem (enrollerclient.Redeem's): the serving key is certified, the four are checked against the digest node-account
|
||||
// recorded.
|
||||
Keys PresentedKeys
|
||||
}
|
||||
|
||||
// PresentedKeys are the public keys a joining machine presented at its redeem, as delivery.Redeem names them.
|
||||
// PresentedKeys are the public keys a joining machine presented at its redeem, as enrollerclient.Redeem names them.
|
||||
type PresentedKeys struct {
|
||||
Public, Overlay, Sealing, Serving string
|
||||
}
|
||||
@@ -260,21 +262,21 @@ func (v *Vault) Sign(caller string, a SignArgs) (meshvault.SignAnswer, error) {
|
||||
return meshvault.SignAnswer{}, errors.New("the vault signs only for a caller the runtime proved")
|
||||
}
|
||||
switch a.Kind {
|
||||
case delivery.KindNodeCertificate:
|
||||
case vaultclient.KindNodeCertificate:
|
||||
if v.fromEnrolment(caller, "sign") == nil {
|
||||
return v.signRedeemingCertificate(caller, a.Node, a.Keys)
|
||||
}
|
||||
return v.signNodeCertificate(caller, a.Node)
|
||||
case delivery.KindEnrolment:
|
||||
case vaultclient.KindEnrolment:
|
||||
if err := v.fromEnrolment(caller, "sign an enrolment"); err != nil {
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("%s may not ask for an enrolment's signature: only the seat %s, which "+
|
||||
"decides who joins, is named for it (novox/hq ADR 0561 §5, ADR 0587 §6)", caller, EnrolmentModule)
|
||||
}
|
||||
return v.signEnrolment(caller, a.Node, a.Reply)
|
||||
case delivery.KindAuthority:
|
||||
case vaultclient.KindAuthority:
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("the authority is the vault's own, signed for itself; %s may not ask it", caller)
|
||||
}
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("%q is not a kind the vault signs (%s)", a.Kind, strings.Join(delivery.SignKinds, ", "))
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("%q is not a kind the vault signs (%s)", a.Kind, strings.Join(vaultclient.SignKinds, ", "))
|
||||
}
|
||||
|
||||
// redeeming says node has an enrolment token held: a machine joining, which the seat mesh-node-enroller redeems.
|
||||
@@ -312,7 +314,7 @@ func (v *Vault) signRedeemingCertificate(caller, node string, keys PresentedKeys
|
||||
return meshvault.SignAnswer{}, errors.New("a joining node's certificate is asked with the keys it presented at the redeem " +
|
||||
"(`public_key`, `overlay_key`, `sealing_key`, `serving_key`), which are checked against node-account's digest")
|
||||
}
|
||||
if got := delivery.KeysDigest(keys.Public, keys.Overlay, keys.Sealing, keys.Serving); got != acct.KeysDigest {
|
||||
if got := enrollerclient.KeysDigest(keys.Public, keys.Overlay, keys.Sealing, keys.Serving); got != acct.KeysDigest {
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("the keys presented for %s are not the ones its account was made for: nothing is signed", node)
|
||||
}
|
||||
pub, err := base64.StdEncoding.DecodeString(keys.Serving)
|
||||
@@ -336,7 +338,7 @@ func (v *Vault) signEnrolment(caller, node, reply string) (meshvault.SignAnswer,
|
||||
if err != nil || len(msg) == 0 {
|
||||
return meshvault.SignAnswer{}, errors.New("an enrolment's signature is asked for the reply's bytes in base64 (`reply`)")
|
||||
}
|
||||
context, err := delivery.SignContext(delivery.KindEnrolment)
|
||||
context, err := vaultclient.SignContext(vaultclient.KindEnrolment)
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
@@ -344,9 +346,9 @@ func (v *Vault) signEnrolment(caller, node, reply string) (meshvault.SignAnswer,
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
answer := meshvault.SignAnswer{Kind: delivery.KindEnrolment, Node: node,
|
||||
Signature: base64.StdEncoding.EncodeToString(delivery.SignMessage(k.key, context, msg)),
|
||||
Signer: delivery.SignerFingerprint(k.key.Public().(ed25519.PublicKey)), Digest: delivery.DigestOf(msg)}
|
||||
answer := meshvault.SignAnswer{Kind: vaultclient.KindEnrolment, Node: node,
|
||||
Signature: base64.StdEncoding.EncodeToString(vaultclient.SignMessage(k.key, context, msg)),
|
||||
Signer: vaultclient.SignerFingerprint(k.key.Public().(ed25519.PublicKey)), Digest: vaultclient.DigestOf(msg)}
|
||||
if err := v.saySigned(caller, SigningKeySecret, answer); err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
@@ -385,14 +387,14 @@ func (v *Vault) signCertificateWith(caller, node, name string, serving ed25519.P
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, delivery.NodeCertificateTemplate(name, serving, k.authorityMade),
|
||||
der, err := x509.CreateCertificate(rand.Reader, vaultclient.NodeCertificateTemplate(name, serving, k.authorityMade),
|
||||
k.parsed, serving, k.authority)
|
||||
if err != nil {
|
||||
return meshvault.SignAnswer{}, fmt.Errorf("the certificate of %s could not be signed: %w", name, err)
|
||||
}
|
||||
fp, _ := delivery.AuthorityFingerprint(k.certificate)
|
||||
answer := meshvault.SignAnswer{Kind: delivery.KindNodeCertificate, Node: node, Authority: k.certificate,
|
||||
Certificate: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), Signer: fp, Digest: delivery.DigestOf(der)}
|
||||
fp, _ := vaultclient.AuthorityFingerprint(k.certificate)
|
||||
answer := meshvault.SignAnswer{Kind: vaultclient.KindNodeCertificate, Node: node, Authority: k.certificate,
|
||||
Certificate: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), Signer: fp, Digest: vaultclient.DigestOf(der)}
|
||||
if err := v.saySigned(caller, AuthorityKeySecret, answer); err != nil {
|
||||
return meshvault.SignAnswer{}, err
|
||||
}
|
||||
@@ -423,7 +425,7 @@ func (v *Vault) servingKey(node string) (ed25519.PublicKey, error) {
|
||||
if v.Haves == nil {
|
||||
return nil, errors.New("the vault cannot read the have bucket (" + HaveRead + "), so it cannot read a node's record")
|
||||
}
|
||||
key, err := delivery.HaveNodeKey(node)
|
||||
key, err := engineclient.HaveNodeKey(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -434,7 +436,7 @@ func (v *Vault) servingKey(node string) (ed25519.PublicKey, error) {
|
||||
if raw == nil {
|
||||
return nil, fmt.Errorf("%s has written no record (%s %s), so it states no serving key", node, HaveRead, key)
|
||||
}
|
||||
h, err := delivery.DecodeHaveNode(raw)
|
||||
h, err := engineclient.DecodeHaveNode(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the record of %s is not readable: %w", node, err)
|
||||
}
|
||||
@@ -525,7 +527,7 @@ func (v *Vault) SigningState() map[string]any {
|
||||
}
|
||||
published := map[string]any{}
|
||||
match := true
|
||||
for name, want := range map[string]string{delivery.SignersKeyKey: key.Fingerprint, delivery.SignersAuthorityKey: auth.Fingerprint} {
|
||||
for name, want := range map[string]string{meshvault.SignersByKeyKey(): key.Fingerprint, meshvault.SignersByAuthorityKey(): auth.Fingerprint} {
|
||||
raw, err := v.Signers.Get(name)
|
||||
var got struct {
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
|
||||
@@ -5,10 +5,15 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
vaultclient "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
)
|
||||
@@ -35,25 +40,25 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
state := &signersState{keys: map[string][]byte{}}
|
||||
v.Signers = state
|
||||
servingPub, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
placements := map[string]delivery.Placement{
|
||||
"ace": {Node: "ace", Decided: delivery.NodeDecided{Address: "10.77.0.3"}},
|
||||
"g14": {Node: "g14", Decided: delivery.NodeDecided{Address: "10.77.0.4"}},
|
||||
placements := map[string]placing.Placement{
|
||||
"ace": {Node: "ace", Decided: placing.NodeDecided{Address: "10.77.0.3"}},
|
||||
"g14": {Node: "g14", Decided: placing.NodeDecided{Address: "10.77.0.4"}},
|
||||
"plain": {Node: "plain"},
|
||||
}
|
||||
// Research 072 row 37: ace and plain in their keys per module and node, g14 still in the form before.
|
||||
v.Placement = PlacementReader{Read: func(key string) ([]byte, error) {
|
||||
node, _, kind, err := delivery.ParsePlacementKey(key)
|
||||
node, _, kind, err := assignerclient.ParseKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, ok := placements[node]
|
||||
if !ok || (node == "g14") != (kind == delivery.PlacementLegacyKind) {
|
||||
if !ok || (node == "g14") != (kind == assignerclient.LegacyKind) {
|
||||
return nil, nil
|
||||
}
|
||||
if kind == delivery.PlacementLegacyKind {
|
||||
return delivery.EncodePlacement(p)
|
||||
if kind == assignerclient.LegacyKind {
|
||||
return assignerclient.EncodePlacement(p)
|
||||
}
|
||||
values, err := delivery.PlacementValues(p)
|
||||
values, err := assignerclient.PlacementValues(p)
|
||||
return values[key], err
|
||||
}}
|
||||
records := map[string]string{"ace": base64.StdEncoding.EncodeToString(servingPub), "plain": base64.StdEncoding.EncodeToString(servingPub)}
|
||||
@@ -63,7 +68,7 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return delivery.EncodeHaveNode(delivery.HaveNode{Record: &delivery.NodeRecord{Node: node, ServingKey: serving}})
|
||||
return engineclient.EncodeHaveNode(nodeseat.HaveByNodeNodeValue{Record: &nodeseat.NodeRecord{Node: node, ServingKey: serving}})
|
||||
}
|
||||
|
||||
// s2: made once, kept with their operator copies; a second pass makes nothing new and writes nothing again (s5).
|
||||
@@ -97,10 +102,10 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
if key2 != key1 || auth2 != auth1 || state.writes != writes {
|
||||
t.Errorf("a second pass changed the keys or wrote signers again (%d writes)", state.writes)
|
||||
}
|
||||
if _, err := delivery.DecodeSignerKey(state.keys[delivery.SignersKeyKey]); err != nil {
|
||||
if _, err := vaultclient.DecodeSignerKey(state.keys[meshvault.SignersByKeyKey()]); err != nil {
|
||||
t.Errorf("signers' key: %v", err)
|
||||
}
|
||||
authority, err := delivery.DecodeSignerAuthority(state.keys[delivery.SignersAuthorityKey])
|
||||
authority, err := vaultclient.DecodeSignerAuthority(state.keys[meshvault.SignersByAuthorityKey()])
|
||||
if err != nil {
|
||||
t.Fatalf("signers' authority: %v", err)
|
||||
}
|
||||
@@ -121,14 +126,14 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
for _, row := range []struct {
|
||||
name, caller, kind, node, refused string
|
||||
}{
|
||||
{"a module is not a node-engine", "ace.mesh-grants", delivery.KindNodeCertificate, "", "not a node-engine"},
|
||||
{"another node's certificate", "node.g14", delivery.KindNodeCertificate, "ace", "its own alone"},
|
||||
{"a node not on the private network", "node.plain", delivery.KindNodeCertificate, "", "not on the private network"},
|
||||
{"a node that states no serving key", "node.g14", delivery.KindNodeCertificate, "", "no record"},
|
||||
{"an enrolment has no caller yet", "node.ace", delivery.KindEnrolment, "", "ADR 0561 §5"},
|
||||
{"the authority is the vault's own", "node.ace", delivery.KindAuthority, "", "the vault's own"},
|
||||
{"an unproven caller", "", delivery.KindNodeCertificate, "", "proved"},
|
||||
{"the node's own certificate", "node.ace", delivery.KindNodeCertificate, "ace", ""},
|
||||
{"a module is not a node-engine", "ace.mesh-grants", vaultclient.KindNodeCertificate, "", "not a node-engine"},
|
||||
{"another node's certificate", "node.g14", vaultclient.KindNodeCertificate, "ace", "its own alone"},
|
||||
{"a node not on the private network", "node.plain", vaultclient.KindNodeCertificate, "", "not on the private network"},
|
||||
{"a node that states no serving key", "node.g14", vaultclient.KindNodeCertificate, "", "no record"},
|
||||
{"an enrolment has no caller yet", "node.ace", vaultclient.KindEnrolment, "", "ADR 0561 §5"},
|
||||
{"the authority is the vault's own", "node.ace", vaultclient.KindAuthority, "", "the vault's own"},
|
||||
{"an unproven caller", "", vaultclient.KindNodeCertificate, "", "proved"},
|
||||
{"the node's own certificate", "node.ace", vaultclient.KindNodeCertificate, "ace", ""},
|
||||
} {
|
||||
v.bus.raw = nil
|
||||
got, err := v.Sign(row.caller, SignArgs{Kind: row.kind, Node: row.node})
|
||||
@@ -144,7 +149,7 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", row.name, err)
|
||||
}
|
||||
if err := delivery.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
if err := vaultclient.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
servingPub, authority.Made.Add(1)); err != nil {
|
||||
t.Errorf("%s: the certificate does not check against signers' authority: %v", row.name, err)
|
||||
}
|
||||
@@ -158,15 +163,15 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
t.Errorf("%s: signed not said: %s", row.name, said)
|
||||
}
|
||||
rows, _ := v.Signatures(10)
|
||||
if len(rows) != 1 || rows[0].Caller != "node.ace" || rows[0].Kind != delivery.KindNodeCertificate || rows[0].Digest != got.Digest {
|
||||
if len(rows) != 1 || rows[0].Caller != "node.ace" || rows[0].Kind != vaultclient.KindNodeCertificate || rows[0].Digest != got.Digest {
|
||||
t.Errorf("%s: vault_signed lists %+v", row.name, rows)
|
||||
}
|
||||
status, _ := json.Marshal(v.SigningState())
|
||||
for _, seed := range seeds {
|
||||
raw, _ := base64.StdEncoding.DecodeString(seed)
|
||||
priv := ed25519.NewKeyFromSeed(raw)
|
||||
for _, text := range []string{string(answer), said, string(status), string(state.keys[delivery.SignersKeyKey]),
|
||||
string(state.keys[delivery.SignersAuthorityKey])} {
|
||||
for _, text := range []string{string(answer), said, string(status), string(state.keys[meshvault.SignersByKeyKey()]),
|
||||
string(state.keys[meshvault.SignersByAuthorityKey()])} {
|
||||
if strings.Contains(text, seed) || strings.Contains(text, base64.StdEncoding.EncodeToString(priv)) {
|
||||
t.Errorf("%s: a signing key's private half is in what the vault says: %s", row.name, text)
|
||||
}
|
||||
@@ -176,15 +181,15 @@ func TestTheVaultSigns(t *testing.T) {
|
||||
// A serving key changed in the record is the one certified next, never the one before.
|
||||
other, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
records["ace"] = base64.StdEncoding.EncodeToString(other)
|
||||
got, err := v.Sign("node.ace", SignArgs{Kind: delivery.KindNodeCertificate})
|
||||
if err != nil || delivery.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
got, err := v.Sign("node.ace", SignArgs{Kind: vaultclient.KindNodeCertificate})
|
||||
if err != nil || vaultclient.CheckNodeCertificate(got.Certificate, []string{authority.Certificate}, overlay.InternalName("ace"),
|
||||
servingPub, authority.Made.Add(1)) == nil {
|
||||
t.Errorf("a certificate for a serving key the record no longer states: %v", err)
|
||||
}
|
||||
|
||||
// s2: one secret lost beside the other, or both lost while signers publishes a key, makes nothing.
|
||||
lost := newVault(t, nil)
|
||||
lost.Signers = &signersState{keys: map[string][]byte{delivery.SignersKeyKey: state.keys[delivery.SignersKeyKey]}}
|
||||
lost.Signers = &signersState{keys: map[string][]byte{meshvault.SignersByKeyKey(): state.keys[meshvault.SignersByKeyKey()]}}
|
||||
if made, err := lost.EnsureSigning(); made || err == nil || !strings.Contains(err.Error(), "MISSING") {
|
||||
t.Errorf("keys made while signers publishes one: %v, %v", made, err)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ package main
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
)
|
||||
|
||||
@@ -234,20 +235,20 @@ func TestSecretOwnListsFingerprintsOfOwnSecretsOnly(t *testing.T) {
|
||||
handing := map[string]string{"controller": h("c"), "node.ace": h("n"), "g14.dunst": h("d")}
|
||||
sealedTo := v.Own.PublicKey
|
||||
have := func() []byte {
|
||||
var accounts []delivery.HandedAccount
|
||||
var accounts []engineclient.HandedAccount
|
||||
for _, u := range []string{"controller", "node.ace", "g14.dunst", "bad.user"} {
|
||||
if hash, ok := handing[u]; ok {
|
||||
accounts = append(accounts, delivery.HandedAccount{User: u, Hash: hash})
|
||||
accounts = append(accounts, engineclient.HandedAccount{User: u, Hash: hash})
|
||||
}
|
||||
}
|
||||
// The list's digest, as the node says it: another list (another hash or user) is another digest.
|
||||
list, _ := json.Marshal(accounts)
|
||||
handed, err := delivery.HandAccounts(accounts, sealedTo, Fingerprint(string(list)), time.Now())
|
||||
handed, err := engineclient.HandAccounts(accounts, sealedTo, Fingerprint(string(list)), time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := delivery.EncodeHave(delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK,
|
||||
UserList: &delivery.UserListWritten{Source: "k", Users: 3, Handed: &handed}})
|
||||
raw, _ := engineclient.EncodeHave(nodeseat.HaveByNodeModuleValue{Phase: engineclient.PhaseRunning, Health: engineclient.HealthOK,
|
||||
UserList: &nodeseat.UserListWritten{Source: "k", Users: 3, Handed: &handed}})
|
||||
return raw
|
||||
}
|
||||
v.Assignments = Mirror{Keys: func() ([]string, error) { return []string{"novox.nats", "novox.mesh-vault", "ace.dunst"}, nil }}
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
"grants part's wanted accounts alone (the placement is the assigner's, through its tool and events, ADR 0615), never the controller's lease (issue 1126)", r)
|
||||
}
|
||||
sources, _ := filepath.Glob("*.go")
|
||||
bucket := regexp.MustCompile(`stdio\.State\(|nats-io|jetstream|KeyValue`)
|
||||
bucket := regexp.MustCompile(`stdio\.State\(|nats-io|jetstream|\bKeyValue\b`)
|
||||
for _, f := range sources {
|
||||
if strings.HasSuffix(f, "_test.go") {
|
||||
continue
|
||||
@@ -182,8 +182,8 @@ func TestNoBucketHoldsASecret(t *testing.T) {
|
||||
}
|
||||
// The signing keys' public halves, in signing.go alone: one read and one write of its own bucket (ADR 0550 §2).
|
||||
if f == "signing.go" {
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(delivery.SignersState).Get(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(delivery.SignersState).Put(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(meshvault.StateSigners).Get(", "", 1))
|
||||
code = []byte(strings.Replace(string(code), "stdio.State(meshvault.StateSigners).Put(", "", 1))
|
||||
}
|
||||
// The haves, in main.go alone: one read, one watch and their keys (own_drift), never a write; one watch of the
|
||||
// assignments, which the vault's rules read from memory once it caught up (issue 1126); and one watch of the wanted
|
||||
|
||||
@@ -3,8 +3,14 @@ module mesh-vault
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshvault v0.0.0-20261104174344-ae5266e77639
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
|
||||
Reference in New Issue
Block a user