Files
mesh-sdk/go/delivery/own_drift.go
T
jschoubben 3028c7ec4e
mesh/delivery merged: source.main moved to novox/mesh-sdk@79eb58efe4b0
mesh/delivery-group group fix/1126-seed-same merged: every member is merged
mesh/check failed: integration: github.com/novox/mesh-host/cmd/mesh-host.TestNatsANodeThePlacementNamesComposesAndAppliesItsOwn: the node did not comp…
A drifted own secret is read from a resource id as a node runs it, its module's name first (only-office.needs-jwt), so the node agent finds what to hand the vault (issue 1126, cause C4; review round 2)
delivery.OwnNeedName reads <module>.needs-<name> and the bare needs-<name>; OwnValuesDiffer and the node agent use it.
The fixtures carry the id shape captured from novox's shadow answer.
2026-10-28 10:00:29 +01:00

96 lines
3.6 KiB
Go

package delivery
// The own secrets a node's shadow answer finds the vault holds another value for than the node runs (novox/hq issue 1126,
// cause C4): the vault's `move-in` never replaces a value it holds, so a value the controller made anew later (a rotation
// at start, a re-issue) runs while the vault keeps the first one it was handed. The node opens both seals with its own
// key and its answer's shown bodies mask the value as MaskedAnotherValue; this reads which own secrets those are, for the
// node agent (which hands the vault what runs) and for the vault's `own_drift` (which lists them mesh-wide), so both read
// the answer one way.
import (
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// MaskedAnotherValue is the word a shown body masks a value with when the node opened both sides with its key and they
// are two values (MaskedPair, maskWord).
const MaskedAnotherValue = "<masked: another value>"
// OwnValueDiffers is one own secret of a module whose value composed (the vault's) is another than the one that runs.
type OwnValueDiffers struct {
Module string `json:"module"`
Name string `json:"name"`
}
// ownNeedPrefix starts the id of the file a module's own secret is placed in (catalogue.NeedID).
var ownNeedPrefix = catalogue.NeedID("")
// OwnNeedName is the name of module's own secret whose file a resource id names, and whether it names one. On a node a
// resource's id carries its module's name (`only-office.needs-jwt`, as a live shadow answer shows it); the bare form a
// declaration composes (`needs-jwt`, catalogue.NeedID) is read too. An id of another module's is not this module's.
func OwnNeedName(module, id string) (string, bool) {
if module != "" {
id = strings.TrimPrefix(id, module+".")
}
name, own := strings.CutPrefix(id, ownNeedPrefix)
return name, own && name != "" && !strings.Contains(name, ".")
}
// OwnValuesDiffer is every own secret a's shown bodies say holds another value composed than running: a body of a
// module's own secret's file (OwnNeedName: `<module>.needs-<name>` on a node) masked MaskedAnotherValue on either side. Sorted by module and name, each
// once. A body the answer did not show (beyond MaxBodies) is not read.
func OwnValuesDiffer(a CandidateAnswer) []OwnValueDiffers {
seen := map[OwnValueDiffers]bool{}
var out []OwnValueDiffers
for _, d := range a.Differs {
for _, b := range d.Bodies {
name, own := OwnNeedName(d.Module, b.ID)
if !own || !anotherValue(b) {
continue
}
o := OwnValueDiffers{Module: d.Module, Name: name}
if !seen[o] {
seen[o] = true
out = append(out, o)
}
}
}
sort.Slice(out, func(i, j int) bool {
if out[i].Module != out[j].Module {
return out[i].Module < out[j].Module
}
return out[i].Name < out[j].Name
})
return out
}
// ValuesDiffer is how many shown bodies of a hold a value masked MaskedAnotherValue, own secrets and every other
// resource alike: the count a node's answer carries as CandidateAnswer.ValuesDiffer.
func ValuesDiffer(a CandidateAnswer) int {
n := 0
for _, d := range a.Differs {
for _, b := range d.Bodies {
if anotherValue(b) {
n++
}
}
}
return n
}
// WithValuesDiffer is answers with each one's ValuesDiffer counted.
func WithValuesDiffer(answers map[string]CandidateAnswer) map[string]CandidateAnswer {
out := make(map[string]CandidateAnswer, len(answers))
for g, a := range answers {
a.ValuesDiffer = ValuesDiffer(a)
out[g] = a
}
return out
}
func anotherValue(b ResourceBodies) bool {
return strings.Contains(b.Composed, MaskedAnotherValue) || strings.Contains(b.Running, MaskedAnotherValue)
}