diff --git a/cmd/mesh-controller/join_through_the_tunnel_test.go b/cmd/mesh-controller/join_through_the_tunnel_test.go new file mode 100644 index 0000000..5782644 --- /dev/null +++ b/cmd/mesh-controller/join_through_the_tunnel_test.go @@ -0,0 +1,102 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/novox/mesh-controller/internal/inventory" +) + +// aMachineJoining is a machine with a live token issued for a tunnel key, given its address — what +// `token issue --overlay-key` records before it pushes the hub (novox/hq ADR 0169). +func aMachineJoining(t *testing.T, ctx context.Context, inv *inventory.Inventory, name string, + validFor time.Duration) string { + t.Helper() + record, err := inv.AddNode(ctx, name) + if err != nil { + t.Fatal(err) + } + if _, err := inv.IssueToken(ctx, name, validFor); err != nil { + t.Fatal(err) + } + key := aPublicKey(t) + if err := inv.RecordOverlayKey(ctx, record.ID, key); err != nil { + t.Fatal(err) + } + if err := inv.BindTokenToKey(ctx, record.ID, key); err != nil { + t.Fatal(err) + } + cidr, err := overlayRange(ctx, inv) + if err != nil { + t.Fatal(err) + } + if _, err := inv.AssignAddress(ctx, record.ID, cidr); err != nil { + t.Fatal(err) + } + return key +} + +// hubPeers are the keys the hub's composed tunnel carries. +func hubPeers(t *testing.T, ctx context.Context, inv *inventory.Inventory) map[string]bool { + t.Helper() + g, err := network(ctx, inv, map[string]bool{"anchor": true, "laptop": true}, nil) + if err != nil { + t.Fatal(err) + } + out := map[string]bool{} + for _, p := range g.Graph()["anchor"] { + out[p.Key] = true + } + return out +} + +// **A machine joining is a peer of the hub while its token can be used, and not after** (novox/hq +// ADR 0169): the hub's composed tunnel carries a machine whose token was issued for its key, so the +// tunnel answers the first time it knocks; a token that expired unused takes the peer with it at the +// hub's next composition. +func TestAMachineJoiningIsAPeerOfTheHubUntilItsTokenExpires(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + inv := open.inventory + + joining := aMachineJoining(t, ctx, inv, "joiner", time.Hour) + expired := aMachineJoining(t, ctx, inv, "late", 2*time.Second) + time.Sleep(2100 * time.Millisecond) + + peers := hubPeers(t, ctx, inv) + if !peers[joining] { + t.Fatalf("the hub does not carry the machine its live token was issued for: %v", peers) + } + if peers[expired] { + t.Fatalf("the hub still carries a machine whose token expired unused: %v", peers) + } + + // A token issued without a key gives the hub nothing to carry: there is no key to carry. + if _, err := inv.AddNode(ctx, "keyless"); err != nil { + t.Fatal(err) + } + if _, err := inv.IssueToken(ctx, "keyless", time.Hour); err != nil { + t.Fatal(err) + } + if after := hubPeers(t, ctx, inv); len(after) != len(peers) { + t.Fatalf("a token issued without a key changed the hub's peers: %v, was %v", after, peers) + } +} + +// **A tunnel key that is not one is refused before anything is recorded** (novox/hq ADR 0169): a +// token issued for it would be a tunnel the hub could never answer. +func TestATokenIsNotIssuedForSomethingThatIsNotATunnelKey(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + record, err := open.inventory.AddNode(ctx, "joiner") + if err != nil { + t.Fatal(err) + } + if _, _, err := throughTheTunnel(ctx, open, record, "not-a-key", "10.77.0.1:4222"); err == nil { + t.Fatal("a token was issued for something that is not a tunnel key") + } + if held := placementOf(t, ctx, open.inventory, "joiner"); held.Key != "" || held.Address != "" { + t.Fatalf("a refused key left a record behind: %+v", held) + } +}