From f87590b0f9014063bfdf97c70077e9de50669008 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 11:33:38 +0000 Subject: [PATCH] Re-make an own secret sealed to a key the machine lost, and name the validator's mesh-sdk pin in a check --- cmd/mesh-controller/compose_perform.go | 4 +- .../compose_perform_integration_test.go | 38 +++++++++++++++++ internal/builder/check.go | 42 ++++++++++++++++--- internal/builder/check_pin_test.go | 26 ++++++++++++ 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 internal/builder/check_pin_test.go diff --git a/cmd/mesh-controller/compose_perform.go b/cmd/mesh-controller/compose_perform.go index 930d6634..c71c4bfe 100644 --- a/cmd/mesh-controller/compose_perform.go +++ b/cmd/mesh-controller/compose_perform.go @@ -204,7 +204,9 @@ func prepareSend(ctx context.Context, open *stores, node string) ([]string, erro if n.SealingKey != "" { held := map[string]bool{} for _, r := range in.OwnSecrets { - if r.Node == node { + // Held only when sealed to the key the machine has now, or given by a person (which is not the mesh's + // to make again): a made row under an old key is re-made, as SecretForModule did. + if r.Node == node && (r.SealedTo == n.SealingKey || r.Origin == inventory.OriginAccepted) { held[r.Module+"/"+r.Name] = true } } diff --git a/cmd/mesh-controller/compose_perform_integration_test.go b/cmd/mesh-controller/compose_perform_integration_test.go index d9ace743..d8aa9bad 100644 --- a/cmd/mesh-controller/compose_perform_integration_test.go +++ b/cmd/mesh-controller/compose_perform_integration_test.go @@ -195,3 +195,41 @@ func TestAFinishedSendMakesNothingTheSecondTime(t *testing.T) { } } } + +// Review round 1, finding 1: a machine that rejoined with a new sealing key keeps its made rows sealed to the old key. A +// send re-makes them for the new key, as SecretForModule did on main, and the file naming the secret is not refused. +func TestASendReMakesAnOwnSecretSealedToAKeyTheMachineNoLongerHas(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + inv := open.inventory + register(t, open, aBot("bot", false)) + if _, err := assign(ctx, open, "laptop", "bot"); err != nil { + t.Fatal(err) + } + if _, err := composeSend(ctx, open, "laptop"); err != nil { + t.Fatalf("the first send is refused: %v", err) + } + if err := inv.RecordSealingKey(ctx, nodeID(t, open, "laptop"), aPublicKey(t)); err != nil { + t.Fatal(err) + } + if _, err := composeSend(ctx, open, "laptop"); err != nil { + t.Fatalf("a machine that rejoined cannot be sent to: %v", err) + } + in, err := readComposeInput(ctx, open) + if err != nil { + t.Fatal(err) + } + n, _ := nodeOf(in.Input, "laptop") + found := false + for _, r := range in.OwnSecrets { + if r.Node == "laptop" && r.Module == "bot" && r.Name == "token" { + found = true + if r.SealedTo != n.SealingKey { + t.Fatalf("the own secret is still sealed to a key the machine no longer has") + } + } + } + if !found { + t.Fatal("the own secret has no row") + } +} diff --git a/internal/builder/check.go b/internal/builder/check.go index 06dab4fd..9b299257 100644 --- a/internal/builder/check.go +++ b/internal/builder/check.go @@ -1194,7 +1194,7 @@ func judgeFor(ctx context.Context, labelled, run Runner, spec CheckSpec, root, t "go", "build", "-o", bin, "./cmd/mesh-controller")...); err != nil { return "", "the change's controller, which judges itself, does not build: " + firstLine(err.Error()), nil } - say("check", "judged by the change's own controller") + say("check", "judged by the change's own controller, with %s", validatorAt(tree)) return bin, "", nil } bin, _, err := judge(ctx, labelled, run, root, inToolchain, say) @@ -1637,6 +1637,37 @@ func dialable(ctx context.Context, address string) error { return fmt.Errorf("nothing took a connection at %s", address) } +// sdkPin is the mesh-sdk version the controller in dir takes (its go.mod's go requirement), or "" when it names none. +func sdkPin(dir string) string { + body, err := os.ReadFile(filepath.Join(dir, "go.mod")) + if err != nil { + return "" + } + for _, line := range strings.Split(string(body), "\n") { + f := strings.Fields(strings.TrimPrefix(strings.TrimSpace(line), "require ")) + if len(f) >= 2 && f[0] == "git.novox.be/novox/mesh-sdk/go" { + return f[1] + } + } + return "" +} + +// validatorAt names the validator of the controller in dir as mesh-sdk's at that controller's pin (issue 1391). +func validatorAt(dir string) string { + if v := sdkPin(dir); v != "" { + return "mesh-sdk's validator at " + v + } + return "mesh-sdk's validator at the controller's pin" +} + +// pinNote is the version of that pin, for a sentence that already says "the pin of": ", " and the version, or nothing. +func pinNote(dir string) string { + if v := sdkPin(dir); v != "" { + return " (" + v + ")" + } + return "" +} + // judge builds the controller that judges a change: the one the mesh runs, beside the check as // mesh-controller — or, when that one predates the merge gate, the controller's main, said. It answers // the binary and the directory it was built from. @@ -1658,7 +1689,7 @@ func judge(ctx context.Context, run, plain Runner, root string, inToolchain func return "", "", fmt.Errorf("the controller the mesh runs does not build: %w", err) } if hasGate() { - say("check", "judged by the controller the mesh runs") + say("check", "judged by the controller the mesh runs, with %s", validatorAt(filepath.Join(root, "mesh-controller"))) return bin, "mesh-controller", nil } } @@ -1668,7 +1699,8 @@ func judge(ctx context.Context, run, plain Runner, root string, inToolchain func if err := build("mesh-controller-main"); err != nil { return "", "", fmt.Errorf("the controller's main does not build: %w", err) } - say("check", "judged by the controller's main: the one the mesh runs predates the merge gate") + say("check", "judged by the controller's main: the one the mesh runs predates the merge gate, with %s", + validatorAt(filepath.Join(root, "mesh-controller-main"))) return bin, "mesh-controller-main", nil } @@ -1783,7 +1815,7 @@ func judgeFromTheGroup(ctx context.Context, labelled, run Runner, spec CheckSpec // its pin. A mesh-host member brings no validator (issue 1391), and its tree need hold none. source = groupDir(spec, root, tree, "mesh-controller") from = "the group's own controller, merge " + headAt(ctx, run, source) - validator = "mesh-sdk's validator at the pin of the group's controller" + validator = "mesh-sdk's validator at the pin of the group's controller" + pinNote(source) inputs = append(inputs, "group member "+spec.Repo+" "+judged.Head+" onto "+judged.Trunk) for _, m := range judged.Members { inputs = append(inputs, "group member "+m.Repo+" "+m.Head+" onto "+m.Trunk) @@ -1791,7 +1823,7 @@ func judgeFromTheGroup(ctx context.Context, labelled, run Runner, spec CheckSpec case spec.Judge != "self" && controller != nil: source = filepath.Join(root, "mesh-controller") from = merge("mesh-controller", controller) - validator = "mesh-sdk's validator at the pin of " + merge("mesh-controller", controller) + validator = "mesh-sdk's validator at the pin of " + merge("mesh-controller", controller) + pinNote(source) default: return "", "", false, nil } diff --git a/internal/builder/check_pin_test.go b/internal/builder/check_pin_test.go new file mode 100644 index 00000000..0814009d --- /dev/null +++ b/internal/builder/check_pin_test.go @@ -0,0 +1,26 @@ +package builder + +import ( + "os" + "path/filepath" + "testing" +) + +// Issue 1391, AC5: what a check says names the validator as mesh-sdk's at the controller's pin, with the version from the +// judge's own go.mod. +func TestTheValidatorIsNamedAsMeshSDKsAtTheControllersPin(t *testing.T) { + dir := t.TempDir() + mod := "module example.test/c\n\ngo 1.25\n\nrequire (\n\tgit.novox.be/novox/mesh-sdk/go v0.1.14-0.20261020085642-ab0c16ddad26\n)\n" + if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(mod), 0o644); err != nil { + t.Fatal(err) + } + if got, want := validatorAt(dir), "mesh-sdk's validator at v0.1.14-0.20261020085642-ab0c16ddad26"; got != want { + t.Fatalf("the check says %q, not %q", got, want) + } + if got, want := pinNote(dir), " (v0.1.14-0.20261020085642-ab0c16ddad26)"; got != want { + t.Fatalf("the pin note is %q, not %q", got, want) + } + if got := validatorAt(t.TempDir()); got != "mesh-sdk's validator at the controller's pin" { + t.Fatalf("a controller with no go.mod is said as %q", got) + } +}