The node agent finds a drifted own secret's running file by its id as the node runs it, its module's name first (issue 1126, cause C4; review round 2)
mesh/delivery merged: source.main moved to novox/mesh-host@f6c0520f26b9
mesh/delivery-group group fix/1126-seed-same merged: every member is merged
mesh/check checking c912bcab6e73 on f6c0520f26b9 | novox/mesh-host#158 in group fix/1126-seed-same

Pins mesh-sdk 3028c7e (delivery.OwnNeedName). The fixtures carry the id shape captured from novox's shadow answer.
This commit is contained in:
2026-10-28 10:01:38 +01:00
parent 63c0b445d7
commit c912bcab6e
4 changed files with 21 additions and 15 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ module github.com/novox/mesh-host
go 1.26.0 go 1.26.0
require ( require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028084903-1062b33d32c5 git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028090029-3028c7ec4ed2
github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0 github.com/nats-io/nats.go v1.54.0
golang.org/x/crypto v0.57.0 golang.org/x/crypto v0.57.0
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028084903-1062b33d32c5 h1:FRNW0kjmm2SYVbEAeG9IsMWALE/qRKt8IMKNnMn2p90= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028090029-3028c7ec4ed2 h1:eSwObLdxs3jxPrlcwdaMo9hzIDTUz2ogWw8cjX8To7I=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028084903-1062b33d32c5/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028090029-3028c7ec4ed2/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= 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= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
+6 -2
View File
@@ -223,13 +223,17 @@ func (r *Resync) running(ctx context.Context, o delivery.OwnValueDiffers) (strin
return "", fmt.Sprintf("what runs (%s, %s) is not yet the composition its want names (%s)", have.Composed, return "", fmt.Sprintf("what runs (%s, %s) is not yet the composition its want names (%s)", have.Composed,
have.Phase, want.Composed) have.Phase, want.Composed)
} }
id := catalogue.NeedID(o.Name) // On a node the resource's id carries its module's name (`only-office.needs-jwt`); delivery.OwnNeedName reads it.
id := o.Module + "." + catalogue.NeedID(o.Name)
for _, res := range comp.Resources { for _, res := range comp.Resources {
var body struct { var body struct {
ID string `json:"id"` ID string `json:"id"`
Sealed any `json:"sealed"` Sealed any `json:"sealed"`
} }
if json.Unmarshal(res, &body) != nil || body.ID != id { if json.Unmarshal(res, &body) != nil {
continue
}
if name, own := delivery.OwnNeedName(o.Module, body.ID); !own || name != o.Name {
continue continue
} }
if s, ok := body.Sealed.(string); ok && s != "" { if s, ok := body.Sealed.(string); ok && s != "" {
+12 -10
View File
@@ -21,9 +21,9 @@ func TestTheNodeHandsTheVaultWhatRunsOfADriftedOwnSecret(t *testing.T) {
fp := "src2:" + strings.Repeat("b", 64) fp := "src2:" + strings.Repeat("b", 64)
sum := sha256.Sum256([]byte(running)) sum := sha256.Sum256([]byte(running))
answers := map[string]delivery.CandidateAnswer{"g": {Head: "h", Verdict: delivery.CandidateDiffers, answers := map[string]delivery.CandidateAnswer{"g": {Head: "h", Verdict: delivery.CandidateDiffers,
Differs: []delivery.ModuleDiffers{{Module: "onlyoffice", Why: "the resource differs", Bodies: []delivery.ResourceBodies{ Differs: []delivery.ModuleDiffers{{Module: "only-office", Why: "the resource differs", Bodies: []delivery.ResourceBodies{
{ID: "needs-jwt", Composed: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`}, {ID: "only-office.needs-jwt", Composed: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`},
{ID: "needs-salt", Composed: `{"sealed":"<masked: another seal of the same value>"}`}}}}}} {ID: "only-office.needs-salt", Composed: `{"sealed":"<masked: another seal of the same value>"}`}}}}}}
for name, row := range map[string]struct { for name, row := range map[string]struct {
composes string composes string
runs string // the have's composition digest runs string // the have's composition digest
@@ -43,14 +43,16 @@ func TestTheNodeHandsTheVaultWhatRunsOfADriftedOwnSecret(t *testing.T) {
have, _ := delivery.EncodeHave(delivery.Have{FP: fp, Composed: row.runs, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}) have, _ := delivery.EncodeHave(delivery.Have{FP: fp, Composed: row.runs, Phase: delivery.PhaseRunning, Health: delivery.HealthOK})
r := New(Env{Node: "novox", Composes: func() string { return row.composes }, r := New(Env{Node: "novox", Composes: func() string { return row.composes },
Running: func() []delivery.Composed { Running: func() []delivery.Composed {
return []delivery.Composed{{Node: "novox", Module: "onlyoffice", FP: fp, Resources: []json.RawMessage{ return []delivery.Composed{{Node: "novox", Module: "only-office", FP: fp, Resources: []json.RawMessage{
json.RawMessage(`{"id":"needs-jwt","type":"file","path":"/etc/jwt","sealed":"box-of-the-running-value"}`)}}} // As novox runs it (its shadow answer, 2026-10-28): the id carries the module's name.
json.RawMessage(`{"id":"only-office.needs-jwt","module":"only-office","type":"file",` +
`"path":"/var/lib/only-office/jwt.secret","sealed":"box-of-the-running-value"}`)}}}
}, },
Get: func(_ context.Context, bucket, key string) ([]byte, error) { Get: func(_ context.Context, bucket, key string) ([]byte, error) {
switch { switch {
case bucket == delivery.AssignerWantsBucket && key == "want.novox.onlyoffice": case bucket == delivery.AssignerWantsBucket && key == "want.novox.only-office":
return want, nil return want, nil
case bucket == delivery.HaveBucket && key == "novox.onlyoffice": case bucket == delivery.HaveBucket && key == "novox.only-office":
return have, nil return have, nil
} }
return nil, nil return nil, nil
@@ -71,15 +73,15 @@ func TestTheNodeHandsTheVaultWhatRunsOfADriftedOwnSecret(t *testing.T) {
return nil, errors.New(row.refuse) return nil, errors.New(row.refuse)
} }
if args["call"] == nil { if args["call"] == nil {
if args["module"] != "onlyoffice" || args["name"] != "jwt" || args["node"] != nil { if args["module"] != "only-office" || args["name"] != "jwt" || args["node"] != nil {
t.Errorf("%s: the first step names %v", name, args) t.Errorf("%s: the first step names %v", name, args)
} }
return json.RawMessage(`{"call":"c1","seal_to":"key","secret":"own/novox/onlyoffice/jwt"}`), nil return json.RawMessage(`{"call":"c1","seal_to":"key","secret":"own/novox/only-office/jwt"}`), nil
} }
if args["sealed"] != "key("+running+")" { if args["sealed"] != "key("+running+")" {
t.Errorf("%s: handed %v, not what runs sealed to the call's key", name, args["sealed"]) t.Errorf("%s: handed %v, not what runs sealed to the call's key", name, args["sealed"])
} }
return json.RawMessage(`{"secret":"own/novox/onlyoffice/jwt","from_generation":1,"to_generation":2,` + return json.RawMessage(`{"secret":"own/novox/only-office/jwt","from_generation":1,"to_generation":2,` +
`"fingerprint":"sha256:` + hex.EncodeToString(sum[:]) + `"}`), nil `"fingerprint":"sha256:` + hex.EncodeToString(sum[:]) + `"}`), nil
}, },
Say: func(s string) { said = append(said, s) }}) Say: func(s string) { said = append(said, s) }})