The leading control plane tells the mirror over its API (PUT /v1/firewall, kept as .firewall.json beside the models and applied by the same path unit control planes use), and keeps the state in the store's cluster_state for members that run no llmgw, which a root timer reads from the member's own copy. The mirror also lets in the network of every node and control plane the leader knows. Tried on the PoC: off, on and a network added, on every kind of machine. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
102 lines
3.5 KiB
Go
102 lines
3.5 KiB
Go
package firewall
|
|
|
|
import (
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// follow runs FollowScript against a rules file and a state, with nft and
|
|
// systemctl replaced by scripts that log what they were asked and answer as
|
|
// a machine whose table is loaded or not.
|
|
func follow(t *testing.T, rules, state string, loaded bool) (string, string, string) {
|
|
t.Helper()
|
|
if _, err := exec.LookPath("python3"); err != nil {
|
|
t.Skip("no python3")
|
|
}
|
|
dir := t.TempDir()
|
|
bin := filepath.Join(dir, "bin")
|
|
_ = os.Mkdir(bin, 0o755)
|
|
log := filepath.Join(dir, "log")
|
|
list := "exit 1"
|
|
if loaded {
|
|
list = "exit 0"
|
|
}
|
|
for name, body := range map[string]string{
|
|
"nft": `echo "nft $*" >> ` + log + "\n" + `[ "$1" = list ] && ` + list + "\nexit 0\n",
|
|
"systemctl": `echo "systemctl $*" >> ` + log + "\nexit 0\n",
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(bin, name), []byte("#!/bin/sh\n"+body), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
rf := filepath.Join(dir, "firewall.nft")
|
|
if rules != "" {
|
|
_ = os.WriteFile(rf, []byte(rules), 0o644)
|
|
}
|
|
script := filepath.Join(dir, "follow")
|
|
_ = os.WriteFile(script, []byte(FollowScript), 0o755)
|
|
cmd := exec.Command("python3", script)
|
|
cmd.Env = append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), "LLMGW_FW_STATE="+state, "LLMGW_FW_RULES="+rf)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("%v: %s", err, out)
|
|
}
|
|
got, _ := os.ReadFile(rf)
|
|
calls, _ := os.ReadFile(log)
|
|
return string(out), string(got), string(calls)
|
|
}
|
|
|
|
var memberRules = Script(Spec{Ports: []Port{Postgres, EtcdClient}, Allow: mustPrefixes("10.0.0.0/24")})
|
|
|
|
func TestAStoreMemberFollowsTheSettings(t *testing.T) {
|
|
// Off: the table goes, the rules stay for later.
|
|
out, rules, calls := follow(t, memberRules, `{"enabled":false}`, true)
|
|
if !strings.Contains(out, "off") || !strings.Contains(calls, "nft delete table inet llmgw") ||
|
|
!strings.Contains(calls, "systemctl disable --now "+Unit) || rules != memberRules {
|
|
t.Errorf("off: %q %q", out, calls)
|
|
}
|
|
// On again: loaded from the rules kept.
|
|
out, _, calls = follow(t, memberRules, `{"enabled":true}`, false)
|
|
if !strings.Contains(out, "on") || !strings.Contains(calls, "nft -f") {
|
|
t.Errorf("on: %q %q", out, calls)
|
|
}
|
|
// On and loaded, nothing new: nothing done.
|
|
out, _, calls = follow(t, memberRules, `{"enabled":true,"networks":["10.0.0.0/24"]}`, true)
|
|
if out != "" || strings.Contains(calls, "nft -f") {
|
|
t.Errorf("unchanged: %q %q", out, calls)
|
|
}
|
|
// Networks the settings add, of both families, are let in beside what
|
|
// init let in; the result reads back as the same ports.
|
|
_, rules, calls = follow(t, memberRules, `{"enabled":true,"networks":["192.168.5.0/24","fd00::/64","nonsense"]}`, true)
|
|
if !strings.Contains(calls, "nft -c -f") || !strings.Contains(calls, "nft -f") {
|
|
t.Errorf("not checked and loaded: %q", calls)
|
|
}
|
|
back := parse(rules)
|
|
var nets []string
|
|
for _, p := range back.Allow {
|
|
nets = append(nets, p.String())
|
|
}
|
|
if strings.Join(nets, ",") != "10.0.0.0/24,192.168.5.0/24,fd00::/64" || len(back.Ports) != 2 {
|
|
t.Errorf("rules now:\n%s", rules)
|
|
}
|
|
// Nothing told yet, or no rules: left alone.
|
|
if out, _, calls := follow(t, memberRules, "", true); out != "" || calls != "" {
|
|
t.Errorf("nothing told: %q %q", out, calls)
|
|
}
|
|
if out, _, calls := follow(t, "", `{"enabled":true}`, false); out != "" || strings.Contains(calls, "nft -f") {
|
|
t.Errorf("no rules: %q %q", out, calls)
|
|
}
|
|
}
|
|
|
|
func mustPrefixes(ss ...string) []netip.Prefix {
|
|
var out []netip.Prefix
|
|
for _, s := range ss {
|
|
out = append(out, netip.MustParsePrefix(s))
|
|
}
|
|
return out
|
|
}
|