85 lines
3.3 KiB
Go
85 lines
3.3 KiB
Go
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"])
|
|
}
|
|
}
|