fix: fill a ptr-secret placeholder from the file-owner's credential, not the last consumer's
The provider-seal-key gate: on a node with two modules requiring the same provision (baserow
and letta both consuming postgres), sealedFor matched a need by provision NAME alone, so a
file's ${secret:X} placeholder took whichever consumer's sealed credential came last in
r.Needs -- the OTHER module's password. baserow was handed letta's password and could not
authenticate. The secrets:-map delivery path already guards this (For == m.Module, novox/hq
04-ISSUES/022); the ${secret:...} placeholder path did not. Added the same guard.
Also dedups the contributions file: when provider and consumer are co-located, grantsFor
enumerates the same-node consumer, so a consumer was emitted twice into the provider's
receives file (once full with its grant, once partial). The m.Contributes loop now skips a
(provision, module) the grants loop already carried; non-grant contributions (routes) still emit.
Regression test added: two consumers of one provision each get their own credential. Proven
end-to-end on a two-node lab install (mesh-lab assigned-two-node-db): baserow and letta on one
node, substrate on another, each authenticates with its own minted password.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -496,6 +496,11 @@ func (r Resolution) contributions(settings SettingsBy, grants []Grant,
|
||||
}
|
||||
return sorted[i].From < sorted[j].From
|
||||
})
|
||||
// A consumer already carried by the grants loop, keyed (provision, module). When provider and
|
||||
// consumer are co-located, `grantsFor` enumerates the same-node consumer too, so without this the
|
||||
// module would be emitted a second time by the m.Contributes loop below — once full (with the
|
||||
// grant's secret/as) and once partial — which is the duplicate seen in a co-located mesh.json.
|
||||
granted := map[string]map[string]bool{}
|
||||
for _, g := range sorted {
|
||||
if g.From == "" {
|
||||
// As above: nothing on that machine asks for this any more, so the provider is not
|
||||
@@ -507,9 +512,20 @@ func (r Resolution) contributions(settings SettingsBy, grants []Grant,
|
||||
As: ConsumerIdentity(g.Consumer, IdentitySource(g.Slug, g.From)),
|
||||
Secret: grantPath(directories[g.Provision], g.Consumer, g.From),
|
||||
})
|
||||
if granted[g.Provision] == nil {
|
||||
granted[g.Provision] = map[string]bool{}
|
||||
}
|
||||
granted[g.Provision][g.From] = true
|
||||
}
|
||||
for _, m := range modules {
|
||||
for _, to := range sortedKeys(m.Contributes) {
|
||||
// The grants loop already emitted this module's contribution to this provision, with its
|
||||
// minted secret — emitting the partial copy again would duplicate it. A contribution with
|
||||
// no grant (a reverse-proxy route names a host, not a credential) is not in `granted`, so
|
||||
// it still reaches the provider from here.
|
||||
if granted[to][m.Module] {
|
||||
continue
|
||||
}
|
||||
// Settings reach a contribution the same way they reach a file. A route's hostname is
|
||||
// exactly the kind of thing that differs between one mesh and the next, and a module
|
||||
// that could not have it set would have to be edited to be reused.
|
||||
|
||||
Reference in New Issue
Block a user