Files
mesh-host/internal/link/messages_test.go
T
jschoubben a488c76b5e A node holds its link open, and applies what the mesh signs
The loop the whole thing exists for: told, apply, report.

`run` holds one outbound connection open and consumes the node's own queue.
Every declaration is verified against the control plane's signing key before a
byte of it is read as an instruction -- not once at connect, every time. The
transport being pinned is a different question from the instruction being
genuine, and pinning only the first would make the second transitive: a
compromised broker could forge declarations, and this host applies whatever the
link delivers.

Malformed and forged are reported differently, because ADR 0004 requires a host
to tell "this is not from the mesh I joined" from "this is broken". One means
somebody is trying and the other means something needs fixing.

A node now keeps what it needs to come back on its own: the broker's address
and fingerprint, the signing key it believes, and its own broker password --
which the mesh issues at enrolment to replace the token's secret, so the
one-time thing stays one-time and the credential it holds for years is not the
one that was pasted into a terminal.

Verified in the lab end to end. The node enrolled, held its link, received a
signed declaration and applied it -- the file is on the machine with the right
contents, and the host's own record lists both resources.

That run also found issue 010, which is recorded in novox/hq: the declaration
removed every container on the machine, including the control plane that sent
it. Correct reconciliation, shared store, and the first thing that happens.
2026-08-29 16:23:27 +02:00

137 lines
4.2 KiB
Go

package link
import (
"context"
"crypto/ed25519"
"encoding/json"
"testing"
)
// verified runs what Run does to a delivery body, without a broker: unmarshal, check the
// signature, and only then apply. Isolating it keeps this test about the check rather than about
// AMQP, which is tested against a real broker in the lab.
func verified(t *testing.T, signer ed25519.PublicKey, body []byte) (Report, bool) {
t.Helper()
applied := false
report := handleBody(context.Background(), Membership{Node: "anchor", Signer: signer}, body,
func(context.Context, []byte) Report {
applied = true
return Report{Applied: []string{"something"}}
})
return report, applied
}
func signedBody(t *testing.T, private ed25519.PrivateKey, declaration string) []byte {
t.Helper()
raw, err := json.Marshal(Signed{
Declaration: []byte(declaration),
Signature: ed25519.Sign(private, []byte(declaration)),
})
if err != nil {
t.Fatal(err)
}
return raw
}
func TestTheMeshsOwnDeclarationIsApplied(t *testing.T) {
public, private, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
report, applied := verified(t, public, signedBody(t, private, `{"declaration":1}`))
if !applied {
t.Fatalf("a declaration the mesh signed was not applied: %s", report.Refused)
}
}
func TestAForgedDeclarationIsNeverApplied(t *testing.T) {
// The check that stands between "the mesh changes this machine" and "anybody does". The host
// applies whatever the link delivers, so a forged declaration is the whole machine.
public, _, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
_, other, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
report, applied := verified(t, public, signedBody(t, other, `{"declaration":1}`))
if applied {
t.Fatal("a declaration signed by another key was applied")
}
if report.Refused != ErrForged.Error() {
t.Errorf("refused, but not as a forgery: %q", report.Refused)
}
}
func TestATamperedDeclarationIsNeverApplied(t *testing.T) {
// A broker that changed the declaration in flight, keeping the signature. This is what makes
// pinning the transport insufficient on its own.
public, private, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
raw, err := json.Marshal(Signed{
Declaration: []byte(`{"declaration":1,"resources":["something else entirely"]}`),
Signature: ed25519.Sign(private, []byte(`{"declaration":1}`)),
})
if err != nil {
t.Fatal(err)
}
report, applied := verified(t, public, raw)
if applied {
t.Fatal("a declaration altered after signing was applied")
}
if report.Refused != ErrForged.Error() {
t.Errorf("refused, but not as a forgery: %q", report.Refused)
}
}
func TestAMalformedMessageIsToldApartFromAForgery(t *testing.T) {
// novox/hq ADR 0004 requires these to be distinguishable: one means somebody is trying, the
// other means something is broken, and they need different responses from a person.
public, _, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
report, applied := verified(t, public, []byte("this is not a message"))
if applied {
t.Fatal("something unparseable was applied")
}
if report.Refused == ErrForged.Error() {
t.Error("a malformed message was reported as a forgery; those must be distinguishable")
}
}
func TestTheWireFormatIsExactlyTheseFieldNames(t *testing.T) {
// The contract with the control plane, which defines these separately. A matching test lives
// there; rename a field on either side and both fail.
for _, c := range []struct {
value any
expect []string
}{
{Signed{Declaration: []byte("{}"), Signature: []byte("x")}, []string{"declaration", "signature"}},
{Report{Node: "n", Applied: []string{"a"}, Failed: map[string]string{"k": "v"}, Refused: "r"},
[]string{"node", "applied", "failed", "refused"}},
} {
raw, err := json.Marshal(c.value)
if err != nil {
t.Fatal(err)
}
var fields map[string]any
if err := json.Unmarshal(raw, &fields); err != nil {
t.Fatal(err)
}
for _, want := range c.expect {
if _, ok := fields[want]; !ok {
t.Errorf("%T has no %q field; the control plane uses that name", c.value, want)
}
}
if len(fields) != len(c.expect) {
t.Errorf("%T has %d fields, expected %d: %v", c.value, len(fields), len(c.expect), fields)
}
}
}