Hand a module exactly when the vault's own answer judged it handed, so the node never composes a secret the vault leaves to the provisioner (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@a4713e2d28d7
mesh/delivery-group group feat/072-moves merged: every member is merged
mesh/check failed: shanks: the check of novox/mesh-controller#487 failed: go mod download in modules/route-proxy: go: git.novox.be/novox/mesh-controll…
mesh/delivery merged: source.main moved to novox/mesh-host@a4713e2d28d7
mesh/delivery-group group feat/072-moves merged: every member is merged
mesh/check failed: shanks: the check of novox/mesh-controller#487 failed: go mod download in modules/route-proxy: go: git.novox.be/novox/mesh-controll…
This commit is contained in:
@@ -3,7 +3,7 @@ module github.com/novox/mesh-host
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082917-2d1d8def7535
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
golang.org/x/crypto v0.57.0
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082917-2d1d8def7535 h1:Bgrm3YfWCxr+FEm03kEP5fDIe9hNbe+j0CDVatGAGKg=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082917-2d1d8def7535/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031083427-de8e82c06f54 h1:xITIabv8ujmwpaEKBiQpkOx6U/ICLTTzX9WWC3DGOPQ=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031083427-de8e82c06f54/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -113,6 +114,10 @@ type Composer struct {
|
||||
// sealedOwn is each own secret's sealed value as first handed, by module/name and generation: a value sealed again
|
||||
// is other bytes, so the one first had is kept while the generation stands, and the composition stays the same.
|
||||
sealedOwn map[string]ownSealed
|
||||
// ownHanded is, by module, whether the vault judged it handed to the provisioner in its last own answer (its
|
||||
// `handed`), nil when that answer said nothing of it (a vault older than the field): the composition hands exactly
|
||||
// those (frombus.HandedReader, issue 774).
|
||||
ownHanded map[string]bool
|
||||
// certified is the serving key a certificate was last asked for successfully, so it is asked once per key.
|
||||
certified string
|
||||
said string
|
||||
@@ -1279,6 +1284,7 @@ func (r reader) OwnSecrets(ctx context.Context, node string) ([]compose.OwnSecre
|
||||
Generation int `json:"generation"`
|
||||
Sealed json.RawMessage `json:"sealed"`
|
||||
} `json:"own"`
|
||||
Handed map[string]bool `json:"handed"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return nil, fmt.Errorf("the vault's own answer is not one: %w", err)
|
||||
@@ -1288,6 +1294,7 @@ func (r reader) OwnSecrets(ctx context.Context, node string) ([]compose.OwnSecre
|
||||
}
|
||||
r.c.mu.Lock()
|
||||
defer r.c.mu.Unlock()
|
||||
r.c.ownHanded = a.Handed
|
||||
out := make([]compose.OwnSecretRow, 0, len(a.Own))
|
||||
for _, o := range a.Own {
|
||||
// The box first had is kept while its generation stands, so sealing it again moves no composition; the box the
|
||||
@@ -1304,6 +1311,19 @@ func (r reader) OwnSecrets(ctx context.Context, node string) ([]compose.OwnSecre
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Handed is, of this node's modules, which the vault judged handed to the provisioner when it last answered own
|
||||
// (frombus.HandedReader, issue 774): the node hands exactly those, so the vault and the node judge a module one way.
|
||||
func (r reader) Handed(_ context.Context, node string) map[string]bool {
|
||||
if node != r.c.env.Node {
|
||||
return nil
|
||||
}
|
||||
r.c.mu.Lock()
|
||||
defer r.c.mu.Unlock()
|
||||
return maps.Clone(r.c.ownHanded)
|
||||
}
|
||||
|
||||
var _ frombus.HandedReader = reader{}
|
||||
|
||||
func (r reader) Credentials(ctx context.Context, node string, asConsumer, asProvider []compose.CredentialKey) ([]compose.Credential, []compose.Issued, error) {
|
||||
if node != r.c.env.Node || r.c.env.Own == nil {
|
||||
return nil, nil, nil
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -163,7 +164,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
asks++
|
||||
sealedAt[fmt.Sprintf(`{"ask":%d}`, asks)] = generation + 1
|
||||
return json.RawMessage(fmt.Sprintf(`{"node":"n1","key":"sk","own":[{"module":"keeper","name":"password","origin":"made",`+
|
||||
`"generation":%d,"sealed":{"ask":%d}}]}`, generation+1, asks)), nil
|
||||
`"generation":%d,"sealed":{"ask":%d}}],"handed":{"keeper":false}}`, generation+1, asks)), nil
|
||||
}
|
||||
secretChanged := make(chan []byte, 1)
|
||||
env.Hear = func(subject string) (<-chan []byte, func(), error) {
|
||||
@@ -767,6 +768,16 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
if rotated.Key != keeperKey {
|
||||
t.Fatalf("a new generation handed %s, want keeper again", rotated.Key)
|
||||
}
|
||||
// Issue 774: what the vault judged handed in its own answer is what the node's composition hands, for n1 alone.
|
||||
b.mu.Lock()
|
||||
asked := asks
|
||||
b.mu.Unlock()
|
||||
if h := (reader{c: c}).Handed(ctx, "n1"); asked == 0 || !reflect.DeepEqual(h, map[string]bool{"keeper": false}) {
|
||||
t.Errorf("the vault's handed verdicts as the composition reads them, after %d asks: %v", asked, h)
|
||||
}
|
||||
if h := (reader{c: c}).Handed(ctx, "n2"); h != nil {
|
||||
t.Errorf("another node's verdicts: %v", h)
|
||||
}
|
||||
|
||||
// Review of L2 (round 2): rotated, then the node-engine restarts. The agent has applied nothing yet, so the box the
|
||||
// node runs comes from keeper's have (Held); the vault seals the value again, other bytes, and the box the have holds
|
||||
|
||||
Reference in New Issue
Block a user