Files
mesh-controller/internal/link/enrol_shape_test.go
T
jschoubben c3b88b9148 Rename mesh-control -> mesh-controller, substrate -> foundation
One name per thing, per the HQ glossary: the module/container/image/binary/repo
becomes mesh-controller, the seat the-controller, and the store+broker pair the
foundation (embedded base bundles, default template and example lock renamed with
their go:embed directives). No behaviour change — a pure vocabulary rename.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 18:40:40 +02:00

127 lines
4.3 KiB
Go

package link_test
import (
"context"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"os"
"strings"
"testing"
"golang.org/x/crypto/nacl/box"
"github.com/novox/mesh-controller/internal/catalogue"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
)
// What a node says when it joins, as that node's own code writes it.
//
// The two ends are separate structs in separate repositories, and a field renamed on one side
// fails silently: enrolment succeeds, a key is simply absent, and the node looks joined until the
// first thing sealed to it cannot be opened — by which time nobody is looking at enrolment.
//
// Skipped unless MESH_ENROL names the file the host's suite wrote:
//
// mesh-host: MESH_ENROL_OUT=/tmp/enrol.json go test ./internal/link/
// mesh-controller: MESH_ENROL=/tmp/enrol.json make check
//
// **What this does not cover**, said so nobody reads more into a pass than is there: the full
// enrolment path also issues a broker account, and that needs a broker. What is checked here is
// the shape the two sides agree on and that a key which arrives this way can actually be sealed
// to — which is the part that was newly wired and the part that fails quietly.
func TestWhatANodeSaysWhenItJoinsIsWhatThisMeshReads(t *testing.T) {
path := os.Getenv("MESH_ENROL")
if path == "" {
t.Skip("set MESH_ENROL to an enrolment request written by the host's suite")
}
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var request link.EnrolRequest
if err := json.Unmarshal(raw, &request); err != nil {
t.Fatalf("this mesh cannot read what a node sends:\n%v", err)
}
for what, got := range map[string]string{
"node": request.Node,
"secret": request.Secret,
"overlay key": request.OverlayKey,
"sealing key": request.SealingKey,
} {
if got == "" {
t.Fatalf("the %s did not survive the crossing — a field name differs", what)
}
}
if len(request.PublicKey) != ed25519.PublicKeySize {
t.Fatalf("the identity arrived as %d bytes", len(request.PublicKey))
}
// And that a key arriving this way is one the mesh can actually seal to. Recording it is not
// the same as it being usable, and "recorded" is what a shape check on its own would prove.
inv := liveInventory(t)
ctx := context.Background()
node, err := inv.AddNode(ctx, request.Node)
if err != nil {
t.Fatal(err)
}
other, err := inv.AddNode(ctx, "the-other-end")
if err != nil {
t.Fatal(err)
}
if err := inv.RecordSealingKey(ctx, node.ID, request.SealingKey); err != nil {
t.Fatal(err)
}
if err := inv.RecordSealingKey(ctx, other.ID, request.SealingKey); err != nil {
t.Fatal(err)
}
// A credential belongs to a module on a machine (novox/hq 04-ISSUES/022), so the module
// holding it has to exist before it can.
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "gitea", Version: "1"},
inventory.Source{}); err != nil {
t.Fatal(err)
}
secret, err := inv.SecretFor(ctx, "postgres-database", request.Node, "gitea", "the-other-end")
if err != nil {
t.Fatalf("nothing could be sealed to a key that arrived from a real node: %v", err)
}
// Opened with the private half the host's suite kept, so this asserts the node could read it
// rather than that a blob exists.
privateRaw, err := os.ReadFile(path + ".sealing-private")
if err != nil {
t.Skipf("no private half beside %s, so this can only check the shape", path)
}
private, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(privateRaw)))
if err != nil || len(private) != 32 {
t.Fatal("the private half beside the request is not a key")
}
public, err := base64.StdEncoding.DecodeString(request.SealingKey)
if err != nil {
t.Fatal(err)
}
var pub, priv [32]byte
copy(pub[:], public)
copy(priv[:], private)
blob, err := base64.StdEncoding.DecodeString(secret.ForConsumer)
if err != nil {
t.Fatal(err)
}
if _, ok := box.OpenAnonymous(nil, blob, &pub, &priv); !ok {
t.Fatal("the node could not open what this mesh sealed to the key it sent")
}
}
// liveInventory is a database of its own for this test, skipping where there is none.
func liveInventory(t *testing.T) *inventory.Inventory {
t.Helper()
if os.Getenv("MESH_TEST_POSTGRES") == "" {
t.Skip("no MESH_TEST_POSTGRES; run `make check` to raise one")
}
return inventory.ForTest(t)
}