The vault takes what runs through move-in's replace_running, not a verb of its own, so no seat promises anything new (issue 1126, cause C4)
This commit is contained in:
@@ -92,7 +92,7 @@ type callKey struct {
|
||||
ask *AskRecord
|
||||
// move is what `move-in` takes: the row of the controller's store (move.go).
|
||||
move *MoveOf
|
||||
// why is what `own-take-running` was asked for, in the caller's words (owntake.go).
|
||||
// why is what `move-in` with replace_running was asked for, in the caller's words (owntake.go).
|
||||
why string
|
||||
public [32]byte
|
||||
private [32]byte
|
||||
@@ -151,6 +151,16 @@ func (v *Vault) openCall(verb, secret, by string, ask *AskRecord) (string, strin
|
||||
return id, base64.StdEncoding.EncodeToString(pub[:]), nil
|
||||
}
|
||||
|
||||
// callKind is the kind of the open call id, or "" when none is open: what its second step is for.
|
||||
func (v *Vault) callKind(id string) string {
|
||||
v.callsMu.Lock()
|
||||
defer v.callsMu.Unlock()
|
||||
if c, ok := v.pending[id]; ok {
|
||||
return c.verb
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// takeCall ends a call: its key is forgotten whatever follows.
|
||||
func (v *Vault) takeCall(verb, id string) (*callKey, error) {
|
||||
v.callsMu.Lock()
|
||||
|
||||
@@ -343,12 +343,20 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
|
||||
}
|
||||
}
|
||||
later = time.Hour
|
||||
// Through move-in's tool as the node-engine asks it: replace_running in the first step, the second known by its call.
|
||||
moveInTool := toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".move-in")
|
||||
take := func(caller string, of OwnTakeOf, value string) (OwnReplaced, error) {
|
||||
opened, err := v.OwnTakeOpen(caller, of)
|
||||
got, err := moveInTool.runAs(caller, map[string]any{ReplaceRunningArg: true, "node": of.Node, "module": of.Module,
|
||||
"name": of.Name, "secret": of.Secret, "why": of.Why})
|
||||
if err != nil {
|
||||
return OwnReplaced{}, err
|
||||
}
|
||||
return v.OwnTakeTake(caller, opened.Call, sealToCall(t, opened.SealTo, value))
|
||||
opened := got.(Opened)
|
||||
got, err = moveInTool.runAs(caller, map[string]any{"call": opened.Call, "sealed": sealToCall(t, opened.SealTo, value)})
|
||||
if err != nil {
|
||||
return OwnReplaced{}, err
|
||||
}
|
||||
return got.(OwnReplaced), nil
|
||||
}
|
||||
pg := OwnTakeOf{Module: "postgres", Name: "superuser-password", Why: "the shadow answer says another value"}
|
||||
for name, row := range map[string]struct {
|
||||
|
||||
@@ -5,9 +5,10 @@ package main
|
||||
// `move-in` never replaces a value the vault holds (move.go), so its first import of a machine's own secret is fixed.
|
||||
// Where the controller later made that secret anew (a rotation at start, a re-issue) without handing it here, the new
|
||||
// value is what runs and what its consumers hold, and the vault keeps the old one. Only the machine the value is sealed
|
||||
// to can open what runs, so its node-engine hands it in, with the verb `own-take-running`, in the two steps of every
|
||||
// intake (intake.go): name the module and the secret and why, and get a call and a key made for it; then send the value
|
||||
// sealed to that key.
|
||||
// to can open what runs, so its node-engine hands it in through `move-in` with replace_running: true, in the two steps
|
||||
// of every intake (intake.go): name the module and the secret and why, and get a call and a key made for it; then send
|
||||
// the value sealed to that key. It is move-in's, not a verb of its own, so a node-engine asks it on the subject it may
|
||||
// already publish and no seat promises anything new (the lead, 2026-10-28).
|
||||
//
|
||||
// **From the node-engine of the machine the secret is for, about its own machine alone**: the machine is the caller's,
|
||||
// as the bus proved it (`node.<machine>`), never an argument; a machine named that is not the caller's is refused, and so
|
||||
@@ -35,8 +36,11 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// OwnTakeRunningVerb is the seat verb a machine's node-engine hands the vault a running own secret with.
|
||||
const OwnTakeRunningVerb = "own-take-running"
|
||||
// ReplaceRunningArg is the argument of `move-in` that makes it take a running own secret over the one the vault holds.
|
||||
const ReplaceRunningArg = "replace_running"
|
||||
|
||||
// ReplaceRunning names the act in what the vault says, and is the kind of its open calls.
|
||||
const ReplaceRunning = "move-in with " + ReplaceRunningArg
|
||||
|
||||
// OwnReplacedEvent is what the vault says when it replaced an own secret with what runs: never a value.
|
||||
const OwnReplacedEvent = "own-replaced"
|
||||
@@ -44,13 +48,13 @@ const OwnReplacedEvent = "own-replaced"
|
||||
// RecentlyChanged is how long after the vault changed a secret a running value is not taken over it.
|
||||
var RecentlyChanged = 15 * time.Minute
|
||||
|
||||
// OwnTakeOf names the secret of one `own-take-running`: Module and Name (or Secret whole), Node only to be checked
|
||||
// OwnTakeOf names the secret of one `move-in` with replace_running: Module and Name (or Secret whole), Node only to be checked
|
||||
// against the caller's machine, and Why in the caller's words.
|
||||
type OwnTakeOf struct {
|
||||
Node, Module, Name, Secret, Why string
|
||||
}
|
||||
|
||||
// OwnReplaced is what one `own-take-running` did: the secret, the generation it replaced and the one it holds now, the
|
||||
// OwnReplaced is what one `move-in` with replace_running did: the secret, the generation it replaced and the one it holds now, the
|
||||
// fingerprint of the value held now (what the node-engine checks against the value it handed), whether the value was
|
||||
// held already. Never a value.
|
||||
type OwnReplaced struct {
|
||||
@@ -74,13 +78,13 @@ func ownTakeSecret(caller string, of OwnTakeOf) (secret, node string, err error)
|
||||
caller = "an unproven caller"
|
||||
}
|
||||
return "", "", fmt.Errorf("%s takes a value from a machine's node-engine alone, about its own machine; %s is "+
|
||||
"not a node-engine; nothing is taken", OwnTakeRunningVerb, caller)
|
||||
"not a node-engine; nothing is taken", ReplaceRunning, caller)
|
||||
}
|
||||
if of.Secret != "" {
|
||||
parts := strings.Split(of.Secret, "/")
|
||||
if len(parts) != 4 || parts[0] != "own" {
|
||||
return "", "", fmt.Errorf("%s is not a module's own secret (own/<machine>/<module>/<name>): %s replaces "+
|
||||
"those alone; nothing is taken", of.Secret, OwnTakeRunningVerb)
|
||||
"those alone; nothing is taken", of.Secret, ReplaceRunning)
|
||||
}
|
||||
if of.Node != "" && of.Node != parts[1] {
|
||||
return "", "", fmt.Errorf("%s is not on %s; nothing is taken", of.Secret, of.Node)
|
||||
@@ -140,7 +144,7 @@ func (v *Vault) OwnTakeOpen(caller string, of OwnTakeOf) (Opened, error) {
|
||||
_ = v.recordHanding(Handing{To: caller, Name: secret, Generation: rec.Generation, Outcome: "own-take-refused", Why: refused})
|
||||
return Opened{}, errors.New(refused)
|
||||
}
|
||||
id, key, err := v.openCall(OwnTakeRunningVerb, secret, caller, nil)
|
||||
id, key, err := v.openCall(ReplaceRunning, secret, caller, nil)
|
||||
if err != nil {
|
||||
return Opened{}, err
|
||||
}
|
||||
@@ -156,14 +160,14 @@ func (v *Vault) OwnTakeOpen(caller string, of OwnTakeOf) (Opened, error) {
|
||||
func (v *Vault) OwnTakeTake(caller, call, sealed string) (OwnReplaced, error) {
|
||||
if _, ok := nodeEngineOf(caller); !ok {
|
||||
return OwnReplaced{}, fmt.Errorf("%s takes a value from a machine's node-engine alone; %q is not one",
|
||||
OwnTakeRunningVerb, caller)
|
||||
ReplaceRunning, caller)
|
||||
}
|
||||
c, err := v.takeCall(OwnTakeRunningVerb, call)
|
||||
c, err := v.takeCall(ReplaceRunning, call)
|
||||
if err != nil {
|
||||
return OwnReplaced{}, err
|
||||
}
|
||||
if c.by != caller {
|
||||
return OwnReplaced{}, fmt.Errorf("the %s call %q was opened by another caller; nothing was taken", OwnTakeRunningVerb, call)
|
||||
return OwnReplaced{}, fmt.Errorf("the %s call %q was opened by another caller; nothing was taken", ReplaceRunning, call)
|
||||
}
|
||||
value, err := c.open(sealed)
|
||||
if err != nil {
|
||||
@@ -227,7 +231,7 @@ type DriftRow struct {
|
||||
ChangedBy string `json:"changed_by,omitempty"`
|
||||
Origin string `json:"origin,omitempty"`
|
||||
Previous int `json:"previous_generation,omitempty"`
|
||||
// Takeable says own-take-running would take the running value now; Refused why not.
|
||||
// Takeable says move-in with replace_running would take the running value now; Refused why not.
|
||||
Takeable bool `json:"takeable"`
|
||||
Refused string `json:"refused,omitempty"`
|
||||
}
|
||||
@@ -250,7 +254,7 @@ type Drift struct {
|
||||
|
||||
// OwnDrift reads every node's own have key (`<node>.node`) and lists, from its latest answers to the placement
|
||||
// candidates, every own secret whose shown bodies say another value (delivery.OwnValuesDiffer), with what the vault holds
|
||||
// of it and whether own-take-running would take the running value now.
|
||||
// of it and whether move-in with replace_running would take the running value now.
|
||||
func (v *Vault) OwnDrift() (Drift, error) {
|
||||
if v.Haves == nil || v.HaveKeys == nil {
|
||||
return Drift{}, errors.New("the vault reads no node's have, so it cannot say what drifted")
|
||||
@@ -262,7 +266,7 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
sort.Strings(keys)
|
||||
rows := map[string]*DriftRow{}
|
||||
out := Drift{Secrets: []DriftRow{}, Nodes: []DriftNode{},
|
||||
Note: "from each node's latest shadow answers; a value is never shown: own-take-running from that node replaces it"}
|
||||
Note: "from each node's latest shadow answers; a value is never shown: move-in with replace_running from that node replaces it"}
|
||||
for _, key := range keys {
|
||||
node, isNode := strings.CutSuffix(key, "."+delivery.HaveNodeWord)
|
||||
if !isNode || !namePart.MatchString(node) {
|
||||
@@ -340,7 +344,7 @@ func (v *Vault) OwnDrift() (Drift, error) {
|
||||
|
||||
// ---- own_take_switch ----------------------------------------------------------------------------------------
|
||||
|
||||
// OwnTakeSwitch is whether own-take-running takes values, who switched it last, when and why. On until a person
|
||||
// OwnTakeSwitch is whether move-in with replace_running takes values, who switched it last, when and why. On until a person
|
||||
// switches it off; kept in the vault's own store, so it holds over a restart.
|
||||
type OwnTakeSwitch struct {
|
||||
On bool `json:"on"`
|
||||
@@ -349,7 +353,7 @@ type OwnTakeSwitch struct {
|
||||
Why string `json:"why,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Store) ownTakeSwitchPath() string { return filepath.Join(s.dir, "own-take-running.json") }
|
||||
func (s *Store) ownTakeSwitchPath() string { return filepath.Join(s.dir, "replace-running.json") }
|
||||
|
||||
// OwnTakeSwitch is the switch as kept: on when nothing was ever switched.
|
||||
func (s *Store) OwnTakeSwitch() (OwnTakeSwitch, error) {
|
||||
@@ -362,12 +366,12 @@ func (s *Store) OwnTakeSwitch() (OwnTakeSwitch, error) {
|
||||
}
|
||||
var sw OwnTakeSwitch
|
||||
if err := json.Unmarshal(raw, &sw); err != nil {
|
||||
return OwnTakeSwitch{}, fmt.Errorf("the switch of %s is not readable: %w", OwnTakeRunningVerb, err)
|
||||
return OwnTakeSwitch{}, fmt.Errorf("the switch of %s is not readable: %w", ReplaceRunning, err)
|
||||
}
|
||||
return sw, nil
|
||||
}
|
||||
|
||||
// SwitchOwnTake switches own-take-running on or off, a person's act with why; the caller is recorded as the bus
|
||||
// SwitchOwnTake switches move-in with replace_running on or off, a person's act with why; the caller is recorded as the bus
|
||||
// proved it.
|
||||
func (v *Vault) SwitchOwnTake(caller string, on bool, why string) (OwnTakeSwitch, error) {
|
||||
why = strings.TrimSpace(why)
|
||||
@@ -379,18 +383,18 @@ func (v *Vault) SwitchOwnTake(caller string, on bool, why string) (OwnTakeSwitch
|
||||
if err := writeWhole(v.Store.ownTakeSwitchPath(), append(raw, '\n')); err != nil {
|
||||
return OwnTakeSwitch{}, err
|
||||
}
|
||||
v.log("%s is switched %s by %s: %s", OwnTakeRunningVerb, map[bool]string{true: "on", false: "off"}[on], sw.By, why)
|
||||
v.log("%s is switched %s by %s: %s", ReplaceRunning, map[bool]string{true: "on", false: "off"}[on], sw.By, why)
|
||||
return sw, nil
|
||||
}
|
||||
|
||||
// ownTakeOff says why own-take-running takes nothing now, or "".
|
||||
// ownTakeOff says why move-in with replace_running takes nothing now, or "".
|
||||
func (v *Vault) ownTakeOff() string {
|
||||
sw, err := v.Store.OwnTakeSwitch()
|
||||
if err != nil {
|
||||
return err.Error() + "; nothing is taken"
|
||||
}
|
||||
if !sw.On {
|
||||
return fmt.Sprintf("%s is switched off (by %s at %s: %s); nothing is taken", OwnTakeRunningVerb, sw.By,
|
||||
return fmt.Sprintf("%s is switched off (by %s at %s: %s); nothing is taken", ReplaceRunning, sw.By,
|
||||
sw.At.Format(time.RFC3339), sw.Why)
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -49,7 +49,7 @@ type Record struct {
|
||||
// handed it in and the controller's generation for it, kept as it was.
|
||||
MovedFrom *MovedFrom `json:"moved-from,omitempty"`
|
||||
// Previous is the value this one replaced when a machine's node-engine handed the vault what runs there
|
||||
// (own-take-running, owntake.go): kept sealed as it was, so the replacement can be read and undone. Any later
|
||||
// (move-in with replace_running, owntake.go): kept sealed as it was, so the replacement can be read and undone. Any later
|
||||
// change of the secret drops it.
|
||||
Previous *PreviousValue `json:"previous,omitempty"`
|
||||
}
|
||||
|
||||
@@ -195,31 +195,8 @@ func definitions(env *Env) []toolDef {
|
||||
}
|
||||
return v.RotateFor(caller, secret, one, arg(a, "by"), arg(a, "why"))
|
||||
}},
|
||||
{Name: Seat + "." + OwnTakeRunningVerb,
|
||||
Description: "Replace one own secret of the asking machine (own/<machine>/<module>/<name>, the machine as the " +
|
||||
"bus proved the caller) with the value that runs there (issue 1126, cause C4), from that machine's " +
|
||||
"node-engine alone, in two steps: name module, name and why, and get a call and a key made for it; then " +
|
||||
"send the call and the value sealed to that key. Keeps the old value as the previous generation, raises " +
|
||||
"the generation and says own-replaced, never the value; answers both generations and the fingerprint of " +
|
||||
"what it holds. Refused: another machine's secret, one that is not an own secret or not held, an empty " +
|
||||
"value, a person's value, and a value the vault changed in the last 15 minutes. The same value is a no-op.",
|
||||
Input: intakeInput(map[string]string{"module": "the module", "name": "its own secret's name",
|
||||
"node": "the machine, when named: the caller's own, or refused",
|
||||
"secret": "or the secret's whole name, own/<machine>/<module>/<name>",
|
||||
"why": "why the value is handed, in words"}),
|
||||
run: func(caller string, a map[string]any) (any, error) {
|
||||
v, err := env.ready()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if call := arg(a, "call"); call != "" {
|
||||
return v.OwnTakeTake(caller, call, arg(a, "sealed"))
|
||||
}
|
||||
return v.OwnTakeOpen(caller, OwnTakeOf{Node: arg(a, "node"), Module: arg(a, "module"), Name: arg(a, "name"),
|
||||
Secret: arg(a, "secret"), Why: arg(a, "why")})
|
||||
}},
|
||||
{Name: "own_take_switch",
|
||||
Description: "Switch own-take-running on or off for every machine, a person's act with why (on until " +
|
||||
Description: "Switch move-in's replace_running on or off for every machine, a person's act with why (on until " +
|
||||
"switched off; kept over a restart). While off, every machine's ask is refused, saying who switched it " +
|
||||
"off and why. own_drift shows the switch.",
|
||||
Input: map[string]any{"type": "object", "required": []string{"on", "why"}, "properties": map[string]any{
|
||||
@@ -240,7 +217,7 @@ func definitions(env *Env) []toolDef {
|
||||
Description: "Every own secret whose node's latest shadow answer says the vault holds another value than runs " +
|
||||
"there (the masked word \"another value\", issue 1126), for all nodes in one call: the secret, the " +
|
||||
"groups that show it, the vault's generation, origin and previous generation, and whether the node's " +
|
||||
"own-take-running would take the running value now (or why not); and per node whose compositions it " +
|
||||
"move-in with replace_running would take the running value now (or why not); and per node whose compositions it " +
|
||||
"applies and how many values differ per group. Never a value.",
|
||||
Input: map[string]any{"type": "object", "properties": map[string]any{}},
|
||||
run: func(_ string, _ map[string]any) (any, error) {
|
||||
@@ -256,7 +233,15 @@ func definitions(env *Env) []toolDef {
|
||||
"module and name for a module's own; provider, provision, consumer, consumer_module and local for a " +
|
||||
"pair credential), its origin and the controller's generation, and get a call and a key made for it; " +
|
||||
"then send the call, the value sealed to that key and read_back_to, a key of your own. Never replaces " +
|
||||
"a value the vault holds; answers what it kept, read back from its store sealed to read_back_to.",
|
||||
"a value the vault holds; answers what it kept, read back from its store sealed to read_back_to. " +
|
||||
"With replace_running: true it does the one thing it otherwise never does (issue 1126, cause C4): it " +
|
||||
"replaces one own secret of the asking machine (the machine as the bus proved the caller) with the " +
|
||||
"value that runs there; first step module, name and why, second step call and sealed. It keeps the " +
|
||||
"old value as the previous generation, raises the generation and says own-replaced, never the value; " +
|
||||
"answers both generations and the fingerprint of what it holds. Refused: another machine's secret, " +
|
||||
"one that is not an own secret or not held, an empty value, a person's value, a provider's but a " +
|
||||
"covered bus account, a value the vault changed in the last 15 minutes, and everything while " +
|
||||
"own_take_switch is off. The same value is a no-op.",
|
||||
Input: intakeInput(map[string]string{"node": "a module's own secret: its machine", "module": "its module",
|
||||
"name": "its name in the module", "provider": "a pair credential: the provider's machine",
|
||||
"provision": "the provision", "consumer": "the consumer's machine",
|
||||
@@ -265,6 +250,10 @@ func definitions(env *Env) []toolDef {
|
||||
"generation": "the controller's generation for the value, kept as it is",
|
||||
"made_at": "when the controller made or took it (RFC 3339)",
|
||||
"read_back_to": "the second step: a raw X25519 public key in base64 the read-back is sealed to",
|
||||
ReplaceRunningArg: "true to replace an own secret of this machine with the value that runs here " +
|
||||
"(the second step is known by its call)",
|
||||
"secret": "with replace_running, or the secret's whole name, own/<machine>/<module>/<name>",
|
||||
"why": "with replace_running: why the value is handed, in words",
|
||||
"sealing_key": "the first step, optional: this machine's sealing key, a raw X25519 public key in " +
|
||||
"base64, which the core's credentials are sealed to from then on (ADR 0349 §16)"}),
|
||||
run: func(caller string, a map[string]any) (any, error) {
|
||||
@@ -272,9 +261,17 @@ func definitions(env *Env) []toolDef {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
replace := isTrue(a[ReplaceRunningArg])
|
||||
if call := arg(a, "call"); call != "" {
|
||||
if replace || v.callKind(call) == ReplaceRunning {
|
||||
return v.OwnTakeTake(caller, call, arg(a, "sealed"))
|
||||
}
|
||||
return v.MoveTake(caller, call, arg(a, "sealed"), arg(a, "read_back_to"))
|
||||
}
|
||||
if replace {
|
||||
return v.OwnTakeOpen(caller, OwnTakeOf{Node: arg(a, "node"), Module: arg(a, "module"),
|
||||
Name: arg(a, "name"), Secret: arg(a, "secret"), Why: arg(a, "why")})
|
||||
}
|
||||
m := MoveOf{Node: arg(a, "node"), Module: arg(a, "module"), Name: arg(a, "name"),
|
||||
Provider: arg(a, "provider"), Provision: arg(a, "provision"), Consumer: arg(a, "consumer"),
|
||||
ConsumerModule: arg(a, "consumer_module"), Local: arg(a, "local"), Origin: arg(a, "origin"),
|
||||
@@ -610,6 +607,17 @@ func (e *Env) ready() (*Vault, error) {
|
||||
return nil, errors.New("the vault is not ready")
|
||||
}
|
||||
|
||||
// isTrue is a switch argument said true, as a boolean or as the word.
|
||||
func isTrue(v any) bool {
|
||||
switch t := v.(type) {
|
||||
case bool:
|
||||
return t
|
||||
case string:
|
||||
return strings.EqualFold(strings.TrimSpace(t), "true")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func arg(a map[string]any, key string) string {
|
||||
s, _ := a[key].(string)
|
||||
return strings.TrimSpace(s)
|
||||
|
||||
@@ -108,7 +108,7 @@ type keepOptions struct {
|
||||
neverReplace bool
|
||||
madeAt time.Time
|
||||
movedFrom *MovedFrom
|
||||
// replaceOnly, keepPrevious and refuse are what own-take-running keeps (owntake.go): only a value held is replaced,
|
||||
// replaceOnly, keepPrevious and refuse are what move-in with replace_running keeps (owntake.go): only a value held is replaced,
|
||||
// keeping its origin and the value it replaces, and refuse, under the store's lock, says why a held value is not.
|
||||
replaceOnly bool
|
||||
keepPrevious bool
|
||||
|
||||
Reference in New Issue
Block a user