diff --git a/cmd/mesh-controller/desk_secret.go b/cmd/mesh-controller/desk_secret.go index 5d91a9e1..1872cd1f 100644 --- a/cmd/mesh-controller/desk_secret.go +++ b/cmd/mesh-controller/desk_secret.go @@ -39,6 +39,9 @@ const deskPromptWithin = 25 type deskGive struct { // declares refuses a module or a secret the mesh would refuse, before anybody is asked to type. declares func(module, name string) error + // known refuses a machine the mesh does not know, before anybody is asked to type; nil knows every one + // (a test that does not look). + known func(machine string) error // trusted says a module runs as an account of its own: its secret is never taken at a desk (below). Nil is // never (a test that does not look). trusted func(module string) (bool, error) @@ -63,6 +66,14 @@ func (d deskGive) give(node, module, name, desk string) (string, error) { return "", fmt.Errorf("%s is not named", what) } } + if d.known != nil { + for what, machine := range map[string]string{"the machine the secret is for": node, "the desk": desk} { + if err := d.known(machine); err != nil { + return "", fmt.Errorf("nobody was asked to type anything: %s, %s, is not a machine this mesh knows: %w", + what, machine, err) + } + } + } if err := d.declares(module, name); err != nil { return "", fmt.Errorf("nobody was asked to type anything: %w", err) } @@ -164,7 +175,11 @@ func giveAtDesk(ctx context.Context, node, module, name, desk string) error { defer open.Close() d := deskGive{ declares: func(module, name string) error { return open.inventory.DeclaresOwnSecret(ctx, module, name) }, - trusted: func(module string) (bool, error) { return open.inventory.RunsAsItsOwnAccount(ctx, module) }, + known: func(machine string) error { + _, err := open.inventory.NodeByName(ctx, machine) + return err + }, + trusted: func(module string) (bool, error) { return open.inventory.RunsAsItsOwnAccount(ctx, module) }, ask: func(machine string, args map[string]any) (json.RawMessage, error) { var result json.RawMessage err := onTheBus(func(conn *nats.Conn) error { diff --git a/cmd/mesh-controller/desk_secret_test.go b/cmd/mesh-controller/desk_secret_test.go index b3ad1873..7c87b3de 100644 --- a/cmd/mesh-controller/desk_secret_test.go +++ b/cmd/mesh-controller/desk_secret_test.go @@ -293,3 +293,69 @@ func TestOnlyTheDeskMachinesLauncherMayAnswerItsPrompt(t *testing.T) { } } } + +// N1-give at the controller's terminal (the confirmation review of 2026-10-09): a trusted party's secret is +// announced before it is kept, and not kept when the announcement fails; another module's is kept first and +// a failed announcement is said, not undone. +func TestATrustedPartysSecretGivenAtTheTerminalIsAnnouncedBeforeItIsKept(t *testing.T) { + var order []string + announce := func(fail bool) func() error { + return func() error { + order = append(order, "announce") + if fail { + return errors.New("no channel") + } + return nil + } + } + keep := func() (bool, error) { order = append(order, "keep"); return false, nil } + + order = nil + if _, unannounced, err := keepGiven(true, announce(false), keep); err != nil || unannounced != nil || + strings.Join(order, ",") != "announce,keep" { + t.Errorf("trusted: %v %v, order %v; want announced, then kept", unannounced, err, order) + } + order = nil + if _, _, err := keepGiven(true, announce(true), keep); err == nil || strings.Join(order, ",") != "announce" { + t.Errorf("trusted, announcement failed: %v, order %v; want refused and nothing kept", err, order) + } + order = nil + if _, unannounced, err := keepGiven(false, announce(true), keep); err != nil || unannounced == nil || + strings.Join(order, ",") != "keep,announce" { + t.Errorf("not trusted: %v %v, order %v; want kept, then the failed announcement said", unannounced, err, order) + } + order = nil + failing := func() (bool, error) { order = append(order, "keep"); return false, errors.New("store away") } + if _, _, err := keepGiven(false, announce(false), failing); err == nil || strings.Join(order, ",") != "keep" { + t.Errorf("not trusted, keep failed: %v, order %v; want refused and nothing announced", err, order) + } +} + +// A machine the mesh does not know, as the secret's or as the desk, is refused before anybody is asked to type. +func TestAGiveNamingAMachineTheMeshDoesNotKnowAsksNobody(t *testing.T) { + for _, unknown := range []string{"elsewhere", "nodesk"} { + d, accepted, acts, asked := aDesk(t, func(map[string]any) (json.RawMessage, error) { + t.Fatal("the desk was asked") + return nil, nil + }) + d.known = func(machine string) error { + if machine == unknown { + return errors.New("no node " + machine) + } + return nil + } + node, desk := "anchor", "laptop" + if unknown == "elsewhere" { + node = unknown + } else { + desk = unknown + } + _, err := d.give(node, "telegram", "telegram-token", desk) + if err == nil || !strings.Contains(err.Error(), "nobody was asked") || !strings.Contains(err.Error(), unknown) { + t.Errorf("%s: %v", unknown, err) + } + if len(*accepted)+len(*acts)+len(*asked) != 0 { + t.Errorf("%s: something happened: %v %v %v", unknown, *accepted, *acts, *asked) + } + } +} diff --git a/cmd/mesh-controller/secret.go b/cmd/mesh-controller/secret.go index 52430a4d..bcb3dd46 100644 --- a/cmd/mesh-controller/secret.go +++ b/cmd/mesh-controller/secret.go @@ -115,20 +115,18 @@ func secretCommand(ctx context.Context, args []string) error { if err != nil { return err } - if trusted { - if err := announceSecretGiven(ctx, node, module, name, "at the controller's terminal"); err != nil { + untilStart, unannounced, err := keepGiven(trusted, + func() error { return announceSecretGiven(ctx, node, module, name, "at the controller's terminal") }, + func() (bool, error) { return open.inventory.AcceptGivenSecret(ctx, node, module, name, value) }) + if err != nil { + if trusted && unannounced != nil { return fmt.Errorf("%s runs as an account of its own, and the change of its %s could not be announced on "+ "your channels first, so nothing was kept: %w", module, name, err) } - } - untilStart, err := open.inventory.AcceptGivenSecret(ctx, node, module, name, value) - if err != nil { return err } - if !trusted { - if err := announceSecretGiven(ctx, node, module, name, "at the controller's terminal"); err != nil { - fmt.Printf(" this change could NOT be announced on the operator's channels: %v\n", err) - } + if unannounced != nil { + fmt.Printf(" this change could NOT be announced on the operator's channels: %v\n", unannounced) } // Not printed back, and there is nowhere it could be printed from: it is sealed to that // machine and the mesh cannot read it again. @@ -487,3 +485,23 @@ func whoAsked() string { } return "the mesh" } + +// keepGiven keeps a value given at the controller's terminal, and announces it on the operator's channels +// (the confirmation review of 2026-10-09, N1-give). **A trusted party's — a module running as an account of its +// own: the router, a verified channel — is announced before it is kept, and not kept when the announcement +// fails**: a channel whose token changed unheard of answers for somebody else. Any other module's is kept first +// and announced after, and a failed announcement is said (unannounced) without undoing it. +func keepGiven(trusted bool, announce func() error, keep func() (bool, error)) (untilStart bool, unannounced, err error) { + if trusted { + if err := announce(); err != nil { + return false, err, err + } + untilStart, err = keep() + return untilStart, nil, err + } + untilStart, err = keep() + if err != nil { + return false, nil, err + } + return untilStart, announce(), nil +}