mesh-vault: read which modules of a machine the vault judges handed, and why, with own_handed (issue 1966) #606
@@ -10,6 +10,7 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -424,3 +425,72 @@ func hands(raw []byte) bool {
|
||||
h, err := handsOf(raw)
|
||||
return err == nil && h
|
||||
}
|
||||
|
||||
// HandedJudged is how the vault judges one module of a machine for `own`: handed (its own secrets left out, for the
|
||||
// provisioner's client to fill from `get`), and why, as the plan in its assignment says it; or why it could not judge.
|
||||
type HandedJudged struct {
|
||||
Module string `json:"module"`
|
||||
Handed bool `json:"handed"`
|
||||
Why string `json:"why"`
|
||||
}
|
||||
|
||||
// HandedOf is the vault's judgement of each module of node, as `own` would answer it now (issue 1966): every module it
|
||||
// keeps an own secret of there and every module placed there, sorted. Read from the same plans `own` reads, so what it
|
||||
// says is what the next answer leaves out. A node composes by its manifest, its build and the machine, and the plans it
|
||||
// makes are what this reads next: a module named here as not handed for want of a plan is handed by its node's next
|
||||
// composition, where the machine takes it.
|
||||
func (v *Vault) HandedOf(node string) ([]HandedJudged, error) {
|
||||
if !namePart.MatchString(node) {
|
||||
return nil, fmt.Errorf("%q is not a machine's name", node)
|
||||
}
|
||||
all, err := v.Store.ListPrefix("own/" + node + "/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modules := map[string]bool{}
|
||||
for _, r := range all {
|
||||
if parts := strings.Split(r.Name, "/"); len(parts) == 4 {
|
||||
modules[parts[2]] = true
|
||||
}
|
||||
}
|
||||
plan := v.plansOf(node)
|
||||
placed, placedErr := plan.placedModules()
|
||||
for _, m := range placed {
|
||||
modules[m] = true
|
||||
}
|
||||
out := make([]HandedJudged, 0, len(modules))
|
||||
for _, module := range slices.Sorted(maps.Keys(modules)) {
|
||||
out = append(out, plan.judged(module))
|
||||
}
|
||||
if placedErr != nil && len(out) == 0 {
|
||||
return nil, fmt.Errorf("the placement of %s could not be read and the vault keeps no own secret there: %w", node, placedErr)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// judged is handed with its why, as HandedOf says it.
|
||||
func (p *planReader) judged(module string) HandedJudged {
|
||||
j := HandedJudged{Module: module}
|
||||
raw, why, err := p.read(module)
|
||||
switch {
|
||||
case why != "":
|
||||
j.Why = "not judged: " + why
|
||||
return j
|
||||
case err != nil:
|
||||
j.Why = fmt.Sprintf("not judged: the assignment %s.%s could not be read: %v", p.node, module, err)
|
||||
return j
|
||||
case raw == nil:
|
||||
j.Why = "no assignment holds a plan of it yet: its own secrets are answered to its node"
|
||||
return j
|
||||
}
|
||||
hands, err := handsOf(raw)
|
||||
switch {
|
||||
case err != nil:
|
||||
j.Why = fmt.Sprintf("not judged: the assignment %s.%s is not the controller's: %v", p.node, module, err)
|
||||
case hands:
|
||||
j.Handed, j.Why = true, "its plan hands it: its own secrets are its provisioner client's, from get"
|
||||
default:
|
||||
j.Why = "its plan hands nothing (none, or provisions alone): its own secrets are answered to its node"
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
@@ -291,4 +291,27 @@ func TestOwnLeavesOutOnlyAModuleWhosePlanHandsIt(t *testing.T) {
|
||||
if want := map[string]bool{"grafana": false, "web": true, "db": true}; !reflect.DeepEqual(got.Handed, want) {
|
||||
t.Errorf("own judged %v handed, want %v", got.Handed, want)
|
||||
}
|
||||
// Issue 1966: the vault's judgement is read by a tool, each module with why; one with no plan yet is not handed, for
|
||||
// want of a plan its node's next composition makes.
|
||||
if _, err := moveIn(t, v, "node.anchor", MoveOf{Node: "anchor", Module: "builder", Name: "token", Origin: "made",
|
||||
Generation: "g-1", SealingKey: pub}, "v-builder"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
answer, err := toolNamed(t, definitions(&Env{Vault: v.Vault}), "own_handed").Run(map[string]any{"node": "anchor"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
judged := map[string]HandedJudged{}
|
||||
for _, j := range answer.(map[string]any)["modules"].([]HandedJudged) {
|
||||
judged[j.Module] = j
|
||||
}
|
||||
for module, want := range map[string][2]string{"builder": {"false", "no assignment holds a plan"},
|
||||
"grafana": {"false", "hands nothing"}, "web": {"true", "its plan hands it"}, "db": {"true", "its plan hands it"}} {
|
||||
if j := judged[module]; fmt.Sprint(j.Handed) != want[0] || !strings.Contains(j.Why, want[1]) {
|
||||
t.Errorf("own_handed says %s is %+v, want handed %s, why saying %q", module, j, want[0], want[1])
|
||||
}
|
||||
}
|
||||
if len(judged) != 4 {
|
||||
t.Errorf("own_handed names %v, want builder, db, grafana and web", judged)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +399,23 @@ func definitions(env *Env) []toolDef {
|
||||
given, _ := a["fingerprint"].(string)
|
||||
return verifyAnswer(env, as, given), nil
|
||||
}},
|
||||
{Name: "own_handed",
|
||||
Description: "Which modules of a machine the vault judges handed to the provisioner, as its verb `own` answers that " +
|
||||
"machine's node-engine now (issue 1966): for every module it keeps an own secret of there and every module " +
|
||||
"placed there, handed or not and why (its plan hands it, hands nothing, no plan yet, or why it could not " +
|
||||
"judge). A handed module's own secrets are left out of `own`, for its client to fill from `get`. Never a value.",
|
||||
Input: map[string]any{"node": str("the machine")},
|
||||
run: func(_ string, a map[string]any) (any, error) {
|
||||
v, err := env.ready()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
judged, err := v.HandedOf(arg(a, "node"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"node": arg(a, "node"), "modules": judged}, nil
|
||||
}},
|
||||
{Name: "secret_handed",
|
||||
Description: "Who was handed which secret by `get`, and when, and who was refused, newest first, as the " +
|
||||
"vault's own store records it. Never a value.",
|
||||
|
||||
@@ -173,6 +173,7 @@
|
||||
"secret_holders",
|
||||
"secret_verify",
|
||||
"secret_handed",
|
||||
"own_handed",
|
||||
"secret_own",
|
||||
"account_hash",
|
||||
"account_adopt",
|
||||
|
||||
Reference in New Issue
Block a user