A node agent that meets a manifest which does not yet say it is the tool runner, or that its own secrets are the core's credentials, keeps what runs of it, its tool runner process and its sealed secrets alike, known by what runs and said; never composed without it (ADR 0601 §4)
This commit is contained in:
@@ -154,6 +154,9 @@ type Scope struct {
|
||||
// key), so a differing resource shows a value sealed again as such (delivery.CompareComposedWith). Nil shows them
|
||||
// as differing.
|
||||
SameValue func(a, b string) (same, known bool)
|
||||
// Kept is told, in words, each module composed as what runs shows because its manifest does not yet say it (the tool
|
||||
// runner, the core's credentials; ADR 0601 §4), so the node says it. Nil: said nowhere.
|
||||
Kept func(words string)
|
||||
}
|
||||
|
||||
// InOwn is, of the modules p places on its node, those whose resources the node's own composition it runs
|
||||
@@ -229,7 +232,16 @@ func input(ctx context.Context, r Reader, mesh Mesh, s Scope, refuse func(module
|
||||
if err != nil {
|
||||
return compose.Input{}, err
|
||||
}
|
||||
return b.finish(ctx, r, r.Build, mesh, s, b.in, refuse, wanted)
|
||||
in, err := b.finish(ctx, r, r.Build, mesh, s, b.in, refuse, wanted)
|
||||
if err == nil {
|
||||
// What runs is kept while a manifest does not yet say what shared code no longer infers (ADR 0601 §4).
|
||||
for _, words := range keepWhatRuns(&in, s.Node, s.Running) {
|
||||
if s.Kept != nil {
|
||||
s.Kept(words)
|
||||
}
|
||||
}
|
||||
}
|
||||
return in, err
|
||||
}
|
||||
|
||||
// base is the part of a node's input that the fingerprints it composes its modules at leave alone (issue 1954): read once
|
||||
|
||||
@@ -148,6 +148,7 @@ func TestInput(t *testing.T) {
|
||||
t.Run("a held module's first reason leads", aHeldModulesFirstReasonLeads)
|
||||
t.Run("a held module of its own account never refuses the node", aHeldModuleOfItsOwnAccountNeverRefusesTheNode)
|
||||
t.Run("another node not resolved is named", otherNodeNotResolvedIsNamed)
|
||||
t.Run("what runs is kept while a manifest does not say it", whatRunsIsKeptWhileAManifestDoesNotSayIt)
|
||||
ctx := context.Background()
|
||||
xterm, err := os.ReadFile("testdata/catalogue-835bf22b-xterm.module.json")
|
||||
if err != nil {
|
||||
@@ -867,3 +868,34 @@ func statesOf(t *testing.T, ctx context.Context, b *bus) mapStates {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ADR 0601 §4: the new node agent meets the tool runner's older manifest (no tool-runner) while the node runs its runtime
|
||||
// process, and the provisioner's older manifest (no core-credentials) while its own secret runs as the vault's box: each is
|
||||
// composed as what runs shows, said, by what runs and never by a name; a manifest that says it, or nothing running so, marks
|
||||
// nothing.
|
||||
func whatRunsIsKeptWhileAManifestDoesNotSayIt(t *testing.T) {
|
||||
running := []delivery.Composed{
|
||||
{Node: "a", Module: "runner-x", Resources: []json.RawMessage{json.RawMessage(`{"id":"runner-x.runtime","type":"process"}`)}},
|
||||
{Node: "a", Module: "prov-y", Resources: []json.RawMessage{json.RawMessage(`{"id":"prov-y.need-broker","type":"file","vault-sealed":{"box":"b"}}`)}},
|
||||
{Node: "a", Module: "app", Resources: []json.RawMessage{json.RawMessage(`{"id":"app.need-broker","type":"file"}`)}},
|
||||
}
|
||||
input := func(runnerSays bool) compose.Input {
|
||||
return compose.Input{Nodes: []compose.Node{{Name: "a", Assigned: []string{"app", "prov-y", "runner-x"}}},
|
||||
Catalogue: map[string]catalogue.Manifest{"runner-x": {Module: "runner-x", ToolRunner: runnerSays}, "prov-y": {Module: "prov-y"},
|
||||
"app": {Module: "app"}}}
|
||||
}
|
||||
in := input(false)
|
||||
said := keepWhatRuns(&in, "a", running)
|
||||
if !in.Catalogue["runner-x"].ToolRunner || !in.Catalogue["prov-y"].CoreCredentials || in.Catalogue["app"].ToolRunner ||
|
||||
in.Catalogue["app"].CoreCredentials || len(said) != 2 {
|
||||
t.Errorf("what runs was not kept: %+v, said %q", in.Catalogue, said)
|
||||
}
|
||||
in = input(true)
|
||||
if said := keepWhatRuns(&in, "a", running[:1]); len(said) != 0 {
|
||||
t.Errorf("a manifest that says it is the tool runner was marked again: %q", said)
|
||||
}
|
||||
in = input(false)
|
||||
if said := keepWhatRuns(&in, "a", nil); len(said) != 0 || in.Catalogue["runner-x"].ToolRunner {
|
||||
t.Errorf("nothing running marked %q", said)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package frombus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-host/internal/compose"
|
||||
"github.com/novox/mesh-host/internal/compose/resolution"
|
||||
)
|
||||
|
||||
// **What runs is kept while a manifest does not yet say what shared code no longer infers** (novox/hq ADR 0601 §4). mesh-sdk
|
||||
// knows the tool runner and the modules whose own secrets are the core's credentials only by what their manifests say
|
||||
// (`tool-runner`, `core-credentials`), no longer by their names. A module placed on the composing node whose manifest (an
|
||||
// older build's) does not say so yet, while what the node runs of it shows it (the tool runner's process, `<module>.runtime`;
|
||||
// an own secret carried as the vault's box, the file's `vault-sealed`), is composed as what runs shows: its tool runner
|
||||
// process and its sealed secrets alike are kept, never composed away, and the node says so until the manifest says it.
|
||||
// Known by what runs, never by a module's name.
|
||||
|
||||
// keepWhatRuns marks, in in, each module placed on node that what runs shows is the tool runner or carries the core's
|
||||
// credentials, where its manifest does not say so; it returns, sorted, what it marked, in words. The tool runner is marked
|
||||
// only where no placed module's manifest says it is one.
|
||||
func keepWhatRuns(in *compose.Input, node string, running []delivery.Composed) []string {
|
||||
placed := assignedOn(*in, node)
|
||||
if len(placed) == 0 || len(running) == 0 {
|
||||
return nil
|
||||
}
|
||||
runner, sealed := "", map[string]bool{}
|
||||
for _, run := range running {
|
||||
for _, raw := range run.Resources {
|
||||
var r struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
if json.Unmarshal(raw, &r) != nil || r.ID == "" {
|
||||
continue
|
||||
}
|
||||
owner := run.Module
|
||||
if owner == "" {
|
||||
owner, _ = declaration.ModuleGuess(r.ID)
|
||||
}
|
||||
if owner == "" || !slices.Contains(placed, owner) {
|
||||
continue
|
||||
}
|
||||
if r.Type == "process" && r.ID == owner+"."+resolution.RuntimeProcessID() {
|
||||
runner = owner
|
||||
}
|
||||
if r.Type == "file" {
|
||||
var fields map[string]json.RawMessage
|
||||
if json.Unmarshal(raw, &fields) == nil {
|
||||
if _, ok := fields[catalogue.VaultSealedField]; ok {
|
||||
sealed[owner] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var said []string
|
||||
saysRunner := false
|
||||
for _, m := range placed {
|
||||
if man, ok := manifestOn(*in, node, m); ok && catalogue.IsToolRunner(man) {
|
||||
saysRunner = true
|
||||
}
|
||||
}
|
||||
mark := func(module string, set func(*catalogue.Manifest), words string) {
|
||||
for i := range in.Nodes {
|
||||
if in.Nodes[i].Name != node {
|
||||
continue
|
||||
}
|
||||
if man, ok := in.Nodes[i].Kept[module]; ok {
|
||||
set(&man)
|
||||
in.Nodes[i].Kept[module] = man
|
||||
said = append(said, words)
|
||||
return
|
||||
}
|
||||
}
|
||||
if man, ok := in.Catalogue[module]; ok {
|
||||
set(&man)
|
||||
in.Catalogue[module] = man
|
||||
said = append(said, words)
|
||||
}
|
||||
}
|
||||
if runner != "" && !saysRunner {
|
||||
mark(runner, func(m *catalogue.Manifest) { m.ToolRunner = true }, fmt.Sprintf("%s runs the tool runner's process here and its "+
|
||||
"manifest does not say it is the tool runner (tool-runner): it is composed as the tool runner it runs as, until its manifest says so", runner))
|
||||
}
|
||||
for _, m := range slices.Sorted(maps.Keys(sealed)) {
|
||||
if man, ok := manifestOn(*in, node, m); ok && !catalogue.CarriesCoreCredentials(man) {
|
||||
mark(m, func(m *catalogue.Manifest) { m.CoreCredentials = true }, fmt.Sprintf("%s carries its own secrets sealed by the vault here "+
|
||||
"and its manifest does not say they are the core's credentials (core-credentials): they stay sealed, until its manifest says so", m))
|
||||
}
|
||||
}
|
||||
sort.Strings(said)
|
||||
return said
|
||||
}
|
||||
@@ -66,6 +66,7 @@ func PlansHeldTwice(ctx context.Context, r Reader, mesh Mesh, s Scope) (at, newe
|
||||
sc.into.Err = err
|
||||
continue
|
||||
}
|
||||
keepWhatRuns(&in, sc.scope.Node, sc.scope.Running) // what runs is kept, as Compose keeps it (ADR 0601 §4)
|
||||
sc.into.Plans, sc.into.Err = plansHeldOf(in, s.Node, held)
|
||||
mark("plans at " + sc.name)
|
||||
}
|
||||
|
||||
@@ -844,7 +844,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
}
|
||||
// Its own certificate, asked of the vault when a module here asks for one and the record holds none it trusts.
|
||||
c.certifyIfWanted(ctx, r, own)
|
||||
scope := frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn, Running: running}
|
||||
scope := frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn, Running: running, Kept: c.sayOnceEach}
|
||||
c.mu.Lock()
|
||||
c.dataScope = frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn} // the data items are worked out as composed
|
||||
c.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user