package link_test import ( "context" "crypto/ed25519" "encoding/base64" "encoding/json" "os" "strings" "testing" "golang.org/x/crypto/nacl/box" "github.com/novox/mesh-control/internal/catalogue" "github.com/novox/mesh-control/internal/inventory" "github.com/novox/mesh-control/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-control: 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) }