package catalogue import ( "fmt" "os" "reflect" "strings" "testing" ) // The catalogue's foundation modules as they are, parsed by the real parser (novox/hq ADR 0100): // the store and the broker say which of their ports the mesh guards on an adopted node, and the // filter module loads its table through a unit of its own whose stop deletes only that table. func catalogueManifest(t *testing.T, module string) Manifest { t.Helper() raw, err := os.ReadFile("../../../mesh-catalog/modules/" + module + "/module.json") if err != nil { t.Skipf("the catalogue is not beside this checkout: %v", err) } m, err := ParseManifest(raw) if err != nil { t.Fatalf("%s does not parse:\n%v", module, err) } return m } func TestTheStoreAndTheBrokerSayWhatTheMeshGuards(t *testing.T) { if got := catalogueManifest(t, "postgres").Guards; !reflect.DeepEqual(got, []int{5432}) { t.Errorf("postgres guards %v; the store's port must be refused from outside", got) } if got := catalogueManifest(t, "lavinmq").Guards; !reflect.DeepEqual(got, []int{15672}) { t.Errorf("lavinmq guards %v; the management port must be refused from outside", got) } } func TestTheFilterModuleNeverFlushesTheRuleset(t *testing.T) { m := catalogueManifest(t, "nftables") var unit, stock, load map[string]any for _, r := range m.Resources { switch r["id"] { case "unit": unit = r case "stock-unit-stop": stock = r case "load": load = r } } if load == nil || load["unit"] != "mesh-filter.service" { t.Fatalf("the filter is not loaded by its own unit: %v", load) } content, _ := unit["content"].(string) if unit == nil || unit["path"] != "/etc/systemd/system/mesh-filter.service" { t.Fatalf("the filter's unit is not written: %v", unit) } if strings.Contains(content, "flush") { t.Fatalf("stopping the filter flushes the whole ruleset — the runtime's and the found "+ "firewall's with it:\n%s", content) } if !strings.Contains(content, "ExecStop=nft delete table inet mesh\n") || !strings.Contains(content, "ExecStart=nft -f "+m.Filtering.Into+"\n") { t.Fatalf("the unit does not load the computed rule set and delete only its own table:\n%s", content) } // A node converged before the filter had its own unit still has the stock nftables.service // enabled, whose stop flushes the whole ruleset: a drop-in makes it delete only the mesh's // table, and the load is restarted on it so the host reloads units and the drop-in is read. if stock == nil || stock["path"] != "/etc/systemd/system/nftables.service.d/mesh.conf" || !strings.HasSuffix(fmt.Sprint(stock["content"]), "[Service]\nExecStop=\nExecStop=nft delete table inet mesh\n") { t.Fatalf("the stock unit's stop is not replaced with deleting the mesh's table: %v", stock) } // A changed rule set is RELOADED — ExecReload replaces the table in one `nft -f`, so the node // is never unfiltered — and only the units themselves restart it, which is the one change a // reload cannot carry. if !reflect.DeepEqual(load["reload-on"], []any{"filtering"}) { t.Fatalf("the filter is restarted rather than reloaded when its rules change, leaving the "+ "node unfiltered in between: %v", load) } if !reflect.DeepEqual(load["restart-on"], []any{"unit", "stock-unit-stop"}) { t.Fatalf("the filter is not restarted when its unit or the stock unit's drop-in changes: %v", load["restart-on"]) } }