package bootstrap import ( "context" "os" "path/filepath" "strings" "testing" "github.com/novox/mesh-host/internal/store" ) // Defends novox/hq ADR 0100: a converged genesis refuses a machine in use, naming every container // and listener it counted. // Lines as `ss -Hltunp` prints them. The ssh, samba, loopback and proxy lines are captured from a // real machine; the resolver and network-manager lines are written in the same shape. What a fresh // machine actually runs is measured in testdata/fresh-machine-listeners.txt. const inUseSockets = `tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1188536,fd=6)) tcp LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=1188536,fd=7)) tcp LISTEN 0 32 127.0.0.1:53 0.0.0.0:* users:(("dnsmasq",pid=1189392,fd=7)) tcp LISTEN 0 4096 127.0.0.1:5432 0.0.0.0:* users:(("docker-proxy",pid=1854543,fd=7)) udp UNCONN 0 0 0.0.0.0:5355 0.0.0.0:* users:(("systemd-resolve",pid=301,fd=11)) udp UNCONN 0 0 192.0.2.10%eth0:68 0.0.0.0:* users:(("systemd-network",pid=280,fd=19)) ` const servingSockets = `tcp LISTEN 0 50 0.0.0.0:445 0.0.0.0:* users:(("smbd",pid=1248,fd=29)) tcp LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=1920035,fd=7)) udp UNCONN 0 0 0.0.0.0:123 0.0.0.0:* users:(("ntpd",pid=1070791,fd=17)) ` type inUseRunner struct{ ps, ss string } func (m inUseRunner) run(_ context.Context, name string, args ...string) (string, error) { if name == "docker" { return m.ps, nil } return m.ss, nil } func TestAFreshMachineIsNotInUse(t *testing.T) { containers, listeners, err := InUse(context.Background(), inUseRunner{ss: inUseSockets}.run, func(string) bool { return false }) if err != nil { t.Fatal(err) } if len(containers) != 0 || len(listeners) != 0 { t.Errorf("ssh, loopback and the daemons a fresh machine runs were counted: %v %v", containers, listeners) } } func TestAMachineServingIsInUse(t *testing.T) { m := inUseRunner{ps: "hello-web\t\nmesh-store\tabc123\n", ss: inUseSockets + servingSockets} containers, listeners, err := InUse(context.Background(), m.run, func(string) bool { return false }) if err != nil { t.Fatal(err) } if len(containers) != 1 || containers[0] != "hello-web" { t.Errorf("containers counted: %v (one a host made is not a predecessor's)", containers) } var by []string for _, l := range listeners { by = append(by, l.By) } if strings.Join(by, " ") != "smbd docker-proxy ntpd" { t.Errorf("listeners counted: %v", listeners) } } func TestAConvergedGenesisRefusesAMachineInUseNamingEverything(t *testing.T) { o := Options{State: filepath.Join(t.TempDir(), "state.json")} m := inUseRunner{ps: "hello-web\t\n", ss: inUseSockets + servingSockets} err := RefuseAMachineInUse(context.Background(), o, m.run, quietly) if err == nil { t.Fatal("a machine in use was not refused") } for _, want := range []string{"container hello-web", "tcp 0.0.0.0:445 by smbd", "tcp 0.0.0.0:8080 by docker-proxy", "udp 0.0.0.0:123 by ntpd", "--adopted"} { if !strings.Contains(err.Error(), want) { t.Errorf("the refusal does not name %q: %v", want, err) } } o.Adopted = true if err := RefuseAMachineInUse(context.Background(), o, m.run, quietly); err != nil { t.Errorf("an adopted genesis was refused a machine in use: %v", err) } } func TestARerunOfGenesisIsNotAMachineInUse(t *testing.T) { o := Options{State: filepath.Join(t.TempDir(), "state.json")} if err := store.Save(o.State, store.State{Resources: []store.Applied{{ID: "store", Type: "container", Target: "mesh-store"}}}); err != nil { t.Fatal(err) } m := inUseRunner{ps: "mesh-gitea-server\t\n", ss: servingSockets} if err := RefuseAMachineInUse(context.Background(), o, m.run, quietly); err != nil { t.Errorf("what an earlier genesis raised was counted as a machine in use: %v", err) } } func TestAFreshlyInstalledMachineAsMeasuredIsNotInUse(t *testing.T) { // Captured with `ss -Hltunp` on a freshly installed lab machine: its resolver listens on TCP on // every address, which the record's words alone would count. raw, err := os.ReadFile("testdata/fresh-machine-listeners.txt") if err != nil { t.Fatal(err) } run := func(ctx context.Context, name string, args ...string) (string, error) { if name == "ss" { return string(raw), nil } return "", nil } containers, listeners, err := InUse(context.Background(), run, func(string) bool { return false }) if err != nil { t.Fatal(err) } if len(containers) != 0 || len(listeners) != 0 { t.Errorf("a fresh machine read as in use: containers %v, listeners %v", containers, listeners) } } // Defends novox/hq ADR 0103: a machine raised adopted stays adopted if genesis is run again. The // installer reads the mode from what the machine records, and refuses a flag that disagrees. func TestARerunWithoutTheFlagOnAnAdoptedMachineIsRefused(t *testing.T) { o := Options{State: filepath.Join(t.TempDir(), "state.json")} adoptedState := store.State{Resources: []store.Applied{ {ID: "store", Type: "container", Target: "mesh-store", Origin: store.OriginCarried}, {ID: "adoption.guard", Type: "file", Target: "/etc/mesh/guard.nft", Origin: store.OriginCarried}, }} if err := store.Save(o.State, adoptedState); err != nil { t.Fatal(err) } m := inUseRunner{ss: inUseSockets} err := RefuseAMachineInUse(context.Background(), o, m.run, quietly) if err == nil || !strings.Contains(err.Error(), "pass --adopted") { t.Fatalf("a re-run without --adopted on an adopted machine was not refused: %v", err) } o.Adopted = true if err := RefuseAMachineInUse(context.Background(), o, m.run, quietly); err != nil { t.Errorf("a re-run with --adopted on an adopted machine was refused: %v", err) } } func TestARerunWithTheFlagOnAConvergedMachineIsRefused(t *testing.T) { o := Options{State: filepath.Join(t.TempDir(), "state.json"), Adopted: true} converged := store.State{Resources: []store.Applied{ {ID: "store", Type: "container", Target: "mesh-store", Origin: store.OriginCarried}, {ID: "base-filter", Type: "file", Target: "/etc/nftables.conf", Origin: store.OriginCarried}, }, // Converged by the controller from adopted: the firewall it found is still recorded. Firewall: &store.FoundFirewall{Kind: "ufw", WasActive: true, DisabledByMesh: true}} if err := store.Save(o.State, converged); err != nil { t.Fatal(err) } err := RefuseAMachineInUse(context.Background(), o, inUseRunner{ss: inUseSockets}.run, quietly) if err == nil || !strings.Contains(err.Error(), "without --adopted") { t.Fatalf("a re-run with --adopted on a converged machine was not refused: %v", err) } o.Adopted = false if err := RefuseAMachineInUse(context.Background(), o, inUseRunner{ss: inUseSockets}.run, quietly); err != nil { t.Errorf("a converged re-run of a converged machine was refused: %v", err) } } func TestOnlyTheDaemonsTheMeasurementFoundAreQuiet(t *testing.T) { // novox/hq ADR 0101: the exempt daemons are the ones a fresh machine was measured to run — // the resolver and the network manager. A time client or a DHCP client listening beyond // loopback is something somebody put there, and that is a machine in use. sockets := `udp UNCONN 0 0 0.0.0.0:123 0.0.0.0:* users:(("systemd-timesyn",pid=260,fd=9)) udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:* users:(("dhcpcd",pid=270,fd=9)) ` _, listeners, err := InUse(context.Background(), inUseRunner{ss: sockets}.run, func(string) bool { return false }) if err != nil { t.Fatal(err) } if len(listeners) != 2 { t.Errorf("counted %d listener(s), want the time client and the DHCP client: %+v", len(listeners), listeners) } }