mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group feat/machine-network-health delivering: 0 of 2 delivered
mesh/delivery delivered
171 lines
6.9 KiB
Go
171 lines
6.9 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
|
|
// the bus, which is tested against a real one 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, []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)
|
|
}
|
|
}
|
|
|
|
// Defends novox/hq ADR 0002: everything reaching a node arrives over the broker — and therefore
|
|
// ADR 0004's consequence, that the broker is not trusted to say who is speaking.
|
|
//
|
|
// A transport nobody authenticates per-message would let whatever holds the connection attribute
|
|
// a declaration to any node it liked.
|
|
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"}},
|
|
// novox/hq ADR 0100: what an adopted node holds, the firewall it was found with, and what
|
|
// is reachable on it.
|
|
{Report{Node: "n", Held: []Held{{ID: "i"}}, Firewall: "ufw", Reachable: []Reach{{Port: 1}}},
|
|
[]string{"node", "held", "firewall", "reachable"}},
|
|
{Held{ID: "i", Module: "m", Kind: "file", Target: "/t", Changed: "rewritten", Kept: "/k"},
|
|
[]string{"id", "module", "kind", "target", "since", "changed", "kept"}},
|
|
{Reach{Protocol: "tcp", Address: "0.0.0.0", Port: 8080, By: "c", Published: true, ContainerPort: 80},
|
|
[]string{"protocol", "address", "port", "by", "published", "container-port"}},
|
|
// novox/hq to-be 45 §8: a witness's verdicts, said on every report while they stand, and the
|
|
// witness contract this host keeps.
|
|
{Report{Node: "n", Rollbacks: []Rollback{{Component: ComponentController}}, Witness: WitnessContract},
|
|
[]string{"node", "rollbacks", "witness"}},
|
|
{Rollback{Component: ComponentNodeTools, From: "sha256:b", To: "sha256:a", Outcome: RolledBack, Why: "w"},
|
|
[]string{"component", "from", "to", "outcome", "why", "at"}},
|
|
// novox/hq ADR 0240: every long-running resource's health, in every report and in its own event.
|
|
{Report{Node: "n", Health: &Health{Contract: LivenessContract}}, []string{"node", "health"}},
|
|
{Health{Contract: LivenessContract, Resources: []ResourceHealth{}}, []string{"contract", "at", "resources"}},
|
|
{ResourceHealth{Module: "m", Resource: "m.r", Kind: "container", Target: "t", State: StateUnhealthy,
|
|
Reason: ReasonRestarting, Streak: 2, Restarts: 3, Check: "http", Needs: "postgres-database"},
|
|
[]string{"module", "resource", "kind", "target", "state", "reason", "since", "streak", "restarts",
|
|
"check", "needs"}},
|
|
{HealthSaid{Node: "n"}, []string{"node", "health"}},
|
|
// novox/hq ADR 0241: the machine's own networking, beside its resources.
|
|
{Health{Contract: ReadinessContract, Resources: []ResourceHealth{}, Network: &NetworkHealth{State: "unhealthy",
|
|
Parts: []NetworkPart{}}}, []string{"contract", "at", "resources", "network"}},
|
|
{NetworkHealth{State: "unhealthy", Parts: []NetworkPart{}}, []string{"state", "since", "parts"}},
|
|
{NetworkPart{Part: "resolv-conf", State: "unhealthy", Reason: "r", Said: "s", Writer: "w", Owner: "o",
|
|
Toward: []string{"hub"}, Streak: 2}, []string{"part", "state", "reason", "said", "writer", "owner", "toward",
|
|
"since", "streak"}},
|
|
} {
|
|
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)
|
|
}
|
|
}
|
|
}
|